Skip to content
Closed
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,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
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
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
50 changes: 44 additions & 6 deletions lib/include/ultrahdr/jpegr.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@

namespace ultrahdr {

// Maximum length of a JPEG marker segment payload (including the 2-byte length field).
constexpr size_t kJpegSegmentMaxLength = 0xffff;
// Maximum byte footprint for a JPEG application segment including the 2-byte marker code.
constexpr size_t kJpegAppSegmentTotalMaxBytes = kJpegSegmentMaxLength + 2;

// Adobe XMP namespaces and segment length limits
constexpr size_t kMaxStandardXmpPayload = kJpegSegmentMaxLength - 2 - 29; // 65504 bytes
// Extended XMP segment header: 2 (length) + 35 (signature) + 32 (GUID) + 4 (totalLength) + 4 (offset) = 77 bytes
constexpr size_t kExtendedXmpHeaderSize = 77;
constexpr size_t kExtendedXmpMaxChunkSize = kJpegSegmentMaxLength - kExtendedXmpHeaderSize; // 65458 bytes

/*
* Holds information of jpeg image
*/
Expand Down Expand Up @@ -78,7 +89,8 @@ class JpegR : public UltraHdr {
* \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
*/
uhdr_error_info_t encodeJPEGR(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 @@ -95,11 +107,14 @@ class JpegR : public UltraHdr {
* \param[in] quality quality factor for sdr intent jpeg 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 encodeJPEGR(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 Encode API-2.
*
Expand All @@ -117,12 +132,15 @@ class JpegR : public UltraHdr {
* \param[in] sdr_intent_compressed sdr intent compressed input image descriptor
* \param[in, out] dest output image descriptor to store compressed ultrahdr
* image
* \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 encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent,
uhdr_compressed_image_t* sdr_intent_compressed,
uhdr_compressed_image_t* dest);
uhdr_compressed_image_t* dest,
uhdr_mem_block_t* xmp = nullptr);

/*!\brief Encode API-3.
*
Expand All @@ -138,12 +156,15 @@ class JpegR : public UltraHdr {
* \param[in] sdr_intent_compressed sdr intent compressed input image descriptor
* \param[in, out] dest output image descriptor to store compressed ultrahdr
* image
* \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 encodeJPEGR(uhdr_raw_image_t* hdr_intent,
uhdr_compressed_image_t* sdr_intent_compressed,
uhdr_compressed_image_t* dest);
uhdr_compressed_image_t* dest,
uhdr_mem_block_t* xmp = nullptr);

/*!\brief Encode API-4.
*
Expand All @@ -157,13 +178,16 @@ class JpegR : public UltraHdr {
* \param[in] metadata gainmap metadata descriptor
* \param[in, out] dest output image descriptor to store compressed ultrahdr
* image
* \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 encodeJPEGR(uhdr_compressed_image_t* base_img_compressed,
uhdr_compressed_image_t* gainmap_img_compressed,
uhdr_gainmap_metadata_ext_t* metadata,
uhdr_compressed_image_t* dest);
uhdr_compressed_image_t* dest,
uhdr_mem_block_t* xmp = nullptr);

/*!\brief Decode API.
*
Expand Down Expand Up @@ -275,6 +299,16 @@ class JpegR : public UltraHdr {
*/
status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpegr_image_info_ptr);

/*!\brief Losslessly strip gain map bitstream and gain map metadata from a JPEG/R stream.
*
* \param[in] in_stream input compressed JPEG/R stream
* \param[in, out] out_stream output memory block receiving the stripped SDR JPEG
* \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, #UHDR_CODEC_INVALID_PARAM
* otherwise.
*/
uhdr_error_info_t stripGainMap(uhdr_compressed_image_t* in_stream,
uhdr_mem_block_t* out_stream);

private:
/*!\brief compress gainmap image
*
Expand Down Expand Up @@ -333,14 +367,18 @@ class JpegR : public UltraHdr {
* \param[in] metadata gainmap metadata descriptor
* \param[in, out] dest output image descriptor to store compressed ultrahdr
* image
* \param[in] pXmp optional primary-image XMP packet to preserve and
* merge when XMP metadata is enabled
*
* \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
* An unsupported or unmergeable XMP packet returns #UHDR_CODEC_INVALID_PARAM in XMP mode.
*/
uhdr_error_info_t appendGainMap(uhdr_compressed_image_t* sdr_intent_compressed,
uhdr_compressed_image_t* gainmap_compressed,
uhdr_mem_block_t* pExif, void* pIcc, size_t icc_size,
uhdr_gainmap_metadata_ext_t* metadata,
uhdr_compressed_image_t* dest);
uhdr_compressed_image_t* dest,
uhdr_mem_block_t* pXmp = nullptr);

/*
* This method will check the validity of the input arguments.
Expand Down
30 changes: 28 additions & 2 deletions lib/include/ultrahdr/jpegrutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ uhdr_error_info_t Write(uhdr_compressed_image_t* destination, const void* source
uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, uint8_t* exif_data,
int exif_size, uhdr_gainmap_metadata_ext_t* metadata);

/*
* Computes 128-bit MD5 digest formatted as a 32-character uppercase hexadecimal GUID.
*/
std::string computeMd5Guid(const uint8_t* data, size_t len);

/*
* Generates a minimal standard XMP packet referencing Extended XMP via xmpNote:HasExtendedXMP.
*/
std::string generateStandardXmpWithExtendedGuid(const std::string& guid);

/*
* This method generates XMP metadata for the primary image.
*
Expand Down Expand Up @@ -124,10 +134,26 @@ uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, uint8_t
* </x:xmpmeta>
*
* @param secondary_image_length length of secondary image
* @return XMP metadata in type of string
* @param user_xmp optional XMP packet from the primary image. Its unrelated XML is preserved while
* the encoder-owned primary-image gain-map description is replaced.
* @return XMP metadata in type of string. An empty string means that a supplied packet could not be
* safely merged.
*/
std::string generateXmpForPrimaryImage(size_t secondary_image_length,
uhdr_gainmap_metadata_ext_t& metadata);
uhdr_gainmap_metadata_ext_t& metadata,
uhdr_mem_block_t* user_xmp = nullptr,
const std::string& extended_xmp_guid = "");

/*
* Strips Ultra HDR gain-map and GContainer directory properties from a primary-image XMP packet
* while preserving all unrelated XMP metadata and packet wrappers.
*
* @param xmp_data Input XMP packet XML string (without the JPEG APP1 namespace header).
* @param stripped_xmp Output string receiving the sanitized XMP packet, or empty string if the
* packet contained only gain-map metadata and no remaining descriptions.
* @return true if the XMP packet was valid and processed, false if the XML could not be parsed.
*/
bool stripGainMapFromXmp(const std::string& xmp_data, std::string* stripped_xmp);

/*
* This method generates XMP metadata for the recovery map image.
Expand Down
Loading
Loading