本篇要解決的問題
原本以為辨識圖片裡的顏色,是需要 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:getColor
、getPalette
。
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], ... ]
image
、quality
這二個參數跟 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 修一下,基本的使用最後會看到像這樣子的結果:
調色盤的部份,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
裡:Blob、Base64。
本篇 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:
Top comments (1)
Thank you for sharing this!