4

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

 3 years ago
source link: https://segmentfault.com/a/1190000040385676
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一起网易云 🍉

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

开玩笑了,最近在网易云听歌时,发现了一个很有意思的特效:

就是切换歌曲时,会根据当前封面替换背景色。作为资深切图仔,我那该死的好奇心兜不住了,不行,我要去一探究竟。

首先我构思了很多它可能的实现方式:

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

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

第二种的话,一般都是利用canvas来实现。

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

做了猜测分析后,我默默打开了熟悉的 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.特殊阶段,带好口罩,做好个人防护。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK