DEV Community

Cover image for Color Thief 找出圖片裡的顏色
Let's Write
Let's Write

Posted on • Edited on • Originally published at letswrite.tw

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>
Enter fullscreen mode Exit fullscreen mode

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

import ColorThief from './../node_modules/colorthief/dist/color-thief.mjs';
Enter fullscreen mode Exit fullscreen mode

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

以下說明二個 function:getColorgetPalette

getColor 取得主要色碼

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

getColor(image [, quality])
// => [R, G, B]
Enter fullscreen mode Exit fullscreen mode

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

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

<img id="i_am_image" src="xxx.jpg">
Enter fullscreen mode Exit fullscreen mode

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

const img = document.getElementById('i_am_image');
Enter fullscreen mode Exit fullscreen mode

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], ... ]
Enter fullscreen mode Exit fullscreen mode

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);
  });
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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

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;
}
Enter fullscreen mode Exit fullscreen mode

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

客製一個 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);
},
Enter fullscreen mode Exit fullscreen mode

進階使用: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
Enter fullscreen mode Exit fullscreen mode

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

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

本篇原始碼及 Demo

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

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

原始碼:

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

Demo:

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

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (1)

Collapse
 
yongchanghe profile image
Yongchang He

Thank you for sharing this!

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay