获取图片主题色
- 一、问题描述
- 二、具体实现
- 1. 图片读取
- 2. 使用Canvas提取图片的像素数据
- 3. 聚合处理
- 三、全部代码
一、问题描述
当我们遇到根据背景图片来修改页面样式的时候,可能就需要去实现获取图片主题色的功能。比如深色背景配白色导航栏,浅色背景配黑色导航栏这种。
二、具体实现
1. 图片读取
根据url将图片读取到dom中,方便后续操作
const imageUrl = 'home_bg.png' // 你的图片地址const sourceImage = document.createElement("img");sourceImage.addEventListener('load' , () => {console.log(111);// TODO});sourceImage.src = imageUrl
2. 使用Canvas提取图片的像素数据
我们可以利用Canvas来获取图片的全部像素数据
基于上面TODO部分:
const canvas = document.createElement('canvas');const context = canvas.getContext('2d');const width = canvas.width = sourceImage.naturalWidth;const height = canvas.height = sourceImage.naturalHeight;context.drawImage(sourceImage, 0, 0, width, height);const imageData = context.getImageData(0, 0, width, height)console.log(imageData)
得到的数据是很多像素点RGBA数据依次排列组成的数组:
R - 红色(0-255)
G - 绿色(0-255)
B - 蓝色(0-255)
A - alpha 通道(0-255; 0 是透明的,255 是完全可见的)
所以我们要对数据进行处理,得到每个像素点的数据
const pixelCount = width * height;const pixels = imageData.data;const pixelArray = [];const quality = 10 // 间隔,如果图片很大时每个像素值都取可能会对性能有所影响,所以按自己需求设置间隔多少像素点取一个值for (let i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {offset = i * 4;r = pixels[offset + 0];g = pixels[offset + 1];b = pixels[offset + 2];a = pixels[offset + 3];// 如果像素点透明则不取值if (typeof a === 'undefined' || a >= 125) {pixelArray.push([r, g, b]);}}console.log(pixelArray);
3. 聚合处理
这里的数据我们可以自己做聚合、分析等等处理方式进行操作,最后得到我们自己想要的值。有兴趣可以自行去了解,我这里就使用了一个现成的库 quantize.js
。https://www.npmjs.com/package/quantize。插件源码我已经附在文档上了。
具体实现:
首先引入quantize.js(模块化的项目中可以使用import或者require,这里只是方便演示)
<script src="quantize.js"></script>
const quantize = MMCQ.quantizeconst colorCount = 5 // 需要聚合出前五的颜色const cMap = quantize(pixelArray, colorCount);console.log(cMap)console.log(cMap ? cMap.palette() : []);
最后可以选择对前五的颜色进行加权处理,也可以直接取排第一的颜色(根据业务要求自行判断)
三、全部代码
记得首先引入quantize.js
<script src="quantize.js"></script>
const quantize = MMCQ.quantize
// canvas实例,方便获取图片像素数据
class CanvasImage {canvas = document.createElement('canvas')context = this.canvas.getContext('2d')constructor(image) {this.width = this.canvas.width = image.naturalWidth;this.height = this.canvas.height = image.naturalHeight;this.context.drawImage(image, 0, 0, this.width, this.height);}getImageData() {return this.context.getImageData(0, 0, this.width, this.height);};
}class ColorStat {quality = 10/**** @param quality 间隔多少个像素点取一个数据*/constructor(quality = 10) {this.quality = quality}/*** @description 提取图片像素点函数* @param imgData 图片像素数据 多组 r、g、b、a 数据组成的一维数组* @param pixelCount 一共多少个像素点* @param quality 间隔多少个像素点取一个数据* @returns {number[][]}*/createPixelArray(imgData, pixelCount, quality) {const pixels = imgData;const pixelArray = [];// 间隔取对应的像素值for (let i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {offset = i * 4;r = pixels[offset];g = pixels[offset + 1];b = pixels[offset + 2];a = pixels[offset + 3];// 如果像素点透明则不取值(透明点不影响主题色)if (typeof a === 'undefined' || a >= 125) {pixelArray.push([r, g, b]);}}return pixelArray;}/*** 获取聚合后的像素值* @param sourceImage 图片dom* @param colorCount 要取的聚合后前几个像素值* @returns {*|*[]}*/getPalette(sourceImage, colorCount) {// 创建canvas实例const image = new CanvasImage(sourceImage);const imageData = image.getImageData();const pixelCount = image.width * image.height;const pixelArray = this.createPixelArray(imageData.data, pixelCount, this.quality);// 使用 quantize.js 聚合统计数据const cmap = quantize(pixelArray, colorCount);return cmap ? cmap.palette() : [];}/*** 通过图片地址获取图片主题色* @param imageUrl 图片地址(模块化项目本地图片需要使用 require(url)* @param colorCount 要取的聚合后前几个像素值* @returns {Promise<number[][] | string>}*/getColorFromUrl(imageUrl, colorCount = 5) {return new Promise((resolve, reject) => {// 创建img dom实例const sourceImage = document.createElement("img");sourceImage.addEventListener('load' , () => {const palette = this.getPalette(sourceImage, colorCount);if (palette) {resolve(palette)} else {reject('图片异常!')}});sourceImage.src = imageUrl})}
}
const colorStat = new ColorStat()
colorStat.getColorFromUrl('./home_bg.png', 5).then(data => {console.log(data);
})