Convert from one PixelFormat to an other WPF

Viewed 669

I know this sounds trivial, but I currently have stuck.

I have images in various formats as input. Gray16,Gray8,Indexed8 and so on. What do I need is to covert them to RGB24 format, so far I've done this

public WriteableBitmap ConvertToRgb(WriteableBitmap source)
        {
            <-- create the new bitmap of the same width/height as the source with specific RGB24 format-->
            var newBitmap = new WriteableBitmap(source.PixelWidth, source.PixelHeight, source.DpiX, source.DpiY, PixelFormats.Rgb24, source.Palette);
            
            <-- create my pixel array -->
            byte[] pixels = new byte[
                        source.PixelHeight * source.PixelWidth *
                            source.Format.BitsPerPixel / 8];
            <-- copy my original pixels into the array) --> 
            source.CopyPixels(pixels, source.PixelWidth * source.Format.BitsPerPixel / 8, 0);
            <-- write the pixels into the new image --> 
            newBitmap.WritePixels(
                 new Int32Rect(0, 0, newBitmap.PixelWidth, newBitmap.PixelHeight),
                 pixels,
                 newBitmap.PixelWidth * newBitmap.Format.BitsPerPixel/8,
                 0);

            return newBitmap;
        }

This method throws an exception "Buffer size is not sufficient"

I guess it has to do something with one of my WritePixels parameters. But I have no idea what is the right size of the buffer. I couldn't find much documentation. Any help would be nice.

The method I'm using to convert is not definitive, if you know a better way of converting please let me know.

2 Answers

First: The default Format for RGB images is (counterintuitively) BGR24 or BGRA32 for ARGB. This is because of the endian-ness of the system (see here for why this happens)

Second: When you use this approach, you will not convert the PixelFormat at all. You will just take all the pixels, convert them into a byte array and use those bytes for the new pixel format - which will interpret the bytes completely differently. So if your source image is in BGRA and you convert to RGB24, the blue component of the first source pixel will be interpreted as the red component of the first destination pixel, and so on - the first source alpha byte will be interpreted as the first red byte for the second pixel (not completely right, but easier to follow. The correct mapping is a>b, r>g, g>r, r>b2... because of how the colors are mapped to bytes directly opposite of how they are named). This is also why your buffer sizes don't match - because different pixel formats need a different number of bytes.

WriteableBitmap's CopyPixels and WritePixels are completely unaware of the underlying PixelFormat and completely ignore it. This will only work, if the source and destination have the exact same PixelFormat and Palette or if you recalculate and rearrange all the bytes in your buffer manually.

The thing you're looking for is much easier: it's called FormatConvertedBitmap. This is a BitmapSource especially used for changing the PixelFormat of an image. This following line should do the job of converting everything into a proper RGBA32 image (use Bgr24 for RGB only).

var rgba32 = new FormatConvertedBitmap(source, PixelFormats.Bgra32, source.Palette,0);

So after many attempts I found the answer.

  public WriteableBitmap ConvertToRgb(WriteableBitmap source)
        {
            var newBitmap = new WriteableBitmap(source.PixelWidth, source.PixelHeight, source.DpiX, source.DpiY, PixelFormats.Rgb24, null);

            byte[] pixels = new byte[
                        source.PixelHeight * source.PixelWidth *
                            source.Format.BitsPerPixel / 8];

            source.CopyPixels(pixels, source.PixelWidth * source.Format.BitsPerPixel / 8, 0);

            byte[] newPixels = new byte[newBitmap.PixelWidth * newBitmap.PixelHeight * newBitmap.Format.BitsPerPixel / 8];

            var pixelCounter = 0;
            var colorArray = source.Palette != null ? source.Palette.Colors.ToArray() : BitmapPalettes.Gray256.Colors.ToArray();

            foreach (var pixel in pixels)
            {
                newPixels[pixelCounter] = colorArray[pixel].R;
                pixelCounter++;
                newPixels[pixelCounter] = colorArray[pixel].G;
                pixelCounter++;
                newPixels[pixelCounter] = colorArray[pixel].B;
                pixelCounter++;
            }

            newBitmap.WritePixels(
                 new Int32Rect(0, 0, newBitmap.PixelWidth, newBitmap.PixelHeight),
                 newPixels,
                 newBitmap.PixelWidth * newBitmap.Format.BitsPerPixel / 8,
                 0);

            return newBitmap;
        }

Basically the problem was somwhere in the Stride I guess and the newPixel array dimensions. This is a fully working converting function.

Related