写在前面
- 是的我要开始做:根据图片的主色调来改变一下页面背景颜色了🙆
- 记录一下,自己碰到的问题以及思考
- miniprogram-main-color 完整代码 点击一下 欢迎大家去star😘
开发准备
- 理一下思路
- 将网络图片绘制进canvas
- 通过canvas的getImageData获取图片的像素数据
- 分析目前主要用于获取图片主色调的算法,并实践
- 得到主要色调,再将rgb转换成hsb,并对b值,进行修改,制作渐变,左侧35,右侧15
- 嗯,祝我成功
颜色空间基础知识背景+减色算法
- 颜色空间 RGB CMY HSV HSL LAB
- 图片颜色量化算法
- 嗯,祝我成功
- 比较常见的应用就是用于提取图片的主色调用于上色配色,当然也可以用于图像分割
- 主流算法
- 两个大方向
- 在颜色空间合理地选取采样点来构造颜色表,使得减色后的图像和原图尽可能地接近
- 直接量化
- 对每个颜色通道单独重新采样,将每个通道的色阶从256减少到某个指定的数字。这样得到一个新的小的多的颜色空间,而原图像中的每一个像素将被用在新的颜色空间中的最近邻取代
- 统计量化
- 核心:调整直方图使得累积分布曲线呈线性,从而使图像像素点的亮度值尽可能均匀地分布
- 利用原图的直方图来引导采样点的选取,使得每个采样点可以大致覆盖相同数量的像素点
- 对每个颜色通道建立直方图,然后根据这些直方图对各个颜色通道单独采样,在像素值分布多的区域进行密集采样,别的区域稀疏采样,再利用这些采样点来组合成最终的颜色表,原图中的每个像素点用颜色表中最接近的颜色替换掉
- 颜色空间分割(Median-Cut)
- 核心:在颜色空间建立一棵二叉树,通过不断地细化这棵树来近似得到一个颜色三维直方图,然后再根据这棵树来分配采样点
- 基于图像颜色样本分布的自适应方法,不论图像中颜色样本的分布如何,总是可以生成一个和颜色样本分布匹配良好的颜色表:在颜色样本分布密集的区域内采样点分布也相对密集,其他区域则分配了较少的采样点。且相同数目的颜色样本总是用同样数量的采样点来代表,所以颜色样本分布密集的区域,采样点的数量自然就会多,反之则相应的比较少
- 最重要、应用最广泛的减色算法之一
- k均值聚类(k-Means clustering)
- 核心:将像素按颜色的相似程度归类
- 直接量化
- 从一个初始的颜色表出发,通过不断修改颜色表来改善减色效果
- 可以简单看作是外层循环为rgba的打平了二维数组
- 如果按满足需求做,只要一个主色调的话,可以把整个getImageData获得的数据,进行各个通道的平均值求值,再拼接,像这样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25getUniqueColor(imageData) {
let res_r = 0
let res_g = 0
let res_b = 0
let res_a = 0
for (let i = 0; i < imageData.length; i += 1) {
if (i % 4 === 0) {
res_r += imageData[i]
} else if (i % 4 === 1) {
res_g += imageData[i]
} else if (i % 4 === 2) {
res_b += imageData[i]
} else if (i % 4 === 3) {
res_a += imageData[i]
}
}
res_r = Math.round(res_r / (imageData.length / 4))
res_g = Math.round(res_g / (imageData.length / 4))
res_b = Math.round(res_b / (imageData.length / 4))
res_a = Math.round(res_a / (imageData.length / 4))
console.log('res_r', res_r)
console.log('res_g', res_g)
console.log('res_b', res_b)
console.log('res_a', res_a)
},
- 如果按满足需求做,只要一个主色调的话,可以把整个getImageData获得的数据,进行各个通道的平均值求值,再拼接,像这样
- 运行截图
- 无相关点越多,误差越大👎
- 我们可以“对每个颜色通道单独重新采样,将每个通道的色阶从256减少到某个指定的数字。这样得到一个新的小的多的颜色空间,而原图像中的每一个像素将被用在新的颜色空间中的最近邻取代”
1
2
3
4
5
6
7
8
9
10
11const COLOR_SIZE = 40 // 单位色块的大小(像素个数,默认40)。以单位色块的平均像素值为作为统计单位
const LEVEL = 32 // 色深,颜色分区参数(0-255),总256,2^8,即8bit,4个通道(rgba),即默认色深4*8bit,32bit
// 分区块,可以拓展性的求主要色板,用来做palette
const mapData = that.getLevelData(imageData);
const colors = that.getMostColor(mapData);
if (!colors) {
return
} else {
const color = that.getAverageColor(colors)
console.log('color', color)
}
- 我们可以“对每个颜色通道单独重新采样,将每个通道的色阶从256减少到某个指定的数字。这样得到一个新的小的多的颜色空间,而原图像中的每一个像素将被用在新的颜色空间中的最近邻取代”
- 将getImageData数据分成特定大小的区块,分别算出各个区块的averageColor,再利用map特性,将averageColor作key,count各个averageColor个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61// 获取每段的颜色数据
// 根据像素数据,按单位色块进行切割
getLevelData(imageData) {
const len = imageData.length;
const mapData = {};
for (let i = 0; i < len; i += COLOR_SIZE * 4) {
const blockColor = this.getBlockColor(imageData, i); // 该区块平均rgba [{r,g,b,a}]数据
// 获取各个区块的平均rgba数据,将各个通道的颜色进行LEVEL色深降级
// 根据r_g_b_a 建立map索引
const key = this.getColorLevel(blockColor);
!mapData[key] && (mapData[key] = []);
mapData[key].push(blockColor);
}
return mapData;
},
// 获取单位块的全部色值
// 并根据全部色值,计算平均色值
// 处理最后边界值,小于COLOR_SIZE
getBlockColor(imageData, start) {
let data = [],
count = COLOR_SIZE,
len = COLOR_SIZE * 4;
imageData.length <= start + len && (count = Math.floor((imageData.length - start - 1) / 4));
for (let i = 0; i < count; i += 4) {
data.push({
r: imageData[start + i + 0],
g: imageData[start + i + 1],
b: imageData[start + i + 2],
a: imageData[start + i + 3]
})
}
return this.getAverageColor(data);
},
// 取出各个通道的平均值,即为改色块的平均色值
getAverageColor(colorArr) {
const len = colorArr.length;
let sr = 0, sg = 0, sb = 0, sa = 0;
colorArr.map(function (item) {
sr += item.r;
sg += item.g;
sb += item.b;
sa += item.a;
});
return {
r: Math.round(sr / len),
g: Math.round(sg / len),
b: Math.round(sb / len),
a: Math.round(sa / len)
}
},
getColorLevel(color) {
return this.getLevel(color.r) + '_' + this.getLevel(color.g) + '_' + this.getLevel(color.b) + '_' + this.getLevel(color.a)
},
// 色深降级
getLevel(value) {
return Math.round(value / LEVEL)
}, - 把出现次数最多的averageColor区块,作为采样区块,再获取一遍averageColor,即是最终主色调
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 根据色块颜色,获取
getMostColor(colorData) {
let rst = null, len = 0;
for (let key in colorData) {
console.log('colorData[key].length', colorData[key].length)
console.log('colorData[key].length', colorData[key])
console.log('colorData[key].length', key)
colorData[key].length > len && (
rst = colorData[key],
len = colorData[key].length
)
}
return rst;
}, - 运行截图
- 颜色可信度提高了很多
拓展实践-医学灰色影像
- 核心:将三通道转成单通道即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28let greyImageData = that.greyTheImage(imageData)
wx.canvasPutImageData({
canvasId: 'myCanvas',
x: 150,
y: 0,
width: 150,
height: 100,
data: greyImageData,
success (res) {
console.log('canvasPutImageData it worked!')
}
})
greyTheImage(imageData) {
// imageData有4个通道rgba
for (let i = 0; i < imageData.length; i += 4) {
let sum_rgb = 0
// 但我们只需要rgb三通道,a-alpha通道无用
for (let j = 0; j < 3; j += 1) {
sum_rgb = sum_rgb + imageData[i + j]
}
let grey = Math.round(sum_rgb / 3)
imageData[i] = grey
imageData[i + 1] = grey
imageData[i + 2] = grey
}
return imageData
}, - 运行截图
- 核心:将三通道转成单通道即可
格式化rgba输出
- 像这样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// 对最终颜色的字符串格式化
/**
* result:{
* hex:'#ffffff', 十六位值
* hexa:'#ffffff00', 十六位值带alpha值
* rgb:'rgb(0,0,0)', RGB值
* rgba:'rgba(0,0,0,0)' RGB值带alpha值
* }
*/
colorStrFormat(color) {
const rgba = 'rgba(' + color.r + ',' + color.g + ',' + color.b + ',' + (color.a / 255).toFixed(4).replace(/\.*0+$/, '') + ')';
const rgb = 'rgb(' + color.r + ',' + color.g + ',' + color.b + ')';
const hex = '#' + this.Num2Hex(color.r) + this.Num2Hex(color.g) + this.Num2Hex(color.b);
const hexa = hex + this.Num2Hex(color.a);
return {
rgba: rgba,
rgb: rgb,
hex: hex,
hexa: hexa
}
},
Num2Hex(num) {
const hex = num.toString(16) + '';
if (hex.length < 2) {
return '0' + hex;
} else {
return hex;
}
},参考文献
- phg1024 JavaScript图像处理(6) - 减色算法(Color Reduction)
- 获取图片主色调的插件
- 其实也可以利用 CSS新特性去改变背景颜色,比如高斯模糊等来达到业务需求
写在后面
- 周六听了一天 John Lennon 歌曲【这也是我拖更的原因😶 羡慕他和 Yoko 那段伊甸园般的爱情,他对 Yoko 的痴爱,他和母亲的两次分别感同身受,Beatles在一起做音乐的欢乐,他对 Sean 的宠溺父爱,但他的人生却在一次枪杀后戛然而止,心痛 心痛 🌧
- 祝大家多多发财