如何對圖片主題色進行提取


一起網易雲 🍉

網易雲音樂想必是大家很熟悉的一款 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 時,總能遇到一些讓你拍手稱贊的效果。而這些特效往往又與我們前端分不開。

俗話說:前端是離產品最近的開發工程師,那最近你有沒有遇到一些讓你感覺很驚艷或者很有想法的效果呢,歡迎在評論區留言 🎯

轉自https://mp.weixin.qq.com/s/6XgDWhIA-2jfacg49j6alA 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM