【问题标题】:Convert RGB to RGBA in C在 C 中将 RGB 转换为 RGBA
【发布时间】:2011-08-15 18:38:07
【问题描述】:

我需要将表示图像的字节数组的内容以 RGB 字节顺序复制到另一个 RGBA(每像素 4 个字节)缓冲区中。稍后将填充 Alpha 通道。实现这一目标的最快方法是什么?

【问题讨论】:

  • 我期待一个 for 循环,但等待一些更好的想法。 :)
  • 我推荐 Mikola 的解决方案。我希望我这样说不是在侮辱你,但你问的是一个相当简单的问题,所以这里是警告:“不要忘记为目的地分配空间。”
  • 另外...您不会比实施以下任何解决方案获得更好的速度。也许您可以使内存中的工作速度提高 10-20%,但不要打扰。这项任务将减少 99% 的 I/O,因此您正在浪费宝贵的人力时间来进行 0.001% 的改进。
  • 是的,我会接受 mikola 的回答。谢谢大家

标签: c image


【解决方案1】:

你想要它有多棘手?您可以将其设置为一次复制一个 4 字节的字,这在某些 32 位系统上可能会快一些:

void fast_unpack(char* rgba, const char* rgb, const int count) {
    if(count==0)
        return;
    for(int i=count; --i; rgba+=4, rgb+=3) {
        *(uint32_t*)(void*)rgba = *(const uint32_t*)(const void*)rgb;
    }
    for(int j=0; j<3; ++j) {
        rgba[j] = rgb[j];
    }
}

最后的额外情况是处理 rgb 数组缺少一个字节的事实。您还可以使用对齐的移动和 SSE 指令使其更快一点,一次以 4 个像素的倍数工作。如果你真的很有野心,你可以尝试更可怕的混淆操作,例如将缓存行预取到 FP 寄存器中,然后一次性将其传送到另一个图像。当然,您从这些优化中获得的里程将在很大程度上取决于您所针对的特定系统配置,我真的怀疑这样做是否有很多好处,而不是简单的事情。

而且我的简单实验证实,这确实有点快了一点,至少在我的 x86 机器上是这样。这是一个基准:

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <time.h>

void fast_unpack(char* rgba, const char* rgb, const int count) {
    if(count==0)
        return;
    for(int i=count; --i; rgba+=4, rgb+=3) {
        *(uint32_t*)(void*)rgba = *(const uint32_t*)(const void*)rgb;
    }
    for(int j=0; j<3; ++j) {
        rgba[j] = rgb[j];
    }
}

void simple_unpack(char* rgba, const char* rgb, const int count) {
    for(int i=0; i<count; ++i) {
        for(int j=0; j<3; ++j) {
            rgba[j] = rgb[j];
        }
        rgba += 4;
        rgb  += 3;
    }
}

int main() {
    const int count = 512*512;
    const int N = 10000;

    char* src = (char*)malloc(count * 3);
    char* dst = (char*)malloc(count * 4);

    clock_t c0, c1;    
    double t;
    printf("Image size = %d bytes\n", count);
    printf("Number of iterations = %d\n", N);

    printf("Testing simple unpack....");
    c0 = clock();
    for(int i=0; i<N; ++i) {
        simple_unpack(dst, src, count);
    }
    c1 = clock();
    printf("Done\n");
    t = (double)(c1 - c0) / (double)CLOCKS_PER_SEC;
    printf("Elapsed time: %lf\nAverage time: %lf\n", t, t/N);


    printf("Testing tricky unpack....");
    c0 = clock();
    for(int i=0; i<N; ++i) {
        fast_unpack(dst, src, count);
    }
    c1 = clock();
    printf("Done\n");
    t = (double)(c1 - c0) / (double)CLOCKS_PER_SEC;
    printf("Elapsed time: %lf\nAverage time: %lf\n", t, t/N);

    return 0;
}

以下是结果(使用 g++ -O3 编译):

图像大小 = 262144 字节

迭代次数 = 10000

测试简单解包....完成

经过时间:3.830000

平均时间:0.000383

测试棘手的解包....完成

经过时间:2.390000

平均时间:0.000239

所以,天气好的时候可能会快 40%。

【讨论】:

  • 这是特定于平台的代码,因为并非每个硬件都支持未对齐的整数指针访问。
  • @Omri Barel:确实优化是特定于平台的,请注意我确实说一些 32 位系统,而不是全部。但这仍然是有效的 C 代码,它适用于任何符合标准的编译器,无论架构如何。此外,在 x86/64 上,它工作得很好,并且确实比简单的解决方案更快(并且比汇编更容易维护,汇编甚至不是编译器可移植的)。如果进行对齐的数据访问(这将更快、更便携),那么事情就会变得很糟糕,你必须同时处理多个像素,而边界情况会变得丑陋。
  • 我采用了您的代码并添加了另一个函数来一次处理 12 个字节(读取 3 个 32 位值并使用 5 个移位和 2 个 OR 生成 4 个 32 位 RGBA 值)。它更快(因为展开)并且没有未对齐的访问。不过,我确信 Abrash 可以做得更快。
  • 我的测试结果来自 iPad (armv7) 图像大小 = 262144 字节 迭代次数 = 100 测试简单解包....完成 已用时间:2.126318 平均时间:0.021263 测试棘手解包... .Done 经过时间:0.646655 平均时间:0.006467 优化版本快 3 倍 :)
【解决方案2】:

最快的方法是使用为您实现转换的库,而不是自己编写。您的目标是哪个平台?

如果你因为某种原因坚持自己写,先写一个简单正确的版本。用那个。如果性能不够,那么你可以考虑优化一下。一般来说,这种转换最好使用向量置换来完成,但确切的最佳序列会因目标架构而异。

【讨论】:

    【解决方案3】:
    struct rgb {
       char r;
       char g;
       char b;
    };
    
    struct rgba {
       char r;
       char g;
       char b;
       char a;
    }
    
    void convert(struct rgba * dst, const struct rgb * src, size_t num)
    {
        size_t i;
        for (i=0; i<num; i++) {
            dst[i].r = src[i].r;
            dst[i].g = src[i].g;
            dst[i].b = src[i].b;
        }
    }
    

    这将是更清洁的解决方案,但是当您提到字节数组时,您应该使用这个:

    // num is still the size in pixels. So dst should have space for 4*num bytes,
    // while src is supposed to be of length 3*num.
    void convert(char * dst, const char * src, size_t num)
    {
        size_t i;
        for (i=0; i<num; i++) {
            dst[4*i] = src[3*i];
            dst[4*i+1] = src[3*i+1];
            dst[4*i+2] = src[3*i+2];
        }
    }
    

    【讨论】:

    • 问题是关于字节数组,而不是结构数组。
    • @Omri Barel:同样的想法很容易奏效。您甚至可以进行强制转换以将结构数组转换为指针数组。但是,需要注意确保编译器不会对结构对齐做任何偷偷摸摸的事情。为了使这个解决方案正确,确实应该在这些结构周围有一个编译指示包。
    • 你说得对 - 我监督了“字节数组”部分,因此添加了另一个解决方案。
    • @Mikola:如果您想要一个不可移植的、依赖于编译器的解决方案,为什么不使用汇编?这肯定是最快的解决方案。
    【解决方案4】:

    我想我记得有一个 Nehe 教程,它做类似的事情,但速度很快。

    它的here

    有趣的部分在这里:

    void flipIt(void* buffer)                       // Flips The Red And Blue Bytes (256x256)
    {
        void* b = buffer;                       // Pointer To The Buffer
        __asm                               // Assembler Code To Follow
        {
            mov ecx, 256*256                    // Set Up A Counter (Dimensions Of Memory Block)
            mov ebx, b                      // Points ebx To Our Data (b)
            label:                          // Label Used For Looping
                mov al,[ebx+0]                  // Loads Value At ebx Into al
                mov ah,[ebx+2]                  // Loads Value At ebx+2 Into ah
                mov [ebx+2],al                  // Stores Value In al At ebx+2
                mov [ebx+0],ah                  // Stores Value In ah At ebx
    
                add ebx,3                   // Moves Through The Data By 3 Bytes
                dec ecx                     // Decreases Our Loop Counter
                jnz label                   // If Not Zero Jump Back To Label
        }
    }
    

    它的作用是不言自明的,应该很容易将其转换为添加 alpha 字节。

    【讨论】:

    • 这并没有真正满足 OP 的要求,即将 3 字节 RGB 数组解压缩为 4 字节 RGBA 数组;而是它只是交换 RGB 数组上的红色/蓝色字节......所以我想我不太确定它为什么相关?
    • 正如我所写的,将其转换为添加 alpha 字节非常容易。
    • 这没有帮助。要么提供一个解决方案来回答所提出的问题,要么忽略它。如果添加 alpha 字节那么容易,请自己做
    【解决方案5】:

    只需创建大小为源数组 4/3 的数组。读取整个数组并将其写入 RGBA 数组,但在每 3 个字节后插入 255 个 alpha。

    【讨论】:

      猜你喜欢
      • 2012-04-11
      • 1970-01-01
      • 1970-01-01
      • 2011-01-04
      • 1970-01-01
      • 2011-10-04
      • 2021-12-17
      • 2012-02-28
      相关资源
      最近更新 更多