feat(jpeg): preserve/expose XMP metadata, Extended XMP, and uhdr_strip_gain_map - #493
Closed
DichenZhang1 wants to merge 6 commits into
Closed
DichenZhang1 wants to merge 6 commits into
DichenZhang1 wants to merge 6 commits into
Conversation
- Expose uhdr_enc_set_xmp_data and uhdr_dec_get_xmp in public C API - Support passing custom XMP metadata into encodeJPEGR and appendGainMap - In ISO metadata mode, pass through user/base XMP packet directly - In XMP metadata mode, merge Ultra HDR container directory into user/base XMP RDF - Automatically extract and preserve base JPEG XMP in API-2/3/4 encoding workflows - Wire -X <xmp_file> and probe XMP display into ultrahdr_app CLI - Add unit tests verifying XMP encoding, decoding, and preservation across API-0 and API-2
- Support encoding and decoding Extended XMP metadata (> 64 KB) in JPEG - Compute 128-bit MD5 GUID and inject xmpNote:HasExtendedXMP into Standard XMP - Chunk serialized Extended XMP payload across multiple http://ns.adobe.com/xmp/extension/\0 APP1 segments - Reassemble Extended XMP chunks during JPEG header parsing and expose reconstructed buffer - Calculate dynamic encoder output buffer allowance based on total XMP size and chunk count - Add unit tests validating multi-segment Extended XMP encoding, decoding, and bit-for-bit roundtrip integrity
…ripping - Add public C API uhdr_strip_gain_map and JpegR::stripGainMap to losslessly strip secondary gain map bitstream, MPF APP2, ISO 21496-1 APP2, and Ultra HDR XMP tags - Preserve primary JPEG entropy-coded scan data bit-for-bit along with Exif, ICC, APP13, Extended XMP, and user XMP metadata - Support size-query mode when out_stream->data is nullptr and out_stream->capacity is 0 - Add namespace-aware XML parsing for primary XMP merging and gain-map tag stripping (MergePrimaryXmp and stripGainMapFromXmp) - Add unit tests covering uhdr_strip_gain_map and primary XMP merging across namespace prefixes and re-encoding
… fixture coverage
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
This PR completes end-to-end JPEG XMP metadata preservation, Adobe XMP Part 3 Extended XMP support, namespace-aware XMP merging, and lossless gain-map stripping (
uhdr_strip_gain_map) acrosslibultrahdr.Key Changes
Preserve & Expose JPEG XMP Metadata Across Encode and Decode (
uhdr_enc_set_xmp_data,uhdr_dec_get_xmp):<rdf:RDF>packet) viauhdr_dec_get_xmp.kJpegMaxSegmentLength,kMaxStandardXmpPayload,kMaxExtendedXmpChunkPayload,kExtendedXmpHeaderSize) injpegr.h.Adobe XMP Part 3 Multi-Segment Extended XMP Support (> 65,503 Bytes):
kMaxTotalXmpBytes(16 MiB) by splitting payloads larger than65,503bytes into 65,458-bytehttp://ns.adobe.com/xmp/extension/\0APP1 chunks linked by an upper-case 32-hex-character MD5 digest (xmpNote:HasExtendedXMP).JpegDecoderHelperwith strict validation (MD5 format, consistenttotal_length, out-of-order and duplicate identical chunk handling, and rejection of conflicting/overlapping/incomplete chunks).~64.9 KB – 65,503 B) whenkWriteXmpMetadata(-DUHDR_WRITE_XMP=ON) is active: if merging the Ultra HDRContainer:Directorypushes a standard-sized user XMP packet overkMaxStandardXmpPayload(65,503bytes),JpegR::appendGainMapspills the unmerged user XMP cleanly into Extended XMP chunks and writes only the Ultra HDR primary XMP +xmpNote:HasExtendedXMPGUID into the Standard XMP segment (avoiding duplicateContainer:Directoryentries).uhdr_encodesizesxmpAllowanceto accommodate both the Standard XMP segment and Extended XMP chunk headers.Namespace-Aware Primary XMP Merging (
MergePrimaryXmpinjpegrutils.cpp):find("</rdf:RDF>")) with a namespace-aware XML token/span parser (XmpMergeXmlHandlerbuilt onimage_io::XmlReader).<r:RDF>,<x:xmpmeta>),<?xpacket?>processing instructions, XML comments, CDATA sections, and non-primary descriptions (rdf:about="http://...",rdf:ID,rdf:nodeID), while replacing stale primary-imageContainer:Directoryandhdrgm:Versionattributes/elements.kWriteXmpMetadata == false), API-4 (encodeJPEGR(sdr_intent_compressed, gainmap_compressed, metadata, dest)) passes through the base JPEG's XMP byte-for-byte without injecting redundantGContainer/hdrgmproperties.Lossless Gain-Map Stripping Public C API (
uhdr_strip_gain_map):UHDR_EXTERN uhdr_error_info_t uhdr_strip_gain_map(uhdr_compressed_image_t* in_stream, uhdr_mem_block_t* out_stream)inultrahdr_api.handJpegR::stripGainMapinjpegr.cpp.SOI..EOI, includingDRIandRST0..RST7restart markers in entropy-coded scan data) without re-encoding pixels.APP2MPF directory segment,APP2ISO 21496-1 segment, and everything after the primary image'sEOImarker (secondary gain-map JPEG stream and any trailing container bytes).APP1segment viastripGainMapFromXmp: stripsContainer(http://ns.google.com/photos/1.0/container/),Item(http://ns.google.com/photos/1.0/container/item/),hdrgm(http://ns.adobe.com/hdr-gain-map/1.0/), andapdi(http://ns.apple.com/HDRGainMap/1.0/) properties while preserving all unrelated user XMP attributes/elements (dc,xmp,exif,GCamera:MicroVideo,xmpNote:HasExtendedXMP, etc.). Drops the Standard XMPAPP1segment completely if it only contained generated gain-map metadata.Fuzzer & Unit Test Coverage:
uhdr_strip_gain_mapanduhdr_dec_get_xmpcalls toultrahdr_dec_fuzzer.cppandultrahdr_enc_fuzzer.cppwithout alteringFuzzedDataProviderconsumption order.jpegr_test.cpp,jpegdecoderhelper_test.cpp, andultrahdr_api_test.cppcovering:rdf:aboutdescriptions, staleContainer:Directoryreplacement, and rejection of malformed/unsupported XML constructs65,200bytes) spilling cleanly into Extended XMP in-DUHDR_WRITE_XMP=ONmode (NearLimitStandardXmpSpillsCleanlyToExtendedXmp)uhdr_strip_gain_mapon synthetic and real-world fixtures (apple_gainmap_new.jpg,apple_gainmap_old.jpg), verifying byte-identical primary entropy scan preservation, EXIF/ICC/Extended XMP retention, trailing trailer removal, and corrupt marker rejectionTest Plan
ultrahdr_unit_testin both CMake configurations:-DUHDR_BUILD_TESTS=ON -DUHDR_WRITE_ISO=ON -DUHDR_WRITE_XMP=OFF): AllUltraHdrApiTest(21/21),JpegRTest, andJpegDecoderHelperTestsuites pass.-DUHDR_BUILD_TESTS=ON -DUHDR_WRITE_ISO=ON -DUHDR_WRITE_XMP=ON): AllUltraHdrApiTest(26/26),JpegRTest(34/34), andJpegDecoderHelperTest(17/17) suites pass.