概述
最近在做“在线地图样式配置”的功能的时候,发现百度地图有个功能时上传一张图片,从图片中提取颜色并进行配图。本文就简单实现一下如何从图片中提取色卡。
效果
实现
实现思路
通过canvasdrawImage
绘制图片,并通过getImageData
获取颜色,根据颜色的距离对颜色进行分组,分组后的结果进行求平均。
实现代码
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.querySelector("#canvas");
const { offsetWidth, offsetHeight } = document.body;
canvas.width = offsetWidth * 0.8;
canvas.height = offsetHeight * 0.8;
const ctx = canvas.getContext("2d");
/**
* 计算颜色距离,判断是否为相近颜色
* @param {Array} color1
* @param {Array} color2
*/
function colorDistance([r1, g1, b1], [r2, g2, b2]) {
const rmean = (r1 + r2) / 2;
const r = r1 - r2;
const g = g1 - g2;
const b = b1 - b2;
return Math.sqrt(
(2 + rmean / 256) * (r * r) +
4 * (g * g) +
(2 + (255 - rmean) / 256) * (b * b)
);
}
const img = new Image();
img.src = "./earth.jpg";
let maxSize = 400
img.onload = () => {
let { width, height } = img;
// 进行等比缩放
if(width > height && width > maxSize) {
const ratio = maxSize / width
width = maxSize
height = ratio * height
}
if(height > width && height > maxSize) {
const ratio = maxSize / height
height = maxSize
width = ratio * width
}
// 绘制图片
ctx.drawImage(img, 0, 0, width, height);
const data = ctx.getImageData(0, 0, width, height).data;
const dict = {};
// 获取分组颜色
const getKey = (color2) => {
const colors = Object.keys(dict).map((c) => c.split(",").map(Number));
if (colors.length === 0) return color2.join(",");
const delt = 88; // 分组范围
let res = color2.join(",");
for (let i = 0; i < colors.length; i++) {
const color = colors[i];
const dis = colorDistance(color, color2);
if (dis < delt) {
res = color.join(",");
break;
}
}
return res;
};
// 将颜色进行分组
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const ckey = getKey([r, g, b]);
if (!dict[ckey]) dict[ckey] = [];
dict[ckey].push([r, g, b]);
}
// 将颜色按照数量进行排序
const result = Object.keys(dict)
.map((c) => {
return {
key: c,
colors: dict[c],
};
})
.sort((a, b) => b.colors.length - a.colors.length);
// 取相近色的颜色平均值
for (let i = 0; i < result.length; i++) {
const { colors } = result[i];
let r = 0,
g = 0,
b = 0;
for (let j = 0; j < colors.length; j++) {
const [r1, g1, b1] = colors[j];
r += r1;
g += g1;
b += b1;
}
r = Math.round(r / colors.length);
g = Math.round(g / colors.length);
b = Math.round(b / colors.length);
result[i]["key"] = [r, g, b].join(",");
}
// 根据颜色亮度进行排序
const colorss = result.map(({ key }) => key).sort((a, b) => {
const [ra, ga, ba] = a.split(",").map(Number)
const [rb, gb, bb] = b.split(",").map(Number)
const la = ra * 0.299 + ga * 0.587 + ba *0.114
const lb = rb * 0.299 + gb * 0.587 + bb *0.114
return lb - la
});
// 渲染颜色
const h = 20;
for (let i = 0; i < colorss.length; i++) {
ctx.fillStyle = `rgb(${colorss[i]})`;
ctx.strokeStyle = `#fff`;
const y = i * h + (height + 20);
ctx.fillRect(0, y, 30, h);
ctx.strokeRect(0, y, 30, h);
}
};
</script>
</body>
</html>