48 位位图中灰色像素的意外 RGB 通道值
Unexpected RGB channel values for gray pixel in 48 bits bitmap
跟进 。
我已经设法使用 Bitmap.LockBits
从位图实例中提取像素信息。 PixelFormat
是Format48bppRbg
,根据我的理解和对上述问题的理解,应该使用两个字节存储每个像素的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。
跟进
我已经设法使用 Bitmap.LockBits
从位图实例中提取像素信息。 PixelFormat
是Format48bppRbg
,根据我的理解和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。