Color Thief 找出圖片裡的顏色

/

本篇要解決的問題

原本以為辨識圖片裡的顏色,是需要 AI 還是什麼超越柯南智商才能做的功能,前陣子好奇查了一下,才發現原來前端就可以做到了!原理是 canvas 有一個 getImageData 的 function,可以抓出每個 pixel 的 RGBA 資訊,再把所有取到的 RGBA 資訊做一個連一年級小學生柯南也算不出的數學處理,就可以算出圖片的色碼出來。

然後,對,工程師的世界裡充滿著維護世界和平的大神,會把這些高難度的數學動作給做成套件。

本篇要使用的是 Color Thief 這個套件,實作一個取出圖片裡色碼的功能,也會延伸做一個讓使用者自己選擇圖片檔案後,顯示出這個圖檔的主色碼以及其他色碼。

推薦 Color Thief 的原因:

  • 支援 Browser、Node.js
  • 文件清楚明暸
  • 簡單好用、範例清晰(讚讚)
  • 開源免費

Color Thief GitHub:

https://github.com/lokesh/color-thief/

Color Thief 說明文件、Demo:

https://lokeshdhakar.com/projects/color-thief/

這邊再提供二個相關資源,是 August 找資料時覺得很有用的資訊。

這是廣告,點擊一下可以幫本站多個一點點的廣告收入,謝謝

本篇最後完成的 Demo:

https://letswritetw.github.io/letswrite-color-thief/

最近想開始碰 Vue.js 3,所以 Demo 的原始檔,JS 是用 Vue 3 寫的。


Color Thief 基本使用

引用 JS

Color Thief 的 JS 檔共有四個:

  • color-thief.js:Node.js 用。
  • color-thief.mjs:ES6 import 用,可用於 Webpack 和 Rollup。
  • color-thief.umd.js、color-thief.min.js:直接當 CDN 引用。

最簡單的方式就是直接 CDN 引用:

<script src="https://cdnjs.cloudflare.com/ajax/libs/color-thief/2.3.0/color-thief.umd.js"></script>

本篇 Demo 用的是 ES6,終端機執行 yarn add colorthief 後,引用方式如下:

import ColorThief from './../node_modules/colorthief/dist/color-thief.mjs';

Color Thief 的好用之處在於,它來來去去也就二個 function,不用看那種落落長到天荒地老的文件。

以下說明二個 function:getColorgetPalette

getColor 取得主要色碼

getColor 是取得圖檔的主要色碼,也就是圖檔裡最多使用到的顏色的色碼。

這是廣告,點擊一下可以幫本站多個一點點的廣告收入,謝謝

getColor(image [, quality])
// => [R, G, B]

image:要給的是 image 本身,而不是給圖檔的路徑。

比方我們引用圖片進來的 HTML 如下:

<img id="i_am_image" src="xxx.jpg">

image 要給的不是 xxx.jpg,而是:

const img = document.getElementById('i_am_image');

quality:可選的參數,給的值是大於 1 的整數,默認值是 10

quality 代表的意思是「每幾個 pixel 執行一次抓值?」,開頭說過,前端判斷圖片的色碼,是把每一個 pixel 抓出來並取得它的 RGBA 值後,做一堆數學運算去算出最常被使用到 RGBA。

如果一張圖只有 10px * 10px,每一個 pixel 都抓就有 100 個 pixel 要抓要計算。

那如果今天圖片是 1024px * 1024px 呢?甚至更大呢?每一個 pixel 都抓就會很耗時間。

quality 就是設定每幾個 pixel 抓一次,默認值 10 就是每 10 個 pixel 抓一次。所以設為 1 時最精準,但如果圖檔很大回傳的速度就會很慢。

getPalette 取得調色盤

一張圖除了主要色碼,也會用到其它色碼。getPalette 就是把這些顏色都抓出來。

這是廣告,點擊一下可以幫本站多個一點點的廣告收入,謝謝

getPalette(image [, colorCount, quality]
// => [[R, G, B], [R, G, B], ... ]

imagequality 這二個參數跟 getColor 相同,這邊不再重覆說明。

colorCount:要抓出幾個顏色出來,默認值是 10

回傳的結果是一個陣列,裡面包有指定數量(colorCount)的陣列,每個陣列的三個值就是 R、G、B。

使用範例

這邊來抓一張圖片,取它的主要色碼跟其它調色盤中的顏色色碼。

圖片是從 Lorem Picsum 找的,這也是一個很方便的工具,可以隨機生成圖片,切版時需要用到假圖的話很好用。

Color Thief 官方文件提供的範例是這樣:

const colorThief = new ColorThief();
const img = document.querySelector('img');

if (img.complete) {
  colorThief.getColor(img);
  colorThief.getPalette(img);
} else {
  image.addEventListener('load', function() {
    colorThief.getColor(img);
    colorThief.getPalette(img);
  });
}

img.complete:確保圖片載入完成。

本篇 Demo 稍微修改一下,把確保圖片載入完成這段,寫成一個 Vue 的 methods。

// ...
methods: {
  // 確定圖片載入完成
  waitImageLoad(img) {
    return new Promise((resolve, reject) => {
      let timer;
      timer = () => {
        setTimeout(() => {
          if(img.complete) resolve(true);
          else timer();
        }, 100);
      }
      timer();
    })
  },
  // 基本使用,取得圖片色碼
  async getColors() {
    const colorThief = new ColorThief();
    const img = document.getElementById('i_am_image');
    await this.waitImageLoad(img);
    let color = this.colorThief.getColor(img);
    let palette = this.colorThief.getPalette(img);
  }
},
mounted() {
  this.getColors();
}

Demo 頁有用 Tailwind CSS 修一下,基本的使用最後會看到像這樣子的結果:

Color Thief 基本使用範例
Color Thief 基本使用範例

調色盤的部份,August 有另外做一個處理,就是讓顏色從亮排到暗。下面一段來說明。

色碼的部份也從 RGB 改成六進位的 Hex,一樣後面幾段會說明。


進階使用:排序色碼由亮到暗

這個方式是 August 自行猜測的,還沒有找其它科學實驗來證明 XD。

我們一般用 CSS 寫顏色,黑、白二色會是這樣:

  • 黑:#000 || rgb(0, 0, 0)
  • 白:#FFF || rgb(255, 255, 255)

rgb 可以知道,三數加總的結果愈小,顏色愈暗,加總的結果愈大則愈亮。

所以我們只需要把 rgb 的三個數字加總起來後,再由大排到小,就可以讓調色盤的色碼是由亮到暗來呈現。

Javascript 中,reduce 可以加總陣列裡的數字。嗯…總覺得很違和,reduce 的英文單字意思不是「減少」嗎?JS 使用時卻是累加,覺得好怪 ~ ” ~。(reduce MDN 說明

sortPalette(array) {
  // 建一個空陣列,把調色盤裡的 RGB 存進去,並存一個三數加總的值
  let tempForCalc = [];
  Array.prototype.forEach.call(array, palette => {
    const sum = palette.reduce((a, b) => a + b, 0);
    const item = {
      color: palette,
      sum: sum
    }
    tempForCalc.push(item);
  });

  // 用三數加總的值做 大 -> 小 排序
  const result = tempForCalc.concat().sort((a, b) => {
    return a.sum > b.sum ? -1 : 1;
  });
  return result;
}

進階使用:取得使用者選擇的圖檔

客製一個 input type="file" 請看之前寫的筆記文,這邊不再說明:

File API 客製上傳檔案按鈕 / input file

當使用者選好檔案後,有二種方式可以把選擇的圖檔塞進 img src 裡:BlobBase64

本篇 Demo 用的是抓圖檔的 Blob,因為寫的行數比較少。(我就廢)

轉 Base64 的 method 也有寫在原始碼裡,可以在原始檔裡的 src/main.js 搜尋 getUploadImgBase64

async getUploadImgBlob(file) {
  const fileData = file.target.files[0];
  const customImg = window.URL.createObjectURL(fileData);

  // 確定 #i_am_image 的圖片載入完成
  // waitImageLoad:前面幾段「確定圖片載入完成」的 method
  const img = document.getElementById('i_am_image');
  await this.waitImageLoad(img);

  // 執行 colorThief
  const colorThief = new ColorThief();
  const color = colorThief.getColor(img, 5);

  // sortPalette:前面幾段「排序色碼由亮到暗」的 method
  let tempPalette = colorThief.getPalette(img, 10, 5);
  const palette = this.sortPalette(tempPalette);
},

進階使用:RGB 轉 Hex 色碼

我們在寫 CSS 的時候,寫顏色的部份很少是寫 RGB,大部份是寫像「#000000」的六進位 Hex 色碼,好啦,至少 August 的習慣是這樣。

官方文件有提供 RGB 轉成 Hex 的方法:

const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => {
  const hex = x.toString(16)
  return hex.length === 1 ? '0' + hex : hex
}).join('')

rgbToHex(102, 51, 153); // #663399

本篇 Demo 把這段 function 改為 Vue 的 method,渲染時會把得到的 RGB 轉成 Hex:

toHex(colors) {
  let hex = colors.map(x => {
    return x.toString(16);
  }).join('');
  return '#' + hex
}

本篇原始碼及 Demo

本篇的原始碼有放在 GitHub 上,也用 GitHub Pages 生成 Demo 頁。

取用之前麻煩分享本篇文章,或是在 GitHub 上點個星星,你的一個小小動作對本站都是大大的鼓勵。

原始碼:

https://github.com/letswritetw/letswrite-color-thief

Demo:

https://letswritetw.github.io/letswrite-color-thief/

Summary
Color Thief 找出圖片裡的顏色
Article Name
Color Thief 找出圖片裡的顏色
Description
本篇大綱:本篇要解決的問題。Color Thief 基本使用、引用 JS、getColor 取得主要色碼、getPalette 取得調色盤、使用範例。排序色碼由亮到暗。取得使用者選擇的圖檔。RGB 轉 Hex 色碼。本篇原始碼及 Demo。
Augustus
Let's Write
Let's Write
https://letswrite.tw/wp-content/uploads/2020/08/logo_512.jpg

以下是留言,但關於留言的部份必需先讓你們知道:

本站的文章都是 August 因為覺得有趣,才會實作並整理成筆記文而後進行發表。

如果留言是希望把 Demo 改成「你想要」的樣子,或是把功能改成「符合你需求」的樣子,

Sorry~ 除非那修改是 August 也有興趣的,不然不會幫你們寫程式去面對工作或是交作業。

未來這類的留言不會再主動回覆。😎

另外,公開信箱是為了讓金流驗證用,

因為之前遇過幾次回信協助解決問題後,對方卻一聲謝謝也沒有,就這樣拿去幫工作交差。

因此決定不再回覆信件,有疑問就利用留言功能囉。

Let's Write

前端工程師 August 的學習筆記 — solving problems, in simple ways.

Follow us Telegram GitHub