All formats

WPF imaging (WmpBitmapDecoder, WmpBitmapEncoder)

LanguageC# (.NET Core 3.0+ and .NET 5+ with a -windows target, .NET Framework 3.0+)
LicenseMIT (dotnet/wpf); the JPEG XR codec itself is the closed-source Windows WIC codec
Repositorygithub.com/dotnet/wpf
DocumentationWmpBitmapDecoder, WmpBitmapEncoder
Depends onWindows (Windows Imaging Component)
Read / writeyes / yes; all pixel formats the WIC codec supports, including HDR half float and float

WPF’s imaging classes wrap the Windows Imaging Component. The Wmp prefix comes from “Windows Media Photo”, the old name of JPEG XR. WmpBitmapDecoder returns a BitmapFrame you can convert to any WPF pixel format or save with another encoder; WmpBitmapEncoder writes JPEG XR with lossless or lossy compression and the codec’s tuning options.

You don’t need a GUI: a console app or service can use these classes if the project targets Windows and enables WPF. They only work on Windows.

Installation#

Nothing to install; enable WPF in the project file:

XML
<PropertyGroup>
  <TargetFramework>net10.0-windows</TargetFramework>
  <UseWPF>true</UseWPF>
</PropertyGroup>

Basic usage: convert JPEG XR to PNG#

C#
using System;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;

using var input = File.OpenRead("in.jxr");
var decoder = new WmpBitmapDecoder(input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapFrame frame = decoder.Frames[0];
Console.WriteLine($"{frame.PixelWidth} x {frame.PixelHeight}, {frame.Format}");

// convert to 8-bit BGRA so the PNG encoder accepts any source format
var bgra = new FormatConvertedBitmap(frame, PixelFormats.Bgra32, null, 0);
var png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bgra));
using var output = File.Create("out.png");
png.Save(output);

Converting an HDR file to Bgra32 doesn’t tone-map it: values above 1.0 are clipped, so bright areas lose detail.

How to write JPEG XR#

C#
using System.IO;
using System.Windows.Media.Imaging;

var source = new BitmapImage(new System.Uri(Path.GetFullPath("in.png")));

var encoder = new WmpBitmapEncoder
{
    Lossless = false,
    ImageQualityLevel = 0.85f,   // 0.0 - 1.0; 1.0 means lossless
};
encoder.Frames.Add(BitmapFrame.Create(source));
using var output = File.Create("out.jxr");
encoder.Save(output);

For lossless output set Lossless = true. With UseCodecOptions = true you control the codec directly: QualityLevel (1-255, 1 is lossless), OverlapLevel (0-2), SubsamplingLevel (chroma subsampling), FrequencyOrder and tile slices.

How to read HDR pixel data#

Windows HDR screenshots are stored as 64 bits per pixel RGBA half float (scRGB, 1.0 = 80 nits). Convert to 128-bit float to get values above 1.0:

C#
using System;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;

using var input = File.OpenRead("screenshot.jxr");
var decoder = new WmpBitmapDecoder(input, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
BitmapFrame frame = decoder.Frames[0];

var rgba = new FormatConvertedBitmap(frame, PixelFormats.Rgba128Float, null, 0);
int stride = rgba.PixelWidth * 16;
var bytes = new byte[stride * rgba.PixelHeight];
rgba.CopyPixels(bytes, stride, 0);

float[] pixels = new float[bytes.Length / 4];
Buffer.BlockCopy(bytes, 0, pixels, 0, bytes.Length);
float maxValue = 0;
foreach (float v in pixels) maxValue = Math.Max(maxValue, v);
Console.WriteLine($"brightest channel value: {maxValue} ({maxValue * 80} nits)");

How to read metadata#

C#
if (frame.Metadata is BitmapMetadata meta)
{
    Console.WriteLine($"camera: {meta.CameraModel}, taken: {meta.DateTaken}, software: {meta.ApplicationName}");
}

Tips#