从网易云音乐的背景聊聊如何对图片主题色进行提取

一块儿网易云 🍉

网易云音乐想必是你们很熟悉的一款 app 了,毕竟你们在深夜都会网抑云前端

开玩笑了,最近在网易云听歌时,发现了一个颇有意思的特效:就是切换歌曲时,会根据当前封面替换背景色。做为资深切图仔,我那该死的好奇心兜不住了,不行,我要去一探究竟。canvas

首先我构思了不少它可能的实现方式:api

  • 机器学习对图片进行色彩分析
  • 前端提取图片主色调,作渐变处理
  • 封面背景图作高斯模糊

对于第一种,他不在个人知识范围内,这里就不展开说明了 😂。数组

第二种的话,通常都是利用canvas来实现。markdown

第三种相对来讲,从技术层面来看,实现上是最为简单的。app

作了猜想分析后,我默默打开了熟悉的 Chrome 控制台,打开了网易云音乐的源代码:机器学习

好家伙,果真是第三种实现方式。🤐异步

原本到这里,本文就该结束了。但以前也有朋友问过我如何对前端图片主题色进行提取的问题,正好以前也作过相似的需求,这里就展开作个说明吧。学习

咱们这里以一个图片网站为例,来展现实际业务中应用较广的场景:测试

在弱网下,图片加载速度较慢,此时在图片彻底加载以前,提取图片的主色调,而后填充为背景色。这样用户体验能有较大的提高。

那具体是怎么实现的呢?🤔

咱们这里采用canvas来实现,具体分为三步:

  • 获取图片数据
  • 对图片数据进行处理
  • 对颜色列表排序

这里咱们使用的测试图片为:

相对来讲,主色调较为明显,也便于测试~

获取图片数据 🦊

咱们知道图片是由一个个像素点组成的。经过 canvas 的getImageData()方法刚好能够获取图片的像素数据:

let imgObj = document.getElementById('yourId');

// 建立画布
let canvas = document.createElement('canvas');
canvas.setAttribute('width', imgObj.width);
canvas.setAttribute('height', imgObj.height);
let context = canvas.getContext('2d');
// 将图片画在画布上
context.drawImage(imgObj, 0, 0);
// 获取像素数据
let imgData = context.getImageData(0, 0, imgObj.width, imgObj.height);
let pixelData = imgData.data;

复制代码

但这时你去打印pixelData,你会发现结果为:

好家伙,全是 0,,,😳

我一时想不到是什么缘由:难道是 canvas 的 api 使用不熟练?

stackoverflow上找到了上面的回答:

可是我修改后仍是不行。

这时,我想到图片加载是异步的。可能图片还没加载完毕就开始从画布读取图片数据了,显然这是不对的。因而我对原有代码作了一番调整:

getMainColor("./test.jpeg");
function getMainColor(image) {
  return new Promise((resolve, reject) => {
    try {
      const canvas = document.createElement("canvas");
      const img = new Image(); // 建立img元素
      img.src = image; // 设置图片源地址
      img.onload = () => {
        let color = getImageColor(canvas, img);
        resolve(color);
      };
    } catch (e) {
      reject(e);
    }
  });
}
function getImageColor(canvas, img) {
  const context = canvas.getContext("2d");
  context.drawImage(img, 0, 0);

  // 获取像素数据
  let pixelData = context.getImageData(
    0,
    0,
    canvas.width,
    canvas.height
  ).data;
  console.log("pixelData", pixelData);
  return pixelData;
}
复制代码

事实证实:it's true

获取了图片数据,下一步就要对其进行相应的处理。

对图片数据进行处理 🦁

展开上一步获得的数据:

这里的数据是什么意思呢?其实就是rgba,分布表明红色(Red)绿色(Green)蓝色(Blue)透明度(Alpha)rgba 的图片每一个像素点是由上面四个数值表示的。也就是说每四个为一组。

知道了规律,那让咱们来对数据作一下清洗:主要就是对颜色进行分组,并统计每种颜色分别出现的次数:

function getImageColor(canvas, img) {
  const context = canvas.getContext("2d");
  context.drawImage(img, 0, 0);

  // 获取像素数据
  let pixelData = context.getImageData(
    0,
    0,
    canvas.width,
    canvas.height
  ).data;
  console.log("pixelData", pixelData);
  return getCountsArr(pixelData);
}
function getCountsArr(pixelData) {
  let colorList = [];
  let rgba = [];
  let rgbaStr = "";
  // 分组循环
  for (let i = 0; i < pixelData.length; i += 4) {
    rgba[0] = pixelData[i];
    rgba[1] = pixelData[i + 1];
    rgba[2] = pixelData[i + 2];
    rgba[3] = pixelData[i + 3];

    if (rgba.indexOf(undefined) !== -1 || pixelData[i + 3] === 0) {
      continue;
    }
    // console.log("rgba", rgba);
    rgbaStr = rgba.join(",");
    if (rgbaStr in colorList) {
      ++colorList[rgbaStr];
    } else {
      colorList[rgbaStr] = 1;
    }
  }
  console.log("colorList", colorList);

  return colorList;
}

复制代码

打印colorList结果为:

到这里,咱们就获得了每种数据分别出现的次数。

对颜色列表排序 🐳

最后一步,对上面获得的色值对象作一个排序:

for (let prop in colorList) {
  arr.push({
    // 若是只获取rgb,则为`rgb(${prop})`
    color: `rgba(${prop})`,
    count: colorList[prop],
  });
}
// 数组排序
arr.sort((a, b) => {
  return b.count - a.count;
});

console.log("arr", arr);
复制代码

排序后获得以下结果:

到这里咱们就获得了图片色值出现次数从大到小的排序数组,咱们来看排在第一位的rgba(206,205,201,255)

再把测试图片贴一下:

肉眼可见的主题色已经被提取出来了!🎉

反思 🚀

最后仍是回到文章最开始提到的网易云音乐的播放器特效。无论它的实现方式是怎么样的,它的这种产品创意是值得咱们学习的。

咱们平时在浏览国内外的一些网站或者使用一些 app 时,总能遇到一些让你拍手称赞的效果。而这些特效每每又与咱们前端分不开。

俗话说:前端是离产品最近的开发工程师,那最近你有没有遇到一些让你感受很惊艳或者颇有想法的效果呢,欢迎在评论区留言 🎯

❤️ 爱心三连

1.若是以为这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~

2.关注公众号前端森林,按期为你推送新鲜干货好文。

3.特殊阶段,带好口罩,作好我的防御。

相关文章
相关标签/搜索