從網易雲音樂的背景聊聊如何對圖片主題色進行提取

一塊兒網易雲 🍉

網易雲音樂想必是你們很熟悉的一款 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.特殊階段,帶好口罩,作好我的防禦。

相關文章
相關標籤/搜索