views:

70

answers:

1

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


JPEG - too saturated

too saturated


PNG - correct

correct


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

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?

+4  A: 

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.

Adam Sills
+1. Thanks, Adam! It makes a lot of sense that not having a background messes things up. Interestingly, if you look at the bmp saved out of HyperSnap, it has a very light blue background. With the png, it's entirely transparent.
DanM
Is it a light blue background or a partially transparent blue background? And are you sure the background is on the element (or children) you are RTB'ing? If it's one level higher, it doesn't count.
Adam Sills