WPF imaging (WmpBitmapDecoder, WmpBitmapEncoder)
| Language | C# (.NET Core 3.0+ and .NET 5+ with a -windows target, .NET Framework 3.0+) |
|---|---|
| License | MIT (dotnet/wpf); the JPEG XR codec itself is the closed-source Windows WIC codec |
| Repository | github.com/dotnet/wpf |
| Documentation | WmpBitmapDecoder, WmpBitmapEncoder |
| Depends on | Windows (Windows Imaging Component) |
| Read / write | yes / 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:
<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
Basic usage: convert JPEG XR to PNG#
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#
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:
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#
if (frame.Metadata is BitmapMetadata meta)
{
Console.WriteLine($"camera: {meta.CameraModel}, taken: {meta.DateTaken}, software: {meta.ApplicationName}");
}
Tips#
BitmapCacheOption.OnLoaddecodes the image immediately, so you can close the stream; without it the stream must stay open while you use the frame.- WIC objects are tied to the thread that created them (they’re
DispatcherObjects). CallFreeze()on a bitmap before passing it to another thread.