这是我参与更文挑战的第 5 天,活动详情查看:更文挑战
前言
一个设计师朋友希望我能够帮他做一个根据图片生成色卡的页面,效果类似下面这张图:
开发
要分析出图片的色彩组成比例,我们就是要能够获取到这张图片上的色彩信息。 在 HTML5 普及的今天,我们可以使用 Canvas 的 getImageData 来获取图片的颜色信息。
**CanvasRenderingContext2D**.getImageData()返回一个ImageData对象,用来描述canvas区域隐含的像素数据,这个区域通过矩形表示,起始点为_(sx, sy)、_宽为_sw、高为_sh。
获取到颜色信息后,我们只需要统计每种颜色出现次数,然后选择颜色出现次数最多的几种颜色就可以实现了。
收集像素信息
export default function collectPixelData (img) {
const canvas = document.createElement('canvas')
const context = canvas.getContext && canvas.getContext('2d')
context.drawImage(img, 0, 0)
const idata = context.getImageData(0, 0, canvas.width, canvas.height);
let curIndex = 0
const step = 4
const cdata = idata.data
const arrayOfPixels = []
while (curIndex + 4 < cdata.length) {
const [r, g, b, a] = [cdata[curIndex], cdata[curIndex+1], cdata[curIndex+2]]
arrayOfPixels.push([r,g,b])
}
return arrayOfPixels
}
getImageData 返回的是一个数组,每四个代表一个像素点的 [r,g,b,a] 信息。万万没想到,当我上传图片等待控制台打印信息的时候,页面崩溃了😠。
万事开头难,经过冷静的思考,我猜测可能是图片信息过多导致的。之后加了一个抽样的逻辑,每 10 个像素抽样 1 个,这样就能流畅地获取图片的像素信息了。
while (curIndex + 4 < cdata.length) {
const [r, g, b, a] = [cdata[curIndex], cdata[curIndex+1], cdata[curIndex+2]]
arrayOfPixels.push([r,g,b])
// 抽样的逻辑
curIndex += 9 * 4
}
像素构成分析
像素信息获取到之后,我们就可以做统计分析了。代码如下:
function rankColors(colorArr) {
const colorMap = {}
for (const item of colorArr) {
const key = item.join('-')
if (colorMap[key]) {
colorMap[key] += 1
} else {
colorMap[key] = 1
}
}
const colors = Object.keys(colorMap).map(item => ({color: item.split('-'), count: colorMap[item]}))
.sort((a, b) => b.count - a.count)
return {colors, totalCount: colors.length}
}
最后的结果如图:
面部的粉色没有展示出来。
上面的两个结果效果都不太理想。图一是主色调分布抽样不够准确,图二是颜色过于分散。
经过分析日志,其实图片上肉眼看起来一样的颜色在程序里的的颜色数值也会不同(比如
rgb(125,0,0) 和 rgb(126,0,0),这就导致了分析不准和颜色过于分散的原因。
颜色提取算法
上面出现的问题怎么解决呢?一个思路就是在颜色相近的颜色可归类为一个颜色,这就涉及到算法的知识了,图像主题色提取算法这篇文章对算法有更为详细的介绍。
后来找到了 quantize 这个开源库可以来优化颜色的提取,优化后的结果如下:
准确了一些,至少脸部的粉色被分析出来了。
还是不太准确,车尾灯的红色也没有展示。
后记
上文示例的源码地址: wuse-color。另外,已经有基于 quantize 获取图片主题色的开源项目了,仓库地址:color-thief。
后来又找到一个颜色分析做的非常好的网站: adobe color。具体效果如下:
它不但考虑了颜色的比例,而且也结合了颜色的位置关系来生成色卡,可以说是非常赞了!