Search code examples
c#drawing.net-standard

Converting an 24bppRGB memory to 32bppRGBx memory


I have an IntPtr pointing at a memory segment that stores pixel sequence in RGB888 format, so the length of this segment should be width * height * 3 bytes. I want to convert them to an byte array of pixels in RGB888x format, in other words, in a format every 4 elements in this array reprents a single pixel that the value of the last element doesn't matter.

Is there an efficient approach to achieve what I described?

I'm using .Net standard 2.0, so Span is available.

I'm currently using the code below :

int length = width * height * 3;
using (MemoryStream ms = new MemoryStream())
{
    for (int ofs = 0; ofs < length; ofs++)
    {
        ms.WriteByte(Marshal.ReadByte(pointer, ofs));
        if (ofs % 3 == 0)
        {
            ms.WriteByte(255);
        }
    }

    return new RawImageData()
    {
        Width = width,
        Height = height,
        ColorType = SKColorType.Rgba8888,
        Data = ms.ToArray()
    };
}

Update 20240813 10:20:00 GMT

According to @500 - Internal Server Error's suggestion, the code now is

int length = width * height * 3;
var buffer = new byte[length];
Marshal.Copy(pointer, buffer, 0, length);
using (var ms = new MemoryStream())
{
    for (var offset = 0; offset < length - 3; offset += 3)
    {
        ms.Write(buffer, offset, 3);
        ms.WriteByte(byte.MaxValue);
    }

    return new RawImageData()
    {
        Width = width,
        Height = height,
        ColorType = SKColorType.Rgba8888,
        Data = ms.ToArray()
    };
}

Solution

  • This is the code I'm currently using

    public static unsafe void Bgr24ToBgra32(
        byte* source,
        byte* target,
        int sourceStride,
        int targetStride,
        int width,
        int height)
    {
        Parallel.For(0, height, y =>
        {
            var sourceRow = source + y * sourceStride;
            var targetRow = (uint*)(target + y * targetStride);
    
            for (int x = 0; x < width; x++)
            {
                var sourceIndex = (sourceRow + x * 3);
                var s = *((uint*)sourceIndex);
                s = s  | 0xff000000;
                targetRow[x] = s;
            }
        });
    }
    

    It uses raw pointers and is therefore unsafe. You could probably do something similar with Span<T> or Memory<T> if you prefer safe code. It also uses unaligned reads that do have a speed penalty. So there are almost certainly faster implementations, especially once you start consider SIMD.

    But I would expect it to be a fair bit faster then your posted code. As always you should do some benchmarking to confirm.

    Note that there are some confusion regarding RGB vs BGR, both names are sometimes used to describe the same actual order, I believe this is due to endianess issues. Also note that this puts the Alpha after the color values, if you need it in front you might need to do something like s = (s << 24) | 0x000000ff instead.