From 2ae3c547c37c0dd19f051cfb7b5427d24eb26138 Mon Sep 17 00:00:00 2001 From: Tejas Patel Date: Wed, 23 Sep 2026 19:25:11 +0000 Subject: [PATCH 1/2] jpegrutils: set use_base_cg for Apple gain maps The Apple decode path in getMetadataFromXMP() returns early without setting use_base_cg, so it stays false. Decoding is unaffected, but re-encoding the decoded base image, gain map and metadata (e.g. to resize an Apple HDR JPEG) then fails in encodeJPEGR with "ICC marker in gainmap jpeg is missing": use_base_cg == false tells the encoder the gain map lives in an alternate color space and must carry its own ICC profile, which Apple gain-map images do not. Apple applies the gain map in the base image's color space, so set use_base_cg = true, matching the ISO and Adobe XMP paths. Test: decodeApple now asserts use_base_cg for both Apple fixtures (it fails without this change). --- lib/src/jpegrutils.cpp | 3 +++ tests/jpegr_test.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/lib/src/jpegrutils.cpp b/lib/src/jpegrutils.cpp index 3f064ade..a1b0ac86 100644 --- a/lib/src/jpegrutils.cpp +++ b/lib/src/jpegrutils.cpp @@ -729,6 +729,9 @@ uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, uint8_t metadata->offset_hdr[c] = 0.0f; } metadata->hdr_capacity_min = 1.0f; + // Apple applies the gain map in the base image's color space, and its gain-map + // image carries no ICC profile. + metadata->use_base_cg = true; float max_content_boost; bool present = false; diff --git a/tests/jpegr_test.cpp b/tests/jpegr_test.cpp index 8748dac1..019c0b85 100644 --- a/tests/jpegr_test.cpp +++ b/tests/jpegr_test.cpp @@ -1753,6 +1753,7 @@ TEST(JpegRTest, decodeApple) { } EXPECT_EQ(gainmapMetadata->hdr_capacity_min, 1.0f); EXPECT_FLOAT_EQ(gainmapMetadata->hdr_capacity_max, headroom); + EXPECT_TRUE(gainmapMetadata->use_base_cg); uhdr_release_decoder(dec); } From 5b2ce500f5f8103a24a388b76d3c6b615d1028e4 Mon Sep 17 00:00:00 2001 From: Tejas Date: Thu, 24 Sep 2026 16:18:33 +0000 Subject: [PATCH 2/2] tests: round-trip Apple gain maps through the compressed-image encoder Re-encode the decoded base image, gain map and metadata with uhdr_enc_set_compressed_image + uhdr_enc_set_gainmap_image and check the result decodes. Without the use_base_cg fix this fails because the Apple gain-map image has no ICC profile. --- tests/jpegr_test.cpp | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/jpegr_test.cpp b/tests/jpegr_test.cpp index 019c0b85..c7bb655a 100644 --- a/tests/jpegr_test.cpp +++ b/tests/jpegr_test.cpp @@ -1755,6 +1755,41 @@ TEST(JpegRTest, decodeApple) { EXPECT_FLOAT_EQ(gainmapMetadata->hdr_capacity_max, headroom); EXPECT_TRUE(gainmapMetadata->use_base_cg); + // Re-encode the decoded base image, gain map and metadata (API-4). This must succeed as + // Apple gain-map images carry no ICC profile. + uhdr_mem_block_t* baseImg = uhdr_dec_get_base_image(dec); + ASSERT_NE(baseImg, nullptr); + uhdr_compressed_image_t baseCompressed{baseImg->data, baseImg->data_sz, baseImg->capacity, + UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, + UHDR_CR_UNSPECIFIED}; + uhdr_compressed_image_t gainmapCompressed{gainMapImg->data, gainMapImg->data_sz, + gainMapImg->capacity, UHDR_CG_UNSPECIFIED, + UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED}; + uhdr_gainmap_metadata_t metadataCopy = *gainmapMetadata; + + uhdr_codec_private_t* enc = uhdr_create_encoder(); + ASSERT_NE(enc, nullptr); + uhdr_error_info_t status = uhdr_enc_set_compressed_image(enc, &baseCompressed, UHDR_BASE_IMG); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) << status.detail; + status = uhdr_enc_set_gainmap_image(enc, &gainmapCompressed, &metadataCopy); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) << status.detail; + status = uhdr_encode(enc); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) << status.detail; + uhdr_compressed_image_t* reencoded = uhdr_get_encoded_stream(enc); + ASSERT_NE(reencoded, nullptr); + + uhdr_codec_private_t* dec2 = uhdr_create_decoder(); + ASSERT_NE(dec2, nullptr); + ASSERT_EQ(uhdr_dec_set_image(dec2, reencoded).error_code, UHDR_CODEC_OK); + status = uhdr_decode(dec2); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) << status.detail; + const uhdr_gainmap_metadata_t* reencodedMetadata = uhdr_dec_get_gainmap_metadata(dec2); + ASSERT_NE(reencodedMetadata, nullptr); + EXPECT_FLOAT_EQ(reencodedMetadata->hdr_capacity_max, headroom); + EXPECT_TRUE(reencodedMetadata->use_base_cg); + uhdr_release_decoder(dec2); + uhdr_release_encoder(enc); + uhdr_release_decoder(dec); } }