为什么在WPF中将BitmapSource保存为bmp、jpeg和png格式时,结果完全不同?

7

我写了一个小型的实用类,可以将BitmapSource对象保存为图像文件。这些图像文件可以是bmp、jpeg或png格式。以下是代码:

public class BitmapProcessor
{
    public void SaveAsBmp(BitmapSource bitmapSource, string path)
    {
        Save(bitmapSource, path, new BmpBitmapEncoder());
    }

    public void SaveAsJpg(BitmapSource bitmapSource, string path)
    {
        Save(bitmapSource, path, new JpegBitmapEncoder());
    }

    public void SaveAsPng(BitmapSource bitmapSource, string path)
    {
        Save(bitmapSource, path, new PngBitmapEncoder());
    }

    private void Save(BitmapSource bitmapSource, string path, BitmapEncoder encoder)
    {
        using (var stream = new FileStream(path, FileMode.Create))
        {
            encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
            encoder.Save(stream);
        }
    }
}

每个 Save 方法都可以工作,但是对于 bmp 和 jpeg 格式,我得到了意外的结果。只有 png 格式可以精确地复制我在使用 WPF Image 控件在屏幕上显示 BitmapSource 时所看到的内容。
以下是结果:

BMP - 太暗了

太暗了 http://img822.imageshack.us/img822/7403/terrainbmp.png


JPEG - 过度饱和

过度饱和 http://img816.imageshack.us/img816/8127/terrainjpeg.jpg


PNG - 正确的格式

正确的格式 http://img810.imageshack.us/img810/6243/terrainpng.png


为什么不同文件类型的结果完全不同?

需要注意的是,我的示例中BitmapSource使用了0.1的alpha值(因此看起来非常褪色),但应该可以在任何图像格式中显示结果颜色。我知道如果我使用HyperSnap之类的工具进行屏幕截图,无论保存成什么文件类型,它都会显示正确。


这是一个以bmp格式保存的HyperSnap屏幕截图:

correct http://img815.imageshack.us/img815/9966/terrainbmphypersnap.png

正如您所看到的,这不是一个问题,因此WPF的图像编码器肯定有一些奇怪的地方。

我是否设置错误?我是否遗漏了什么?


你是在内存中绘制这些图像然后保存吗?还是从磁盘加载的图像? - Adam Sills
@Adam,这些图像是从内存(和屏幕)中的Viewport3D使用RenderTargetBitmap.Render(UIElement)捕获的。 - devuxer
1个回答

7
我个人认为看到您所看到的内容并不太令人惊讶。BMP和JPG不支持透明度,而PNG支持。
接下来是一段代码,它在图像中创建了一个部分透明的蓝色矩形。
WriteableBitmap bm = new WriteableBitmap( 100, 100, 96, 96, PixelFormats.Pbgra32, null );
bm.Lock();

Bitmap bmp = new Bitmap( bm.PixelWidth, bm.PixelHeight, bm.BackBufferStride, System.Drawing.Imaging.PixelFormat.Format32bppArgb, bm.BackBuffer );
using( Graphics g = Graphics.FromImage( bmp ) ) {
    var color = System.Drawing.Color.FromArgb( 20, System.Drawing.Color.Blue);
    g.FillRectangle(
        new System.Drawing.SolidBrush( color ),
        new RectangleF( 0, 0, bmp.Width, bmp.Height ) );
}

bmp.Save( @".\000_foo.bmp", System.Drawing.Imaging.ImageFormat.Bmp );
bmp.Save( @".\000_foo.jpg", System.Drawing.Imaging.ImageFormat.Jpeg );
bmp.Save( @".\000_foo.png", System.Drawing.Imaging.ImageFormat.Png );

bmp.Dispose();

bm.AddDirtyRect( new Int32Rect( 0, 0, bm.PixelWidth, bm.PixelHeight ) );
bm.Unlock();

new BitmapProcessor().SaveAsBmp( bm, @".\foo.bmp" );
new BitmapProcessor().SaveAsJpg( bm, @".\foo.jpg" );
new BitmapProcessor().SaveAsPng( bm, @".\foo.png" );

PNG格式始终有效,无论是System.Drawing还是WPF编码器。JPG和BMP编码器不起作用。它们显示一个纯蓝色的矩形。

关键在于我没有指定图像的背景颜色。如果没有背景颜色,在不支持alpha通道(BMP/JPG)的格式中,图像将无法正确呈现。只需添加一行额外的代码:

g.Clear( System.Drawing.Color.White );
g.FillRectangle(
    new System.Drawing.SolidBrush( color ),
    new RectangleF( 0, 0, bmp.Width, bmp.Height ) );

我的图像有背景色,因此不支持alpha通道的编码器可以确定每个像素的输出颜色。现在我的所有图像看起来都正确。
在你的情况下,你应该渲染一个指定了背景颜色的控件,或者在渲染图像时绘制背景色。
另外,你需要知道的是,第三方截屏工具之所以能够正常工作,是因为最终透明颜色在那时有一个背景色(在具有背景颜色的窗口上)。但在WPF内部,你处理的是没有设置背景颜色的元素;在元素上使用RenderTargetBitmap不会继承其各个父元素的属性,如背景颜色。

+1。谢谢,亚当!没有背景会让事情变得混乱,这很有道理。有趣的是,如果你看一下HyperSnap保存的bmp文件,它有一个非常浅的蓝色背景。而对于png文件,它完全是透明的。 - devuxer
这是淡蓝色的背景还是半透明的蓝色背景?你确定背景在元素(或子元素)上吗?如果它在更高一级别,那就不算。 - Adam Sills
实际上,BMP文件确实支持透明度。自BITMAPINFOHEADER以来,这已经是该格式的一部分,但很少正确使用,如果有的话。Windows XP在UI中的许多地方都使用了它。 - Tom Lint

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接