Search code examples
c#.netwicsystem.windows.mediabitmapencoder

JPEGEncoder Windows Media Imaging not honoring color profile of Image


I have the following image (have put a screen-grab of the image as its size is more than 2 MB - the original can be downloaded from https://drive.google.com/file/d/1rC2QQBzMhZ8AG5Lp5PyrpkOxwlyP9QaE/view?usp=sharing

enter image description here

I'm reading the image using the BitmapDecoder class and saving it using a JPEG Encoder. This results in the following image which is off color and faded.

var frame = BitmapDecoder.Create(new Uri(inputFilePath, UriKind.RelativeOrAbsolute),BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None).Frames[0];
    var encoder = new JpegBitmapEncoder();    
     encoder.Frames.Add(frame);
     using (var stream = File.OpenWrite(outputFilePath))
     {
     encoder.Save(stream);
     }

enter image description here

The image is using PhotoShop RGB Color scheme.I tried setting the color profile using the following code,but this results in this error The designated BitmapEncoder does not support ColorContexts

encoder.ColorContexts = frame.ColorContexts;

Update: Cloning the image seems to fix the issue. But when i resize the image using the following code for transformation the color profile is not preserved

Transform transform = new ScaleTransform(width / frame.Width * 96 / frame.DpiX, height / frame.Height * 96 / frame.DpiY, 0, 0); 
     var copy = BitmapFrame.Create(frame);
     var resized = BitmapFrame.Create(new 
     TransformedBitmap(copy, transform));          
     encoder.Frames.Add(resized);
     using (var stream = File.OpenWrite(outputFilePath))
     {
     encoder.Save(stream);
     }

Solution

  • The image bits are identical. This is a metadata issue. This image file contains lots of metadata (Xmp, Adobe, unknown, etc.) and this metadata contains two color profiles/spaces/contexts:

    1. ProPhoto RG (usually found in C:\WINDOWS\system32\spool\drivers\color\ProPhoto.icm)
    2. sRGB IEC61966-2.1 (usually found in WINDOWS\system32\spool\drivers\color\sRGB Color Space Profile.icm)

    The problem occurs because the two contexts order may differ in the target file for some reason. An image viewer can either use no color profile (Paint, Pain3D, Paint.NET, IrfanView, etc.), or use (from my experience) the last color profile in the file (Windows Photo Viewer, Photoshop, etc.).

    You can fix your issue if you clone the frame, ie:

    var frame = BitmapDecoder.Create(new Uri(inputFilePath, UriKind.RelativeOrAbsolute),BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None).Frames[0];
    var encoder = new JpegBitmapEncoder();    
    var copy = BitmapFrame.Create(frame);
    encoder.Frames.Add(copy);
    using (var stream = File.OpenWrite(outputFilePath))
    {
        encoder.Save(stream);
    }
    

    As in this case, the order is preserved as is.

    If you recreate the frame or transform it in any way, you can copy the metadata and color contexts like this:

    var ctxs = new List<ColorContext>();
    ctxs.Add(frame.ColorContexts[1]); // or just remove this
    ctxs.Add(frame.ColorContexts[0]); // first color profile is ProPhoto RG, make sure it's last
    var resized = BitmapFrame.Create(new TransformedBitmap(frame, transform), frame.Thumbnail, (BitmapMetadata)frame.Metadata, ctxs.AsReadOnly());
    var encoder = new JpegBitmapEncoder();
    encoder.Frames.Add(resized);
    using (var stream = File.OpenWrite("resized.jpg"))
    {
        encoder.Save(stream);
    }
    

    Note an image that has more than one color context is painful (and IMHO should not be created / saved). Color profiles are for there to ensure a correct display, so two or more (rather conflicting! sRGB vs ProPhoto) profiles associated with one image means it can be displayed ... in two or more ways.

    You'll have to determine what's you're preferred color profile in this strange case.