本片文章主要涉及两个场景:
- 根据图片的平均色值获取文字的最佳显示颜色
- 提取图片的主题色
一、根据图片的平均色值获取文字的最佳显示颜色
登录页面由登录框,铺满背景的图片,位于背景图片上方的文字构成,背景图片可配置,如下图。如果这时候文字的颜色固定为白色,图片配置为白雪背景的图片,那么就会出现版权信息与白色背景太过相似,而显示不清晰的情况。思路是通过计算图片的平均色值,基于此判断文本应该为黑色或白色能够拥有更高的对比度。具体实现过程如下:
1. 创建图片标签
1 | export const createImage = (url: string): Promise<HTMLImageElement> => { |
2. 将图片绘制到 canvas
容器中,获取其像素数组
这里因为我获取的是局部图片而非整张图片的像素数组,所以针对截取哪部分,做了一下处理。即可以通过传入相应的比例系数截取指定部分,其实就是相当于对 canvas
的 drawImage
方法的参数做了一下处理。如果截取整张图片就要简单的多,可以看一下该方法的文档
这里的参数含义如下图:
1 | export const getImgData = async ( |
3. 根据像素数组计算出平均像素
1 | export const getAverageColor = (params: GetImgDataRes): number[] => { |
这里解释一下这个为什么要这样写,看下面这张图你就明白了。我们通过 getImageData
得到的像素数组是一个一维数组,包含以 RGBA 顺序的数据,数据使用 0 至 255(包含)的整数表示。就像这样:
4. 使用平均像素计算 YIQ
值,通过该值判断文本显示颜色。
YIQ
值具体指什么感兴趣的可以查一下,我这里直接解释为色系,就是通过这个值判断色彩是偏黑色系还是白色系。从而判断文本应该是黑色还是白色,基于哪个会具有更高的对比度,以此来提供最佳的可读性。
1 | export const getContrastYIQ = (r: number, g: number, b: number) => { |
像这样,总能找到合适的显示颜色:
二、提取图片的主题色
上传一张主题图片,提取该图片中的主题色。一开始我是打算直接使用 color-thief 的,参照官方文档中的使用 ES6
方式引入行不通,我就去看了一下源代码,一通研究之下发现其实挺简单,最核心的代码是引入 quantize
包处理颜色数组的一段,下面我会讲到,于是我就基于他的源码做了一下处理。以下是实现过程:
1. 绘制图片到 canvas 中,提取颜色数组
提取主题色的过程前半段与我们上面的场景一致,我们都是需要先获取到图片的像素数组,代码可以参考上面,这里不再赘述。
2. 整理有效像素数组
imgData
就是我们要提取图片的像素数组;pixelCount
是像素点的数量,也就是图片的尺寸;quality
是精度,因为很多时候其实我们没必要挨着去将每个像素点取出来,从下面的方法中我们能看出,该值越大我们就会跳过更多的像素点,即获取到的色值就会越不准确,但是同时处理速度也会有所上升,所以需要做权衡
1 | export const createPixelArray = ({ |
我们需要把获取到像素数组做一些处理,因为我们只是提取主题色,所以我们不需要过于透明的颜色。即 rgba
中 a
值不存在或小于 125 的。rgb
三个值同时大于 250 的我们也不需要,因为我们认为他过于贴近白色,去除掉这部分不会影响提取效果,还可以提高处理效率。
3.量化颜色数组,并返回调色板
有关颜色提取的算法主要有:最小差值法
、中位切分法
、八叉树算法
等。这里使用了 quantize 包来处理颜色,这个包使用的是中位切分法
1 | export const getPalette = async ({ |
效果如下:
结尾
两个场景就介绍完了,一开始本来只是讲显示文字颜色这个功能的,但是后面发现这两个场景有很大的共同点,所以就把之前的方法整理了一下,重新进行了一次封装,上述讲到的所有功能方法我已经封装完毕并发布到 npm
并上传至 github
,感兴趣的朋友可以看代码,仓库地址:https://github.com/chanceyliu/react-img-contrast/tree/master