Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions fuzzer/ultrahdr_dec_fuzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ void UltraHdrDecFuzzer::process() {

(void)is_uhdr_image(buffer.data(), buffer.size());

uhdr_mem_block_t stripped_size{nullptr, 0, 0};
if (uhdr_strip_gain_map(&jpegImgR, &stripped_size).error_code == UHDR_CODEC_OK &&
stripped_size.data_sz > 0 && stripped_size.data_sz <= buffer.size() + 65536) {
std::vector<uint8_t> stripped_buf(stripped_size.data_sz);
uhdr_mem_block_t stripped_out{stripped_buf.data(), 0, stripped_buf.size()};
(void)uhdr_strip_gain_map(&jpegImgR, &stripped_out);
}

uhdr_codec_private_t* dec_handle = uhdr_create_decoder();
if (dec_handle) {
ON_ERR(uhdr_dec_set_image(dec_handle, &jpegImgR))
Expand Down Expand Up @@ -106,6 +114,7 @@ void UltraHdrDecFuzzer::process() {

uhdr_dec_get_exif(dec_handle);
uhdr_dec_get_icc(dec_handle);
uhdr_dec_get_xmp(dec_handle);
uhdr_dec_get_base_image(dec_handle);
uhdr_dec_get_gainmap_image(dec_handle);
uhdr_dec_get_gainmap_metadata(dec_handle);
Expand Down
8 changes: 8 additions & 0 deletions fuzzer/ultrahdr_enc_fuzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,13 @@ void UltraHdrEncFuzzer::process() {
if (status.error_code == UHDR_CODEC_OK) {
auto output = uhdr_get_encoded_stream(enc_handle);
if (output != nullptr) {
uhdr_mem_block_t stripped_size{nullptr, 0, 0};
if (uhdr_strip_gain_map(output, &stripped_size).error_code == UHDR_CODEC_OK &&
stripped_size.data_sz > 0) {
std::vector<uint8_t> stripped_buf(stripped_size.data_sz);
uhdr_mem_block_t stripped_out{stripped_buf.data(), 0, stripped_buf.size()};
ON_ERR(uhdr_strip_gain_map(output, &stripped_out))
}
uhdr_codec_private_t* dec_handle = uhdr_create_decoder();
if (dec_handle) {
ON_ERR(uhdr_dec_set_image(dec_handle, output))
Expand All @@ -438,6 +445,7 @@ void UltraHdrEncFuzzer::process() {
else
ON_ERR(uhdr_dec_set_out_img_format(dec_handle, UHDR_IMG_FMT_32bppRGBA1010102))
ON_ERR(uhdr_decode(dec_handle))
uhdr_dec_get_xmp(dec_handle);
uhdr_release_decoder(dec_handle);
}
}
Expand Down
9 changes: 8 additions & 1 deletion lib/src/jpegr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@ uhdr_error_info_t JpegR::appendGainMap(uhdr_compressed_image_t* sdr_intent_compr
}
}

bool spill_user_xmp_to_extended = false;
if (kWriteXmpMetadata) {
if (user_xmp_str.empty()) {
xmp_primary_str = generateXmpForPrimaryImage(secondary_image_size, *metadata, nullptr);
Expand All @@ -1300,15 +1301,21 @@ uhdr_error_info_t JpegR::appendGainMap(uhdr_compressed_image_t* sdr_intent_compr
"unable to safely merge supplied XMP metadata into the primary image");
return status;
}
if (xmp_primary_str.size() > kMaxStandardXmpPayload) {
xmp_primary_str = user_xmp_str;
spill_user_xmp_to_extended = true;
}
} else {
xmp_primary_str = user_xmp_str;
spill_user_xmp_to_extended = true;
}
} else if (!user_xmp_str.empty()) {
xmp_primary_str = user_xmp_str;
spill_user_xmp_to_extended = xmp_primary_str.size() > kMaxStandardXmpPayload;
}

if (!xmp_primary_str.empty()) {
if (xmp_primary_str.size() <= kMaxStandardXmpPayload) {
if (!spill_user_xmp_to_extended && xmp_primary_str.size() <= kMaxStandardXmpPayload) {
// Write single standard XMP segment
const size_t length = 2 + xmpNameSpaceLength + xmp_primary_str.size();
const uint8_t lengthH = ((length >> 8) & 0xff);
Expand Down
19 changes: 4 additions & 15 deletions lib/src/ultrahdr_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@
#include "ultrahdr/editorhelper.h"
#include "ultrahdr/jpegr.h"

#ifdef UHDR_ENABLE_HEIF
#include "ultrahdr/heifultrahdr.h"
#include "ultrahdr/avifultrahdr.h"
#include "ultrahdr/gainmapmetadata.h"
#endif


#ifdef UHDR_ENABLE_HEIF
#include "ultrahdr/heifultrahdr.h"
#include "ultrahdr/avifultrahdr.h"
Expand Down Expand Up @@ -1319,14 +1312,10 @@ uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc) {
const bool mayWriteXmp = !handle->m_xmp.empty() || !handle->m_compressed_images.empty();
size_t xmpAllowance = 0;
if (mayWriteXmp) {
if (handle->m_xmp.size() <= ultrahdr::kMaxStandardXmpPayload) {
xmpAllowance = ultrahdr::kJpegAppSegmentTotalMaxBytes;
} else {
const size_t xmp_sz = handle->m_xmp.size();
const size_t num_chunks =
(xmp_sz + ultrahdr::kExtendedXmpMaxChunkSize - 1) / ultrahdr::kExtendedXmpMaxChunkSize;
xmpAllowance = ultrahdr::kJpegAppSegmentTotalMaxBytes + xmp_sz + num_chunks * 79;
}
const size_t xmp_sz = handle->m_xmp.size();
const size_t num_chunks =
(xmp_sz + ultrahdr::kExtendedXmpMaxChunkSize - 1) / ultrahdr::kExtendedXmpMaxChunkSize;
xmpAllowance = ultrahdr::kJpegAppSegmentTotalMaxBytes + xmp_sz + num_chunks * 79;
}
auto addXmpAllowance = [xmpAllowance](size_t base_size, size_t* output_size) {
if (xmpAllowance > (std::numeric_limits<size_t>::max)() - base_size) return false;
Expand Down
90 changes: 90 additions & 0 deletions tests/ultrahdr_api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,96 @@ TEST_F(UltraHdrApiTest, StripGainMapInvalidParamsAndCorruptInput) {
EXPECT_EQ(uhdr_strip_gain_map(&no_sos_in, &out).error_code, UHDR_CODEC_INVALID_PARAM);
}

TEST_F(UltraHdrApiTest, StripGainMapOnAppleGainMapFixtures) {
for (const char* filename : {"apple_gainmap_new.jpg", "apple_gainmap_old.jpg"}) {
SCOPED_TRACE(filename);
std::vector<uint8_t> apple_data;
ASSERT_TRUE(loadFile(filename, apple_data));
ASSERT_EQ(is_uhdr_image(apple_data.data(), static_cast<int>(apple_data.size())), 1);

uhdr_compressed_image_t apple_in{apple_data.data(), apple_data.size(), apple_data.size(),
UHDR_CG_DISPLAY_P3, UHDR_CT_SRGB, UHDR_CR_FULL_RANGE};
uhdr_mem_block_t size_query{nullptr, 0, 0};
ASSERT_EQ(uhdr_strip_gain_map(&apple_in, &size_query).error_code, UHDR_CODEC_OK);
ASSERT_GT(size_query.data_sz, 0u);
ASSERT_LT(size_query.data_sz, apple_data.size());

std::vector<uint8_t> stripped_buf(size_query.data_sz);
uhdr_mem_block_t stripped_block{stripped_buf.data(), 0, stripped_buf.size()};
ASSERT_EQ(uhdr_strip_gain_map(&apple_in, &stripped_block).error_code, UHDR_CODEC_OK);
EXPECT_EQ(stripped_block.data_sz, size_query.data_sz);
EXPECT_EQ(is_uhdr_image(stripped_block.data, static_cast<int>(stripped_block.data_sz)), 0);

// Primary entropy-coded scan (including DRI and RST0..RST7 markers) must be bit-identical.
const std::vector<uint8_t> orig_scan =
extractPrimaryScanBytes(apple_data.data(), apple_data.size());
const std::vector<uint8_t> stripped_scan =
extractPrimaryScanBytes(stripped_block.data, stripped_block.data_sz);
ASSERT_FALSE(orig_scan.empty());
EXPECT_EQ(orig_scan, stripped_scan);

// Verify EXIF and ICC profile are preserved and stripped JPEG decodes cleanly.
JpegDecoderHelper orig_decoder;
ASSERT_EQ(orig_decoder.parseImage(apple_data.data(), apple_data.size()).error_code,
UHDR_CODEC_OK);
JpegDecoderHelper stripped_decoder;
ASSERT_EQ(stripped_decoder
.decompressImage(stripped_block.data, stripped_block.data_sz, DECODE_TO_RGB_CS)
.error_code,
UHDR_CODEC_OK);
ASSERT_EQ(stripped_decoder.getEXIFSize(), orig_decoder.getEXIFSize());
EXPECT_EQ(memcmp(stripped_decoder.getEXIFPtr(), orig_decoder.getEXIFPtr(),
orig_decoder.getEXIFSize()),
0);
ASSERT_EQ(stripped_decoder.getICCSize(), orig_decoder.getICCSize());
EXPECT_EQ(memcmp(stripped_decoder.getICCPtr(), orig_decoder.getICCPtr(),
orig_decoder.getICCSize()),
0);
}
}

TEST_F(UltraHdrApiTest, NearLimitStandardXmpSpillsCleanlyToExtendedXmp) {
// 65,200 bytes is <= kMaxStandardXmpPayload (65,503 B), but when UHDR_WRITE_XMP is enabled,
// merging the ~600-byte Ultra HDR container directory pushes it over 65,503 B into Extended XMP.
constexpr size_t kNearLimitSize = 65200;
const std::string near_limit_xmp = makeLargeXmp(kNearLimitSize);
uhdr_mem_block_t xmp_block{const_cast<char*>(near_limit_xmp.data()), near_limit_xmp.size(),
near_limit_xmp.size()};
uhdr_gainmap_metadata_t metadata = makeTestGainmapMetadata();

EncoderPtr enc = makeEncoder();
ASSERT_NE(enc, nullptr);
ASSERT_EQ(configureJpegGainmapEncoder(enc.get(), &mSdrCompressed, &mSdrCompressed, &metadata,
&xmp_block)
.error_code,
UHDR_CODEC_OK);
ASSERT_EQ(uhdr_encode(enc.get()).error_code, UHDR_CODEC_OK);
uhdr_compressed_image_t* uhdr_stream = uhdr_get_encoded_stream(enc.get());
ASSERT_NE(uhdr_stream, nullptr);

// Verify uhdr_dec_get_xmp recovers the exact 65,200-byte user XMP packet.
DecoderPtr dec = makeDecoder();
ASSERT_NE(dec, nullptr);
ASSERT_EQ(uhdr_dec_set_image(dec.get(), uhdr_stream).error_code, UHDR_CODEC_OK);
ASSERT_EQ(uhdr_dec_probe(dec.get()).error_code, UHDR_CODEC_OK);
uhdr_mem_block_t* decoded_xmp = uhdr_dec_get_xmp(dec.get());
ASSERT_NE(decoded_xmp, nullptr);
EXPECT_EQ(getXmpPacket(decoded_xmp), near_limit_xmp);

// Verify uhdr_strip_gain_map also preserves the 65,200-byte user XMP packet cleanly.
std::vector<uint8_t> stripped_buf(uhdr_stream->data_sz);
uhdr_mem_block_t stripped_block{stripped_buf.data(), 0, stripped_buf.size()};
ASSERT_EQ(uhdr_strip_gain_map(uhdr_stream, &stripped_block).error_code, UHDR_CODEC_OK);
EXPECT_EQ(is_uhdr_image(stripped_block.data, static_cast<int>(stripped_block.data_sz)), 0);

JpegDecoderHelper stripped_decoder;
ASSERT_EQ(stripped_decoder.parseImage(stripped_block.data, stripped_block.data_sz).error_code,
UHDR_CODEC_OK);
uhdr_mem_block_t stripped_xmp_mem{stripped_decoder.getXMPPtr(), stripped_decoder.getXMPSize(),
stripped_decoder.getXMPSize()};
EXPECT_EQ(getXmpPacket(&stripped_xmp_mem), near_limit_xmp);
}

TEST_F(UltraHdrApiTest, JpegEncodeWithXmpAndDecode) {
uhdr_codec_private_t* enc = uhdr_create_encoder();
ASSERT_NE(enc, nullptr);
Expand Down
Loading