Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
59418fb
feat(jpeg): preserve and expose XMP metadata in libultrahdr
DichenZhang1 Sep 11, 2026
8ff0b83
Fix JPEG XMP output sizing and reject oversized packets
gregbenz Sep 12, 2026
271a3d7
refactor(jpeg): unify JPEG segment size constants in jpegr.h
DichenZhang1 Sep 16, 2026
5dbaf8e
feat(jpeg): support Adobe XMP Part 3 multi-segment Extended XMP
DichenZhang1 Sep 17, 2026
09a3936
feat(api): add uhdr_strip_gain_map and namespace-aware XMP merging/st…
DichenZhang1 Oct 2, 2026
259f3ed
fix(xmp): handle near-limit XMP spill to Extended XMP, add fuzzer and…
DichenZhang1 Oct 2, 2026
a4e9fa2
Preserve namespace declarations used by retained XMP (#495)
gregbenz Oct 5, 2026
262cb76
Strip gain-map metadata from linked Extended XMP (#496)
gregbenz Oct 6, 2026
773d924
Merge branch 'main' into feat/preserve-xmp-metadata
DichenZhang1 Oct 9, 2026
b3581bb
fix(build): add libheif to libuhdr.pc Requires.private and fix GCC 15…
DichenZhang1 Oct 9, 2026
ea4d7e4
chore(release): bump version to 2.1.0 and include libheif in pkg-config
DichenZhang1 Oct 9, 2026
94eeda4
Address PR #497 review feedback on Extended XMP and codec XMP validation
DichenZhang1 Oct 13, 2026
19808d4
fix(jpeg): scope temporary XMP strings before libjpeg decompress setj…
DichenZhang1 Oct 14, 2026
4dc9648
fix(xmp): preserve inherited rdf prefix, default xmlns, and xml:lang …
DichenZhang1 Oct 16, 2026
51355f9
Consolidate XMP packet parsing and editing helpers
DichenZhang1 Oct 16, 2026
4d137cf
Support XMP metadata in HEIF and AVIF codecs
DichenZhang1 Oct 16, 2026
fa20466
fix(xmp): normalize uhdr_dec_get_xmp to bare XML
DichenZhang1 Oct 16, 2026
b78ec52
fix(heif): strip gainmap XMP when encoding XMP
DichenZhang1 Oct 16, 2026
f0e4619
fix(heif): skip empty XMP and propagate read err
DichenZhang1 Oct 16, 2026
491b444
feat(heif): extract EXIF and ICC in uhdr_dec_probe
DichenZhang1 Oct 16, 2026
1b7639e
test(fuzzer): fuzz uhdr_enc_set_xmp_data
DichenZhang1 Oct 16, 2026
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
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ cmake_minimum_required(VERSION 3.15)
cmake_policy(SET CMP0091 NEW)

set(UHDR_MAJOR_VERSION 2)
set(UHDR_MINOR_VERSION 0)
set(UHDR_PATCH_VERSION 2)
set(UHDR_MINOR_VERSION 1)
set(UHDR_PATCH_VERSION 0)
project(libuhdr
VERSION ${UHDR_MAJOR_VERSION}.${UHDR_MINOR_VERSION}.${UHDR_PATCH_VERSION}
LANGUAGES C CXX
Expand Down Expand Up @@ -651,6 +651,7 @@ if(UHDR_ENABLE_HEIF)
if(LIBHEIF_FOUND)
message(STATUS "Found libheif: ${LIBHEIF_LIBRARIES}")
add_compile_options(-DUHDR_ENABLE_HEIF)
set(UHDR_PC_REQUIRES_PRIVATE " libheif")
if(LIBHEIF_HAS_ITEM_API)
message(STATUS "libheif item inspection API - Available")
add_compile_options(-DUHDR_HAS_HEIF_ITEM_API)
Expand Down Expand Up @@ -752,6 +753,7 @@ if(UHDR_BUILD_TESTS)
target_include_directories(ultrahdr_unit_test PRIVATE
${PRIVATE_INCLUDE_DIR}
${GTEST_INCLUDE_DIRS}
"${THIRD_PARTY_DIR}/image_io/includes"
)
if(UHDR_BUILD_FUZZERS)
target_link_options(ultrahdr_unit_test PRIVATE -fsanitize=fuzzer-no-link)
Expand Down
2 changes: 1 addition & 1 deletion cmake/libuhdr.pc.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: @PROJECT_NAME@
Description: @CMAKE_PROJECT_DESCRIPTION@
Version: @PROJECT_VERSION@
Requires.private: libjpeg
Requires.private: libjpeg@UHDR_PC_REQUIRES_PRIVATE@
Cflags: -I${includedir}
Libs: -L${libdir} -l@UHDR_TARGET_NAME@
Libs.private: @CMAKE_THREAD_LIBS_INIT@ @UHDR_GL_DEPS@
49 changes: 44 additions & 5 deletions examples/ultrahdr_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,9 @@ class UltraHdrAppInput {
public:
UltraHdrAppInput(const char* hdrIntentRawFile, const char* sdrIntentRawFile,
const char* sdrIntentCompressedFile, const char* gainmapCompressedFile,
const char* gainmapMetadataCfgFile, const char* exifFile, const char* outputFile,
int width, int height, uhdr_img_fmt_t hdrCf = UHDR_IMG_FMT_32bppRGBA1010102,
const char* gainmapMetadataCfgFile, const char* exifFile, const char* xmpFile,
const char* outputFile, int width, int height,
uhdr_img_fmt_t hdrCf = UHDR_IMG_FMT_32bppRGBA1010102,
uhdr_img_fmt_t sdrCf = UHDR_IMG_FMT_32bppRGBA8888,
uhdr_color_gamut_t hdrCg = UHDR_CG_DISPLAY_P3,
uhdr_color_gamut_t sdrCg = UHDR_CG_BT_709,
Expand All @@ -277,6 +278,7 @@ class UltraHdrAppInput {
mGainMapCompressedFile(gainmapCompressedFile),
mGainMapMetadataCfgFile(gainmapMetadataCfgFile),
mExifFile(exifFile),
mXmpFile(xmpFile),
mUhdrFile(nullptr),
mOutputFile(outputFile),
mWidth(width),
Expand Down Expand Up @@ -312,6 +314,7 @@ class UltraHdrAppInput {
mGainMapCompressedFile(nullptr),
mGainMapMetadataCfgFile(gainmapMetadataCfgFile),
mExifFile(nullptr),
mXmpFile(nullptr),
mUhdrFile(uhdrFile),
mOutputFile(outputFile),
mWidth(0),
Expand Down Expand Up @@ -370,6 +373,7 @@ class UltraHdrAppInput {
}
}
if (mExifBlock.data) free(mExifBlock.data);
if (mXmpBlock.data) free(mXmpBlock.data);
if (mUhdrImage.data) free(mUhdrImage.data);
}

Expand All @@ -385,6 +389,7 @@ class UltraHdrAppInput {
bool fillGainMapCompressedImageHandle();
bool fillGainMapMetadataDescriptor();
bool fillExifMemoryBlock();
bool fillXmpMemoryBlock();
void writeGainMapMetadataToFile(uhdr_gainmap_metadata_t* metadata, std::ostream& file);
bool convertRgba8888ToYUV444Image();
bool convertRgba1010102ToYUV444Image();
Expand All @@ -401,6 +406,7 @@ class UltraHdrAppInput {
const char* mGainMapCompressedFile;
const char* mGainMapMetadataCfgFile;
const char* mExifFile;
const char* mXmpFile;
const char* mUhdrFile;
const char* mOutputFile;
const int mWidth;
Expand Down Expand Up @@ -435,6 +441,7 @@ class UltraHdrAppInput {
uhdr_compressed_image_t mGainMapCompressedImage{};
uhdr_gainmap_metadata mGainMapMetadata{};
uhdr_mem_block_t mExifBlock{};
uhdr_mem_block_t mXmpBlock{};
uhdr_compressed_image_t mUhdrImage{};
uhdr_raw_image_t mDecodedUhdrRgbImage{};
uhdr_raw_image_t mDecodedUhdrYuv444Image{};
Expand Down Expand Up @@ -614,6 +621,19 @@ bool UltraHdrAppInput::fillExifMemoryBlock() {
return false;
}

bool UltraHdrAppInput::fillXmpMemoryBlock() {
std::ifstream ifd(mXmpFile, std::ios::binary | std::ios::ate);
if (ifd.good()) {
auto size = ifd.tellg();
mXmpBlock.data = nullptr;
mXmpBlock.data_sz = size;
mXmpBlock.capacity = size;
ifd.close();
return loadFile(mXmpFile, mXmpBlock.data, size);
}
return false;
}

void UltraHdrAppInput::writeGainMapMetadataToFile(uhdr_gainmap_metadata_t* metadata,
std::ostream& file) {
bool allChannelsIdentical = metadata->max_content_boost[0] == metadata->max_content_boost[1] &&
Expand Down Expand Up @@ -725,6 +745,12 @@ bool UltraHdrAppInput::encode() {
return false;
}
}
if (mXmpFile != nullptr) {
if (!fillXmpMemoryBlock()) {
std::cerr << " failed to load file " << mXmpFile << std::endl;
return false;
}
}

#define RET_IF_ERR(x) \
{ \
Expand Down Expand Up @@ -766,6 +792,9 @@ bool UltraHdrAppInput::encode() {
if (mExifFile != nullptr) {
RET_IF_ERR(uhdr_enc_set_exif_data(handle, &mExifBlock))
}
if (mXmpFile != nullptr) {
RET_IF_ERR(uhdr_enc_set_xmp_data(handle, &mXmpBlock))
}

if (mOutputFile != nullptr) {
std::string outStr(mOutputFile);
Expand Down Expand Up @@ -856,6 +885,11 @@ bool UltraHdrAppInput::decode() {
if (mProbe) {
std::cout << "Ultra HDR Image: Yes \nGainMap Metadata: " << std::endl;
writeGainMapMetadataToFile(metadata, std::cout);
uhdr_mem_block_t* xmp = uhdr_dec_get_xmp(handle);
if (xmp != nullptr && xmp->data != nullptr && xmp->data_sz > 0) {
std::cout << "XMP Metadata (" << xmp->data_sz << " bytes): \n"
<< std::string(static_cast<const char*>(xmp->data), xmp->data_sz) << std::endl;
}
uhdr_release_decoder(handle);
return true;
}
Expand Down Expand Up @@ -1501,6 +1535,7 @@ static void usage(const char* name) {
" For PQ content, this defaults to 10000 nits. \n"
" any real number in range [203, 10000]. \n");
fprintf(stderr, " -x binary input resource containing exif data to insert, optional. \n");
fprintf(stderr, " -X resource containing xmp data to insert, optional. \n");
fprintf(stderr, "\n## decoder options : \n");
fprintf(stderr, " -j ultra hdr compressed input resource, required. \n");
fprintf(stderr,
Expand Down Expand Up @@ -1594,10 +1629,11 @@ static void usage(const char* name) {
}

int main(int argc, char* argv[]) {
char opt_string[] = "p:y:i:g:f:w:h:C:c:t:q:o:O:m:j:e:a:b:z:R:s:M:Q:G:x:u:D:k:K:L:P";
char opt_string[] = "p:y:i:g:f:w:h:C:c:t:q:o:O:m:j:e:a:b:z:R:s:M:Q:G:x:X:u:D:k:K:L:P";
char *hdr_intent_raw_file = nullptr, *sdr_intent_raw_file = nullptr, *uhdr_file = nullptr,
*sdr_intent_compressed_file = nullptr, *gainmap_compressed_file = nullptr,
*gainmap_metadata_cfg_file = nullptr, *output_file = nullptr, *exif_file = nullptr;
*gainmap_metadata_cfg_file = nullptr, *output_file = nullptr, *exif_file = nullptr,
*xmp_file = nullptr;
int width = 0, height = 0;
uhdr_color_gamut_t hdr_cg = UHDR_CG_DISPLAY_P3;
uhdr_color_gamut_t sdr_cg = UHDR_CG_BT_709;
Expand Down Expand Up @@ -1702,6 +1738,9 @@ int main(int argc, char* argv[]) {
case 'x':
exif_file = optarg_s;
break;
case 'X':
xmp_file = optarg_s;
break;
case 'u':
enable_gles = atoi(optarg_s) == 1 ? true : false;
break;
Expand Down Expand Up @@ -1744,7 +1783,7 @@ int main(int argc, char* argv[]) {
}
UltraHdrAppInput appInput(
hdr_intent_raw_file, sdr_intent_raw_file, sdr_intent_compressed_file,
gainmap_compressed_file, gainmap_metadata_cfg_file, exif_file,
gainmap_compressed_file, gainmap_metadata_cfg_file, exif_file, xmp_file,
output_file ? output_file : "out.jpeg", width, height, hdr_cf, sdr_cf, hdr_cg, sdr_cg,
hdr_tf, quality, out_tf, out_cf, use_full_range_color_hdr, gainmap_scale_factor,
gainmap_compression_quality, use_multi_channel_gainmap, gamma, enable_gles, enc_preset,
Expand Down
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
23 changes: 23 additions & 0 deletions fuzzer/ultrahdr_enc_fuzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,15 @@ void UltraHdrEncFuzzer::process() {
uhdr_mem_block_t exif{greeting, mFdp.ConsumeIntegralInRange<uint8_t>(0, sizeof greeting * 2),
sizeof greeting};

// xmp
std::string xmp_str = mFdp.ConsumeRandomLengthString(128 * 1024);
uhdr_mem_block_t xmp{xmp_str.empty() ? nullptr : const_cast<char*>(xmp_str.data()),
xmp_str.size(), xmp_str.size()};

// output format
uhdr_codec_t output_codec =
mFdp.PickValueInArray({UHDR_CODEC_JPG, UHDR_CODEC_HEIF, UHDR_CODEC_AVIF});

ALOGV("encoding configuration options : ");
ALOGV("encoding api - %d ", (int)muxSwitch);
ALOGV("image dimensions %d x %d ", (int)width, (int)height);
Expand Down Expand Up @@ -376,6 +385,10 @@ void UltraHdrEncFuzzer::process() {
ON_ERR(uhdr_enc_set_quality(enc_handle, base_quality, UHDR_BASE_IMG))
ON_ERR(uhdr_enc_set_quality(enc_handle, gainmap_quality, UHDR_GAIN_MAP_IMG))
ON_ERR(uhdr_enc_set_exif_data(enc_handle, &exif))
if (mFdp.ConsumeBool()) {
ON_ERR(uhdr_enc_set_xmp_data(enc_handle, &xmp))
}
ON_ERR(uhdr_enc_set_output_format(enc_handle, output_codec))
ON_ERR(uhdr_enc_set_using_multi_channel_gainmap(enc_handle, multi_channel_gainmap))
ON_ERR(uhdr_enc_set_gainmap_scale_factor(enc_handle, gm_scale_factor))
ON_ERR(uhdr_enc_set_gainmap_gamma(enc_handle, gamma[0]))
Expand Down Expand Up @@ -427,6 +440,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 +458,9 @@ 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_exif(dec_handle);
uhdr_dec_get_icc(dec_handle);
uhdr_dec_get_xmp(dec_handle);
uhdr_release_decoder(dec_handle);
}
}
Expand Down
15 changes: 12 additions & 3 deletions lib/include/ultrahdr/avifultrahdr.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,14 @@ class AvifUltraHdr : public UltraHdr {
* \param[in] quality quality factor for sdr intent heif/avif compression
* \param[in] exif optional exif metadata that needs to be inserted in
* compressed output
* \param[in] xmp optional xmp metadata that needs to be inserted in
* compressed output
*
* \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
*/
uhdr_error_info_t encodeAvifUltraHdr(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest,
int quality, uhdr_mem_block_t* exif);
int quality, uhdr_mem_block_t* exif,
uhdr_mem_block_t* xmp = nullptr);

/*!\brief Encode API-1.
*
Expand All @@ -88,11 +91,14 @@ class AvifUltraHdr : public UltraHdr {
* \param[in] quality quality factor for sdr intent heif/avif compression
* \param[in] exif optional exif metadata that needs to be inserted in
* compressed output
* \param[in] xmp optional xmp metadata that needs to be inserted in
* compressed output
*
* \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
*/
uhdr_error_info_t encodeAvifUltraHdr(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent,
uhdr_compressed_image_t* dest, int quality, uhdr_mem_block_t* exif);
uhdr_compressed_image_t* dest, int quality,
uhdr_mem_block_t* exif, uhdr_mem_block_t* xmp = nullptr);

/*!\brief Decode API.
*
Expand Down Expand Up @@ -159,6 +165,8 @@ class AvifUltraHdr : public UltraHdr {
* compressed output
* \param[in] baseIcc base image icc
* \param[in] alternateIcc alternate image icc
* \param[in] xmp optional xmp metadata that needs to be inserted in
* compressed output
*
* \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
*/
Expand All @@ -168,7 +176,8 @@ class AvifUltraHdr : public UltraHdr {
uhdr_gainmap_metadata_ext_t* metadata,
uhdr_compressed_image_t* dest, int quality,
uhdr_mem_block_t* exif, DataStruct* baseIcc,
DataStruct* alternateIcc);
DataStruct* alternateIcc,
uhdr_mem_block_t* xmp = nullptr);

uhdr_codec_t mCodec;
};
Expand Down
15 changes: 12 additions & 3 deletions lib/include/ultrahdr/heifultrahdr.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,14 @@ class HeifUltraHdr : public UltraHdr {
* \param[in] quality quality factor for sdr intent heif/avif compression
* \param[in] exif optional exif metadata that needs to be inserted in
* compressed output
* \param[in] xmp optional xmp metadata that needs to be inserted in
* compressed output
*
* \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
*/
uhdr_error_info_t encodeHeicUltraHdr(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest,
int quality, uhdr_mem_block_t* exif);
int quality, uhdr_mem_block_t* exif,
uhdr_mem_block_t* xmp = nullptr);

/*!\brief Encode API-1.
*
Expand All @@ -88,11 +91,14 @@ class HeifUltraHdr : public UltraHdr {
* \param[in] quality quality factor for sdr intent heif/avif compression
* \param[in] exif optional exif metadata that needs to be inserted in
* compressed output
* \param[in] xmp optional xmp metadata that needs to be inserted in
* compressed output
*
* \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
*/
uhdr_error_info_t encodeHeicUltraHdr(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent,
uhdr_compressed_image_t* dest, int quality, uhdr_mem_block_t* exif);
uhdr_compressed_image_t* dest, int quality,
uhdr_mem_block_t* exif, uhdr_mem_block_t* xmp = nullptr);

/*!\brief Decode API.
*
Expand Down Expand Up @@ -159,6 +165,8 @@ class HeifUltraHdr : public UltraHdr {
* compressed output
* \param[in] baseIcc base image icc
* \param[in] alternateIcc alternate image icc
* \param[in] xmp optional xmp metadata that needs to be inserted in
* compressed output
*
* \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
*/
Expand All @@ -168,7 +176,8 @@ class HeifUltraHdr : public UltraHdr {
uhdr_gainmap_metadata_ext_t* metadata,
uhdr_compressed_image_t* dest, int quality,
uhdr_mem_block_t* exif, DataStruct* baseIcc,
DataStruct* alternateIcc);
DataStruct* alternateIcc,
uhdr_mem_block_t* xmp = nullptr);

uhdr_codec_t mCodec;
};
Expand Down
Loading
Loading