tags:

views:

188

answers:

1

How can I save an Image with it's original encoding?

It seems that the only way to save an Image is by using a BitmapEncoder but I don't know how I can get the correct format from the image.

Example:

Clipboard.GetImage() returns a InteropBitmap which doesn't seem to contain any information about the original format.

I also tried using an Extension method:

public static void Save(this BitmapImage image, System.IO.Stream stream)
{
    var decoder = BitmapDecoder.Create(image.StreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
    var encoder = BitmapEncoder.Create(decoder.CodecInfo.ContainerFormat);
    foreach (var frame in decoder.Frames)
    {
        encoder.Frames.Add(BitmapFrame.Create(frame));
    }
    encoder.Save(stream);
}

but the problem is that

  1. the ImageSource is not always a BitmapImage (Clipboard.GetImage()) for example and
  2. the image.StreamSource can be null in some cases (seems to be when the image is loaded via a relative Uri)

Any suggestions?

+7  A: 

The fundamental question here is "How do I take an arbitrary ImageSource and learn what format it was originally encoded in?"

The answer is that you can't always do this, but for most practical purposes there is a workaround available. As your code above shows, once you've learned what format to use the rest is easy.

The reason you can't always find out the original format is that it is possible (with some very tricky code!) to subclass BitmapSource to create a new ImageSource that gets its data from anywhere you like. For example, I could implement a PseudoRandomBitmapSource that returns random pixels. In this case the "original format" is probably the seed used for the random number generator.

If you are dealing with one of the built in ImageSource classes, the way to find out the original encoding depends on which exact class you are using:

  1. For BitmapImage, you can use StreamSource or UriSource, whichever one is set. Either of these can be passed to a BitmapDecoder.Create overload. Your example code shows how to do this for StreamSource. It is exactly the same for UriSource except you need to new Uri(BaseUri, UriSource) and pass that to the BitmapDecoder.Create overload that takes a Uri.

  2. For ColorConvertedBitmap, CroppedBitmap, FormatConvertedBitmap, and TransformedBitmap there is a public "Source" property which you can use to get the underlying source, then recursively check its encoding using this algorithm.

  3. For CachedBitmap, you can only get to the source bitmap through an internal field. You can access using reflection if you have enough privileges, otherwise you're out of luck.

  4. For RenderTargetBitmap, WritableBitmap, D3DImage, and DrawingImage there is no original encoding since the image is being constructed "on the fly" from a vector format or an algorithm.

  5. For BitmapFrame, use the Decoder property to get the decoder, then use CodecInfo.ContainerFormat.

  6. For InteropBitmap or UnmanagedBitmapWrapper it is very complicated. Basically you need to use reflection to read the internal WicSourceHandle property, then call DangerousGetHandle() to get an IntPtr which is actually an unmanaged IUnkown. Using unmanaged code, QueryInterface for IWICBitmapDecoder. If successful you can call IWICBitmapDecoder.GetContainerFormat to get the format Guid (this is still unmanaged code). If not, all information about the original source has been lost.

As you can see, there are quite a few situations where you can't get the source (eg an InteropBitmap for a fully decoded bitmap) or where getting the source requires special techniques and privileges (unmanaged code for InteropBitmap, or reflection on internal fields for CachedBitmap).

Because it is often difficult to impossible to get the original format, it is a good idea to look for ways to preserve this information as it is passed into your code. If you control the source of the image, it may be as simple as creating a ImageSourceAndFormat class:

public class BitmapSourceAndFormat
{
  public ImageSource Source { get; set; }
  public Guid OriginalFormat { get; set; }
}

This is particularly useful if you are placing the image on the clipboard. In addition to adding the image to the DataObject normally, you can also add a BitmapSourceAndFormat object. If you do this, the Paste operation will receive a DataObject containing both of these formats. Your code then only needs to check for the BitmapSourceAndFormat object first. If found it can simply access it to get the original format. If not, it must resort to the means described above.

One last note: For clipboard pastes, you can check the available data format strings. Some useful ones are: Bitmap, Dib, Tiff, Gif, Jpeg, and FileName. For "Tiff", "Gif", and "Jpeg" you can hard-code the required format. For "FileName" you can open the file yourself to get a stream to pass to a BitmapDecoder. For "Dib" or "Bitmap" without anything else, you know the original format has been lost.

Ray Burns
wow - this is an excellent answer. Thank you so much! Great tips regarding copy/paste!
Patrick Klug
+ thanks for not only telling me what I wanted to know but also telling me what my fundamental question is! I should have described it better.
Patrick Klug