I wrote a little utility class that saves BitmapSource
objects to image files. The image files can be either bmp, jpeg, or png. Here is the code:
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);
}
}
}
Each of the three Save
methods work, but I get unexpected results with bmp and jpeg. Png is the only format that produces an exact reproduction of what I see if I show the BitmapSource
on screen using a WPF Image
control.
Here are the results:
BMP - too dark
too dark http://img822.imageshack.us/img822/7403/terrainbmp.png
JPEG - too saturated
too saturated http://img816.imageshack.us/img816/8127/terrainjpeg.jpg
PNG - correct
correct http://img810.imageshack.us/img810/6243/terrainpng.png
Why am I getting completely different results for different file types?
I should note that the BitmapSource
in my example uses an alpha value of 0.1 (which is why it appears very desaturated), but it should be possible to show the resulting colors in any image format. I know if I take a screen capture using something like HyperSnap, it will look correct regardless of what file type I save to.
Here's a HyperSnap screen capture saved as a bmp:
correct http://img815.imageshack.us/img815/9966/terrainbmphypersnap.png
As you can see, this isn't a problem, so there's definitely something strange about WPF's image encoders.
Do I have a setting wrong? Am I missing something?
I don't personally think it too surprising to see what you're seeing. BMP and JPG don't support opacity and PNG does.
Take this code, which creates a partially transparent blue rectangle in an image.
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" );
The PNG formats always work, whether it's System.Drawing or the WPF encoders. The JPG and BMP encoders do not work. They show a solid blue rectangle.
The key here is I failed to specify a background color in my image. Without a background color, the image won't render correctly in formats that don't support an alpha channel (BMP/JPG). With one extra line of code:
g.Clear( System.Drawing.Color.White );
g.FillRectangle(
new System.Drawing.SolidBrush( color ),
new RectangleF( 0, 0, bmp.Width, bmp.Height ) );
My image has a background color, so the encoders that do not support an alpha channel can determine what the output color should be per pixel. Now all my images look correct.
In your case, you should either RenderTargetBitmap a control with a background color specified, or paint a background color when you're rendering your image.
And FYI, the reason your 3rd party print screen works is that ultimately the transparent colors have a background color at that point (being on a window which has a background color). But inside WPF, you're dealing with elements that don't have one set; using RTB on an element does not inherit its various parent element's properties like background color.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With