48 位位图中灰色像素的意外 RGB 通道值

Unexpected RGB channel values for gray pixel in 48 bits bitmap

跟进

我已经设法使用 Bitmap.LockBits 从位图实例中提取像素信息。 PixelFormatFormat48bppRbg,根据我的理解和对上述问题的理解,应该使用两个字节存储每个像素的RGB颜色通道。字节的组合值应等于 0 到 8192 之间的数字,表示 RBG 通道强度值。该值是通过将两个字节传递给 BitConverter.ToUInt16 方法获得的。

因此,在为以下 3 x 3 位图提取像素信息时(为清楚起见,此处已放大):

我的第一行像素通道是这样的:

像素颜色 红色强度 绿色强度 蓝色强度
红色 8192 0 0
绿色 0 8192 0
蓝色 0 0 8192

到目前为止一切顺利。

然而,在第二行,它是这样的:

像素颜色 红色强度 绿色强度 蓝色强度
白色 8192 8192 8192
灰色 (!) 1768 (!) 1768 (!) 1768 (!)
黑色 0 0 0

白色和黑色像素通道值对我来说很有意义。

然而,灰色没有。

如果您在上面的灰色像素上使用任何颜色选择器,您应该会得到完美的中灰色。在 24 位颜色中,它应该等效于 (R: 128, G: 128, B: 128),或十六进制形式的#808080。

那么为什么在 Format48bppRpg 像素格式中,通道强度远低于预期的中间 4096 值?这个基于 GDI+ 的 0-8192 范围不应该像它的 8 位对应物一样工作,0 是最低强度而 8192 是最高强度吗?我在这里错过了什么?

作为参考,这是来自 Visual Studio 调试器的屏幕截图,显示了原始字节索引和值,以及关于步幅、通道位置及其提取的强度值的附加说明,直至灰色像素:

您陈述错误假设的部分是 #808080 是“完美的中灰色”。它不是,至少如果你从某种角度看它不是(更多关于它here)。

许多颜色标准,包括 sRGB,使用 gamma compression 使较暗的颜色更多 space 在通常用于存储 RGB 颜色的 256 个值的范围内。这大致意味着在编码该值之前取相对分量值的平方根(或 2.2 根)。原因是人眼(像其他感官一样)以对数方式感知亮度,因此即使是算术上很小的亮度变化也很重要,如果它实际上意味着亮度加倍的话。

128 的字节值实际上大约是全亮度的 21.95 % (128/255 ^ 2.2),这就是您在 16 位组件的情况下所看到的。 space 的可能值要大得多,因此 GDI(或格式)不再需要以特殊方式存储它们。

如果您需要算法,取值的 2.2 根通常效果很好,但正确的公式有点不同,请参阅 here。根函数通常具有接近零的无限斜率,因此特定公式试图通过使该部分线性化来解决该问题。一段代码可以很容易地从中导出:

static byte TransformComponent(ushort linear)
{
    const double a = 0.055;
    var c = linear / 8192.0;
    c = c <= 0.0031308 ? 12.92 * c : (1 + a) * Math.Pow(c, 1/2.4) - a;
    return (byte)Math.Round(c * Byte.MaxValue);
}

1768 的值为 128。