【问题标题】:Fast Converting RGBA to ARGB快速将 RGBA 转换为 ARGB
【发布时间】:2012-06-29 09:35:49
【问题描述】:

我正在尝试将 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);
    }
}

【问题讨论】:

    标签: performance rgba argb


    【解决方案1】:

    我将只关注交换功能:

    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
    }
    

    【讨论】:

    • 问题是关于 RGBA 到 ARGB,而不是 ARGB 到 BGRA。
    • 我可能不得不撤回,因为 ARGB 和 RGBA 显然是毫无意义的词。这似乎仍然与问题不匹配(它没有将 alpha 设置为 0xff)。 en.wikipedia.org/wiki/RGBA_color_space#Representation
    【解决方案2】:

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

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

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

    我会重写代码如下:

    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 访问,但我认为这不仅仅是通过避免按位运算和索引计算来抵消。另一种方法是(如在您的代码中)一次获取/存储一个像素(int),并在本地进行处理(这可以节省内存访问),但除非您有一些有效的方法来交换两个字节并设置本地 alpha (例如一些内联汇编,以便确保所有内容都在寄存器级别执行),它不会真正有帮助。

    【讨论】:

      【解决方案3】:

      你提供的代码很奇怪,因为它洗牌的不是 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 进行混洗。

      【讨论】:

      • 尽管这似乎是最快的实现,但实际图像显示为红色,因此操作中有问题。我需要提一下,这是针对 Android 手机的。
      • 咳咳,考虑到字节序,您似乎想转换 abgr->argb 即。您想一次转换 rgba->argb 并更改字节序。我说的对吗?
      • 另外,你需要把图片倒过来吗?
      • 我需要从 rgba->argb 转换,而不需要倒置图像。整个问题来自我使用 opengl glReadPixels(...) 以 RGBA 格式返回数据的事实,然后我必须将其转换为需要 ARGB 的兼容 android 位图。并且从 RGBA 到 ARGB 的实际转换必须以最大可能的速度执行。对于我正在处理的场景,没有使用 glReadPixels (例如帧缓冲区)的解决方法,因此我试图在每种可能的算法上压缩一些速度
      • 但是看你的代码有一个翻转和字节序变化。我还稍微更改了代码,因此它会像您的代码一样重新排序组件
      【解决方案4】:

      我已经很晚了。但是在动态生成视频时我遇到了完全相同的问题。通过重用缓冲区,我可以只为每一帧设置 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 毫秒,这对于我的用例来说已经足够了。

      【讨论】:

        【解决方案5】:

        使用汇编,以下是针对英特尔的。

        这个例子交换了红色和蓝色。

        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
        }
        

        【讨论】:

        • 这是低级优化不起作用的情况。我试图用字节移位编译一些琐碎的 C++ 代码,编译器将整个代码优化为 bswap edi。见godbolt.org/g/hk2QiR
        【解决方案6】:

        (pixel &lt;&lt; 24) | (pixel &gt;&gt; 8) 将 32 位整数向右旋转 8 位,这会将 32 位 RGBA 值转换为 ARGB。这是因为:

        • pixel &lt;&lt; 24 丢弃了左侧的RGBARGB 部分,从而产生A000
        • pixel &gt;&gt; 8 丢弃了右侧的RGBAA 部分,从而产生0RGB
        • A000 | 0RGB == ARGB

        【讨论】:

          猜你喜欢
          • 2012-02-04
          • 1970-01-01
          • 2020-08-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多