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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ build*
third_party/googletest
third_party/turbojpeg
third_party/benchmark
tests/data
tests/data
!tests/data/
tests/data/*
!tests/data/gainmap_hevc_16x16.heic
!tests/data/GAINMAP_FIXTURE_README.md
22 changes: 18 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ file(GLOB UHDR_TEST_SRCS_LIST "${TESTS_DIR}/*.cpp")
file(GLOB UHDR_BM_SRCS_LIST "${BENCHMARK_DIR}/*.cpp")
file(GLOB IMAGE_IO_SRCS_LIST "${THIRD_PARTY_DIR}/image_io/src/**/*.cc")

set(PRIVATE_INCLUDE_DIR ${SOURCE_DIR}/include/ ${JPEG_INCLUDE_DIRS})
set(PRIVATE_INCLUDE_DIR ${SOURCE_DIR}/include/ ${EXPORT_INCLUDE_DIR} ${JPEG_INCLUDE_DIRS})
set(PRIVATE_LINK_LIBS ${JPEG_LIBRARIES} Threads::Threads)
if(UHDR_ENABLE_SMPTE2094_50)
list(APPEND PRIVATE_LINK_LIBS smpte2094_50_utils)
Expand All @@ -556,11 +556,13 @@ if(UHDR_ENABLE_GLES)
list(APPEND PRIVATE_LINK_LIBS ${EGL_LIBRARIES} ${OPENGLES3_LIBRARIES})
endif()
if(UHDR_ENABLE_HEIF)
set(LIBHEIF_SYSTEM_INCOMPATIBLE FALSE)
if(NOT UHDR_BUILD_DEPS)
find_package(libheif QUIET)
if(LIBHEIF_FOUND AND NOT LIBHEIF_HAS_GAIN_MAP)
message(WARNING "System libheif found but lacks ISO 21496-1 gain map API (PR 1503). "
"Switching to bundled dependency build.")
message(WARNING "System libheif found but lacks the required ISO 21496-1 gain map API. "
"HEIF/AVIF support will be disabled because UHDR_BUILD_DEPS is OFF.")
set(LIBHEIF_SYSTEM_INCOMPATIBLE TRUE)
set(LIBHEIF_FOUND FALSE)
endif()
endif()
Expand Down Expand Up @@ -642,14 +644,26 @@ if(UHDR_ENABLE_HEIF)
INTERFACE_LINK_LIBRARIES "${CMAKE_DL_LIBS}"
)
endif()
set(LIBHEIF_HAS_GAIN_MAP TRUE)
set(LIBHEIF_HAS_ITEM_API TRUE)
set(LIBHEIF_FOUND TRUE)
endif()
if(LIBHEIF_FOUND)
message(STATUS "Found libheif: ${LIBHEIF_LIBRARIES}")
add_compile_options(-DUHDR_ENABLE_HEIF)
if(LIBHEIF_HAS_ITEM_API)
message(STATUS "libheif item inspection API - Available")
add_compile_options(-DUHDR_HAS_HEIF_ITEM_API)
else()
message(WARNING "libheif item inspection API unavailable. HEIF/AVIF encoding and decoding "
"remain enabled, but uhdr_is_supported_gainmap_image() returns 0 for "
"HEIF/AVIF.")
endif()
list(APPEND PRIVATE_LINK_LIBS libheif::heif)
else()
message(WARNING "libheif not found. Building without HEIF/AVIF support.")
if(NOT LIBHEIF_SYSTEM_INCOMPATIBLE)
message(WARNING "libheif not found. Building without HEIF/AVIF support.")
endif()
set(UHDR_ENABLE_HEIF FALSE)
endif()
endif()
Expand Down
31 changes: 30 additions & 1 deletion cmake/Findlibheif.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
# Result Variables:
# LIBHEIF_FOUND - True if libheif was found
# LIBHEIF_HAS_GAIN_MAP - True if libheif includes ISO 21496-1 gain map APIs
# LIBHEIF_HAS_ITEM_API - True if libheif exposes item inspection APIs
# LIBHEIF_VERSION - Version of libheif found
# LIBHEIF_INCLUDE_DIRS - Include directories for libheif
# LIBHEIF_LIBRARIES - Libraries needed to link against libheif

include(CheckCXXSymbolExists)
include(CheckCXXSourceCompiles)
include(FindPackageHandleStandardArgs)

set(LIBHEIF_TARGET "")
Expand Down Expand Up @@ -91,7 +93,8 @@ if(LIBHEIF_TARGET)
list(APPEND CMAKE_REQUIRED_INCLUDES ${LIBHEIF_INCLUDE_DIR})
endif()
if(LIBHEIF_DEFS)
set(CMAKE_REQUIRED_DEFINITIONS "-D${LIBHEIF_DEFS}")
set(CMAKE_REQUIRED_DEFINITIONS ${LIBHEIF_DEFS})
list(TRANSFORM CMAKE_REQUIRED_DEFINITIONS PREPEND "-D")
endif()

# Perform a compile-only check to avoid linking transitive dependencies
Expand All @@ -104,6 +107,32 @@ if(LIBHEIF_TARGET)
"libheif/heif.h"
LIBHEIF_HAS_GAIN_MAP
)
check_cxx_source_compiles(
[=[
#include "libheif/heif.h"
#include "libheif/heif_items.h"

int main() {
const heif_context* context = nullptr;
const heif_image_handle* handle = nullptr;
const heif_item_id item_id = heif_image_handle_get_item_id(handle);
const uint32_t item_type = heif_item_get_item_type(context, item_id);
const int aux_filter = 0;
heif_item_id auxiliary_ids[1] = {};
heif_image_handle* auxiliary_handle = nullptr;
const char* auxiliary_type = nullptr;

(void)heif_image_handle_get_number_of_auxiliary_images(handle, aux_filter);
(void)heif_image_handle_get_list_of_auxiliary_image_IDs(handle, aux_filter, auxiliary_ids, 1);
(void)heif_image_handle_get_auxiliary_type(handle, &auxiliary_type);
(void)heif_image_handle_release_auxiliary_type(handle, &auxiliary_type);
(void)heif_image_handle_get_auxiliary_image_handle(handle, item_id, &auxiliary_handle);
(void)item_type;
return 0;
}
]=]
LIBHEIF_HAS_ITEM_API
)

set(CMAKE_TRY_COMPILE_TARGET_TYPE ${_saved_try_compile_target_type})
unset(CMAKE_REQUIRED_INCLUDES)
Expand Down
1 change: 1 addition & 0 deletions fuzzer/ultrahdr_dec_fuzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ void UltraHdrDecFuzzer::process() {
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
(void)uhdr_is_supported_gainmap_image(data, size);
UltraHdrDecFuzzer fuzzHandle(data, size);
fuzzHandle.process();
return 0;
Expand Down
14 changes: 12 additions & 2 deletions lib/include/ultrahdr/jpegr.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ class JpegR : public UltraHdr {
uhdr_error_info_t getJPEGRInfo(uhdr_compressed_image_t* uhdr_compressed_img,
jr_info_ptr uhdr_image_info);

// Internal fast-probe variant. Compressed JPEG payloads are borrowed while parsing and the MPF
// directory must identify the exact secondary JPEG range.
uhdr_error_info_t getJPEGRInfoForProbe(uhdr_compressed_image_t* uhdr_compressed_img,
jr_info_ptr uhdr_image_info);

/* \brief Alias of Encode API-0.
*
* \deprecated This function is deprecated. Use its alias
Expand Down Expand Up @@ -276,6 +281,10 @@ class JpegR : public UltraHdr {
status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpegr_image_info_ptr);

private:
uhdr_error_info_t getJPEGRInfo(uhdr_compressed_image_t* uhdr_compressed_img,
jr_info_ptr uhdr_image_info, bool copy_image_data,
bool require_mpf_association);

/*!\brief compress gainmap image
*
* \param[in] gainmap_img gainmap image descriptor
Expand All @@ -296,7 +305,8 @@ class JpegR : public UltraHdr {
*/
uhdr_error_info_t extractPrimaryImageAndGainMap(uhdr_compressed_image_t* jpegr_image,
uhdr_compressed_image_t* primary_image,
uhdr_compressed_image_t* gainmap_image);
uhdr_compressed_image_t* gainmap_image,
bool require_mpf_association = false);

/*!\brief This function parses the bitstream and returns metadata that is useful for actual
* decoding. This does not decode the image. That is handled by decompressImage().
Expand All @@ -310,7 +320,7 @@ class JpegR : public UltraHdr {
*/
uhdr_error_info_t parseJpegInfo(uhdr_compressed_image_t* jpeg_image, j_info_ptr image_info,
unsigned int* img_width = nullptr,
unsigned int* img_height = nullptr);
unsigned int* img_height = nullptr, bool copy_image_data = true);

/*!\brief This method takes compressed sdr intent, compressed gainmap coefficient, gainmap
* metadata and creates a ultrahdr image. This is done by first generating XMP packet from gainmap
Expand Down
167 changes: 160 additions & 7 deletions lib/src/jpegr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1438,14 +1438,27 @@ uhdr_error_info_t JpegR::appendGainMap(uhdr_compressed_image_t* sdr_intent_compr

uhdr_error_info_t JpegR::getJPEGRInfo(uhdr_compressed_image_t* uhdr_compressed_img,
jr_info_ptr uhdr_image_info) {
return getJPEGRInfo(uhdr_compressed_img, uhdr_image_info, true, false);
}

uhdr_error_info_t JpegR::getJPEGRInfoForProbe(uhdr_compressed_image_t* uhdr_compressed_img,
jr_info_ptr uhdr_image_info) {
return getJPEGRInfo(uhdr_compressed_img, uhdr_image_info, false, true);
}

uhdr_error_info_t JpegR::getJPEGRInfo(uhdr_compressed_image_t* uhdr_compressed_img,
jr_info_ptr uhdr_image_info, bool copy_image_data,
bool require_mpf_association) {
uhdr_compressed_image_t primary_image, gainmap;

UHDR_ERR_CHECK(extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_image, &gainmap))
UHDR_ERR_CHECK(extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_image, &gainmap,
require_mpf_association))

UHDR_ERR_CHECK(parseJpegInfo(&primary_image, uhdr_image_info->primaryImgInfo,
&uhdr_image_info->width, &uhdr_image_info->height))
&uhdr_image_info->width, &uhdr_image_info->height, copy_image_data))
if (uhdr_image_info->gainmapImgInfo != nullptr) {
UHDR_ERR_CHECK(parseJpegInfo(&gainmap, uhdr_image_info->gainmapImgInfo))
UHDR_ERR_CHECK(
parseJpegInfo(&gainmap, uhdr_image_info->gainmapImgInfo, nullptr, nullptr, copy_image_data))
}

return g_no_error;
Expand Down Expand Up @@ -1861,9 +1874,137 @@ uhdr_error_info_t UltraHdr::applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
return g_no_error;
}

namespace {

bool readMpfU16(const uint8_t* data, size_t size, size_t offset, bool big_endian, uint16_t* value) {
if (offset > size || size - offset < sizeof(uint16_t)) return false;
*value = big_endian ? static_cast<uint16_t>((data[offset] << 8) | data[offset + 1])
: static_cast<uint16_t>(data[offset] | (data[offset + 1] << 8));
return true;
}

bool readMpfU32(const uint8_t* data, size_t size, size_t offset, bool big_endian, uint32_t* value) {
if (offset > size || size - offset < sizeof(uint32_t)) return false;
if (big_endian) {
*value = (static_cast<uint32_t>(data[offset]) << 24) |
(static_cast<uint32_t>(data[offset + 1]) << 16) |
(static_cast<uint32_t>(data[offset + 2]) << 8) | data[offset + 3];
} else {
*value = data[offset] | (static_cast<uint32_t>(data[offset + 1]) << 8) |
(static_cast<uint32_t>(data[offset + 2]) << 16) |
(static_cast<uint32_t>(data[offset + 3]) << 24);
}
return true;
}

bool hasValidMpfAssociation(const uhdr_compressed_image_t* image, const JpegInfo& info) {
const auto& image_ranges = info.GetImageRanges();
if (image_ranges.size() != 2) return false;
const JpegSegmentInfo mpf = info.GetSegmentInfo(0, kMpf);
if (!mpf.IsValid() || image->data == nullptr) return false;
const DataRange& mpf_range = mpf.GetDataRange();
if (mpf_range.GetEnd() > image->data_sz || mpf_range.GetBegin() > mpf_range.GetEnd())
return false;
const size_t mpf_end = mpf_range.GetEnd();

const uint8_t* bytes = static_cast<const uint8_t*>(image->data);
constexpr size_t kJpegSegmentHeaderSize = 4;
if (mpf_range.GetBegin() > mpf_end ||
mpf_end - mpf_range.GetBegin() < kJpegSegmentHeaderSize + sizeof(kMpfSig)) {
return false;
}
const size_t signature = mpf_range.GetBegin() + kJpegSegmentHeaderSize;
if (memcmp(bytes + signature, kMpfSig, sizeof(kMpfSig)) != 0) return false;

const size_t tiff = signature + sizeof(kMpfSig);
if (tiff > mpf_end || mpf_end - tiff < kMpEndianSize + sizeof(uint32_t)) {
return false;
}
const bool big_endian = memcmp(bytes + tiff, kMpBigEndian, kMpEndianSize) == 0;
if (!big_endian && memcmp(bytes + tiff, kMpLittleEndian, kMpEndianSize) != 0) return false;

uint32_t ifd_offset = 0;
if (!readMpfU32(bytes, mpf_end, tiff + kMpEndianSize, big_endian, &ifd_offset) ||
ifd_offset > mpf_end - tiff) {
return false;
}
const size_t ifd = tiff + ifd_offset;
uint16_t tag_count = 0;
if (!readMpfU16(bytes, mpf_end, ifd, big_endian, &tag_count) || tag_count > 64) {
return false;
}

size_t entries_offset = 0;
uint32_t entries_size = 0;
uint32_t image_count = 0;
bool has_valid_version = false;
bool has_image_count = false;
bool has_mp_entries = false;
size_t tag = ifd + sizeof(uint16_t);
for (uint16_t i = 0; i < tag_count; ++i, tag += kTagSize) {
uint16_t id = 0;
uint16_t type = 0;
uint32_t count = 0;
uint32_t value = 0;
if (!readMpfU16(bytes, mpf_end, tag, big_endian, &id) ||
!readMpfU16(bytes, mpf_end, tag + 2, big_endian, &type) ||
!readMpfU32(bytes, mpf_end, tag + 4, big_endian, &count) ||
!readMpfU32(bytes, mpf_end, tag + 8, big_endian, &value)) {
return false;
}
if (id == kVersionTag) {
if (has_valid_version || type != kVersionType || count != kVersionCount ||
memcmp(bytes + tag + 8, kVersionExpected, kVersionSize) != 0) {
return false;
}
has_valid_version = true;
} else if (id == kNumberOfImagesTag) {
if (has_image_count || type != kTypeLong || count != kNumberOfImagesCount) return false;
has_image_count = true;
image_count = value;
} else if (id == kMPEntryTag) {
if (has_mp_entries || type != kTypeUndefined || count < 2 * kMPEntrySize) return false;
has_mp_entries = true;
entries_offset = value;
entries_size = count;
}
}
if (!has_valid_version || !has_image_count || !has_mp_entries || image_count != 2 ||
entries_size != image_count * kMPEntrySize ||
entries_offset > mpf_end - tiff || mpf_end - (tiff + entries_offset) < entries_size) {
return false;
}

const size_t primary_entry = tiff + entries_offset;
const size_t secondary_entry = tiff + entries_offset + kMPEntrySize;
uint32_t primary_attributes = 0;
uint32_t primary_offset = 0;
uint32_t secondary_attributes = 0;
uint32_t secondary_size = 0;
uint32_t secondary_offset = 0;
constexpr uint32_t kMpfFormatAndTypeMask = 0x0fffffff;
if (!readMpfU32(bytes, mpf_end, primary_entry, big_endian, &primary_attributes) ||
!readMpfU32(bytes, mpf_end, primary_entry + 8, big_endian, &primary_offset) ||
!readMpfU32(bytes, mpf_end, secondary_entry, big_endian, &secondary_attributes) ||
!readMpfU32(bytes, mpf_end, secondary_entry + 4, big_endian, &secondary_size) ||
!readMpfU32(bytes, mpf_end, secondary_entry + 8, big_endian, &secondary_offset) ||
(primary_attributes & kMpfFormatAndTypeMask) != kMPEntryAttributeTypePrimary ||
(secondary_attributes & kMpfFormatAndTypeMask) != kMPEntryAttributeFormatJpeg ||
primary_offset != 0 || secondary_offset > image->data_sz - tiff) {
return false;
}
// Only the secondary entry is needed to associate the gain-map JPEG. Some Apple-authored files
// use legacy primary-size bookkeeping, so do not reject an otherwise exact association for it.
return tiff + secondary_offset == image_ranges[1].GetBegin() &&
secondary_size == image_ranges[1].GetLength();
}

} // namespace

uhdr_error_info_t JpegR::extractPrimaryImageAndGainMap(uhdr_compressed_image_t* jpegr_image,
uhdr_compressed_image_t* primary_image,
uhdr_compressed_image_t* gainmap_image) {
uhdr_compressed_image_t* gainmap_image,
bool require_mpf_association) {
MessageHandler msg_handler;
msg_handler.SetMessageWriter(make_unique<AlogMessageWriter>(AlogMessageWriter()));

Expand Down Expand Up @@ -1916,6 +2057,15 @@ uhdr_error_info_t JpegR::extractPrimaryImageAndGainMap(uhdr_compressed_image_t*
return status;
}

if (require_mpf_association && !hasValidMpfAssociation(jpegr_image, jpeg_info)) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"input uhdr image does not contain a valid MPF gain map association");
return status;
}

if (gainmap_image != nullptr) {
gainmap_image->data = static_cast<uint8_t*>(jpegr_image->data) + image_ranges[1].GetBegin();
gainmap_image->data_sz = image_ranges[1].GetLength();
Expand All @@ -1931,7 +2081,8 @@ uhdr_error_info_t JpegR::extractPrimaryImageAndGainMap(uhdr_compressed_image_t*
}

uhdr_error_info_t JpegR::parseJpegInfo(uhdr_compressed_image_t* jpeg_image, j_info_ptr image_info,
unsigned int* img_width, unsigned int* img_height) {
unsigned int* img_width, unsigned int* img_height,
bool copy_image_data) {
JpegDecoderHelper jpeg_dec_obj;
UHDR_ERR_CHECK(jpeg_dec_obj.parseImage(jpeg_image->data, jpeg_image->data_sz))
unsigned int imgWidth, imgHeight, numComponents;
Expand All @@ -1943,8 +2094,10 @@ uhdr_error_info_t JpegR::parseJpegInfo(uhdr_compressed_image_t* jpeg_image, j_in
image_info->width = imgWidth;
image_info->height = imgHeight;
image_info->numComponents = numComponents;
image_info->imgData.resize(jpeg_image->data_sz, 0);
memcpy(static_cast<void*>(image_info->imgData.data()), jpeg_image->data, jpeg_image->data_sz);
if (copy_image_data) {
image_info->imgData.resize(jpeg_image->data_sz, 0);
memcpy(static_cast<void*>(image_info->imgData.data()), jpeg_image->data, jpeg_image->data_sz);
}
if (jpeg_dec_obj.getICCSize() != 0) {
image_info->iccData.resize(jpeg_dec_obj.getICCSize(), 0);
memcpy(static_cast<void*>(image_info->iccData.data()), jpeg_dec_obj.getICCPtr(),
Expand Down
Loading
Loading