【问题标题】:Convert Pixel Buffer to B8G8R8A8_UNorm from 16Bit将 Pixel Buffer 从 16Bit 转换为 B8G8R8A8_UNorm
【发布时间】:2016-05-30 11:22:57
【问题描述】:

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

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

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

我已经看到使用位移方法完成此类任务的各种代码 sn-ps,但没有一个是针对我的需求或格式的。它没有帮助我零,nada,none,zilch 任何关于位移的线索,或者它是如何完成的。

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

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

我认为我需要转换像素缓冲区的原因是,如果我使用B8G8R8A8_UNorm 绘制图像,图像有一个绿色覆盖并且像素到处都是,因此我认为我需要首先将像素缓冲区转换或“升级”为所需的格式。

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

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

【问题讨论】:

    标签: c# image pixel direct2d slimdx


    【解决方案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 位。将此掩码与移位 5 倍的数字对齐,当两个位为 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
        };
    }
    

    【讨论】:

    • 贾斯汀。谢谢你的信息。在接下来的几周内,我将经常提到这一点。并将充分利用您的示例和细节!其详细,知情和回答!谢谢
    猜你喜欢
    • 2017-04-03
    • 2017-06-16
    • 1970-01-01
    • 2016-06-19
    • 2012-01-10
    • 2013-07-19
    • 2023-03-21
    • 2020-08-25
    • 2020-09-10
    相关资源
    最近更新 更多