快速将 RGBA 转换为 ARGB



我正在尝试将 rgba 缓冲区转换为 argb,有没有办法改进下一个算法,或者任何其他更快的方法来执行此类操作?考虑到 alpha 值在 argb 缓冲区中并不重要,并且应始终以0xFF结尾。

int y, x, pixel;
for (y = 0; y < height; y++)
{
    for (x = 0; x < width; x++)
    {
     pixel = rgbaBuffer[y * width + x];
     argbBuffer[(height - y - 1) * width + x] = (pixel & 0xff00ff00) | ((pixel << 16) & 0x00ff0000) | ((pixel >> 16) & 0xff);
    }
}

我将只关注交换函数:

typedef unsigned int Color32;
inline Color32 Color32Reverse(Color32 x)
{
    return
    // Source is in format: 0xAARRGGBB
        ((x & 0xFF000000) >> 24) | //______AA
        ((x & 0x00FF0000) >>  8) | //____RR__
        ((x & 0x0000FF00) <<  8) | //__GG____
        ((x & 0x000000FF) << 24);  //BB______
    // Return value is in format:  0xBBGGRRAA
}

假设代码没有错误(只是效率低下),我可以猜到你想做的只是每秒交换一次(偶数)字节(当然还有反转缓冲区),不是吗?

因此,您可以通过以下方式实现一些优化:

  • 避免移位和掩蔽操作
  • 优化循环,例如在指数计算中节省

我会重写代码如下:

int y, x;
for (y = 0; y < height; y++)
{
    unsigned char *pRGBA= (unsigned char *)(rgbaBuffer+y*width);
    unsigned char *pARGB= (unsigned char *)(argbBuffer+(height-y-1)*width);
    for (x = 4*(width-1); x>=0; x-=4)
    {
        pARGB[x  ]   = pRGBA[x+2];
        pARGB[x+1]   = pRGBA[x+1];
        pARGB[x+2]   = pRGBA[x  ];
        pARGB[x+3]   = 0xFF;
    }
}

请注意,更复杂的指数计算仅在外部循环中执行。每个像素的 rgbaBuffer 和 argbBuffer 都有四个 acess,但我认为这不仅仅是通过避免按位运算和 indixes 计算来抵消的。另一种选择是(就像在你的代码中一样)一次获取/存储一个像素(int),并在本地进行处理(这节省了内存访问),但除非你有一些有效的方法来交换两个字节并在本地设置 alpha(例如一些内联程序集,以便确保一切都在寄存器级别执行), 它不会真正帮助。

您提供的代码非常奇怪,因为它打乱的颜色组件不是rgba->argb,而是rgba->rabg。

我已经制作了此例程的正确和优化版本。

int pixel;
int size = width * height;
for (unsigned int * rgba_ptr = rgbaBuffer, * argb_ptr = argbBuffer + size - 1; argb_ptr >= argbBuffer; rgba_ptr++, argb_ptr--)
{
    // *argb_ptr = *rgba_ptr >> 8 | 0xff000000;  // - this version doesn't change endianess
    *argb_ptr = __builtin_bswap32(*rgba_ptr) >> 8 | 0xff000000;  // This does
}

我做的第一件事是简化你的随机表达。很明显,XRGB只是RGBA>>8。此外,我还删除了每次迭代时数组索引的计算,并使用指针作为循环变量。这个版本比我机器上的原始版本快大约 2 倍。

如果此代码适用于 x86 CPU,则还可以使用 SSE 进行随机播放。

我来晚了。但是在即时生成视频时,我遇到了完全相同的问题。通过重用缓冲区,我可以只为每个帧设置 R、G、B 值,并且只设置 A 一次。

请参阅下面的代码:

byte[] _workingBuffer = null;
byte[] GetProcessedPixelData(SKBitmap bitmap)
{
    ReadOnlySpan<byte> sourceSpan = bitmap.GetPixelSpan();
    if (_workingBuffer == null || _workingBuffer.Length != bitmap.ByteCount)
    {
        // Alloc buffer
        _workingBuffer = new byte[sourceSpan.Length];
        // Set all the alpha
        for (int i = 0; i < sourceSpan.Length; i += 4) _workingBuffer[i] = byte.MaxValue;
    }
    Stopwatch w = Stopwatch.StartNew();
    for (int i = 0; i < sourceSpan.Length; i += 4)
    {
        // A
        // Dont set alpha here. The alpha is already set in the buffer
        //_workingBuffer[i] = byte.MaxValue;
        //_workingBuffer[i] = sourceSpan[i + 3];
        // R
        _workingBuffer[i + 1] = sourceSpan[i];
        // G
        _workingBuffer[i + 2] = sourceSpan[i + 1];
        // B
        _workingBuffer[i + 3] = sourceSpan[i + 2];
    }
    Debug.Print("Copied " + sourceSpan.Length + " in " + w.Elapsed.TotalMilliseconds);
    return _workingBuffer;
}

这使我在iPhone上为~8mb的(1920 * 1080 * 4)缓冲区提供了大约15毫秒。

这对我来说还远远不够。我的最终解决方案是做一个偏移内存拷贝(C#中的Buffer.BlockCopy),因为alpha并不重要。

    byte[] _workingBuffer = null;
    byte[] GetProcessedPixelData(SKBitmap bitmap)
    {
        ReadOnlySpan<byte> sourceSpan = bitmap.GetPixelSpan();
        byte[] sourceArray = sourceSpan.ToArray();
        if (_workingBuffer == null || _workingBuffer.Length != bitmap.ByteCount)
        {
            // Alloc buffer
            _workingBuffer = new byte[sourceSpan.Length];
            // Set first byte. This is the alpha component of the first pixel
            _workingBuffer[0] = byte.MaxValue;
        }
        // Converts RGBA to ARGB in ~2 ms instead of ~15 ms
        // 
        // Copies the whole buffer with a offset of 1
        //                                      R   G   B   A   R   G   B   A   R   G   B   A
        // Originally the source buffer has:    R1, G1, B1, A1, R2, G2, B2, A2, R3, G3, B3, A3
        //                                   A  R   G   B   A   R   G   B   A   R   G   B   A
        // After the copy it looks like:     0, R1, G1, B1, A1, R2, G2, B2, A2, R3, G3, B3, A3
        // So essentially we get the wrong alpha for every pixel. But all alphas should be 255 anyways.
        // The first byte is set in the alloc
        Buffer.BlockCopy(sourceArray, 0, _workingBuffer, 1, sourceSpan.Length - 1);
        // Below is an inefficient method of converting RGBA to ARGB. Takes ~15 ms on iPhone 12 Pro Max for a 8mb buffer (1920 * 1080 * 4 bytes)
        /*
        for (int i = 0; i < sourceSpan.Length; i += 4)
        {
            // A
            // Dont set alpha here. The alpha is already set in the buffer
            //_workingBuffer[i] = byte.MaxValue;
            //_workingBuffer[i] = sourceSpan[i + 3];
            byte sR = sourceSpan[i];
            byte sG = sourceSpan[i + 1];
            byte sB = sourceSpan[i + 2];
            if (sR == 0 && sG == byte.MaxValue && sB == 0)
                continue;
            // R
            _workingBuffer[i + 1] = sR;
            // G
            _workingBuffer[i + 2] = sG;
            // B
            _workingBuffer[i + 3] = sB;
        }
        */
        return _workingBuffer;
    }

代码被注释了它是如何工作的。在我的同一部iPhone上,它需要~2毫秒,这对于我的用例来说已经足够了。

使用汇编,以下内容适用于英特尔。

此示例交换红色和蓝色。

void* b = pixels;
UINT len = textureWidth*textureHeight;
__asm                                                       
{
    mov ecx, len                // Set loop counter to pixels memory block size
    mov ebx, b                  // Set ebx to pixels pointer
    label:                      
        mov al,[ebx+0]          // Load Red to al
        mov ah,[ebx+2]          // Load Blue to ah
        mov [ebx+0],ah          // Swap Red
        mov [ebx+2],al          // Swap Blue
        add ebx,4               // Move by 4 bytes to next pixel
        dec ecx                 // Decrease loop counter
        jnz label               // If not zero jump to label
}
(pixel << 24) | (pixel >> 8)

32 位整数向右旋转 8 位,这会将 32 位 RGBA 值转换为 ARGB。这是有效的,因为:

  • pixel << 24RGBARGB部分从左侧丢弃,导致A000
  • pixel >> 8RGBAA部分从右侧丢弃,从而导致0RGB
  • A000 | 0RGB == ARGB .

最新更新