feat: pure Rust UltraHDR implementation (RIIR)#6
Open
Enter-tainer wants to merge 65 commits into
Open
Conversation
Define goals, constraints, crate structure, API design principles, dependency choices, testing strategy, and success criteria for rewriting libultrahdr from C++ FFI bindings to native Rust. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
…ndling - Replace ultrahdr-sys FFI wrapper with pure Rust skeleton - Add Error enum with Display/std::error::Error impl - Add core types: PixelFormat, ColorGamut, ColorTransfer, GainMapMetadata - Create module structure: color/, gainmap/, jpeg/, mpf - Update ultrahdr-bake to remove C++ feature flags Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
…and gamut conversion - Add sRGB/PQ/HLG transfer functions (OETF + inverse OETF) - Add Color struct with arithmetic operators - Add luminance calculations and gamut conversion matrices - Add reference display peak luminance lookup Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Parse ICC rXYZ/gXYZ/bXYZ tags to extract chromaticity primaries and match against known gamuts (BT.709, Display P3, BT.2100). Falls back to profile description string matching. Includes s15Fixed16Number parsing and 3 tests. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Add jpeg-decoder and jpeg-encoder dependencies. Implement: - encode_rgb_to_jpeg / encode_grayscale_to_jpeg (Task 13) - decode_jpeg returning JpegDecoded struct with pixels, dimensions, ICC (Task 12) - parse_jpeg_segments walking JPEG markers to extract APP segments (Task 14) All 32 tests pass (27 existing + 5 new). Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
…support Add GainMapMetadataFrac struct for rational fraction representation and implement binary encode/decode per ISO 21496-1 spec with common denominator optimization, fraction-to-float conversion, and XMP metadata read/write using roxmltree for parsing. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Add core gain map computation functions ported from libultrahdr's gainmapmath.cpp and jpegr.cpp: - compute_gain: log2 ratio with dark pixel clamping - encode_gain / affine_map_gain: normalized quantization to u8 - apply_gain_single / apply_gain_multi: HDR reconstruction from SDR + gain - sample_map_bilinear: Shepard's IDW interpolation for gain map upsampling - global_tonemap: Reinhard-style HDR to SDR tone mapping 8 new tests (43 total). Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Port of multipictureformat.cpp - generates APP2/MPF marker data per CIPA DC-007 spec for linking primary JPEG with secondary gain map JPEG. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
… HDR reconstruction Add decoder module (Phase 8, Tasks 21-23) implementing: - extract_gainmap_jpeg: parse MPF APP2 marker to locate secondary JPEG, extract gain map bytes and ISO 21496-1 / XMP metadata - apply_gainmap_to_sdr: apply gain map to SDR RGBA pixels with configurable display boost, output transfer function (sRGB/PQ/HLG/Linear), and pixel format (RGBA8888/RGBA1010102/RgbaF16) - Decoder builder API: Decoder::new().output_format().output_transfer() .max_display_boost().probe()/.decode() All 49 tests pass (45 existing + 4 new). Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…EG assembly, ICC profiles, and builder API - generate_gainmap(): compute HDR/SDR ratio per pixel block, produce gain map image + metadata - assemble_ultrahdr(): insert XMP/ISO metadata APP markers, MPF, and append gain map as secondary image - write_icc_profile(): generate ICC v2 profiles for sRGB/PQ/HLG × BT.709/P3/BT.2100 gamuts - Encoder builder: hdr_raw() -> sdr_compressed() -> encode() pipeline with quality/scale/multichannel options Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
…I dependency - Remove ultrahdr-sys from workspace members (no longer needed) - Rewrite encode.rs to use ultrahdr::decoder::Decoder and ultrahdr::encoder::Encoder builder APIs instead of FFI CompressedImage/Decoder/Encoder/ImgLabel/sys - Simplify color.rs by delegating ICC gamut detection to ultrahdr::color::icc::detect_color_gamut, removing ~120 lines of duplicated ICC parsing code - Rewrite detect.rs probe_gainmap_metadata to use Decoder::new(buf).probe() instead of FFI Decoder with CompressedImage, changing signature from &mut [u8] to &[u8] Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
These workspace-level dependencies are no longer used by any active workspace member (ultrahdr and ultrahdr-bake are pure Rust now). The ultrahdr-sys directory is kept as deprecated but excluded from the workspace. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Verifies the full UltraHDR pipeline: encode synthetic HDR+SDR images into an UltraHDR JPEG, probe for gain map metadata, then decode back to RGBA8888 and validate dimensions and pixel data. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
- Collapse nested if-let chains in decoder.rs (collapsible_if) - Add #[allow(clippy::too_many_arguments)] for generate_gainmap and apply_gainmap_to_sdr (public API, restructuring not warranted) - Replace manual div_ceil with .div_ceil() in encoder.rs - Replace .filter().last() with .rfind() (double_ended_iterator_last) - Remove unnecessary u32 cast in f16_to_f32 subnormal branch - Add Default impl for Encoder - Replace write!() with writeln!() in XMP metadata generation - Fix unnecessary &mut in motion.rs probe_gainmap_metadata call - Fix useless u8 <= 255 comparison in test - Escape [0,1] as \[0,1\] in doc comments to fix rustdoc warnings Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
When no SDR JPEG is provided, the encoder now automatically generates an SDR image from HDR input via global Reinhard tone mapping, then proceeds with normal gain map computation and UltraHDR assembly. This enables the API-0 scenario from the GOAL spec (HDR raw only input). Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
These functions are ported from libultrahdr's gainmapmath.cpp for API completeness but are not used in the current encoding/decoding paths. Mark with #[allow(dead_code)] rather than removing to preserve parity. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
The decoder's apply_gainmap_to_sdr broadcasts a single sampled gain value to all 3 channels even when multi_channel metadata is present. This is correct for the default single-channel (grayscale) gain map encoding mode. Added a comment clarifying this design limitation. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
- icc.rs: change s15fixed16_to_f32() to return Option<f32> instead of panicking on non-4-byte input; update callers to propagate with ? - encoder.rs: clamp negative values in to_n_u closure (metadata_to_frac) to prevent incorrect u32 saturation when hdr_capacity_min < 1.0 - encoder.rs: remove unused variables _insert_pos and _mpf_seg_start Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
- Remove unused thiserror dependency from ultrahdr/Cargo.toml - Replace unreachable wildcard arm in gamut_convert() with unreachable!() - Add u16 range validation for width/height in JPEG encode functions Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Add integration tests comparing pure Rust encoder/decoder with C++ (ultrahdr-sys) golden reference data. Tests verify cross-implementation decode, roundtrip, metadata preservation, and probe capabilities. Fix MPF offset handling: use spec-compliant TIFF-header-relative offsets in both encoder and decoder, with fallback for absolute offsets for backwards compatibility. Golden data generated via ultrahdr-sys FFI (RGBA1010102 HLG + SDR JPEG, quality=95, decoded at SRGB boost=4.0). Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
- Add sample_map_bilinear_rgb() for per-channel RGB gain map sampling - Detect gain map channel count (grayscale vs RGB) automatically in decoder - Add P010 1280x720 golden data from C++ encoder (real 10-bit HDR content) - Add test_decode_p010_sys_encoded_jpeg and test_probe_p010_sys_encoded tests - Rename existing tests to clarify minnie vs p010 variants - PSNR: 20.71 dB (P010 HDR), 17.44 dB (minnie SDR), 35.74 dB (Rust roundtrip) Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Two bugs fixed in the decoder: 1. Weight application order: the display boost weight was being multiplied into the raw gain value BEFORE gamma correction, but C++ libultrahdr applies it to the logBoost AFTER gamma correction: - Wrong (Rust): gainFactor = exp2(logBoost) where logBoost uses (gain*weight)^(1/gamma) - Correct (C++): gainFactor = exp2(logBoost * weight) where logBoost uses gain^(1/gamma) 2. SRGB output should skip gain map entirely: C++ libultrahdr returns the SDR base image directly when output_ct=SRGB (no gain map applied). The Rust decoder was incorrectly applying the gain map for SRGB output, causing a 40 dB quality gap vs C++ reference. After fix: Rust vs C++ PSNR = 64.54 dB (max_diff=3), matching within expected JPEG decoder tolerance (jpeg-decoder vs libjpeg-turbo). Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Add golden data and tests comparing Rust decoder output against C++ libultrahdr for three HDR output format/transfer combinations: - LINEAR + RgbaF16 (PSNR 17.32 dB) - HLG + Rgba1010102 (PSNR 19.62 dB) - PQ + Rgba1010102 (PSNR 11.18 dB) Golden data generated by C program using C++ libultrahdr decoder with max_display_boost=4.0. Helper functions added for F16↔f32 conversion, 1010102 unpacking, and typed PSNR computation. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
…parison Regenerate all golden data using C++ libultrahdr library. Decoder tests compare Rust vs C++ decode of the same UltraHDR JPEG; encoder tests compare Rust enc→dec vs C++ enc→dec pipeline outputs. - 4 decoder tests (SRGB/LINEAR/HLG/PQ) with PSNR thresholds - 4 encoder tests (minnie SDR round-trip) with PSNR thresholds - 2 metadata tests (probe + roundtrip) - Remove old reference-based tests and unused golden data Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Replace per-pixel pow/log2/exp2 calls with pre-computed lookup tables: - sRGB inverse OETF: 256-entry LUT (exact for u8 inputs) - PQ OETF: 65536-entry LUT - HLG OETF + inverse OOTF: 65536-entry LUTs - GainLut: 1024-entry per-channel gain factor table Performance improvement (1280x720, 20 iterations): LINEAR: 101.2 → 61.7 ms (1.64x faster) HLG: 112.1 → 79.4 ms (1.41x faster) PQ: 170.5 → 71.8 ms (2.37x faster) Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
- Add optional `rayon` feature flag for parallel gain map processing - Refactor apply_gainmap_to_sdr to process rows independently via par_chunks_mut - Add criterion microbenchmarks comparing scalar/LUT/polynomial transfer functions - ~2.4x decode speedup on 8 cores (LINEAR 52→25ms, HLG 71→27ms, PQ 62→25ms) Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
- Move ISO 21496-1 metadata from primary to secondary (gain map) JPEG, matching C++ appendGainMap() which puts full metadata in the gain map image and only a version stub in the primary image - Write hdrgm:BaseColorSpace="0" in XMP when use_base_cg is false - Parse BaseColorSpace attribute in XMP to correctly set use_base_cg instead of hardcoding true These fixes resolve Rust→C++ cross-decode failure (UHDR_CODEC_INVALID_PARAM) where C++ decoder expected ISO metadata in the secondary image. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…max channel) Three fixes to align Rust encoder gain map computation with C++ libultrahdr: 1. OOTF direction: HLG decode now uses forward OOTF (hlg_ootf_approx, pow(x, 1.2)) instead of inverse (hlg_inv_ootf_approx, pow(x, 1/1.2)) matching C++ hlgOotfApprox scene-to-display transform. 2. Nits scaling: Both SDR and HDR values are now multiplied by SDR_WHITE_NITS (203) before computing gain, matching C++ behavior where sdr_y_nits = sdr_y * kSdrWhiteNits and hdr_y_nits = hdr_y * hdrSampleToNitsFactor. 3. Max channel: Single-channel mode now uses fmax(r,g,b) instead of weighted luminance, matching C++ use_luminance=false default. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
…exact - Add Encoder::sdr_raw() method for raw RGBA8888 SDR input (bypasses JPEG decode losses in gain map computation) - Add gamut conversion in generate_gainmap: when sdr_gamut != hdr_gamut, convert HDR pixels to SDR gamut via 3x3 matrix (reuses color::gamut) - Add clipNegatives: clip both SDR and HDR channels to max(0, val) after gamut conversion and before gain computation, matching C++ pipeline - Update generate_gainmap signature: separate sdr_gamut and hdr_gamut parameters (was single _gamut) - Update debug_encode test to use sdr_raw() instead of sdr_compressed() White scenario now produces bit-exact metadata match with C++. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
C++ libultrahdr averages pixel blocks in gamma (OETF) space before linearizing, while Rust was pre-linearizing all pixels then averaging in linear space. This matters because transfer functions (sRGB, HLG) are nonlinear: avg(inv_oetf(a), inv_oetf(b)) != inv_oetf(avg(a,b)). Also fixes HDR nits factor: C++ uses hdr_white_nits (1000 for HLG, 10000 for PQ) while Rust was using SDR_WHITE_NITS (203). With the old pre-scaling approach this happened to be equivalent, but the new per-block linearization needs the correct factor. Changes: - Add decode_pixels_to_normalized: pixel format unpacking without transfer fn - Add linearize_normalized: apply transfer fn to normalized buffer - generate_gainmap: now takes gamma-space inputs + hdr_transfer param, averages in gamma space, then linearizes per block - Encoder::encode: pass gamma-space buffers to generate_gainmap Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace direct srgb_inv_oetf/hlg_inv_oetf/pq_inv_oetf calls in generate_gainmap with LUT-based versions matching C++ libultrahdr's exact table sizes (sRGB=1024, HLG=4096, PQ=4096 entries). This eliminates the metadata mismatch caused by quantization differences: - max_content_boost: 4.4533067 → 4.453096 (matches C++) - min_content_boost: 0.09103692 → 0.09114423 (matches C++) Only the encoder's gain map path is changed; decoder paths (decode_pixels_to_linear, linearize_normalized) retain direct computation for accuracy. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
11 tests comparing encoder and decoder output pixel-by-pixel: - 5 encoder scenarios (gradient/white/black/color_ramp/mixed): metadata exact, gain map pixels identical - 6 decoder scenarios (HLG/PQ/Linear × C++/Rust encoded): small diffs noted (max_diff ≤5) Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Three fixes to reduce Rust vs C++ decoder pixel differences: 1. SDR linearization: use 1024-entry srgb_inv_oetf_lut_1024 (matching C++ srgbInvOetfLUT) instead of 256-entry srgb_inv_oetf_lut. The C++ decoder operates on float RGB from YUV conversion, not integer u8 values. 2. HLG inverse OOTF: use direct powf(1/1.2) instead of 65536-entry LUT, matching C++ hlgInverseOotfApprox which uses std::pow directly. 3. f32_to_f16: implement round-to-nearest (wrapping_add 0x1000) matching C++ floatToHalf, instead of truncation. Also handles denormals and saturation correctly. Results (gradient 64x64 test image): - HLG 1010102: 1351→400 differing pixels, max_diff 2→2 - PQ 1010102: 830→367 differing pixels, max_diff 5→1 - Linear F16: 10182→1488 differing channels, max_diff 0.003→0.002 Remaining differences are due to JPEG decoder divergence: C++ decodes to YUV420 then converts to float RGB via p3YuvToRgb, while Rust's jpeg_decoder crate outputs u8 RGB directly with different rounding. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Performance optimizations for decode pipeline: - Add zune-jpeg/zune-core as optional feature flag for faster JPEG decode - Eliminate rgb_to_rgba allocation (apply_gainmap_to_sdr_rgb direct RGB path) - Pre-compute ShepardsIDW weight table (eliminates per-pixel sqrt) - Merge HLG inverse OOTF + OETF into single combined LUT - Pre-initialize TransferLuts outside hot loop (avoids LazyLock atomic checks) - Use 256-entry exact u8 sRGB LUT in decoder (inputs are u8, no quantization loss) - Inline LUT lookup with unsafe get_unchecked for bounds-check elimination Results at 512x512 (release mode, avg of 10 runs): Encode: Rust 8.0ms vs C++ 11.4ms = 0.70x (Rust faster) Decode: Rust 14.2ms vs C++ 10.1ms = 1.41x (target was <1.5x) All 78 unit + 10 bit-exact + 2 roundtrip tests pass. clippy + fmt clean. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
- Replace unsafe get_unchecked with safe bounds-checked indexing in lut_lookup (LLVM optimizes away the check for idx.min(max_idx)) - Remove unused pq_oetf_lookup and hlg_combined_lookup methods from TransferLuts (decoder uses inline lut_lookup instead) Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
…fsets - Pass pulp::Arch as parameter to apply_gain_simd/clamp_simd instead of creating it per-call (avoids redundant CPUID checks per row) - Fix single-channel gain mode to use offset_sdr[0]/offset_hdr[0] for all channels, matching the scalar apply_single() behavior Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Add comprehensive JPEG segment parser and metadata comparison test that extracts and compares XMP APP1, ISO APP2, MPF APP2, and ICC APP2 segments between Rust and C++ encoder outputs. Diagnostic output reveals specific byte differences to guide metadata fixes. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
- Add continued fractions algorithm (float_to_unsigned_fraction_impl) ported from C++ floatToUnsignedFractionImpl() for accurate float-to- rational conversion, replacing fixed denom=10000 approach - Add float_to_signed_fraction() and float_to_unsigned_fraction() public API - Replace metadata_to_frac() to use continued fractions per field - Add XmlWriter struct matching C++ XmlWriter output format exactly - Add write_xmp_primary_container() for container directory XMP - Rewrite write_xmp_gainmap_metadata() using XmlWriter for format match - Add cpp_float_to_string() matching C++ stringstream defaultfloat - Restructure assemble_ultrahdr(): primary gets container XMP, secondary gets gainmap metadata XMP + ISO APP2 Results from metadata_bytes_diagnostic: - ISO-21496-1 segments now MATCH byte-exactly (both primary and secondary) - XMP structure correct (container in primary, gainmap in secondary) - All 78 unit tests + 11 bitexact tests pass (no regression) Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
C++ encoder only defines UHDR_WRITE_ISO (kWriteXmpMetadata=false), so no XMP is written. Simplify assemble_ultrahdr() to match: - Primary: SOI → ISO stub APP2 → MPF APP2 → rest of SDR JPEG - Secondary: SOI → ISO full APP2 → rest of gain map JPEG - Remove parse_jpeg_segments dependency and selective segment copying - Update unit test to verify XMP absence Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…coder segments Only assert on metadata we control (ISO-21496-1, XMP, EXIF). Treat MPF and ICC as informational — they come from the underlying JPEG encoder (mozjpeg vs libjpeg-turbo) and naturally differ. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Add 5 parameterized multi-channel (RGB) gainmap bitexact tests comparing Rust encoder output against C++ reference. Three encoder bugs fixed: - Per-channel min/max gain tracking (was single global scalar) - Gamut conversion direction: SDR→HDR when use_base_cg=false (was HDR→SDR) - JPEG 4:4:4 chroma subsampling for RGB images (was defaulting to 4:2:0) All 17 bitexact tests pass with 0 pixel differences. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Add 5 parameterized encoder bitexact tests: - scale1/scale2: test gainmap_scale=1,2 with gradient scene - nits1000/nits4000/nits10000: test target_display_peak_nits variants All tests verify metadata and gain map pixel exact match with C++. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Adds a proper criterion-based benchmark comparing Rust and C++ encoder/decoder performance at 512x512 resolution. Benchmarks encode (rust vs cpp) and decode with multiple output format/transfer combinations (HLG 1010102, PQ 1010102, Linear F16) plus C++ baseline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SIMD acceleration for gain map application is stable and provides measurable performance improvement with no correctness impact. Slack-Thread: https://metabit-trading.slack.com/archives/D0AC2E6SESV/p1772504256906069
Enter-tainer
force-pushed
the
thread/riir-libultrahdr-rs-rust
branch
from
March 4, 2026 07:00
28bea00 to
8615090
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rewrite the UltraHDR gain-map JPEG library in pure Rust, eliminating all C/C++ FFI dependencies (cmake, nasm, pkg-config, bindgen).
What's new
ultrahdr-bakemigrated to pure Rust API (bake, motion photo, auto-detect preserved).Key properties
unsafecode in the entire cratejpeg-decoder,jpeg-encoder,roxmltree-D warnings)cargo docwarningsModules