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

时间:2016-05-30 11:22:57

标签: c# image pixel direct2d slimdx

所以我从外部本机库(c ++)获得了一个看似16Bit RGB(SlimDX等效为B5G6R5_UNorm)的像素缓冲区。

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

因此我需要将此像素缓冲区转换为B8G8R8A8_UNorm

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

我所追求的是这样一个任务的C♯代码示例或任何内置的转换方法 - 我不介意使用其他库

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

我相信我需要转换像素缓冲区的原因是,如果我使用B8G8R8A8_UNorm绘制图像,图像会有绿色覆盖,像素就到处都是,所以为什么我相信我需要首先转换或升级'像素缓冲区为所需格式。

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

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

1 个答案:

答案 0 :(得分:1)

在处理图像格式时,位移和逻辑运算符非常有用,因此您需要自己阅读更多相关信息。但是,我可以快速了解这种像素格式代表的内容,以及如何从一种格式转换为另一种格式。我应该在我的回答前面加上一个警告说我真的不太了解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.在代码中,这可以通过"掩盖"我们不关心的那些东西。移动确实听起来像是:它向右或向左移动位。当你向右移动位时,最右边的位被丢弃,新的最左边的位被分配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位是绿色位(我已经移位/丢弃了5个蓝色位)。我们几乎提取了我们需要的确切位,但是绿色位左边还有5个红色位。要删除这些,我们使用"逻辑和"操作以仅屏蔽最右边的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语言。这是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
    };
}