• [go]根据背景色计算文本颜色



    在图片中添加文字是常见的操作(如何添加参见《 gg库绘图与添加文字》),怎样保证添加的文字有很好的辨识度呢?

    颜色与灰度

    RGB颜色空间以R(Red:红)、G(Green:绿)、B(Blue:蓝)三种基本色为基础,进行不同程度的叠加,产生丰富而广泛的颜色。

    最能反映人眼感知的是灰度图像,要获取清晰的前景色,可转换为灰度图后来计算前景色。

    互补色与对比色

    色相环(colorcircle):是指一种圆形排列的色相光谱(SPECTRUM),色彩是按照光谱在自然中出现的顺序来排列的;暖色(WARMCOLOR)位于包含红色和黄色的半圆之内,冷色则包含在绿色和紫色的那个半圆内。
    色相环

    互补色:当两个颜色的色相,在色相环中的夹角为180°时,这两个颜色互为补色,也就是“互补色”。

    通过RGB可方便地计算器互补色:用255减去对应的RGB后,得到的颜色即为互补色。
    互补色

    对比色:当两个颜色的色相,在色相环中的夹角大于130°时,这两个颜色就有明显的对比关系了,夹角越大,对比关系越强。

    灰度

    灰度图,每个像素点只能有一个值表示颜色,它的像素值在0到255之间,0是黑色,255是白色,中间值是一些不同等级的灰色,可以说灰度是黑与白之间的过渡色。

    通过RGB,可方便计算出对应的灰度:
    (299*R + 587*G + 114*B)/1000

    一个图像经过上面公式计算后,即得到人眼感知的灰度图;当期望在背景色上显示清晰的前景色(文本)时,即可根据背景的灰度确定使用黑色或白色。

    透明度混合

    透明度混合(Alpha blending)是把透明度混合进个颜色中,方便颜色计算:
    DestinationColor.rgb = (SourceColor.rgb * SourceColor.a) + (DestinationColor.rgb * (1 - SourceColor.a))

    Alpha blending

    最常见的像素表示格式是RGBA8888,即(r,g,b,a),每个通道8位。为了表示方便,alpha通道一般记成正规化为0~1的浮点数:
    如红色60%透明度就是(255, 0, 0, 153),正规化后为(255, 0, 0, 0.6);而 Premultiplied Alpha 则是把RGB通道乘以透明度也就是(r*a, g*a, b*a, a),也就是变成(153, 0, 0, 0.6)。

    透明通道在渲染的时候通过Alpha Blending产生作用,混合后的颜色计算公式(如一个透明度为 a s a_s as的颜色 C s C_s Cs,渲染到颜色 C d C_d Cd上):
    C o = a s ∗ C s + ( 1 − a s ) ∗ C d C_o = a_s*C_s + (1 − a_s)*C_d Co=asCs+(1as)Cd

    如果颜色以Premultiplied Alpha形式存储( C s C_s Cs已经乘以透明度了),则混合公式变成了:
    C o = C s ′ + ( 1 − a s ) ∗ C d C_o = C_s^′ + (1 − a_s)*C_d Co=Cs+(1as)Cd

    Premultiplied Alpha

    Premultiplied Alpha后的像素格式变得不直观,但混合的时候可以少一次乘法,这可以提高一些效率;除此之外最主要的是:没有Premultiplied Alph的纹理无法进行Texture Filtering(纹理过滤,除非使用最近邻插值):

    Premultiplied Alpha最重要的意义是使得带透明度的图片纹理可以正常的进行线性插值;这样旋转、缩放或者非整数的纹理坐标才能正常显示,否则在透明像素边缘附近产生奇怪的颜色。

    go文本颜色

    go中通过image.At获取对应位置的颜色,然后通过color.RGBA()返回对应的R,G,B值;但其值是Premultiplied Alpha的,要转换成灰度,需要:
    (19595*r + 38470*g + 7471*b + 1<<15) >> 24

    若只是把图片转换成黑白的,可直接使用color.GrayModel.Convert来方便实现。

    要根据背景色来确定文本颜色(以黑、白为例),提取文字显示位置处的颜色,转换为灰度值后,根据其值确定是黑色或白色。

    func calcTextColor(dc *gg.Context, x, y float64) color.Color {
        cur := dc.Image().At(int(x), int(y))
        //gray := color.GrayModel.Convert(cur)
        r, g, b, _ := cur.RGBA()
        lum := (19595*r + 38470*g + 7471*b + 1<<15) >> 24
    
        var txtColor color.Color
        if lum > 158 {
            txtColor = color.NRGBA{A: 255}
        } else {
            txtColor = color.NRGBA{R: 255, G: 255, B: 255, A: 255}
        }
        return txtColor
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    要更好地处理文本颜色,可多取几个点平均,或者每个文本处获取对应的显示颜色。

    文本蒙版

    除计算文本颜色,便于显示文本外;还可以通过文本蒙版的方式显示:

    func AddText(dc *gg.Context, txt string, tX int, tY int) {
    	w, h := dc.MeasureString(txt)
    
    	dc.SetHexColor("#CCCCCCCC")
    	minY := math.Max(0.0, float64(tY)-h)
    	dc.DrawRectangle(float64(tX), minY, w, h)
    	dc.Fill()
    
    	dc.SetHexColor("#333333")
    	dc.DrawString(txt, float64(tX), minY+h)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过画一个与文本相同大小的底框(填充深色),然后在上面显示文本(浅色)。

  • 相关阅读:
    linux 开机遇见unmount and run xfs_repair
    图解LeetCode——667. 优美的排列 II(难度:中等)
    【C++】空间配置器 allocator:原理及底层解析
    PHP7和PHP8的新特性
    【Pandas】Python数据分析活用Pandas库学习笔记(三)
    设计模式--建造者模式
    Java的反射机制
    java计算机毕业设计ssm+vue杂货网络销售及配送系统
    批量将所有文件的磁盘路径名称提取到 txt 记事本文件中
    12.JVM
  • 原文地址:https://blog.csdn.net/alwaysrun/article/details/126815994