将像素缓冲区从 16 位转换为 B8G8R8A8_UNorm

Convert Pixel Buffer to B8G8R8A8_UNorm from 16Bit

所以我有一个来自外部本机库 (c++) 的像素缓冲区,它似乎是 16 位 RGB(SlimDX 等效项是 B5G6R5_UNorm)。

我想使用 Direct2D 显示此缓冲区表示的图像。但是Direct2D不支持B5G6R5_UNorm.

所以我需要将这个像素缓冲区转换为 B8G8R8A8_UNorm

我看到过使用移位方法的此类任务的各种代码片段,但 none 其中的代码片段是特定于我的需要或格式的。它没有帮助我有零,nada,none,zilch 任何关于位移的线索,或者它是如何完成的。

我想要的是此类任务的 C♯ 代码示例或任何内置方法来进行转换 - 我不介意使用其他库的

请注意:我知道这可以使用 C♯ 位图 类 来完成,但我试图不依赖这些内置的 类 (关于 GDI 我不知道不喜欢),图像(以像素缓冲区的形式)将变得又厚又快,我选择 SlimDX 是因为它的易用性和性能。

我认为我需要转换像素缓冲区的原因是,如果我使用 B8G8R8A8_UNorm 绘制图像,图像有绿色覆盖物并且像素到处都是,因此我相信我需要先将像素缓冲区 'upgrade' 转换为所需格式。

补充一下:当我在不转换缓冲区的情况下执行上述操作时,图像不会填满整个几何图形。

像素缓冲区通过 byte[] 个对象提供

位移位和逻辑运算符在处理图像格式时非常有用,因此您应该阅读更多相关内容。但是,我可以快速 运行-down 介绍这种像素格式代表什么,以及如何从一种格式转换为另一种格式。我应该在我的回答前加上一个警告,即我真的不太了解 C# 及其支持库,因此可能有适合您的现成解决方案。

首先,您的像素缓冲区的格式为 B5G6R5_UNORM。所以我们为每个像素分配了 16 位(5 红色、6 绿色和 5 蓝色)。我们可以将这种像素格式的位布局可视化为"RRRRRGGGGGGBBBBB",其中'R'代表属于红色通道的位,'G'代表属于绿色通道的位,'B' 用于属于蓝色通道的位。

现在,假设像素缓冲区的前 16 位(两个字节)为 1111110100101111。将其与像素格式的位布局对齐...

RRRRRGGGGGGBBBBB
1111110100101111

这意味着红色通道的位为 11111,绿色通道为 101001,蓝色通道为 01111。从二进制转换为十进制:红色=31,绿色=41,蓝色=15。您会注意到红色通道的所有位都设置为 1,但其值 (31) 实际上小于绿色通道 (41)。但是,这并不意味着显示时颜色比红色更绿。绿色通道有一个额外的位,因此它可以表示比红色和蓝色通道更多的值,但在这个特定示例中,输出颜色中实际上有更多的红色!这就是 UNORM 部分的用武之地...

UNORM代表无符号归一化整数;这意味着颜色通道值将被解释为从 0.0 到 1.0 的均匀间隔的浮点数。这些值通过分配的位数进行归一化。这到底是什么意思?假设您有一种只有 3 位的格式来存储频道。这意味着通道可以有 2^3=8 个不同的值,这些值分别用十进制、二进制和归一化表示显示在下面。归一化值就是十进制值除以可以用N位表示的最大可能十进制值。

Decimal | Binary | Normalized
-----------------------------
0       | 000    | 0/7 =  0.000
1       | 001    | 1/7 =~ 0.142
2       | 010    | 2/7 =~ 0.285
3       | 011    | 3/7 =~ 0.428
4       | 100    | 4/7 =~ 0.571
5       | 101    | 5/7 =~ 0.714
6       | 110    | 6/7 =~ 0.857
7       | 111    | 7/7 =  1.000

回到前面的示例,其中像素的位为 1111110100101111,我们已经知道三个颜色通道的十进制值:RGB = {31, 41, 15}。我们需要归一化值,因为十进制值具有误导性,并且在不知道它们存储了多少位的情况下不会告诉我们太多信息。红色和蓝色通道以 5 位存储,因此最大的十进制值为 2^5 -1=31;然而,绿色通道的最大十进制值为 2^6-1=63。知道这一点,归一化的颜色通道是:

// NormalizedValue = DecimalValue / MaxDecimalValue
R = 31 / 31 =  1.000
G = 41 / 63 =~ 0.650
B = 15 / 31 =~ 0.483

重申一下,归一化值很有用,因为它们代表输出中每个颜色通道的相对贡献。向给定通道添加更多位不会影响可能的颜色范围,它只会提高颜色准确性(基本上是该颜色通道的更多阴影)。

了解以上所有内容后,您应该能够从任何 RGB(A) 格式转换为任何其他 RGB(A) 格式,而不管每个通道中存储了多少位。例如,让我们将刚刚计算的归一化值转换为 B8G8R8A8_UNORM。一旦计算出标准化值,这就很容易了,因为您只需按新格式的最大值进行缩放。每个通道使用 8 位,因此最大值为 2^8-1=255。由于原始格式没有 alpha 通道,您通常只存储最大值(意味着完全不透明)。

// OutputValue = InputValueNormalized * MaxOutputValue
B = 0.483 * 255 = 123.165
G = 0.650 * 255 = 165.75
R = 1.000 * 255 = 255
A = 1.000 * 255 = 255

在您编写代码之前,现在只缺少一件事。在上面,我能够通过将它们排成一行并复制它们来为每个通道提取位。这就是我得到绿色位 101001 的方式。在代码中,这可以通过 "masking" 去掉我们不关心的位来完成。移位就像它听起来的那样:它将位向右或向左移动。当您向右移动位时,最右边的位被丢弃,新的最左边的位被分配为 0。下面使用上面的 16 位示例进行可视化。

1111110100101111 // original 16 bits
0111111010010111 // shift right 1x
0011111101001011 // shift right 2x
0001111110100101 // shift right 3x
0000111111010010 // shift right 4x
0000011111101001 // shift right 5x

你可以继续移动,最终你会得到 16 个 0。但是,我出于某种原因停止了五班倒。注意现在最右边的 6 位是绿色位(我有 shifted/discarded 5 个蓝色位)。我们几乎已经提取出我们需要的确切位,但在绿色位的左侧还有额外的 5 个红色位。要删除这些,我们使用 "logical and" 操作仅屏蔽掉最右边的 6 位。掩码二进制为 0000000000111111; 1 表示我们想要这个位,0 表示我们不想要它。掩码除最后 6 个位置外全为 0,因为我们只需要最后 6 位。将此掩码与 5x 移位数对齐,当两位都为 1 时输出为 1,每隔一位为 0:

0000011111101001 // original 16 bits shifted 5x to the right
0000000000111111 // bit mask to extract the rightmost 6 bits
------------------------------------------------------------
0000000000101001 // result of the 'logical and' of the two above numbers

结果正是我们要查找的数字:6 个绿色位,除此之外别无其他。回想一下,前导 0 对十进制值没有影响(它仍然是 41)。在 C# 或任何其他类 C 语言中执行 'shift right' (>>) 和 'logical and' (&) 操作非常简单。这是它在 C# 中的样子:

// 0xFD2F is 1111110100101111 in binary
uint pixel = 0xFD2F;

// 0x1F is 00011111 in binary (5 rightmost bits are 1)
uint mask5bits = 0x1F;

// 0x3F is 00111111 in binary (6 rightmost bits are 1)
uint mask6bits = 0x3F;

// shift right 11x (discard 5 blue + 6 green bits), then mask 5 bits
uint red   = (pixel >> 11) & mask5bits;

// shift right 5x (discard 5 blue bits), then mask 6 bits
uint green = (pixel >> 5)  & mask6bits;

// mask 5 rightmost bits
uint blue  = pixel & mask5bits;

将所有内容放在一起,您最终可能会得到一个看起来与此类似的例程。但是,请务必仔细阅读字节顺序,以确保字节按您期望的方式排序。在这种情况下,参数是一个 32 位无符号整数(忽略前 16 位)

byte[] R5G6B5toR8G8B8A8(UInt16 input)
{
    return new byte[]
    {
        (byte)((input & 0x1F) / 31.0f * 255),         // blue
        (byte)(((input >> 5) & 0x3F) / 63.0f * 255),  // green
        (byte)(((input >> 11) & 0x1F) / 31.0f * 255), // red
        255                                           // alpha
    };
}