使用 JavaScript 从图像中提取调色板
介绍
今天我给大家带来一些非常有趣的东西,我觉得值得分享。首先,我先来展示一下最终成果。
如果您迫不及待并想亲自测试,这里是应用程序演示和存储库的链接。
解释
我们可以加载任何图像并提取调色板,每种颜色都伴随着它的对立颜色(互补色)。
在Spotify中可以找到类似技术的示例,当您导航到歌曲/播放列表或专辑时,您会在顶部看到一个自定义颜色渐变,代表图片的主色调,这种渐变为每个页面增添了独特的感觉,这实际上也是我写这篇文章的原因。
有几个网站提供这项服务,例如coolors.co或canva.com,如果您想知道它是如何工作的,那么您来对地方了,让我们来找出答案。
📝 步骤
现在我们知道了我们要处理什么,让我们首先解释一下这个过程:
- 将图像加载到画布中。
- 提取图像信息。
- 构建 RGB 颜色数组。
- 应用颜色量化。
奖励曲目
- 按亮度排列颜色。
- 创建每种颜色的互补版本。
- 构建 HTML 以显示调色板。
🖼️ 将图像加载到画布中
首先,我们创建页面的基本 HTML,我们需要一个文件类型的表单输入来上传图像和一个画布元素,因为这是我们访问图像数据的方式。
索引.html
<form action="#">
<input type="file" id="imgfile" />
<input type="button" id="btnLoad" value="Load" onclick="main();" />
</form>
<canvas id="canvas"></canvas>
<div id="palette"></div>
<div id="complementary"></div>
🚜 提取图像信息
我们使用事件处理程序将图像加载到画布中,这使我们能够从画布 API.onload
访问getImageData()方法。
index.js
const main = () => {
const imgFile = document.getElementById("imgfile");
const image = new Image();
const file = imgFile.files[0];
const fileReader = new FileReader();
fileReader.onload = () => {
image.onload = () => {
const canvas = document.getElementById("canvas");
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
}
}
}
返回的信息getImageData()
代表了组成图像的所有像素,这意味着我们有一个以下格式的庞大的值数组:
{
data: [133,38,51,255,133,38,255,120...],
colorSpace: "srgb",
height: 420,
width: 320
}
data里面的每个值代表一个像素的一个通道R(红色)、G(绿色)、B(蓝色)和A(Alpha),data数组的每四个元素组成RGBA颜色模型。
🏗️ 构建 RGB 颜色数组
获取图像数据后,我们必须立即将其解析为更易读的内容,这将使我们将来的生活更加轻松。
我们每四个元素循环遍历图像数据,并返回RGB模式而不是 RGBA 模式的颜色对象数组。
index.js
const buildRgb = (imageData) => {
const rgbValues = [];
for (let i = 0; i < imageData.length; i += 4) {
const rgb = {
r: imageData[i],
g: imageData[i + 1],
b: imageData[i + 2],
};
rgbValues.push(rgb);
}
return rgbValues;
};
🎨 颜色量化
构建 rgb 颜色数组后,我们需要以某种方式知道哪些颜色最能代表图像,为了获得这一点,我们使用颜色量化。
维基百科将颜色量化描述为
减少图像中使用的不同颜色数量的过程,通常目的是使新图像在视觉上尽可能与原始图像相似。
中值切割算法
为了实现颜色量化,我们将使用一种称为中值切割的算法,其过程如下:
- 找到图像中范围最大的颜色通道(红色、绿色或蓝色)。
- 按该通道对像素进行排序。
- 将列表分成两半。
- 对每半部分重复该过程,直到获得所需数量的颜色。
这听起来很简单,但实际上有点复杂,所以我将尽力解释下面的代码。
让我们首先创建一个函数来查找具有最大范围的颜色通道。
将最小 rgb 值初始化为最大数,将最大 rgb 值初始化为最小数,这样我们就可以准确地确定最低值和最高值。
然后,循环遍历每个像素并使用Math.min和Math.max将其与当前值进行比较。
随后,我们检查每个通道最小值和最大值结果之间的差异,并返回范围最大的通道的字母。
index.js
const findBiggestColorRange = (rgbValues) => {
let rMin = Number.MAX_VALUE;
let gMin = Number.MAX_VALUE;
let bMin = Number.MAX_VALUE;
let rMax = Number.MIN_VALUE;
let gMax = Number.MIN_VALUE;
let bMax = Number.MIN_VALUE;
rgbValues.forEach((pixel) => {
rMin = Math.min(rMin, pixel.r);
gMin = Math.min(gMin, pixel.g);
bMin = Math.min(bMin, pixel.b);
rMax = Math.max(rMax, pixel.r);
gMax = Math.max(gMax, pixel.g);
bMax = Math.max(bMax, pixel.b);
});
const rRange = rMax - rMin;
const gRange = gMax - gMin;
const bRange = bMax - bMin;
const biggestRange = Math.max(rRange, gRange, bRange);
if (biggestRange === rRange) {
return "r";
} else if (biggestRange === gRange) {
return "g";
} else {
return "b";
}
};
递归时间
现在我们有了颜色范围最大的组件(R、G 或 B),对其进行排序,然后将其分成两半,使用这两半重复相同的过程并再次调用该函数,每次都为深度添加一个值。
index.js
const quantization = (rgbValues, depth) => {
// base code goes here
const componentToSortBy = findBiggestColorRange(rgbValues);
rgbValues.sort((p1, p2) => {
return p1[componentToSortBy] - p2[componentToSortBy];
});
const mid = rgbValues.length / 2;
return [
...quantization(rgbValues.slice(0, mid), depth + 1),
...quantization(rgbValues.slice(mid + 1), depth + 1),
];
}
至于基本情况,当我们的深度等于 MAX_DEPTH 时我们输入它,在我们的情况中为 4,然后将所有值相加并除以二分之一得到平均值。
注意:在这种情况下,深度表示我们想要的颜色数为 2 的幂。
index.js
const quantization = (rgbValues, depth) => {
const MAX_DEPTH = 4;
if (depth === MAX_DEPTH || rgbValues.length === 0) {
const color = rgbValues.reduce(
(prev, curr) => {
prev.r += curr.r;
prev.g += curr.g;
prev.b += curr.b;
return prev;
},
{
r: 0,
g: 0,
b: 0,
}
);
color.r = Math.round(color.r / rgbValues.length);
color.g = Math.round(color.g / rgbValues.length);
color.b = Math.round(color.b / rgbValues.length);
return [color];
}
// recursion code goes below
}
就是这样,我们完成了中值切割和调色板提取。
📑 额外步骤
我们可以在这里做很多事情,但我不想浪费您宝贵的时间,如果您有兴趣稍微扩大项目范围,请检查存储库,它包含所有额外的代码。
🗃️ 资源
如果您想进一步了解该主题,我建议尝试不同的算法来创建调色板,找到主要色调,了解色彩空间的工作原理或添加不同的配色方案,这里有一些例子可以帮助您:
- 使用K-means算法创建调色板。
- 使用八叉树算法实现调色板。
- 观看 John Austin 的有关颜色的演讲“RGB 到 XYZ:颜色的科学和历史”。
- 添加不同的颜色组合,如单色或三色,查看此页面以获取更多示例。
👋 最后的评论
感谢您抽出时间,希望您喜欢这篇文章并有所收获,祝您有美好的一天 :)