为啥在 WPF 中将 BitmapSource 保存为 bmp、jpeg 和 png 时会得到完全不同的结果
Posted
技术标签:
【中文标题】为啥在 WPF 中将 BitmapSource 保存为 bmp、jpeg 和 png 时会得到完全不同的结果【英文标题】:Why do I get completely different results when saving a BitmapSource to bmp, jpeg, and png in WPF为什么在 WPF 中将 BitmapSource 保存为 bmp、jpeg 和 png 时会得到完全不同的结果 【发布时间】:2010-06-15 00:54:59 【问题描述】:我编写了一个小实用程序类,将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 得到了意想不到的结果。如果我使用 WPF Image
控件在屏幕上显示 BitmapSource
,Png 是唯一一种可以精确复制我看到的内容的格式。
结果如下:
BMP - 太暗
too dark http://img822.imageshack.us/img822/7403/terrainbmp.png
JPEG - 太饱和了
too saturated http://img816.imageshack.us/img816/8127/terrainjpeg.jpg
PNG - 正确
correct 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,这些图像是使用RenderTargetBitmap.Render(UIElement)
从内存中(和屏幕上)Viewport3D
捕获的。
【参考方案1】:
我个人认为看到你所看到的并不太令人惊讶。 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 通道的编码器可以确定每个像素的输出颜色。现在我所有的图像看起来都是正确的。
在您的情况下,您应该 RenderTargetBitmap 一个指定背景颜色的控件,或者在渲染图像时绘制背景颜色。
仅供参考,您的第 3 方打印屏幕工作的原因是最终透明颜色在该点具有背景颜色(在具有背景颜色的窗口上)。但是在 WPF 中,您正在处理没有一组的元素;在元素上使用 RTB 不会继承其各种父元素的属性,例如背景颜色。
【讨论】:
+1。谢谢,亚当!没有背景会把事情搞砸是很有意义的。有趣的是,如果您查看从 HyperSnap 中保存的 bmp,它的背景是非常浅的蓝色。使用 png,它是完全透明的。 是浅蓝色背景还是部分透明的蓝色背景?你确定背景在你正在 RTB 的元素(或子元素)上吗?如果再高一级,则不算。 实际上,BMP 文件确实 支持透明度。至少从 BITMAPINFOHEADER 开始,它就成为格式的一部分,但很少正确使用,如果有的话。 Windows XP 在 UI 的很多地方都使用了它。以上是关于为啥在 WPF 中将 BitmapSource 保存为 bmp、jpeg 和 png 时会得到完全不同的结果的主要内容,如果未能解决你的问题,请参考以下文章
在 LyncSDK 视频对话中将传出视频源设置为 BitmapSource?
WPF 使用不安全代码快速从数组转 WriteableBitmap