Skip to main content

Crate ultrajpeg

Crate ultrajpeg 

Source
Expand description

§ultrajpeg

ultrajpeg is a Rust library for working with JPEG images.

It provides a native Rust API for encoding and decoding plain JPEG images, MPF-bundled gain-map JPEGs, ICC/EXIF payloads, UltraHDR XMP, and ISO 21496-1 metadata.

§What The Crate Does

ultrajpeg sits at the point where three concerns meet:

  • JPEG pixel coding
  • JPEG container metadata and marker layout
  • Ultra HDR gain-map packaging and recovery

The crate is synchronous and native-first. The public API is centered around:

  • inspect(...) for metadata-only inspection
  • inspect_container_layout(...) for codestream-boundary inspection
  • decode(...) and decode_with_options(...) for pixel decode
  • encode(...) and Encoder for structured JPEG and Ultra HDR authoring
  • parse_gain_map_xmp(...) and parse_iso_21496_1(...) for raw payload parsing
  • prepare_sdr_primary(...) for caller-managed HDR workflows
  • compute_gain_map(...) and encode_ultra_hdr(...) for gain-map workflows

§Choosing An Entry Point

Use:

  • inspect(...) when you only need JPEG, ICC, EXIF, XMP, or ISO 21496-1 metadata and do not want to decode pixels
  • decode(...) when you want the decoded primary image and, when present, the decoded gain-map image
  • inspect_container_layout(...) when you need codestream offsets and lengths without decoding pixels
  • decode_with_options(...) when you also need to retain the raw primary JPEG or gain-map JPEG codestream bytes
  • encode(...) when you already have the primary image and optional gain-map payload you want to package choose scan mode with progressive and size-vs-time policy with CompressionEffort
  • parse_gain_map_xmp(...) or parse_iso_21496_1(...) when you need to validate or compare raw Ultra HDR metadata payloads yourself
  • prepare_sdr_primary(...) when you manage HDR pixel transforms yourself and need a supported SDR primary image plus matching metadata before computing a gain map use your own SDR primary instead when you already have a caller-specific SDR rendering policy you want to preserve exactly
  • compute_gain_map(...) when you want to generate a gain map from HDR and SDR inputs without encoding yet
  • encode_ultra_hdr(...) when you want the crate to compute the gain map and package the final Ultra HDR JPEG in one step

§Public Surface Summary

The main public API lives at the crate root:

  • functions:
    • inspect
    • inspect_container_layout
    • decode
    • decode_with_options
    • encode
    • parse_gain_map_xmp
    • parse_iso_21496_1
    • prepare_sdr_primary
    • compute_gain_map
    • encode_ultra_hdr
  • core types:
    • Image
    • PixelFormat
    • ColorGamut
    • ColorTransfer
    • GainMap
    • GainMapMetadata
    • HdrOutputFormat
  • structured crate types:
    • ParsedGainMapXmp
    • ContainerKind
    • CodestreamLayout
    • ContainerLayout
    • ColorMetadata
    • PrimaryMetadata
    • UltraHdrMetadata
    • MetadataLocation
    • GainMapMetadataSource
    • DecodedGainMap
    • DecodedImage
    • Inspection
    • DecodeOptions
    • GainMapChannels
    • GainMapScale
    • ComputeGainMapOptions
    • PreparePrimaryOptions
    • PreparedPrimary
    • ComputedGainMap
    • GainMapBundle
    • CompressionEffort
    • EncodeOptions
    • UltraHdrEncodeOptions
    • Encoder
  • module:
    • icc

§Major Scenarios

§Plain JPEG Encode

let image = Image::from_data(
    2,
    2,
    PixelFormat::Rgb8,
    ColorGamut::DisplayP3,
    ColorTransfer::Srgb,
    vec![
        255, 0, 0, 0, 255, 0,
        0, 0, 255, 255, 255, 255,
    ],
)?;

let jpeg = ultrajpeg::encode(&image, &EncodeOptions::default())?;

§Plain JPEG Decode

let decoded = decode(bytes)?;

assert_eq!(decoded.image.width, 4);
assert!(decoded.gain_map.is_none());
assert!(decoded.primary_jpeg.is_none());

§Inspect Metadata Without Decoding Pixels

let inspection = inspect(bytes)?;

assert!(inspection.primary_jpeg_len > 0);
assert!(inspection.gain_map_jpeg_len.is_some());
assert!(inspection.primary_metadata.color.icc_profile.is_some());
assert!(inspection.ultra_hdr.is_some());

§Inspect Container Layout Without Decoding Pixels

let layout = inspect_container_layout(bytes)?;

assert_eq!(layout.kind, ContainerKind::Mpf);
assert_eq!(layout.primary_index, 0);
assert_eq!(layout.gain_map_index, Some(1));
assert_eq!(layout.codestreams.len(), 2);

§Parse Raw Ultra HDR Payloads Explicitly

let inspection = inspect(bytes)?;
let ultra_hdr = inspection.ultra_hdr.as_ref().unwrap();

let parsed_xmp = parse_gain_map_xmp(ultra_hdr.xmp.as_deref().unwrap())?;
let parsed_iso = parse_iso_21496_1(ultra_hdr.iso_21496_1.as_deref().unwrap())?;

// `ultra_hdr.iso_21496_1` is the effective gain-map payload. Do not pass the
// primary JPEG's four-byte version-only ISO APP2 block to `parse_iso_21496_1`.

assert!(parsed_xmp.metadata.hdr_capacity_max >= 4.0);
assert!(parsed_iso.hdr_capacity_max >= 4.0);

§Encode An Ultra HDR JPEG From An Existing Gain Map

let primary = Image::from_data(
    2,
    2,
    PixelFormat::Rgb8,
    ColorGamut::DisplayP3,
    ColorTransfer::Srgb,
    vec![
        255, 0, 0, 0, 255, 0,
        0, 0, 255, 255, 255, 255,
    ],
)?;
let gain_map = Image::from_data(
    2,
    2,
    PixelFormat::Gray8,
    ColorGamut::Bt709,
    ColorTransfer::Linear,
    vec![0, 64, 128, 255],
)?;

let jpeg = ultrajpeg::encode(
    &primary,
    &EncodeOptions {
        gain_map: Some(GainMapBundle {
            image: gain_map,
            metadata: GainMapMetadata::new(),
            quality: 85,
            progressive: false,
            compression: CompressionEffort::Balanced,
        }),
        ..EncodeOptions::ultra_hdr_defaults()
    },
)?;

§Compute A Gain Map First, Then Bundle It

let hdr = Image::from_data(
    1,
    1,
    PixelFormat::Rgba32F,
    ColorGamut::DisplayP3,
    ColorTransfer::Linear,
    [1.5f32, 0.5, 0.5, 1.0]
        .into_iter()
        .flat_map(f32::to_le_bytes)
        .collect(),
)?;
let primary = Image::from_data(
    1,
    1,
    PixelFormat::Rgb8,
    ColorGamut::DisplayP3,
    ColorTransfer::Srgb,
    vec![255, 128, 128],
)?;

let computed = ultrajpeg::compute_gain_map(
    &hdr,
    &primary,
    &ComputeGainMapOptions {
        scale: GainMapScale::Default,
        ..ComputeGainMapOptions::default()
    },
)?;
let jpeg = ultrajpeg::encode(
    &primary,
    &EncodeOptions {
        gain_map: Some(computed.into_bundle(90, false, CompressionEffort::Balanced)),
        ..EncodeOptions::ultra_hdr_defaults()
    },
)?;

GainMapScale::Default computes the gain map at half primary-image width and height. Use GainMapScale::Full when quality matters most, or GainMapScale::Smallest when you explicitly want the most aggressive quarter-resolution tradeoff.

§Prepare An SDR Primary Image From HDR Pixels

let hdr = Image::from_data(
    1,
    1,
    PixelFormat::Rgba32F,
    ColorGamut::DisplayP3,
    ColorTransfer::Linear,
    [1.5f32, 0.5, 0.5, 1.0]
        .into_iter()
        .flat_map(f32::to_le_bytes)
        .collect(),
)?;

let prepared = prepare_sdr_primary(&hdr, &PreparePrimaryOptions::ultra_hdr_defaults())?;
let computed = compute_gain_map(&hdr, &prepared.image, &Default::default())?;
let jpeg = ultrajpeg::encode(
    &prepared.image,
    &EncodeOptions {
        primary_metadata: prepared.metadata.clone(),
        gain_map: Some(computed.into_bundle(90, false, CompressionEffort::Balanced)),
        ..EncodeOptions::default()
    },
)?;

If the source HDR peak is known more precisely, pass it explicitly:

let prepared = prepare_sdr_primary(
    &hdr,
    &PreparePrimaryOptions {
        source_peak_nits: Some(4000.0),
        ..PreparePrimaryOptions::ultra_hdr_defaults()
    },
)?;

§One-Shot Ultra HDR Packaging

let hdr = Image::from_data(
    1,
    1,
    PixelFormat::Rgba32F,
    ColorGamut::DisplayP3,
    ColorTransfer::Linear,
    [1.5f32, 0.5, 0.5, 1.0]
        .into_iter()
        .flat_map(f32::to_le_bytes)
        .collect(),
)?;
let primary = Image::from_data(
    1,
    1,
    PixelFormat::Rgb8,
    ColorGamut::DisplayP3,
    ColorTransfer::Srgb,
    vec![255, 128, 128],
)?;

let jpeg = encode_ultra_hdr(&hdr, &primary, &UltraHdrEncodeOptions::default())?;

For both EncodeOptions and UltraHdrEncodeOptions, the progressive flags select scan mode only. Use the matching CompressionEffort field when you want to choose between balanced and size-oriented encoding.

§Decode And Reconstruct HDR Output

let decoded = decode(bytes)?;
let hdr = decoded.reconstruct_hdr(4.0, HdrOutputFormat::LinearFloat)?;

assert!(decoded.gain_map.is_some());
assert_eq!(hdr.width, decoded.image.width);

§Retain Raw JPEG Codestreams Explicitly

let decoded = decode_with_options(
    bytes,
    DecodeOptions {
        retain_primary_jpeg: true,
        retain_gain_map_jpeg: true,
        ..DecodeOptions::default()
    },
)?;

assert!(decoded.primary_jpeg.is_some());
assert!(decoded.gain_map.as_ref().unwrap().jpeg_bytes.is_some());

§Inspect Metadata Provenance

let inspection = inspect(bytes)?;
let ultra_hdr = inspection.ultra_hdr.as_ref().unwrap();

assert!(matches!(
    ultra_hdr.xmp_location,
    Some(MetadataLocation::Primary | MetadataLocation::GainMap)
));

§Color, ICC Profiles, And Gamuts

ultrajpeg intentionally does not collapse all color semantics into one opaque type.

Instead, the stable model separates:

  • icc_profile: the embedded ICC payload, if present
  • gamut: a convenience named gamut classification
  • gamut_info: the authoritative structured gamut representation when gamut coordinates could be recovered
  • transfer: the explicitly tracked transfer function

This matters because a JPEG may contain:

  • an ICC profile with precise primaries and white point
  • explicit crate-tracked gamut and transfer signaling
  • both
  • or neither

gamut_info is the richer result. gamut is only the best matching named classification when one is available.

§Bundled Display-P3 Helper

let color = ColorMetadata::display_p3();
assert_eq!(color.icc_profile.as_deref(), Some(ultrajpeg::icc::display_p3()));
assert_eq!(color.gamut, Some(ColorGamut::DisplayP3));
assert_eq!(color.transfer, Some(ColorTransfer::Srgb));

let options = EncodeOptions::ultra_hdr_defaults();
assert_eq!(
    options.primary_metadata.color.icc_profile.as_deref(),
    Some(ultrajpeg::icc::display_p3())
);

When packaging a gain map:

  • if EncodeOptions::primary_metadata.color.icc_profile is already set, it is embedded as-is
  • if no ICC profile is set and the resolved primary image is Display-P3 plus sRGB, ultrajpeg injects the bundled Display-P3 ICC profile automatically
  • otherwise gain-map packaging preserves the caller-provided absence of an ICC profile

The crate does not synthesize arbitrary ICC profiles.

§Practical Guidance

If you want a spec-friendly Display-P3 primary image for Ultra HDR packaging, prefer:

  • ColorMetadata::display_p3()
  • EncodeOptions::ultra_hdr_defaults()
  • icc::display_p3()

If your primary image uses a different color space, provide the explicit ICC profile you want embedded in the primary JPEG instead of relying on the Display-P3 helper path.

§Ultra HDR Metadata Behavior

UltraHdrMetadata exposes the effective metadata used by the crate after fallback and recovery logic.

It includes:

  • xmp and xmp_location
  • iso_21496_1 and iso_21496_1_location
  • gain_map_metadata
  • gain_map_metadata_source

Important behavior:

  • ISO 21496-1 is preferred over XMP when both are present and valid
  • metadata may come from the primary JPEG or the gain-map JPEG
  • the crate can recover malformed-but-usable files where the primary JPEG metadata is incomplete but MPF still points to a gain-map JPEG that carries usable gain-map semantics

§What Gets Written On Encode

When EncodeOptions::gain_map is Some(...), the crate writes:

  • MPF directory metadata on the primary JPEG
  • container or directory XMP on the primary JPEG
  • a version-only ISO 21496-1 APP2 block on the primary JPEG
  • hdrgm:* XMP on the gain-map JPEG
  • canonical ISO 21496-1 gain-map metadata on the gain-map JPEG

§What Gets Resolved On Decode

On decode and inspect, the crate resolves the effective gain-map metadata from the available payloads and exposes where those payloads came from.

That means callers do not need to parse raw XMP or raw ISO 21496-1 bytes themselves unless they want to.

When callers do want to reason about the raw payloads directly, the crate also provides:

  • parse_gain_map_xmp(...)
  • parse_iso_21496_1(...)

Those entry points are intentionally raw:

  • they do not apply decode-time precedence
  • they do not apply the crate’s defensive fallback filters
  • they are meant for explicit validation and comparison workflows

parse_iso_21496_1(...) expects one gain-map ISO payload. The primary JPEG’s four-byte version-only ISO APP2 block is structural only and returns an error if passed to that raw parser directly.

§Gain-Map Computation Scale

ComputeGainMapOptions exposes two independent knobs:

  • channels, which controls whether the computed gain map is single-channel or multichannel
  • scale, which controls gain-map resolution relative to the primary image

The supported GainMapScale values are:

  • GainMapScale::Full, which keeps full primary-image resolution and is recommended for best quality
  • GainMapScale::Default, which computes the gain map at half width and height and is the crate’s default
  • GainMapScale::Smallest, which computes the gain map at quarter width and height and matches the most aggressive Android Ultra HDR-style recommendation, but may noticeably reduce quality

The default compute_gain_map(...) path therefore computes a single-channel gain map at half primary-image resolution unless the caller opts into a different channel layout or scale.

§Container Structure Inspection

inspect_container_layout(...) exposes:

  • codestream offsets and lengths
  • whether the input was recognized as MPF or only as concatenated JPEG codestreams
  • which codestreams the crate treats as the primary and gain-map JPEG payloads

This surface is intentionally structural and inspection-oriented. It does not yet expose a generic public MPF rewrite API.

§SDR Primary Preparation

prepare_sdr_primary(...) is the supported high-level bridge for workflows where the caller:

  • starts from HDR pixels
  • resizes, crops, or otherwise edits those pixels first
  • then needs an SDR primary image before calling compute_gain_map(...)

The helper returns both:

  • the prepared Rgb8 primary image
  • matching PrimaryMetadata

That metadata should be used together with the returned image on subsequent encode(...) calls.

The current helper:

  • supports Rgb8, Rgba8, Rgba16F, Rgba32F, Rgba1010102Pq, and Rgba1010102Hlg inputs
  • produces sRGB-transfer output in either BT.709 or Display-P3 gamut
  • floors the SDR primary brightness so the default compute_gain_map(...) path stays within the crate’s default gain-map boost envelope
  • injects bundled Display-P3 ICC metadata automatically when Display-P3 output is requested

§Policy Notes For prepare_sdr_primary(...)

prepare_sdr_primary(...) is a supported default policy, not a promise to reproduce every caller’s preferred SDR rendering intent.

In practice:

  • if you already have a caller-chosen SDR primary image, keep using that image with compute_gain_map(...) and encode(...)
  • if you want ultrajpeg to derive a reasonable SDR primary for a transformed HDR image, use prepare_sdr_primary(...)
  • if you know the source HDR peak more precisely, set PreparePrimaryOptions::source_peak_nits explicitly instead of relying on the transfer-based default

Current defaults are:

  • PQ input with source_peak_nits: None assumes 10000 nits
  • HLG input with source_peak_nits: None assumes 1000 nits
  • linear input with source_peak_nits: None assumes 1000 nits
  • sRGB input with source_peak_nits: None assumes 203 nits

The helper also enforces a small but important compatibility rule: it floors the derived SDR primary brightness so the returned image composes with the crate’s default compute_gain_map(...) configuration instead of immediately falling outside the default gain-map boost range.

That makes the default workflow easier to use, but it also means the output is not just a naive one-pass tone map. If you need exact custom SDR rendering intent, prepare the SDR primary image yourself and treat prepare_sdr_primary as the optional convenience path rather than as the only supported one.

§Ownership And Performance Semantics

The public API is explicit about allocation behavior:

  • inspect(...) does not decode pixels
  • decode(...) decodes pixels and retains no raw JPEG codestreams by default
  • decode_with_options(...) is the explicit escape hatch for retained JPEG codestream bytes
  • large Ultra HDR decodes may use internal Rayon-based parallelism, but the public API remains synchronous

The crate is designed so that callers do not accidentally retain large input codestreams unless they opt in.

§Limitations And Non-Goals

The crate deliberately does not:

  • choose an SDR primary image for you implicitly during encode
  • downscale, filter, or otherwise reshape gain maps automatically
  • synthesize arbitrary ICC profiles
  • infer complete color policy from partial hints
  • act as a full conformance validator for every malformed Ultra HDR file shape

The caller remains responsible for:

  • selecting the SDR primary image, unless it explicitly uses prepare_sdr_primary(...)
  • deciding EXIF policy
  • providing explicit ICC data when the primary image is not Display-P3 plus sRGB and a gain map is being bundled
  • choosing the desired HDR reconstruction output format and display boost

Current limitations:

  • the public API targets JPEG and MPF-bundled gain-map JPEG workflows
  • Ultra HDR decode can recover some malformed files, but recovery is pragmatic, not a guarantee of full conformance validation
  • inspect_container_layout(...) is structural inspection only; it is not yet a public generic MPF rewrite API
  • the crate re-encodes JPEG pixel data on encode; it is not a marker-only remuxer for arbitrary already-encoded primary and gain-map codestream pairs
  • CompressionEffort::Smallest currently provides an extra size-oriented backend path only for progressive JPEGs; sequential JPEGs still accept it for API consistency, but currently use the same effective backend settings as CompressionEffort::Balanced
  • ICC parsing is currently used to recover structured gamut information, not to expose a full public ICC inspection API

§Migration Guide: 0.4.0 to 0.5.0

This guide covers migration from the 0.4.0 API line to the native stable-API direction implemented in 0.5.0.

It covers both:

  • the main public-API refactor that established the 0.5.0 line
  • the additive issue-#4 APIs added afterward on the same 0.5.0 line

§Summary

0.5.0 removes the wrapper-era public API and keeps one coherent native surface at the crate root.

The biggest changes are:

  • the compatibility API is no longer public
  • the main image type is now ultrajpeg::Image
  • DecodedJpeg became DecodedImage
  • InspectedJpeg became Inspection
  • GainMapEncodeOptions became GainMapBundle
  • UltraJpegEncoder became Encoder
  • EncodeOptions::color_metadata became EncodeOptions::primary_metadata
  • EXIF moved out of ColorMetadata into PrimaryMetadata
  • decode(...) no longer retains raw JPEG codestream bytes by default
  • ComputedGainMap::into_encode_options(...) became into_bundle(...)
  • primary and gain-map encode settings now include an explicit CompressionEffort
  • raw Ultra HDR payload parsing is now available directly from ultrajpeg
  • structural bundled-container inspection is now available directly from ultrajpeg
  • a supported SDR-primary preparation helper now exists for caller-managed HDR workflows

§Import Mapping

Old:

use ultrajpeg::{
    ColorMetadata, DecodeOptions, EncodeOptions, GainMapEncodeOptions,
    UltraJpegEncoder, decode, inspect,
};

New:

use ultrajpeg::{
    ColorMetadata, DecodeOptions, EncodeOptions, Encoder, GainMapBundle,
    PrimaryMetadata, decode, inspect,
};

§Type Renames

Direct renames:

  • CoreRawImage -> Image
  • DecodedJpeg -> DecodedImage
  • InspectedJpeg -> Inspection
  • GainMapEncodeOptions -> GainMapBundle
  • UltraJpegEncoder -> Encoder

Method rename:

  • ComputedGainMap::into_encode_options(...) -> ComputedGainMap::into_bundle(...)

§Metadata Model Changes

§ColorMetadata

Old ColorMetadata bundled together:

  • ICC profile
  • EXIF payload
  • gamut
  • transfer

New ColorMetadata contains only color-related state:

  • icc_profile
  • gamut
  • gamut_info
  • transfer

EXIF moved to PrimaryMetadata.

§PrimaryMetadata

New:

pub struct PrimaryMetadata {
    pub color: ColorMetadata,
    pub exif: Option<Vec<u8>>,
}

If you previously wrote:

let options = EncodeOptions {
    color_metadata: ColorMetadata {
        icc_profile: Some(profile),
        exif: Some(exif),
        gamut: Some(ColorGamut::DisplayP3),
        transfer: Some(ColorTransfer::Srgb),
    },
    ..EncodeOptions::default()
};

You now write:

let options = EncodeOptions {
    primary_metadata: PrimaryMetadata {
        color: ColorMetadata {
            icc_profile: Some(profile),
            gamut: Some(ColorGamut::DisplayP3),
            gamut_info: None,
            transfer: Some(ColorTransfer::Srgb),
        },
        exif: Some(exif),
    },
    ..EncodeOptions::default()
};

§Encode Migration

§Old

let bytes = UltraJpegEncoder::new(options).encode(&primary)?;

§New

Either:

let bytes = Encoder::new(options).encode(&primary)?;

Or, when you do not need a reusable encoder instance:

let bytes = ultrajpeg::encode(&primary, &options)?;

§Gain-map payload

Old:

gain_map: Some(GainMapEncodeOptions {
    image,
    metadata,
    quality,
    progressive,
})

New:

gain_map: Some(GainMapBundle {
    image,
    metadata,
    quality,
    progressive,
    compression: CompressionEffort::Balanced,
})

§compute_gain_map(...)

Old:

let computed = ultrajpeg::compute_gain_map(&hdr, &primary, &Default::default())?;
let options = EncodeOptions {
    gain_map: Some(computed.into_encode_options(90, false)),
    ..EncodeOptions::ultra_hdr_defaults()
};

New:

let computed = ultrajpeg::compute_gain_map(&hdr, &primary, &Default::default())?;
let options = EncodeOptions {
    gain_map: Some(computed.into_bundle(90, false, CompressionEffort::Balanced)),
    ..EncodeOptions::ultra_hdr_defaults()
};

To preserve the previous default encode behavior explicitly, set:

compression: CompressionEffort::Balanced

Use CompressionEffort::Smallest when you want the most size-oriented configuration available for the chosen scan mode.

ComputeGainMapOptions also now exposes a typed scale control through GainMapScale. The default is GainMapScale::Default, which computes the gain map at half primary-image width and height. Use GainMapScale::Full for best quality or GainMapScale::Smallest for the most aggressive quarter-resolution size tradeoff.

§Decode Migration

§Result type and fields

Old:

let decoded = ultrajpeg::decode(bytes)?;
let image = decoded.primary_image;
let icc = decoded.color_metadata.icc_profile;
let exif = decoded.color_metadata.exif;

New:

let decoded = ultrajpeg::decode(bytes)?;
let image = decoded.image;
let icc = decoded.primary_metadata.color.icc_profile;
let exif = decoded.primary_metadata.exif;

Field mapping:

  • decoded.primary_image -> decoded.image
  • decoded.color_metadata -> decoded.primary_metadata.color
  • decoded.color_metadata.exif -> decoded.primary_metadata.exif

§Default codestream retention changed

In 0.4.0, decode(...) retained the raw primary JPEG and gain-map JPEG codestreams by default.

In 0.5.0, decode(...) retains neither by default.

If you previously relied on:

  • decoded.primary_jpeg
  • decoded.gain_map.as_ref().unwrap().jpeg_bytes

you must opt in explicitly:

let decoded = ultrajpeg::decode_with_options(
    bytes,
    ultrajpeg::DecodeOptions {
        retain_primary_jpeg: true,
        retain_gain_map_jpeg: true,
        ..Default::default()
    },
)?;

Also note the field types changed:

  • old primary_jpeg: Vec<u8>
  • new primary_jpeg: Option<Vec<u8>>
  • old jpeg_bytes: Vec<u8>
  • new jpeg_bytes: Option<Vec<u8>>

§Inspect Migration

Old:

let inspected = ultrajpeg::inspect(bytes)?;
let icc = inspected.color_metadata.icc_profile;

New:

let inspected = ultrajpeg::inspect(bytes)?;
let icc = inspected.primary_metadata.color.icc_profile;

Field mapping:

  • inspected.color_metadata -> inspected.primary_metadata.color

§New Metadata Provenance

UltraHdrMetadata now exposes provenance:

  • xmp_location
  • iso_21496_1_location
  • gain_map_metadata_source

If your previous code only consumed gain_map_metadata, it can keep doing so.

If you need to know whether effective metadata came from the primary JPEG or the gain-map JPEG, or whether parsed effective gain-map metadata came from ISO 21496-1 or XMP, you can now inspect those fields directly.

§ColorMetadata::gamut_info

In 0.4.0, ColorMetadata exposed a gamut_info() helper method.

In 0.5.0, gamut_info is a field:

Old:

let standard = decoded
    .color_metadata
    .gamut_info()
    .as_ref()
    .and_then(|info| info.standard);

New:

let standard = decoded
    .primary_metadata
    .color
    .gamut_info
    .as_ref()
    .and_then(|info| info.standard);

The semantics are the same: gamut_info is the richer, authoritative gamut representation, and gamut remains the convenience named classification.

§Display-P3 Helpers

The helpers still exist, but their placement changed through the PrimaryMetadata split.

These remain available:

  • ColorMetadata::display_p3()
  • EncodeOptions::ultra_hdr_defaults()
  • icc::display_p3()

When packaging a gain map, the crate still auto-injects the bundled Display-P3 ICC profile when:

  • the resolved primary image is Display-P3 plus sRGB
  • and no explicit primary ICC profile is already present

§encode_ultra_hdr(...)

This API remains, but the primary subtree inside UltraHdrEncodeOptions inherits the new EncodeOptions structure.

That means:

  • options.primary.primary_metadata now holds the primary JPEG metadata
  • options.primary.progressive and options.primary.compression control the primary JPEG scan mode and compression effort
  • options.primary.gain_map must still remain None
  • options.gain_map_progressive and options.gain_map_compression control the computed secondary gain-map JPEG scan mode and compression effort

§New Additive APIs After The Main Refactor

The final 0.5.0 surface also adds a few APIs that were not present in the initial 0.5.0 refactor pass. These are additive rather than breaking, but they matter if you were still depending directly on ultrahdr-core for a few gaps.

§Raw Ultra HDR Payload Parsing

New root functions:

  • parse_gain_map_xmp(...)
  • parse_iso_21496_1(...)

New supporting type:

  • ParsedGainMapXmp

Use these when you need to reason about one raw payload directly.

Use inspect(...) or decode(...) when you want the crate’s effective metadata view after precedence and recovery rules.

In other words:

  • parse_gain_map_xmp(...) parses one raw hdrgm:* XMP payload
  • parse_iso_21496_1(...) parses one raw gain-map ISO 21496-1 payload
  • inspect(...) and decode(...) expose the crate’s effective metadata view

Do not pass the primary JPEG’s four-byte version-only ISO APP2 block to parse_iso_21496_1(...); that block is structural only and does not carry gain-map parameters.

If your old 0.4.0 code imported ultrahdr-core only to parse one of those raw payload forms, you can now keep that workflow inside ultrajpeg.

§Structural Container Inspection

New root function:

  • inspect_container_layout(...)

New supporting types:

  • ContainerKind
  • CodestreamLayout
  • ContainerLayout

Use this API when you need codestream offsets and lengths from a bundled JPEG container without decoding pixels.

This is intentionally structural:

  • it tells you where codestreams are
  • it tells you which codestreams ultrajpeg treats as primary and gain-map candidates structurally
  • it does not by itself prove that the second codestream is semantically a valid gain map
  • it does not expose a generic public MPF rewrite API

If your old code imported ultrahdr-core only for parse_mpf(...) or find_jpeg_boundaries(...) in order to inspect container structure, this new root API is the supported replacement for the common inspection case.

§SDR Primary Preparation

New root function:

  • prepare_sdr_primary(...)

New supporting types:

  • PreparePrimaryOptions
  • PreparedPrimary

This is the supported high-level path for workflows where you:

  • start from HDR pixels
  • resize, crop, or otherwise edit them
  • then need an SDR primary image before calling compute_gain_map(...)

prepare_sdr_primary(...) returns both:

  • the prepared SDR primary image
  • matching PrimaryMetadata

Those two values are intended to be used together on subsequent encode calls.

If you already have a caller-specific SDR rendering intent and SDR primary image, keep using that image directly with compute_gain_map(...) and encode(...). prepare_sdr_primary(...) is the supported default policy, not a forced replacement for bespoke SDR preparation.

Also note one important policy detail: the helper floors the derived SDR primary brightness so the returned image composes with the crate’s default compute_gain_map(...) configuration instead of immediately falling outside the default gain-map boost range.

§Compatibility API Removal

The most disruptive change is that the wrapper-era compatibility API is no longer public.

Removed root exports include:

  • CompressedImage
  • RawImage
  • Decoder
  • EncodedStream
  • DecodedPacked
  • ImgLabel
  • sys
  • jpeg
  • mozjpeg
  • the compatibility Encoder

If your code depended on that API, you have two options:

  1. stay on 0.4.0 for now
  2. port to the native root API

For most users, the target surface should now be:

  • Image
  • encode(...) or Encoder
  • decode(...) and DecodedImage
  • inspect(...) and Inspection

§Migration Checklist

  1. Replace legacy type names with the new native names.
  2. Replace EncodeOptions::color_metadata with EncodeOptions::primary_metadata.
  3. Move EXIF payload handling into PrimaryMetadata::exif.
  4. Replace GainMapEncodeOptions with GainMapBundle.
  5. Replace UltraJpegEncoder::new(...).encode(...) with either Encoder::new(...).encode(...) or encode(...).
  6. Replace ComputedGainMap::into_encode_options(...) with into_bundle(...) and pass an explicit CompressionEffort.
  7. Audit all decode call sites that relied on retained codestream bytes and add DecodeOptions retention flags explicitly.
  8. Update any gamut_info() method calls to field access.
  9. If you used the compatibility API, plan a full port or remain on 0.4.0.
  10. If you still depend directly on ultrahdr-core only for raw Ultra HDR payload parsing, structural codestream inspection, or SDR-primary preparation, switch those use cases to the new ultrajpeg root APIs.

§Rationale For The Break

The 0.5.0 changes are intentionally opinionated:

  • one coherent root API instead of mixed native and wrapper-era surfaces
  • explicit ownership behavior
  • better separation between primary-JPEG metadata and Ultra HDR metadata
  • more discoverable naming
  • less hidden allocation in the default decode path
  • fewer reasons for consumers to reach through ultrajpeg into ultrahdr-core for normal HDR JPEG workflows

That is why this release is a migration-heavy pre-1.0 step rather than an incremental rename-only release.

Modules§

icc
Built-in ICC profiles for common JPEG workflows.

Structs§

Chromaticity
An xy chromaticity coordinate. An xy chromaticity coordinate.
CodestreamLayout
Structural layout of one embedded JPEG codestream. Byte range of one embedded JPEG codestream inside an input buffer.
ColorMetadata
Color-related metadata attached to the primary JPEG image. Color-related metadata attached to the primary JPEG image.
ComputeGainMapOptions
Options for gain-map computation from HDR and SDR inputs. Options for gain-map computation from HDR and SDR primary images.
ComputedGainMap
Result of computing an Ultra HDR gain map from HDR and SDR inputs. Result of computing an Ultra HDR gain map from HDR and SDR images.
ContainerLayout
Structural layout of a JPEG or multi-image JPEG container. Structural layout of a JPEG or multi-image JPEG container.
DecodeOptions
Decode configuration. Decode configuration.
DecodedGainMap
Decoded gain-map JPEG payload and associated metadata. Decoded gain-map payload and associated metadata.
DecodedImage
Fully decoded JPEG or Ultra HDR JPEG. Fully decoded JPEG/UltraHDR image.
EncodeOptions
Encode configuration for the primary JPEG and optional gain-map bundle. Encode configuration for the primary image and optional bundled gain map.
Encoder
Reusable stateful encoder. Reusable stateful encoder.
GainMap
Gain-map image representation produced by decode and used for HDR reconstruction. A gain map image (8-bit grayscale or per-channel).
GainMapBundle
Gain-map payload and metadata to bundle into an Ultra HDR JPEG. Gain-map payload and metadata to bundle into the final output.
GainMapMetadata
Structured Ultra HDR gain-map metadata. Gain map metadata (linear scale values). These values describe how to interpret the gain map.
GamutInfo
Structured gamut information recovered from explicit metadata or ICC data. Structured gamut information derived from explicit signaling or an ICC profile.
Image
Stable image type used by ultrajpeg for decoded pixels, encoder input, and gain-map computation. A raw (uncompressed) image.
Inspection
Metadata-only inspection result. Metadata-only JPEG or Ultra HDR inspection result.
ParsedGainMapXmp
Parsed raw hdrgm:* XMP payload. Parsed hdrgm:* XMP payload.
PreparePrimaryOptions
Options for deriving an SDR primary image from source pixels. Options for deriving an SDR primary image from source pixels.
PreparedPrimary
Prepared SDR primary image and matching primary-JPEG metadata. Prepared SDR primary image and matching primary-JPEG metadata.
PrimaryMetadata
Primary-JPEG metadata handled by the crate. Primary-JPEG metadata handled by the crate.
UltraHdrEncodeOptions
Convenience options for one-shot Ultra HDR packaging. High-level convenience options for direct Ultra HDR packaging from HDR and SDR inputs.
UltraHdrMetadata
Structured effective Ultra HDR metadata resolved by the crate. Structured effective Ultra HDR metadata resolved by the crate.
UltraHdrMetadataEmission
Opt-out controls for Ultra HDR metadata emission during gain-map packaging. Opt-out controls for Ultra HDR metadata emission during gain-map packaging.

Enums§

ChromaSubsampling
JPEG chroma-subsampling modes supported when encoding primary images. Chroma subsampling modes exposed by the public API.
ColorGamut
Named color gamut classification used by decoded images and metadata. Color gamut / color space primaries.
ColorTransfer
Color transfer function used by decoded images and metadata. Electro-optical transfer function (EOTF/OETF).
CompressionEffort
JPEG compression effort used during encoding. JPEG compression effort.
ContainerKind
Structural classification of a JPEG container. Structural classification of a JPEG container.
Error
Public error type for codec, container, and metadata failures. Public error type for codec, container, and metadata failures.
GainMapChannels
Channel layout used when computing a gain map. Gain-map channel layout for computed Ultra HDR metadata.
GainMapMetadataSource
Representation from which effective gain-map metadata was parsed. Representation from which effective gain-map metadata was parsed.
GainMapScale
Supported spatial scales for computed gain maps. Supported spatial scales for computed gain maps.
HdrOutputFormat
HDR reconstruction output formats supported by the crate. Output format for HDR reconstruction.
MetadataLocation
Location from which Ultra HDR metadata was resolved. Location from which Ultra HDR metadata was resolved.
PixelFormat
Stable pixel-format type used by Image. Pixel format for raw images.

Functions§

compute_gain_map
Compute a gain map from an HDR image and a caller-chosen SDR primary image.
decode
Decode a JPEG or Ultra HDR JPEG using the default decode configuration.
decode_with_options
Decode a JPEG or Ultra HDR JPEG using explicit decode options.
encode
Encode a primary JPEG, optionally bundling a gain map and Ultra HDR metadata.
encode_ultra_hdr
Convenience wrapper that computes a gain map and packages an Ultra HDR JPEG.
inspect
Inspect JPEG or Ultra HDR container metadata without decoding image pixels.
inspect_container_layout
Inspect JPEG codestream boundaries and bundled-container structure.
parse_gain_map_xmp
Parse a raw hdrgm:* XMP payload into structured gain-map metadata.
parse_iso_21496_1
Parse a raw ISO 21496-1 gain-map payload into structured metadata.
prepare_sdr_primary
Prepare an SDR primary image from source pixels for gain-map workflows.

Type Aliases§

Result
Public error type for codec, container, and metadata failures. Result type used by the crate.