为什么 golang RGBA.RGBA() 方法使用 |和 <<?

Why does golang RGBA.RGBA() method use | and <<?

在 golang 颜色包中,有一种方法可以从 RGBA 对象中获取 r,g,b,a 值:

func (c RGBA) RGBA() (r, g, b, a uint32) {
    r = uint32(c.R)
    r |= r << 8
    g = uint32(c.G)
    g |= g << 8
    b = uint32(c.B)
    b |= b << 8
    a = uint32(c.A)
    a |= a << 8
    return
}

如果我要实现这个简单的功能,我会这样写

func (c RGBA) RGBA() (r, g, b, a uint32) {
    r = uint32(c.R)
    g = uint32(c.G)
    b = uint32(c.B)
    a = uint32(c.A)
    return
}

使用r |= r << 8的原因是什么?

要将每个 RGB 分量从 8 位转换为 16 位,请将字节复制到 16 位值的高字节。例如,0x03 变为 0x0303,0xFE 变为 0xFEFE,因此 8 位值 0 到 255 (0xFF) 产生 16 位值 0 到 65,535 (0xFFFF),值分布均匀。

来自优秀的“The Go image package”博文:

[...] the channels have a 16-bit effective range: 100% red is represented by RGBA returning an r of 65535, not 255, so that converting from CMYK or YCbCr is not as lossy. Third, the type returned is uint32, even though the maximum value is 65535, to guarantee that multiplying two values together won't overflow.

Note that the R field of an RGBA is an 8-bit alpha-premultiplied color in the range [0, 255]. RGBA satisfies the Color interface by multiplying that value by 0x101 to generate a 16-bit alpha-premultiplied color in the range [0, 65535]

因此,如果我们查看具有值 c.R = 10101010 的颜色的位表示,那么此操作

r = uint32(c.R)
r |= r << 8

有效地将第一个字节复制到第二个字节。

   00000000000000000000000010101010 (r)
 | 00000000000000001010101000000000 (r << 8)
--------------------------------------
   00000000000000001010101010101010 (r |= r << 8)

这相当于乘以因数 0x101 并将所有 256 个可能的值均匀分布在 [0, 65535] 范围内。

color.RGBA类型实现RGBA方法满足color.Color接口:

type Color interface {
    // RGBA returns the alpha-premultiplied red, green, blue and alpha values
    // for the color. Each value ranges within [0, 0xffff], but is represented
    // by a uint32 so that multiplying by a blend factor up to 0xffff will not
    // overflow.
    //
    // An alpha-premultiplied color component c has been scaled by alpha (a),
    // so has valid values 0 <= c <= a.
    RGBA() (r, g, b, a uint32)
}

现在RGBA类型代表uint8类型的颜色通道,范围为[0, 0xff]。简单地将这些值转换为 uint32 不会将范围扩展到 [0, 0xffff]。

适当的转换类似于:

r = uint32((float64(c.R) / 0xff) * 0xffff)

但是,他们想避免浮点运算。幸运的是 0xffff / 0xff0x0101,所以我们可以简化表达式(暂时忽略类型转换):

r = c.R * 0x0101
  = c.R * 0x0100 + c.R
  = (c.R << 8) + c.R    # multiply by power of 2 is equivalent to shift
  = (c.R << 8) | c.R    # equivalent, since bottom 8 bits of first operand are 0

标准库中的代码基本上就是这样做的。

将 0 到 255 范围内的值(8 位 RGB 分量)转换为 0 到 65535 范围内的值(16 位 RGB 分量)的方法是将 8 位值乘以65535/255; 65535/255 恰好是 257,即十六进制 101,因此将一个字节乘以 65535/255 可以通过将该字节值左移 8 位并将其与原始值进行或运算来完成。

(在将 8 位 RGB/RGBA 组件转换为 16 位 RGB/RGBA 组件时,在其他地方,在其他语言中也有类似的技巧。)