从一种 PixelFormat 转换为另一种 WPF

Convert from one PixelFormat to an other WPF

我知道这听起来微不足道,但我目前已经卡住了。

我有各种格式的图像作为输入。 Gray16、Gray8、Indexed8 等。我需要做的是将它们转换为 RGB24 格式,到目前为止我已经完成了这个

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

此方法抛出异常“缓冲区大小不足”

我猜它与我的 WritePixels 参数之一有关。但我不知道缓冲区的正确大小是多少。我找不到太多文档。任何帮助都会很好。

我使用的转换方法不是确定的,如果您知道更好的转换方法请告诉我。

首先:RGB 图像的默认格式(违反直觉)是 ARGB 的 BGR24 或 BGRA32。这是因为系统的字节序 (see here for why this happens)

其次:当你使用这种方法时,你根本不会转换PixelFormat。您只需获取所有像素,将它们转换为字节数组并将这些字节用于新的像素格式——这将完全不同地解释字节。因此,如果您的源图像是 BGRA 并且您转换为 RGB24,则第一个源像素的蓝色分量将被解释为第一个目标像素的红色分量,依此类推 - 第一个源 alpha 字节将被解释为第一个第二个像素的红色字节(不完全正确,但更容易理解。正确的映射是 a>b, r>g, g>r, r>b2... 因为颜色是如何映射到与它们是如何命名的)。这也是您的缓冲区大小不匹配的原因 - 因为不同的像素格式需要不同的字节数。

WriteableBitmap 的CopyPixels 和WritePixels 完全不知道底层的PixelFormat,完全忽略它。这仅在源和目标具有完全相同的 PixelFormat 和 Palette 或如果您手动重新计算和重新排列缓冲区中的所有字节时才有效。

您要找的东西要简单得多:它叫做 FormatConvertedBitmap。这是一个 BitmapSource,专门用于更改图像的 PixelFormat。下面这行应该完成将所有内容转换为正确的 RGBA32 图像的工作(仅对 RGB 使用 Bgr24)。

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

经过多次尝试,我找到了答案。

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

基本上问题出在我猜的 Stride 和 newPixel 数组维度中的某个地方。这是一个完全有效的转换函数。