WPF TransformedBitmap / ScaleTransform打破alpha /全黑

时间:2015-12-26 06:56:38

标签: c# wpf bitmap

我的后端代码存储带有alpha的图像以保持与尽可能多的格式的兼容性,但是当我尝试调整大小并转换为JPG(没有alpha)时,我在没有设置alpha通道的地方全黑。

// Creation
BitmapImage bmp = new BitmapImage();
bmp.BeginInit();
bmp.DecodePixelWidth = decodeWidth;
bmp.DecodePixelHeight = decodeHeight;
bmp.UriSource = new Uri(Filename);
bmp.CacheOption = BitmapCacheOption.OnLoad;
bmp.EndInit();
bmp.Freeze();

// Scaling
var scalar = new ScaleTransform(scale, scale);
var bmp = new TransformedBitmap(img, scalar);
bmp.Freeze();

// Testing
var formatted = new FormatConvertedBitmap(bmp, PixelFormats.Bgr32, null, 0);
PngBitmapEncoder png = new PngBitmapEncoder();
MemoryStream ms = new MemoryStream();
png.Frames.Add(BitmapFrame.Create(formatted);
png.Save(ms);
File.WriteAllBytes(dest, ms);

显然,所有代码都不在同一个函数中,但要点就在那里。 在缩放后保存FormatConvertedBitmap时,如果原始位图中指定了alpha,则为黑色。 在缩放之前,它按预期工作。没有黑暗。

比较 (因链接限制而删除)

您可以从上图中看到,大多数原件都有一个Alpha通道,只有在转换后才会转换为黑色。

我找到了Saving Windows.Media.Drawing with BmpBitmapEncoder black image - how to remove alpha? 我的问题很相似,只是这不仅仅是空白背景。这种黑暗有“细节”。

TLDR 转换/缩放似乎打破了更改WPF中像素格式的能力。
我想要的是什么:非黑色图片。 alpha下方的RGB绝对不是黑色 我得到了什么: alpha通道对结果有一些影响的黑色图像 原因:据我所知,这是由于重新调整尺寸 我做错了什么,或者这是一个奇怪的错误?

原始dds
https://dl.dropboxusercontent.com/u/37301843/MASSEFFECT3.EXE_0xCFC054A8%20No%20MIPS.dds

TEST PROJECT https://dl.dropboxusercontent.com/u/37301843/StackOverflowExample.7z
这个项目充分展示了我的情况 它需要Windows 8.1+(对于dds编解码器)和.NET 4.6。

思考/测试过程
我想我会在这个阶段加入一些背景。
1.写了一个dds友好的图像转换器(实际上这个工具的原始目的) 2.发现使用JpegBitmapEncoder类转换为jpg时,使用alpha通道的dds'变黑了。
3.通过图案来表明这是一个阿尔法问题 4.通过在各个点保存图像进行调试,显示图像很好,直到调整大小为止 5.在调整大小之前,保存为jpg工作。没有黑暗 6.调整大小后,jpg在alpha = 0的所有区域都是黑色的 7.想象编码器是预乘alpha,使得得到的像素是黑色的 8.无法解决为什么缩放会做这样的事情,但是试图通过转换为Bgr32来剥离alpha 9.缩放后转换时仍为黑色。

2 个答案:

答案 0 :(得分:0)

我不喜欢这个作为答案,因为它更像是一种解决方法,而不是解决为什么缩放不能保留alpha。

但这是我的解决方法。表现出色得令人惊讶。
1)拉出alpha通道 2)使用alpha值构建新的位图作为所有rgb通道,即rgb = alpha,以便生成的图像为灰度。
3)将原始图像的色彩空间从ARGB更改为RGB。由于删除了alpha,因此不会破坏任何内容 4)缩放两个图像(原始RGB和原始alpha) 5)将缩放后的RGB色彩空间更改回ARGB(同样,这并没有真正改变任何内容)。
6)将缩放的alpha合并为缩放的RGB(现在的ARGB)位图。

WriteableBitmap bmp = mipMap.BaseImage;
int origWidth = bmp.PixelWidth;
int origHeight = bmp.PixelHeight;
int origStride = origWidth * 4;
int newWidth = (int)(origWidth * scale);
int newHeight = (int)(origHeight * scale);
int newStride = newWidth * 4;



// Pull out alpha since scaling with alpha doesn't work properly for some reason
WriteableBitmap alpha = new WriteableBitmap(origWidth, origHeight, 96, 96, PixelFormats.Bgr32, null);
unsafe
{
    int index = 3;
    byte* alphaPtr = (byte*)alpha.BackBuffer.ToPointer();
    byte* mainPtr = (byte*)bmp.BackBuffer.ToPointer();
    for(int i = 0; i < origWidth * origHeight * 3; i += 4)
    {
        // Set all pixels in alpha to value of alpha from original image - otherwise scaling will interpolate colours
        alphaPtr[i] = mainPtr[index];
        alphaPtr[i+1] = mainPtr[index];
        alphaPtr[i+2] = mainPtr[index];
        alphaPtr[i+3] = mainPtr[index];
        index += 4;
    }
}

FormatConvertedBitmap main = new FormatConvertedBitmap(bmp, PixelFormats.Bgr32, null, 0);

// Scale RGB and alpha
ScaleTransform scaletransform = new ScaleTransform(scale, scale);
TransformedBitmap scaledMain = new TransformedBitmap(main, scaletransform);
TransformedBitmap scaledAlpha = new TransformedBitmap(alpha, scaletransform);

// Put alpha back in
FormatConvertedBitmap newConv = new FormatConvertedBitmap(scaledMain, PixelFormats.Bgra32, null, 0);
WriteableBitmap resized = new WriteableBitmap(newConv);
WriteableBitmap newAlpha = new WriteableBitmap(scaledAlpha);
unsafe
{
    byte* resizedPtr = (byte*)resized.BackBuffer.ToPointer();
    byte* alphaPtr = (byte*)newAlpha.BackBuffer.ToPointer();
    for (int i = 3; i < newStride; i += 4)
        resizedPtr[i] = alphaPtr[i];
}

似乎表现不错,但更重要的是它可以满足我的需求。

答案 1 :(得分:0)

我注意到了同样的问题,并修改了一下你的版本。在缩放之前,Alpha被放入一个gray8位图。

    private static BitmapSource GetAphaAsGrayBitmap(BitmapSource rgba)
    {
        WriteableBitmap bmp = new WriteableBitmap(rgba);
        WriteableBitmap alpha = new WriteableBitmap(rgba.PixelWidth, rgba.PixelHeight, 96, 96, PixelFormats.Gray8, null);

        unsafe
        {
            byte* alphaPtr = (byte*)alpha.BackBuffer.ToPointer();
            byte* mainPtr = (byte*)bmp.BackBuffer.ToPointer();
            for (int i = 0; i < bmp.PixelWidth * bmp.PixelHeight; i++)
                alphaPtr[i] = mainPtr[i * 4 + 3];
        }

        return alpha;
    }

    private static BitmapSource MergeAlphaAndRGB(BitmapSource rgb, BitmapSource alpha)
    {
        // Put alpha back in
        WriteableBitmap dstW = new WriteableBitmap(new FormatConvertedBitmap(rgb, PixelFormats.Bgra32, null, 0));
        WriteableBitmap alphaW = new WriteableBitmap(alpha);
        unsafe
        {
            byte* resizedPtr = (byte*)dstW.BackBuffer.ToPointer();
            byte* alphaPtr = (byte*)alphaW.BackBuffer.ToPointer();
            for (int i = 0; i < dstW.PixelWidth * dstW.PixelHeight; i++)
                resizedPtr[i * 4 + 3] = alphaPtr[i];
        }

        return dstW;
    }

    private static BitmapSource GetScaledBitmap(BitmapSource src, ScaleTransform scale)
    {
        if (src.Format == PixelFormats.Bgra32) // special case when image has an alpha channel
        {
            // Put alpha in a gray bitmap and scale it
            BitmapSource alpha = GetAphaAsGrayBitmap(src);
            TransformedBitmap scaledAlpha = new TransformedBitmap(alpha, scale);

            // Scale RGB without taking in account alpha
            TransformedBitmap scaledSrc = new TransformedBitmap(new FormatConvertedBitmap(src, PixelFormats.Bgr32, null, 0), scale);

            // Merge them back
            return MergeAlphaAndRGB(scaledSrc, scaledAlpha);
        }
        else
        {
            return new TransformedBitmap(src, scale);
        }
    }