在C语言中将RGB转换为RGBA

5

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


我本来期望使用for循环,但等待更好的想法。 :) - Vlad
我建议使用Mikola的解决方案。希望我这么说不会冒犯你,但你问的是一个相当简单的问题,所以这里有个警告:“别忘了”为目标分配空间。 - FastAl
此外,你不可能比下面的任何一种解决方案实现更快的速度。也许你可以让内存工作快10-20%,但是不要费心了。这个任务99%都是I/O操作,所以你浪费宝贵的人力时间只为了0.001%的改进。 - FastAl
我会选择Mikola的答案。谢谢大家。 - Yannis
5个回答

5
你想要多棘手的?你可以将其设置为每次复制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
@Omri Barel:优化确实是特定于平台的,但请注意我说的是一些32位系统,而不是所有系统。但这仍然是有效的C代码,并且它将在任何符合标准的编译器上工作,无论架构如何。此外,在x86/64上,它可以正常工作,而且确实比朴素的解决方案更快(比汇编更容易维护,甚至不具备编译器可移植性)。如果进行对齐数据访问(这将更快且更具可移植性),那么情况会变得很糟糕,您必须同时处理多个像素,并且边界情况变得丑陋。 - Mikola
我已经拿了你的代码,并添加了另一个函数,以每次处理12个字节(读取3个32位值并使用5个移位和2个OR运算生成4个32位RGBA值)。它更快(因为展开)且没有未对齐的访问。不过,我相信Abrash可以更快地完成它。 - Omri Barel
这是我的iPad(armv7)的测试结果: 图像大小=262144字节 迭代次数=100 测试简单解包....完成 经过时间:2.126318 平均时间:0.021263 测试棘手的解包....完成 经过时间:0.646655 平均时间:0.006467 优化版本快了3倍 :) - Evgen Bodunov

4

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

如果出于某些原因坚持自己编写,请先编写简单且正确的版本。使用该版本。如果性能不足,然后再考虑优化。一般来说,这种转换最好使用向量排列,但确切的最佳序列取决于目标架构。


2
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
@Omri Barel:同样的想法可以轻松实现。你甚至可以只需进行强制类型转换,将结构数组转换为指针数组。但是,需要注意一些细节,以确保编译器不会在结构对齐方面做出任何奇怪的事情。为了使这个解决方案正确,这些结构体周围真的应该有一个#pragma pack。 - Mikola
你们两个都是对的 - 我忽略了“字节数组”这一部分,因此添加了另一个解决方案。 - glglgl
@Mikola:如果你要选择一个非便携式、依赖编译器的解决方案,为什么不使用汇编语言呢?那肯定是最快的解决方案。 - Omri Barel

1

我记得有一篇Nehe的教程,讲述了如何快速做到这样的事情。

它在 这里

有趣的部分在这里:

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
    }
}

它的功能相当自我解释,将其转换为添加阿尔法字节应该很容易。


2
这并不完全符合OP的要求,OP要求将一个3字节的RGB数组解包成一个4字节的RGBA数组;而这只是在RGB数组中就地交换红/蓝字节...所以我不太确定它为什么相关? - Mikola
正如我所写的那样,将其转换为添加 alpha 字节将非常容易。 - Martin Kristiansen
2
这并不是有帮助的。要么提供一个回答问题的解决方案,要么就忽略它。如果添加 alpha 字节很容易,那就自己动手做。 - JamEngulfer

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接