diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f3d0bc87b..badf1e31ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -485,6 +485,8 @@ option(WITH_HEADER_COMPRESSION OFF) option(ENABLE_MULTITHREADING_SUPPORT "Switch off for platforms without multithreading support" ON) option(ENABLE_PARALLEL_TILE_DECODING "Will launch multiple decoders to decode tiles in parallel (requires ENABLE_MULTITHREADING_SUPPORT)" ON) +option(ENABLE_EXPERIMENTAL_MINI_FORMAT "Enable experimental (draft) low-overhead box format (likely reduced interoperability)." OFF) + if (WITH_REDUCED_VISIBILITY) set(CMAKE_CXX_VISIBILITY_PRESET hidden) else () diff --git a/CMakePresets.json b/CMakePresets.json index 11bac1524b..7cceccfe00 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -23,6 +23,7 @@ "WITH_AOM_ENCODER_PLUGIN" : "OFF", "WITH_DAV1D" : "ON", "WITH_DAV1D_PLUGIN" : "OFF", + "ENABLE_EXPERIMENTAL_MINI_FORMAT" : "ON", "WITH_LIBDE265" : "ON", "WITH_LIBDE265_PLUGIN" : "OFF", "WITH_RAV1E" : "ON", diff --git a/go/heif/heif.go b/go/heif/heif.go index e94b42c14e..d0c9287c15 100644 --- a/go/heif/heif.go +++ b/go/heif/heif.go @@ -335,6 +335,8 @@ const ( SuberrorNoIcbrBox = C.heif_suberror_No_icbr_box + SuberrorInvalidMiniBox = C.heif_suberror_Invalid_mini_box + // --- Unsupported_feature --- // Image was coded with an unsupported compression method. diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt index 5116467034..4530e93705 100644 --- a/libheif/CMakeLists.txt +++ b/libheif/CMakeLists.txt @@ -227,6 +227,13 @@ if (WITH_UNCOMPRESSED_CODEC) codecs/uncompressed/decoder_tile_component_interleave.cc) endif () +if (ENABLE_EXPERIMENTAL_MINI_FORMAT) + target_compile_definitions(heif PUBLIC ENABLE_EXPERIMENTAL_MINI_FORMAT=1) + target_sources(heif PRIVATE + mini.h + mini.cc) +endif () + write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake COMPATIBILITY ExactVersion) install(TARGETS heif EXPORT ${PROJECT_NAME}-config diff --git a/libheif/api/libheif/heif.cc b/libheif/api/libheif/heif.cc index 0e398115b4..3a328223e6 100644 --- a/libheif/api/libheif/heif.cc +++ b/libheif/api/libheif/heif.cc @@ -175,6 +175,9 @@ heif_error heif_has_compatible_filetype(const uint8_t* data, int len) heif_brand2_miaf, heif_brand2_mif1, heif_brand2_mif2 +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + , heif_brand2_mif3 +#endif }; auto it = supported_brands.find(main_brand); @@ -302,6 +305,13 @@ heif_brand2 heif_fourcc_to_brand(const char* fourcc_string) return fourcc(fourcc_string); } +heif_brand2 heif_read_minor_version_brand(const uint8_t* data, int len) +{ + if (len < 16) { + return heif_unknown_brand; + } + return heif_fourcc_to_brand((char*) (data + 12)); +} void heif_brand_to_fourcc(heif_brand2 brand, char* out_fourcc) { @@ -462,6 +472,22 @@ const char* heif_get_file_mime_type(const uint8_t* data, int len) else if (mainBrand == heif_avis) { return "image/avif-sequence"; } +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + else if (mainBrand == heif_brand2_mif3) { + heif_brand2 minorBrand = heif_read_minor_version_brand(data, len); + if (minorBrand == heif_brand2_avif) { + return "image/avif"; + } + if (minorBrand == heif_brand2_heic || + minorBrand == heif_brand2_heix || + minorBrand == heif_brand2_heim || + minorBrand == heif_brand2_heis) { + return "image/heic"; + } + // There could be other options in here, like VVC or J2K + return "image/heif"; + } +#endif else if (mainBrand == heif_j2ki) { return "image/hej2k"; } diff --git a/libheif/api/libheif/heif.h b/libheif/api/libheif/heif.h index 46f7b31c69..d7c4c497cb 100644 --- a/libheif/api/libheif/heif.h +++ b/libheif/api/libheif/heif.h @@ -251,6 +251,9 @@ enum heif_suberror_code heif_suberror_No_avcC_box = 143, + // we got a mini box, but could not read it properly + heif_suberror_Invalid_mini_box = 149, + // Decompressing generic compression or header compression data failed (e.g. bitstream corruption) heif_suberror_Decompression_invalid_data = 150, @@ -773,6 +776,13 @@ typedef uint32_t heif_brand2; */ #define heif_brand2_mif2 heif_fourcc('m','i','f','2') +/** + * HEIF image structural brand (`mif3`). + * + * This indicates the low-overhead (ftyp+mini) structure. + */ +#define heif_brand2_mif3 heif_fourcc('m','i','f','3') + /** * HEIF image sequence structural brand (`msf1`). * @@ -874,6 +884,10 @@ typedef uint32_t heif_brand2; LIBHEIF_API heif_brand2 heif_read_main_brand(const uint8_t* data, int len); +// input data should be at least 16 bytes +LIBHEIF_API +heif_brand2 heif_read_minor_version_brand(const uint8_t* data, int len); + // 'brand_fourcc' must be 4 character long, but need not be 0-terminated LIBHEIF_API heif_brand2 heif_fourcc_to_brand(const char* brand_fourcc); @@ -1966,6 +1980,13 @@ struct heif_decoded_mastering_display_colour_volume double min_display_mastering_luminance; }; +struct heif_ambient_viewing_environment +{ + uint32_t ambient_illumination; + uint16_t ambient_light_x; + uint16_t ambient_light_y; +}; + LIBHEIF_API int heif_image_has_mastering_display_colour_volume(const struct heif_image*); diff --git a/libheif/api/libheif/heif_emscripten.h b/libheif/api/libheif/heif_emscripten.h index 5dfc813a73..da50c9e4d4 100644 --- a/libheif/api/libheif/heif_emscripten.h +++ b/libheif/api/libheif/heif_emscripten.h @@ -361,6 +361,7 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_Missing_grid_images", heif_suberror_Missing_grid_images) .value("heif_suberror_No_av1C_box", heif_suberror_No_av1C_box) .value("heif_suberror_No_avcC_box", heif_suberror_No_avcC_box) + .value("heif_suberror_Invalid_mini_box", heif_suberror_Invalid_mini_box) .value("heif_suberror_Invalid_clean_aperture", heif_suberror_Invalid_clean_aperture) .value("heif_suberror_Invalid_overlay_data", heif_suberror_Invalid_overlay_data) .value("heif_suberror_Overlay_image_outside_of_canvas", heif_suberror_Overlay_image_outside_of_canvas) diff --git a/libheif/bitstream.cc b/libheif/bitstream.cc index 1cfe4abc31..127f236326 100644 --- a/libheif/bitstream.cc +++ b/libheif/bitstream.cc @@ -520,6 +520,11 @@ uint8_t BitReader::get_bits8(int n) return static_cast(get_bits(n)); } +uint16_t BitReader::get_bits16(int n) +{ + assert(n>0 && n <= 16); + return static_cast(get_bits(n)); +} uint32_t BitReader::get_bits32(int n) { @@ -527,6 +532,20 @@ uint32_t BitReader::get_bits32(int n) return static_cast(get_bits(n)); } +bool BitReader::get_flag() +{ + return (get_bits(1) == 0x01); +} + +std::vector BitReader::read_bytes(uint32_t n) +{ + // TODO: this implementation isn't very efficient + std::vector bytes; + for (uint32_t i = 0; i < n; i++) { + bytes.push_back(get_bits8(8)); + } + return bytes; +} int BitReader::get_bits_fast(int n) { diff --git a/libheif/bitstream.h b/libheif/bitstream.h index 6d1c8ed13a..be73eb64fe 100644 --- a/libheif/bitstream.h +++ b/libheif/bitstream.h @@ -383,8 +383,19 @@ class BitReader uint8_t get_bits8(int n); + uint16_t get_bits16(int n); + uint32_t get_bits32(int n); + /** + * Get a one-bit flag value. + * + * @returns true if the next bit value is 1, otherwise false + */ + bool get_flag(); + + std::vector read_bytes(uint32_t n); + int get_bits_fast(int n); int peek_bits(int n); diff --git a/libheif/box.cc b/libheif/box.cc index 9f2821f7da..a72f5ab46f 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -596,6 +596,10 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result, const heif_ box = std::make_shared(); break; + case fourcc("amve"): + box = std::make_shared(); + break; + case fourcc("cmin"): box = std::make_shared(); break; @@ -686,6 +690,18 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result, const heif_ box = std::make_shared(); break; +#if WITH_EXPERIMENTAL_FEATURES + case fourcc("tilC"): + box = std::make_shared(); + break; +#endif + +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + case fourcc("mini"): + box = std::make_shared(); + break; +#endif + case fourcc("mdat"): // avoid generating a 'Box_other' box = std::make_shared(); @@ -1055,7 +1071,7 @@ Error Box_ftyp::parse(BitstreamRange& range, const heif_security_limits* limits) m_minor_version = range.read32(); uint64_t box_size = get_box_size(); - if (box_size < 8 || box_size - 8 <= get_header_size()) { + if (box_size < 8 || box_size - 8 < get_header_size()) { // Sanity check. return Error(heif_error_Invalid_input, heif_suberror_Invalid_box_size, @@ -1088,8 +1104,15 @@ std::string Box_ftyp::dump(Indent& indent) const sstr << BoxHeader::dump(indent); sstr << indent << "major brand: " << fourcc_to_string(m_major_brand) << "\n" - << indent << "minor version: " << m_minor_version << "\n" - << indent << "compatible brands: "; + << indent << "minor version: "; + if (m_minor_version < ('A' << 24)) { + // This is probably a version number + sstr << m_minor_version; + } else { + // probably a 4CC, as used for mif3 + sstr << fourcc_to_string(m_minor_version); + } + sstr << "\n" << indent << "compatible brands: "; bool first = true; for (uint32_t brand : m_compatible_brands) { @@ -2581,6 +2604,160 @@ Error Box_mdcv::write(StreamWriter& writer) const return Error::Ok; } +Box_amve::Box_amve() +{ + set_short_type(fourcc("amve")); + + // These values are not valid. + amve.ambient_illumination = 0; + amve.ambient_light_x = 0; + amve.ambient_light_y = 0; +} + +Error Box_amve::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + amve.ambient_illumination = range.read32(); + amve.ambient_light_x = range.read16(); + amve.ambient_light_y = range.read16(); + + return range.get_error(); +} + + +std::string Box_amve::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + + sstr << indent << "ambient_illumination: " << amve.ambient_illumination << "\n"; + sstr << indent << "ambient_light_x: " << amve.ambient_light_x << "\n"; + sstr << indent << "ambient_light_y: " << amve.ambient_light_y << "\n"; + + return sstr.str(); +} + + +Error Box_amve::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + writer.write32(amve.ambient_illumination); + writer.write16(amve.ambient_light_x); + writer.write16(amve.ambient_light_y); + + prepend_header(writer, box_start); + + return Error::Ok; +} + + +Box_cclv::Box_cclv() +{ + set_short_type(fourcc("cclv")); + + m_ccv_primaries_valid = false; + m_ccv_min_luminance_valid = false; + m_ccv_max_luminance_valid = false; + m_ccv_avg_luminance_valid = false; +} + + +void Box_cclv::set_primaries(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2) +{ + m_ccv_primaries_valid = true; + m_ccv_primaries_x[0] = x0; + m_ccv_primaries_y[0] = y0; + m_ccv_primaries_x[1] = x1; + m_ccv_primaries_y[1] = y1; + m_ccv_primaries_x[2] = x2; + m_ccv_primaries_y[2] = y2; +} + + +Error Box_cclv::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + uint8_t flags = range.read8(); + m_ccv_primaries_valid = (flags & 0b00100000); + m_ccv_min_luminance_valid = (flags & 0b00010000); + m_ccv_max_luminance_valid = (flags & 0b00001000); + m_ccv_avg_luminance_valid = (flags & 0b00000100); + if (m_ccv_primaries_valid) { + for (int c = 0; c < 3; c++) { + m_ccv_primaries_x[c] = range.read32s(); + m_ccv_primaries_y[c] = range.read32s(); + } + } + if (m_ccv_min_luminance_valid) { + m_ccv_min_luminance_value = range.read32(); + } + if (m_ccv_max_luminance_valid) { + m_ccv_max_luminance_value = range.read32(); + } + if (m_ccv_avg_luminance_valid) { + m_ccv_avg_luminance_value = range.read32(); + } + return range.get_error(); +} + + +std::string Box_cclv::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + + sstr << indent << "ccv_primaries_present_flag: " << m_ccv_primaries_valid << "\n"; + sstr << indent << "ccv_min_luminance_value_present_flag: " << m_ccv_min_luminance_valid << "\n"; + sstr << indent << "ccv_max_luminance_value_present_flag: " << m_ccv_max_luminance_valid << "\n"; + sstr << indent << "ccv_avg_luminance_value_present_flag: " << m_ccv_avg_luminance_valid << "\n"; + if (m_ccv_primaries_valid) { + sstr << indent << "ccv_primaries (x,y): "; + sstr << "(" << m_ccv_primaries_x[0] << ";" << m_ccv_primaries_y[0] << "), "; + sstr << "(" << m_ccv_primaries_x[1] << ";" << m_ccv_primaries_y[1] << "), "; + sstr << "(" << m_ccv_primaries_x[2] << ";" << m_ccv_primaries_y[2] << ")\n"; + } + if (m_ccv_min_luminance_valid) { + sstr << indent << "ccv_min_luminance_value: " << m_ccv_min_luminance_value << "\n"; + } + if (m_ccv_max_luminance_valid) { + sstr << indent << "ccv_max_luminance_value: " << m_ccv_max_luminance_value << "\n"; + } + if (m_ccv_avg_luminance_valid) { + sstr << indent << "ccv_avg_luminance_value: " << m_ccv_avg_luminance_value << "\n"; + } + return sstr.str(); +} + + +Error Box_cclv::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + uint8_t flags = 0; + flags |= m_ccv_primaries_valid ? 0b00100000 : 0; + flags |= m_ccv_min_luminance_valid ? 0b00010000 : 0; + flags |= m_ccv_max_luminance_valid ? 0b00001000 : 0; + flags |= m_ccv_avg_luminance_valid ? 0b00000100 : 0; + writer.write8(flags); + if (m_ccv_primaries_valid) { + for (int c = 0; c < 3; c++) { + writer.write32s(m_ccv_primaries_x[c]); + writer.write32s(m_ccv_primaries_y[c]); + } + } + if (m_ccv_min_luminance_valid) { + writer.write32(m_ccv_min_luminance_value); + } + if (m_ccv_max_luminance_valid) { + writer.write32(m_ccv_max_luminance_value); + } + if (m_ccv_avg_luminance_valid) { + writer.write32(m_ccv_avg_luminance_value); + } + prepend_header(writer, box_start); + + return Error::Ok; +} + Error Box_ipco::get_properties_for_item_ID(uint32_t itemID, const std::shared_ptr& ipma, diff --git a/libheif/box.h b/libheif/box.h index 119c2a662b..051abcc2f7 100644 --- a/libheif/box.h +++ b/libheif/box.h @@ -386,8 +386,12 @@ class Box_ftyp : public Box std::vector list_brands() const { return m_compatible_brands; } + uint32_t get_major_brand() const { return m_major_brand; } + void set_major_brand(heif_brand2 major_brand) { m_major_brand = major_brand; } + uint32_t get_minor_version() const { return m_minor_version; } + void set_minor_version(uint32_t minor_version) { m_minor_version = minor_version; } void clear_compatible_brands() { m_compatible_brands.clear(); } @@ -551,6 +555,8 @@ class Box_iloc : public FullBox Error write_mdat_after_iloc(StreamWriter& writer); + void append_item(Item &item) { m_items.push_back(item); } + protected: Error parse(BitstreamRange& range, const heif_security_limits*) override; @@ -1233,6 +1239,72 @@ class Box_mdcv : public Box }; +class Box_amve : public Box +{ +public: + Box_amve(); + + heif_ambient_viewing_environment amve; + + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + +protected: + Error parse(BitstreamRange& range, const heif_security_limits*) override; +}; + + +class Box_cclv : public Box +{ +public: + Box_cclv(); + + bool ccv_primaries_are_valid() const { return m_ccv_primaries_valid; } + int32_t get_ccv_primary_x0() const { return m_ccv_primaries_x[0]; } + int32_t get_ccv_primary_y0() const { return m_ccv_primaries_y[0]; } + int32_t get_ccv_primary_x1() const { return m_ccv_primaries_x[1]; } + int32_t get_ccv_primary_y1() const { return m_ccv_primaries_y[1]; } + int32_t get_ccv_primary_x2() const { return m_ccv_primaries_x[2]; } + int32_t get_ccv_primary_y2() const { return m_ccv_primaries_y[2]; } + void set_primaries(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2); + + bool min_luminance_is_valid() const { return m_ccv_min_luminance_valid; } + uint32_t get_min_luminance() const { return m_ccv_min_luminance_value; } + void set_min_luminance(uint32_t luminance) { m_ccv_min_luminance_valid = true; m_ccv_min_luminance_value = luminance; } + + bool max_luminance_is_valid() const { return m_ccv_max_luminance_valid; } + uint32_t get_max_luminance() const { return m_ccv_max_luminance_value; } + void set_max_luminance(uint32_t luminance) { m_ccv_max_luminance_valid = true; m_ccv_max_luminance_value = luminance; } + + bool avg_luminance_is_valid() const { return m_ccv_avg_luminance_valid; } + uint32_t get_avg_luminance() const { return m_ccv_avg_luminance_value; } + void set_avg_luminance(uint32_t luminance) { m_ccv_avg_luminance_valid = true; m_ccv_avg_luminance_value = luminance; } + + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + +protected: + Error parse(BitstreamRange& range, const heif_security_limits*) override; + +private: + bool m_ccv_primaries_valid; + int32_t m_ccv_primaries_x[3]; + int32_t m_ccv_primaries_y[3]; + bool m_ccv_min_luminance_valid; + uint32_t m_ccv_min_luminance_value; + bool m_ccv_max_luminance_valid; + uint32_t m_ccv_max_luminance_value; + bool m_ccv_avg_luminance_valid; + uint32_t m_ccv_avg_luminance_value; +}; + + class Box_cmin : public FullBox { public: diff --git a/libheif/codecs/avif_boxes.h b/libheif/codecs/avif_boxes.h index b233c6c2d7..8b678e511a 100644 --- a/libheif/codecs/avif_boxes.h +++ b/libheif/codecs/avif_boxes.h @@ -35,6 +35,10 @@ class Box_av1C : public Box { + +// allow access to protected parse() method +friend class HeifFile; + public: Box_av1C() { diff --git a/libheif/error.cc b/libheif/error.cc index 873bd264da..ce71e9a02a 100644 --- a/libheif/error.cc +++ b/libheif/error.cc @@ -176,6 +176,8 @@ const char* Error::get_error_string(heif_suberror_code err) return "Invalid data in generic compression inflation"; case heif_suberror_No_icbr_box: return "No 'icbr' box"; + case heif_suberror_Invalid_mini_box: + return "Unsupported or invalid 'mini' box"; // --- Memory_allocation_error --- diff --git a/libheif/file.cc b/libheif/file.cc index 83c61ffcb0..338116c852 100644 --- a/libheif/file.cc +++ b/libheif/file.cc @@ -26,6 +26,7 @@ #include "image-items/jpeg2000.h" #include "image-items/jpeg.h" #include "image-items/vvc.h" +#include "codecs/avif_boxes.h" #include "codecs/uncompressed/unc_boxes.h" #include @@ -153,7 +154,9 @@ void HeifFile::new_empty_file() m_iinf_box = std::make_shared(); m_iprp_box = std::make_shared(); m_pitm_box = std::make_shared(); - +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + m_mini_box = std::make_shared(); +#endif m_meta_box->append_child_box(m_hdlr_box); m_meta_box->append_child_box(m_pitm_box); m_meta_box->append_child_box(m_iloc_box); @@ -167,6 +170,9 @@ void HeifFile::new_empty_file() m_top_level_boxes.push_back(m_ftyp_box); m_top_level_boxes.push_back(m_meta_box); +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + m_top_level_boxes.push_back(m_mini_box); +#endif } @@ -244,6 +250,12 @@ void HeifFile::set_brand(heif_compression_format format, bool miaf_compatible) void HeifFile::write(StreamWriter& writer) { for (auto& box : m_top_level_boxes) { +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + if (box == nullptr) { + // Either mini or meta will be null, just ignore that one + continue; + } +#endif box->derive_box_version_recursive(); box->write(writer); } @@ -259,6 +271,12 @@ std::string HeifFile::debug_dump_boxes() const bool first = true; for (const auto& box : m_top_level_boxes) { +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + if (box == nullptr) { + // Either mini or meta will be null, just ignore that one + continue; + } +#endif // dump box content for debugging if (first) { @@ -275,6 +293,18 @@ std::string HeifFile::debug_dump_boxes() const return sstr.str(); } +#if ENABLE_EXPERIMENTAL_MINI_FORMAT +static uint32_t get_item_type_for_brand(const heif_brand2 brand) +{ + switch(brand) { + case heif_brand2_avif: + return fourcc("av01"); + // TODO: more + default: + return 0; + } +} +#endif Error HeifFile::parse_heif_file() { @@ -322,6 +352,10 @@ Error HeifFile::parse_heif_file() m_top_level_boxes.push_back(m_meta_box); // TODO: we are missing 'mdat' top level boxes +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + m_mini_box = m_file_layout->get_mini_box(); + m_top_level_boxes.push_back(m_mini_box); +#endif // --- check whether this is a HEIF file and its structural format @@ -335,6 +369,9 @@ Error HeifFile::parse_heif_file() !m_ftyp_box->has_compatible_brand(heif_brand2_mif1) && !m_ftyp_box->has_compatible_brand(heif_brand2_avif) && !m_ftyp_box->has_compatible_brand(heif_brand2_1pic) && +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + !(m_ftyp_box->get_major_brand() == heif_brand2_mif3) && +#endif !m_ftyp_box->has_compatible_brand(heif_brand2_jpeg)) { std::stringstream sstr; sstr << "File does not include any supported brands.\n"; @@ -344,6 +381,208 @@ Error HeifFile::parse_heif_file() sstr.str()); } +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + if (m_mini_box) { + m_hdlr_box = std::make_shared(); + m_hdlr_box->set_handler_type(fourcc("pict")); + + m_pitm_box = std::make_shared(); + m_pitm_box->set_item_ID(1); + + std::shared_ptr primary_infe_box = std::make_shared(); + primary_infe_box->set_version(2); + primary_infe_box->set_item_ID(1); + // TODO: check explicit codec flag + uint32_t minor_version = m_ftyp_box->get_minor_version(); + heif_brand2 mini_brand = minor_version; + uint32_t infe_type = get_item_type_for_brand(mini_brand); + if (infe_type == 0) { + // not found + std::stringstream sstr; + sstr << "Minimised file requires brand " << fourcc_to_string(mini_brand) << " but this is not yet supported."; + return Error(heif_error_Unsupported_filetype, + heif_suberror_Unspecified, + sstr.str()); + } + primary_infe_box->set_item_type_4cc(infe_type); + m_infe_boxes.insert(std::make_pair(1, primary_infe_box)); + + if (m_mini_box->get_alpha_item_data_size() != 0) { + std::shared_ptr alpha_infe_box = std::make_shared(); + alpha_infe_box->set_version(2); + alpha_infe_box->set_flags(1); + alpha_infe_box->set_item_ID(2); + alpha_infe_box->set_item_type_4cc(infe_type); + m_infe_boxes.insert(std::make_pair(2, alpha_infe_box)); + } + + if (m_mini_box->get_exif_flag()) { + std::shared_ptr exif_infe_box = std::make_shared(); + exif_infe_box->set_version(2); + exif_infe_box->set_flags(1); + exif_infe_box->set_item_ID(6); + exif_infe_box->set_item_type_4cc(fourcc("Exif")); + m_infe_boxes.insert(std::make_pair(6, exif_infe_box)); + } + + if (m_mini_box->get_xmp_flag()) { + std::shared_ptr xmp_infe_box = std::make_shared(); + xmp_infe_box->set_version(2); + xmp_infe_box->set_flags(1); + xmp_infe_box->set_item_ID(7); + xmp_infe_box->set_item_type_4cc(fourcc("mime")); + xmp_infe_box->set_content_type("application/rdf+xml"); + m_infe_boxes.insert(std::make_pair(7, xmp_infe_box)); + } + + m_ipco_box = std::make_shared(); + + // TODO: we should look this up based on the infe prop, not assume Box_av1C. + std::shared_ptr main_item_codec_prop = std::make_shared(); + std::shared_ptr istr = std::make_shared( + m_mini_box->get_main_item_codec_config().data(), + m_mini_box->get_main_item_codec_config().size(), + false + ); + BitstreamRange codec_range(istr, m_mini_box->get_main_item_codec_config().size(), nullptr); + main_item_codec_prop->parse(codec_range, heif_get_global_security_limits()); + m_ipco_box->append_child_box(main_item_codec_prop); // entry 1 + + std::shared_ptr ispe = std::make_shared(); + ispe->set_size(m_mini_box->get_width(), m_mini_box->get_height()); + m_ipco_box->append_child_box(ispe); // entry 2 + + std::shared_ptr pixi = std::make_shared(); + pixi->set_version(0); + // pixi->set_version(1); // TODO: when we support version 1 + // TODO: there is more when we do version 1, and anything other than RGB + pixi->add_channel_bits(m_mini_box->get_bit_depth()); + pixi->add_channel_bits(m_mini_box->get_bit_depth()); + pixi->add_channel_bits(m_mini_box->get_bit_depth()); + m_ipco_box->append_child_box(pixi); // entry 3 + + std::shared_ptr colr = std::make_shared(); + std::shared_ptr nclx = std::make_shared(); + nclx->set_colour_primaries(m_mini_box->get_colour_primaries()); + nclx->set_transfer_characteristics(m_mini_box->get_transfer_characteristics()); + nclx->set_matrix_coefficients(m_mini_box->get_matrix_coefficients()); + nclx->set_full_range_flag(m_mini_box->get_full_range_flag()); + colr->set_color_profile(nclx); + m_ipco_box->append_child_box(colr); // entry 4 + + std::shared_ptr colr_icc = std::make_shared(); + std::shared_ptr icc = std::make_shared(fourcc("prof"), m_mini_box->get_icc_data()); + colr_icc->set_color_profile(icc); + m_ipco_box->append_child_box(colr_icc); // entry 5 + + if (m_mini_box->get_alpha_item_codec_config().size() != 0) { + std::shared_ptr alpha_item_codec_prop = std::make_shared(); + std::shared_ptr istr = std::make_shared( + m_mini_box->get_alpha_item_codec_config().data(), + m_mini_box->get_alpha_item_codec_config().size(), + false + ); + BitstreamRange alpha_codec_range(istr, m_mini_box->get_alpha_item_codec_config().size(), nullptr); + alpha_item_codec_prop->parse(alpha_codec_range, heif_get_global_security_limits()); + m_ipco_box->append_child_box(alpha_item_codec_prop); // entry 6 + } + + if (m_mini_box->get_alpha_item_data_size() != 0) { + std::shared_ptr aux_type = std::make_shared(); + aux_type->set_aux_type("urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"); + m_ipco_box->append_child_box(aux_type); // entry 7 + } + + // 8 + + // 9 + + // 10 + m_ipma_box = std::make_shared(); + m_ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{true, uint16_t(1)}); + m_ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{false, uint16_t(2)}); + m_ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{false, uint16_t(3)}); + m_ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{true, uint16_t(4)}); + if (m_mini_box->get_icc_flag()) { + // m_ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{true, uint16_t(5)}); + } + if (m_mini_box->get_alpha_item_data_size() > 0) { + m_ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{true, uint16_t(6)}); + m_ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{false, uint16_t(2)}); + m_ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{true, uint16_t(7)}); + // m_ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{false, uint16_t(8)}); + // m_ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{true, uint16_t(9)}); + // m_ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{true, uint16_t(10)}); + } + // TODO: will need more + + + + m_iloc_box = std::make_shared(); + Box_iloc::Item main_item; + main_item.item_ID = 1; + main_item.construction_method = 0; + main_item.base_offset = 0; + main_item.data_reference_index = 0; + Box_iloc::Extent main_item_extent; + main_item_extent.offset = m_mini_box->get_main_item_data_offset(); + main_item_extent.length = m_mini_box->get_main_item_data_size(); + main_item.extents.push_back(main_item_extent); + m_iloc_box->append_item(main_item); + + if (m_mini_box->get_alpha_item_data_size() != 0) { + Box_iloc::Item alpha_item; + alpha_item.item_ID = 2; + alpha_item.base_offset = 0; + alpha_item.data_reference_index = 0; + Box_iloc::Extent alpha_item_extent; + alpha_item_extent.offset = m_mini_box->get_alpha_item_data_offset(); + alpha_item_extent.length = m_mini_box->get_alpha_item_data_size(); + alpha_item.extents.push_back(alpha_item_extent); + m_iloc_box->append_item(alpha_item); + } + if (m_mini_box->get_exif_flag()) { + Box_iloc::Item exif_item; + exif_item.item_ID = 6; + exif_item.base_offset = 0; + exif_item.data_reference_index = 0; + Box_iloc::Extent exif_item_extent; + exif_item_extent.offset = m_mini_box->get_exif_item_data_offset(); + exif_item_extent.length = m_mini_box->get_exif_item_data_size(); + exif_item.extents.push_back(exif_item_extent); + m_iloc_box->append_item(exif_item); + } + if (m_mini_box->get_xmp_flag()) { + Box_iloc::Item xmp_item; + xmp_item.item_ID = 7; + xmp_item.base_offset = 0; + xmp_item.data_reference_index = 0; + Box_iloc::Extent xmp_item_extent; + xmp_item_extent.offset = m_mini_box->get_xmp_item_data_offset(); + xmp_item_extent.length = m_mini_box->get_xmp_item_data_size(); + xmp_item.extents.push_back(xmp_item_extent); + m_iloc_box->append_item(xmp_item); + } + + m_iref_box = std::make_shared(); + std::vector to_items = {1}; + if (m_mini_box->get_alpha_item_data_size() != 0) { + m_iref_box->add_references(2, fourcc("auxl"), to_items); + } + // TODO: if alpha prem + // TODO: if gainmap flag && item 4 + // TODO: if gainmap flag && !item 4 + if (m_mini_box->get_exif_flag()) { + m_iref_box->add_references(6, fourcc("cdsc"), to_items); + } + if (m_mini_box->get_xmp_flag()) { + m_iref_box->add_references(7, fourcc("cdsc"), to_items); + } + return Error::Ok; + } + + // if we didn't find the mini box, meta is required +#endif if (!m_meta_box) { return Error(heif_error_Invalid_input, heif_suberror_No_meta_box); diff --git a/libheif/file.h b/libheif/file.h index 2a3eb2024a..fbe2ef38c8 100644 --- a/libheif/file.h +++ b/libheif/file.h @@ -211,6 +211,9 @@ class HeifFile std::shared_ptr m_ftyp_box; std::shared_ptr m_hdlr_box; std::shared_ptr m_meta_box; +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + std::shared_ptr m_mini_box; // meta alternative +#endif std::shared_ptr m_ipco_box; std::shared_ptr m_ipma_box; diff --git a/libheif/file_layout.cc b/libheif/file_layout.cc index dcc1947c17..8f6c536a30 100644 --- a/libheif/file_layout.cc +++ b/libheif/file_layout.cc @@ -148,6 +148,43 @@ Error FileLayout::read(const std::shared_ptr& stream, const heif_s break; } +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + // TODO: this is basically the same as the meta box case above, with different error handling. + if (box_header.get_short_type() == fourcc("mini")) { + const uint64_t mini_box_start = next_box_start; + if (box_header.get_box_size() == 0) { + // TODO: get file-size from stream and compute box size + return {heif_error_Invalid_input, + heif_suberror_Invalid_mini_box, + "Cannot read mini box with unspecified size"}; + } + uint64_t end_of_mini_box = mini_box_start + box_header.get_box_size(); + if (m_max_length < end_of_mini_box) { + m_max_length = m_stream_reader->request_range(mini_box_start, end_of_mini_box); + } + + if (m_max_length < end_of_mini_box) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_mini_box, + "Cannot read full mini box"}; + } + BitstreamRange mini_box_range(m_stream_reader, mini_box_start, end_of_mini_box); + std::shared_ptr mini_box; + err = Box::read(mini_box_range, &mini_box, heif_get_global_security_limits()); + if (err) { + std::cout << "error reading mini box" << std::endl; + return err; + } + + m_boxes.push_back(mini_box); + m_mini_box = std::dynamic_pointer_cast(mini_box); + if (m_mini_box == nullptr) { + std::cout << "error casting mini box" << std::endl; + } + return Error::Ok; + } +#endif + if (box_header.get_box_size() == 0) { return {heif_error_Invalid_input, heif_suberror_No_meta_box, diff --git a/libheif/file_layout.h b/libheif/file_layout.h index 5700a130d0..1e59e4d1e0 100644 --- a/libheif/file_layout.h +++ b/libheif/file_layout.h @@ -24,6 +24,9 @@ #include "error.h" #include "bitstream.h" #include "box.h" +#if ENABLE_EXPERIMENTAL_MINI_FORMAT +#include "mini.h" +#endif #include #include @@ -55,6 +58,10 @@ class FileLayout std::shared_ptr get_meta_box() { return m_meta_box; } +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + std::shared_ptr get_mini_box() { return m_mini_box; } +#endif + private: WriteMode m_writeMode = WriteMode::Floating; @@ -67,6 +74,9 @@ class FileLayout std::shared_ptr m_ftyp_box; std::shared_ptr m_meta_box; +#if ENABLE_EXPERIMENTAL_MINI_FORMAT + std::shared_ptr m_mini_box; +#endif uint64_t m_max_length = 0; // Length seen so far. It can grow over time. diff --git a/libheif/mini.cc b/libheif/mini.cc new file mode 100644 index 0000000000..468025a441 --- /dev/null +++ b/libheif/mini.cc @@ -0,0 +1,697 @@ +/* + * HEIF codec. + * Copyright (c) 2024 Brad Hards + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#include "mini.h" + +#include +#include +#include +#include +#include + +Error Box_mini::parse(BitstreamRange &range, const heif_security_limits *limits) +{ + uint64_t start_offset = range.get_istream()->get_position(); + std::size_t length = range.get_remaining_bytes(); + std::vector mini_data(length); + range.read(mini_data.data(), mini_data.size()); + BitReader bits(mini_data.data(), (int)(mini_data.size())); + m_version = bits.get_bits8(2); + m_explicit_codec_types_flag = bits.get_flag(); + m_float_flag = bits.get_flag(); + m_full_range_flag = bits.get_flag(); + m_alpha_flag = bits.get_flag(); + m_explicit_cicp_flag = bits.get_flag(); + m_hdr_flag = bits.get_flag(); + m_icc_flag = bits.get_flag(); + m_exif_flag = bits.get_flag(); + m_xmp_flag = bits.get_flag(); + m_chroma_subsampling = bits.get_bits8(2); + m_orientation = bits.get_bits8(3) + 1; + bool small_dimensions_flag = bits.get_flag(); + if (small_dimensions_flag) + { + m_width = 1 + bits.get_bits32(7); + m_height = 1 + bits.get_bits32(7); + } + else + { + m_width = 1 + bits.get_bits32(15); + m_height = 1 + bits.get_bits32(15); + } + if ((m_chroma_subsampling == 1) || (m_chroma_subsampling == 2)) + { + m_chroma_is_horizontally_centred = bits.get_flag(); + } + if (m_chroma_subsampling == 1) + { + m_chroma_is_vertically_centred = bits.get_flag(); + } + + bool high_bit_depth_flag = false; + if (m_float_flag) + { + uint8_t bit_depth_log2_minus4 = bits.get_bits8(2); + m_bit_depth = (uint8_t)powl(2, (bit_depth_log2_minus4 + 4)); + } + else + { + high_bit_depth_flag = bits.get_flag(); + if (high_bit_depth_flag) + { + m_bit_depth = 9 + bits.get_bits8(3); + } + } + + if (m_alpha_flag) + { + m_alpha_is_premultiplied = bits.get_flag(); + } + + if (m_explicit_cicp_flag) + { + m_colour_primaries = bits.get_bits8(8); + m_transfer_characteristics = bits.get_bits8(8); + if (m_chroma_subsampling != 0) + { + m_matrix_coefficients = bits.get_bits8(8); + } + else + { + m_matrix_coefficients = 2; + } + } + else + { + m_colour_primaries = m_icc_flag ? 2 : 1; + m_transfer_characteristics = m_icc_flag ? 2 : 13; + m_matrix_coefficients = (m_chroma_subsampling == 0) ? 2 : 6; + } + + if (m_explicit_codec_types_flag) + { + m_infe_type = bits.get_bits32(32); + m_codec_config_type = bits.get_bits32(32); + } + if (m_hdr_flag) + { + m_gainmap_flag = bits.get_flag(); + if (m_gainmap_flag) + { + uint32_t gainmap_width_minus1 = bits.get_bits32(small_dimensions_flag ? 7 : 15); + m_gainmap_width = gainmap_width_minus1 + 1; + uint32_t gainmap_height_minus1 = bits.get_bits32(small_dimensions_flag ? 7 : 15); + m_gainmap_height = gainmap_height_minus1 + 1; + m_gainmap_matrix_coefficients = bits.get_bits8(8); + m_gainmap_full_range_flag = bits.get_flag(); + m_gainmap_chroma_subsampling = bits.get_bits8(2); + if ((m_gainmap_chroma_subsampling == 1) || (m_gainmap_chroma_subsampling == 2)) + { + m_gainmap_chroma_is_horizontally_centred = bits.get_flag(); + } + if (m_gainmap_chroma_subsampling == 1) + { + m_gainmap_chroma_is_vertically_centred = bits.get_flag(); + } + m_gainmap_float_flag = bits.get_flag(); + + bool gainmap_high_bit_depth_flag = false; + if (m_gainmap_float_flag) + { + uint8_t bit_depth_log2_minus4 = bits.get_bits8(2); + m_gainmap_bit_depth = (uint8_t)powl(2, (bit_depth_log2_minus4 + 4)); + } + else + { + gainmap_high_bit_depth_flag = bits.get_flag(); + if (gainmap_high_bit_depth_flag) + { + m_gainmap_bit_depth = 9 + bits.get_bits8(3); + } + } + m_tmap_icc_flag = bits.get_flag(); + m_tmap_explicit_cicp_flag = bits.get_flag(); + if (m_tmap_explicit_cicp_flag) + { + m_tmap_colour_primaries = bits.get_bits8(8); + m_tmap_transfer_characteristics = bits.get_bits8(8); + m_tmap_matrix_coefficients = bits.get_bits8(8); + m_tmap_full_range_flag = bits.get_flag(); + } + else + { + m_tmap_colour_primaries = 1; + m_tmap_transfer_characteristics = 13; + m_tmap_matrix_coefficients = 6; + m_tmap_full_range_flag = true; + } + } + m_clli_flag = bits.get_flag(); + m_mdcv_flag = bits.get_flag(); + m_cclv_flag = bits.get_flag(); + m_amve_flag = bits.get_flag(); + m_reve_flag = bits.get_flag(); + m_ndwt_flag = bits.get_flag(); + if (m_clli_flag) + { + m_clli = std::make_shared(); + m_clli->clli.max_content_light_level = bits.get_bits16(16); + m_clli->clli.max_pic_average_light_level = bits.get_bits16(16); + } + if (m_mdcv_flag) + { + m_mdcv = std::make_shared(); + for (int c = 0; c < 3; c++) + { + m_mdcv->mdcv.display_primaries_x[c] = bits.get_bits16(16); + m_mdcv->mdcv.display_primaries_y[c] = bits.get_bits16(16); + } + + m_mdcv->mdcv.white_point_x = bits.get_bits16(16); + m_mdcv->mdcv.white_point_y = bits.get_bits16(16); + m_mdcv->mdcv.max_display_mastering_luminance = bits.get_bits32(32); + m_mdcv->mdcv.min_display_mastering_luminance = bits.get_bits32(32); + } + if (m_cclv_flag) + { + m_cclv = std::make_shared(); + bits.skip_bits(2); + bool ccv_primaries_present_flag = bits.get_flag(); + bool ccv_min_luminance_value_present_flag = bits.get_flag(); + bool ccv_max_luminance_value_present_flag = bits.get_flag(); + bool ccv_avg_luminance_value_present_flag = bits.get_flag(); + bits.skip_bits(2); + if (ccv_primaries_present_flag) + { + int32_t x0 = bits.get_bits32(32); + int32_t y0 = bits.get_bits32(32); + int32_t x1 = bits.get_bits32(32); + int32_t y1 = bits.get_bits32(32); + int32_t x2 = bits.get_bits32(32); + int32_t y2 = bits.get_bits32(32); + m_cclv->set_primaries(x0, y0, x1, y1, x2, y2); + } + if (ccv_min_luminance_value_present_flag) + { + m_cclv->set_min_luminance(bits.get_bits32(32)); + } + if (ccv_max_luminance_value_present_flag) + { + m_cclv->set_max_luminance(bits.get_bits32(32)); + } + if (ccv_avg_luminance_value_present_flag) + { + m_cclv->set_avg_luminance(bits.get_bits32(32)); + } + } + if (m_amve_flag) + { + m_amve = std::make_shared(); + m_amve->amve.ambient_illumination = bits.get_bits32(32); + m_amve->amve.ambient_light_x = bits.get_bits16(16); + m_amve->amve.ambient_light_y = bits.get_bits16(16); + } + if (m_reve_flag) + { + // TODO: ReferenceViewingEnvironment isn't published yet + bits.skip_bits(32); + bits.skip_bits(16); + bits.skip_bits(16); + bits.skip_bits(32); + bits.skip_bits(16); + bits.skip_bits(16); + } + if (m_ndwt_flag) + { + // TODO: NominalDiffuseWhite isn't published yet + bits.skip_bits(32); + } + if (m_gainmap_flag) + { + m_tmap_clli_flag = bits.get_flag(); + m_mdcv_flag = bits.get_flag(); + m_tmap_cclv_flag = bits.get_flag(); + m_tmap_amve_flag = bits.get_flag(); + m_tmap_reve_flag = bits.get_flag(); + m_tmap_ndwt_flag = bits.get_flag(); + + if (m_tmap_clli_flag) + { + m_tmap_clli = std::make_shared(); + m_tmap_clli->clli.max_content_light_level = (uint16_t)bits.get_bits32(16); + m_tmap_clli->clli.max_pic_average_light_level = (uint16_t)bits.get_bits32(16); + } + if (m_tmap_mdcv_flag) + { + m_tmap_mdcv = std::make_shared(); + for (int c = 0; c < 3; c++) + { + m_tmap_mdcv->mdcv.display_primaries_x[c] = bits.get_bits16(16); + m_tmap_mdcv->mdcv.display_primaries_y[c] = bits.get_bits16(16); + } + + m_tmap_mdcv->mdcv.white_point_x = bits.get_bits16(16); + m_tmap_mdcv->mdcv.white_point_y = bits.get_bits16(16); + m_tmap_mdcv->mdcv.max_display_mastering_luminance = bits.get_bits32(32); + m_tmap_mdcv->mdcv.min_display_mastering_luminance = bits.get_bits32(32); + } + if (m_tmap_cclv_flag) + { + m_tmap_cclv = std::make_shared(); + bits.skip_bits(2); + bool ccv_primaries_present_flag = bits.get_flag(); + bool ccv_min_luminance_value_present_flag = bits.get_flag(); + bool ccv_max_luminance_value_present_flag = bits.get_flag(); + bool ccv_avg_luminance_value_present_flag = bits.get_flag(); + bits.skip_bits(2); + if (ccv_primaries_present_flag) + { + int32_t x0 = bits.get_bits32(32); + int32_t y0 = bits.get_bits32(32); + int32_t x1 = bits.get_bits32(32); + int32_t y1 = bits.get_bits32(32); + int32_t x2 = bits.get_bits32(32); + int32_t y2 = bits.get_bits32(32); + m_tmap_cclv->set_primaries(x0, y0, x1, y1, x2, y2); + } + if (ccv_min_luminance_value_present_flag) + { + m_tmap_cclv->set_min_luminance(bits.get_bits32(32)); + } + if (ccv_max_luminance_value_present_flag) + { + m_tmap_cclv->set_max_luminance(bits.get_bits32(32)); + } + if (ccv_avg_luminance_value_present_flag) + { + m_tmap_cclv->set_avg_luminance(bits.get_bits32(32)); + } + } + if (m_tmap_amve_flag) + { + m_tmap_amve = std::make_shared(); + m_tmap_amve->amve.ambient_illumination = bits.get_bits32(32); + m_tmap_amve->amve.ambient_light_x = bits.get_bits16(16); + m_tmap_amve->amve.ambient_light_y = bits.get_bits16(16); + } + if (m_tmap_reve_flag) + { + // TODO: ReferenceViewingEnvironment isn't published yet + bits.skip_bits(32); + bits.skip_bits(16); + bits.skip_bits(16); + bits.skip_bits(32); + bits.skip_bits(16); + bits.skip_bits(16); + } + if (m_tmap_ndwt_flag) + { + // TODO: NominalDiffuseWhite isn't published yet + bits.skip_bits(32); + } + } + } + + // Chunk sizes + bool few_metadata_bytes_flag = false; + if (m_icc_flag || m_exif_flag || m_xmp_flag || (m_hdr_flag && m_gainmap_flag)) + { + few_metadata_bytes_flag = bits.get_flag(); + } + bool few_codec_config_bytes_flag = bits.get_flag(); + bool few_item_data_bytes_flag = bits.get_flag(); + + uint32_t icc_data_size = 0; + if (m_icc_flag) + { + icc_data_size = bits.get_bits32(few_metadata_bytes_flag ? 10 : 20) + 1; + } + uint32_t tmap_icc_data_size = 0; + if (m_hdr_flag && m_gainmap_flag && m_tmap_icc_flag) + { + tmap_icc_data_size = bits.get_bits32(few_metadata_bytes_flag ? 10 : 20) + 1; + } + uint32_t gainmap_metadata_size = 0; + if (m_hdr_flag && m_gainmap_flag) + { + gainmap_metadata_size = bits.get_bits32(few_metadata_bytes_flag ? 10 : 20); + } + if (m_hdr_flag && m_gainmap_flag) + { + m_gainmap_item_data_size = bits.get_bits32(few_item_data_bytes_flag ? 15 : 28); + } + uint32_t gainmap_item_codec_config_size = 0; + if (m_hdr_flag && m_gainmap_flag && (m_gainmap_item_data_size > 0)) + { + gainmap_item_codec_config_size = bits.get_bits32(few_codec_config_bytes_flag ? 3 : 12); + } + + uint32_t main_item_codec_config_size = bits.get_bits32(few_codec_config_bytes_flag ? 3 : 12); + m_main_item_data_size = bits.get_bits32(few_item_data_bytes_flag ? 15 : 28) + 1; + + if (m_alpha_flag) + { + m_alpha_item_data_size = bits.get_bits32(few_item_data_bytes_flag ? 15 : 28); + } + uint32_t alpha_item_codec_config_size = 0; + if (m_alpha_flag && (m_alpha_item_data_size > 0)) + { + alpha_item_codec_config_size = bits.get_bits32(few_codec_config_bytes_flag ? 3 : 12); + } + + if (m_exif_flag) + { + m_exif_item_data_size = bits.get_bits32(few_metadata_bytes_flag ? 10 : 20) + 1; + } + if (m_xmp_flag) + { + m_xmp_item_data_size = bits.get_bits32(few_metadata_bytes_flag ? 10 : 20) + 1; + } + + bits.skip_to_byte_boundary(); + + // Chunks + if (m_alpha_flag && (m_alpha_item_data_size > 0) && (alpha_item_codec_config_size > 0)) + { + m_alpha_item_codec_config = bits.read_bytes(alpha_item_codec_config_size); + } + if (m_hdr_flag && m_gainmap_flag && (gainmap_item_codec_config_size > 0)) + { + m_gainmap_item_codec_config = bits.read_bytes(gainmap_item_codec_config_size); + } + if (main_item_codec_config_size > 0) + { + m_main_item_codec_config = bits.read_bytes(main_item_codec_config_size); + } + + if (m_icc_flag) + { + m_icc_data = bits.read_bytes(icc_data_size); + } + if (m_hdr_flag && m_gainmap_flag && m_tmap_icc_flag) + { + m_tmap_icc_data = bits.read_bytes(tmap_icc_data_size); + } + if (m_hdr_flag && m_gainmap_flag && (gainmap_metadata_size > 0)) + { + m_gainmap_metadata = bits.read_bytes(gainmap_metadata_size); + } + + if (m_alpha_flag && (m_alpha_item_data_size > 0)) + { + m_alpha_item_data_offset = bits.get_current_byte_index() + start_offset; + bits.skip_bytes(m_alpha_item_data_size); + } + if (m_alpha_flag && m_gainmap_flag && (m_gainmap_item_data_size > 0)) + { + m_gainmap_item_data_offset = bits.get_current_byte_index() + start_offset; + bits.skip_bits(m_gainmap_item_data_size); + } + + m_main_item_data_offset = bits.get_current_byte_index() + start_offset; + bits.skip_bytes(m_main_item_data_size); + + if (m_exif_flag) + { + m_exif_item_data_offset = bits.get_current_byte_index() + start_offset; + bits.skip_bytes(m_exif_item_data_size); + } + if (m_xmp_flag) + { + m_xmp_item_data_offset = bits.get_current_byte_index() + start_offset; + bits.skip_bytes(m_xmp_item_data_size); + } + return range.get_error(); +} + +std::string Box_mini::dump(Indent &indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "version: " << (int)m_version << "\n"; + + sstr << indent << "explicit_codec_types_flag: " << m_explicit_codec_types_flag << "\n"; + sstr << indent << "float_flag: " << m_float_flag << "\n"; + sstr << indent << "full_range_flag: " << m_full_range_flag << "\n"; + sstr << indent << "alpha_flag: " << m_alpha_flag << "\n"; + sstr << indent << "explicit_cicp_flag: " << m_explicit_cicp_flag << "\n"; + sstr << indent << "hdr_flag: " << m_hdr_flag << "\n"; + sstr << indent << "icc_flag: " << m_icc_flag << "\n"; + sstr << indent << "exif_flag: " << m_exif_flag << "\n"; + sstr << indent << "xmp_flag: " << m_xmp_flag << "\n"; + + sstr << indent << "chroma_subsampling: " << (int)m_chroma_subsampling << "\n"; + sstr << indent << "orientation: " << (int)m_orientation << "\n"; + + sstr << indent << "width: " << m_width << "\n"; + sstr << indent << "height: " << m_height << "\n"; + + if ((m_chroma_subsampling == 1) || (m_chroma_subsampling == 2)) + { + sstr << indent << "chroma_is_horizontally_centered: " << m_chroma_is_horizontally_centred << "\n"; + } + if (m_chroma_subsampling == 1) + { + sstr << indent << "chroma_is_vertically_centered: " << m_chroma_is_vertically_centred << "\n"; + } + + sstr << "bit_depth: " << (int)m_bit_depth << "\n"; + + if (m_alpha_flag) + { + sstr << "alpha_is_premultiplied: " << m_alpha_is_premultiplied << "\n"; + } + + sstr << "colour_primaries: " << (int)m_colour_primaries << "\n"; + sstr << "transfer_characteristics: " << (int)m_transfer_characteristics << "\n"; + sstr << "matrix_coefficients: " << (int)m_matrix_coefficients << "\n"; + + if (m_explicit_codec_types_flag) + { + sstr << "infe_type: " << fourcc_to_string(m_infe_type) << " (" << m_infe_type << ")" << "\n"; + sstr << "codec_config_type: " << fourcc_to_string(m_codec_config_type) << " (" << m_codec_config_type << ")" << "\n"; + } + + if (m_hdr_flag) + { + sstr << indent << "gainmap_flag: " << m_gainmap_flag << "\n"; + if (m_gainmap_flag) + { + sstr << indent << "gainmap_width: " << m_gainmap_width << "\n"; + sstr << indent << "gainmap_height: " << m_gainmap_height << "\n"; + sstr << indent << "gainmap_matrix_coefficients: " << (int)m_gainmap_matrix_coefficients << "\n"; + sstr << indent << "gainmap_full_range_flag: " << m_gainmap_full_range_flag << "\n"; + sstr << indent << "gainmap_chroma_subsampling: " << (int)m_gainmap_chroma_subsampling << "\n"; + if ((m_gainmap_chroma_subsampling == 1) || (m_gainmap_chroma_subsampling == 2)) + { + sstr << indent << "gainmap_chroma_is_horizontally_centred: " << m_gainmap_chroma_is_horizontally_centred << "\n"; + } + if (m_gainmap_chroma_subsampling == 1) + { + sstr << indent << "gainmap_chroma_is_vertically_centred: " << m_gainmap_chroma_is_vertically_centred << "\n"; + } + sstr << indent << "gainmap_float_flag: " << m_gainmap_float_flag << "\n"; + sstr << "gainmap_bit_depth: " << (int)m_gainmap_bit_depth << "\n"; + sstr << indent << "tmap_icc_flag: " << m_tmap_icc_flag << "\n"; + sstr << indent << "tmap_explicit_cicp_flag: " << m_tmap_explicit_cicp_flag << "\n"; + if (m_tmap_explicit_cicp_flag) + { + sstr << "tmap_colour_primaries: " << (int)m_tmap_colour_primaries << "\n"; + sstr << "tmap_transfer_characteristics: " << (int)m_tmap_transfer_characteristics << "\n"; + sstr << "tmap_matrix_coefficients: " << (int)m_tmap_matrix_coefficients << "\n"; + sstr << "tmap_full_range_flag: " << m_tmap_full_range_flag << "\n"; + } + } + sstr << indent << "clli_flag: " << m_clli_flag << "\n"; + sstr << indent << "mdcv_flag: " << m_mdcv_flag << "\n"; + sstr << indent << "cclv_flag: " << m_cclv_flag << "\n"; + sstr << indent << "amve_flag: " << m_amve_flag << "\n"; + sstr << indent << "reve_flag: " << m_reve_flag << "\n"; + sstr << indent << "ndwt_flag: " << m_ndwt_flag << "\n"; + if (m_clli_flag) + { + sstr << indent << "ccli.max_content_light_level: " << m_clli->clli.max_content_light_level << "\n"; + sstr << indent << "ccli.max_pic_average_light_level: " << m_clli->clli.max_pic_average_light_level << "\n"; + } + if (m_mdcv_flag) + { + sstr << indent << "mdcv.display_primaries (x,y): "; + sstr << "(" << m_mdcv->mdcv.display_primaries_x[0] << ";" << m_mdcv->mdcv.display_primaries_y[0] << "), "; + sstr << "(" << m_mdcv->mdcv.display_primaries_x[1] << ";" << m_mdcv->mdcv.display_primaries_y[1] << "), "; + sstr << "(" << m_mdcv->mdcv.display_primaries_x[2] << ";" << m_mdcv->mdcv.display_primaries_y[2] << ")\n"; + + sstr << indent << "mdcv.white point (x,y): (" << m_mdcv->mdcv.white_point_x << ";" << m_mdcv->mdcv.white_point_y << ")\n"; + sstr << indent << "mdcv.max display mastering luminance: " << m_mdcv->mdcv.max_display_mastering_luminance << "\n"; + sstr << indent << "mdcv.min display mastering luminance: " << m_mdcv->mdcv.min_display_mastering_luminance << "\n"; + } + if (m_cclv_flag) + { + sstr << indent << "cclv.ccv_primaries_present_flag: " << m_cclv->ccv_primaries_are_valid() << "\n"; + sstr << indent << "cclv.ccv_min_luminance_value_present_flag: " << m_cclv->min_luminance_is_valid() << "\n"; + sstr << indent << "cclv.ccv_max_luminance_value_present_flag: " << m_cclv->max_luminance_is_valid() << "\n"; + sstr << indent << "cclv.ccv_avg_luminance_value_present_flag: " << m_cclv->avg_luminance_is_valid() << "\n"; + if (m_cclv->ccv_primaries_are_valid()) + { + sstr << indent << "cclv.ccv_primaries (x,y): "; + sstr << "(" << m_cclv->get_ccv_primary_x0() << ";" << m_cclv->get_ccv_primary_y0() << "), "; + sstr << "(" << m_cclv->get_ccv_primary_x1() << ";" << m_cclv->get_ccv_primary_y1() << "), "; + sstr << "(" << m_cclv->get_ccv_primary_x2() << ";" << m_cclv->get_ccv_primary_y2() << ")\n"; + } + if (m_cclv->min_luminance_is_valid()) + { + sstr << indent << "cclv.ccv_min_luminance_value: " << m_cclv->get_min_luminance() << "\n"; + } + if (m_cclv->max_luminance_is_valid()) + { + sstr << indent << "cclv.ccv_max_luminance_value: " << m_cclv->get_max_luminance() << "\n"; + } + if (m_cclv->avg_luminance_is_valid()) + { + sstr << indent << "cclv.ccv_avg_luminance_value: " << m_cclv->get_avg_luminance() << "\n"; + } + } + if (m_amve_flag) + { + sstr << indent << "amve.ambient_illumination: " << m_amve->amve.ambient_illumination << "\n"; + sstr << indent << "amve.ambient_light_x: " << m_amve->amve.ambient_light_x << "\n"; + sstr << indent << "amve.ambient_light_y: " << m_amve->amve.ambient_light_y << "\n"; + } + if (m_reve_flag) + { + // TODO - this isn't published yet + } + if (m_ndwt_flag) + { + // TODO - this isn't published yet + } + if (m_gainmap_flag) + { + sstr << indent << "tmap_clli_flag: " << m_tmap_clli_flag << "\n"; + sstr << indent << "tmap_mdcv_flag: " << m_tmap_mdcv_flag << "\n"; + sstr << indent << "tmap_cclv_flag: " << m_tmap_cclv_flag << "\n"; + sstr << indent << "tmap_amve_flag: " << m_tmap_amve_flag << "\n"; + sstr << indent << "tmap_reve_flag: " << m_tmap_reve_flag << "\n"; + sstr << indent << "tmap_ndwt_flag: " << m_tmap_ndwt_flag << "\n"; + if (m_tmap_clli_flag) + { + sstr << indent << "tmap_ccli.max_content_light_level: " << m_tmap_clli->clli.max_content_light_level << "\n"; + sstr << indent << "tmap_ccli.max_pic_average_light_level: " << m_tmap_clli->clli.max_pic_average_light_level << "\n"; + } + if (m_tmap_mdcv_flag) + { + sstr << indent << "tmap_mdcv.display_primaries (x,y): "; + sstr << "(" << m_tmap_mdcv->mdcv.display_primaries_x[0] << ";" << m_tmap_mdcv->mdcv.display_primaries_y[0] << "), "; + sstr << "(" << m_tmap_mdcv->mdcv.display_primaries_x[1] << ";" << m_tmap_mdcv->mdcv.display_primaries_y[1] << "), "; + sstr << "(" << m_tmap_mdcv->mdcv.display_primaries_x[2] << ";" << m_tmap_mdcv->mdcv.display_primaries_y[2] << ")\n"; + + sstr << indent << "tmap_mdcv.white point (x,y): (" << m_tmap_mdcv->mdcv.white_point_x << ";" << m_tmap_mdcv->mdcv.white_point_y << ")\n"; + sstr << indent << "tmap_mdcv.max display mastering luminance: " << m_tmap_mdcv->mdcv.max_display_mastering_luminance << "\n"; + sstr << indent << "tmap_mdcv.min display mastering luminance: " << m_tmap_mdcv->mdcv.min_display_mastering_luminance << "\n"; + } + if (m_tmap_cclv_flag) + { + sstr << indent << "tmap_cclv.ccv_primaries_present_flag: " << m_tmap_cclv->ccv_primaries_are_valid() << "\n"; + sstr << indent << "tmap_cclv.ccv_min_luminance_value_present_flag: " << m_tmap_cclv->min_luminance_is_valid() << "\n"; + sstr << indent << "tmap_cclv.ccv_max_luminance_value_present_flag: " << m_tmap_cclv->max_luminance_is_valid() << "\n"; + sstr << indent << "tmap_cclv.ccv_avg_luminance_value_present_flag: " << m_tmap_cclv->avg_luminance_is_valid() << "\n"; + if (m_tmap_cclv->ccv_primaries_are_valid()) + { + sstr << indent << "tmap_cclv.ccv_primaries (x,y): "; + sstr << "(" << m_tmap_cclv->get_ccv_primary_x0() << ";" << m_tmap_cclv->get_ccv_primary_y0() << "), "; + sstr << "(" << m_tmap_cclv->get_ccv_primary_x1() << ";" << m_tmap_cclv->get_ccv_primary_y1() << "), "; + sstr << "(" << m_tmap_cclv->get_ccv_primary_x2() << ";" << m_tmap_cclv->get_ccv_primary_y2() << ")\n"; + } + if (m_tmap_cclv->min_luminance_is_valid()) + { + sstr << indent << "tmap_cclv.ccv_min_luminance_value: " << m_tmap_cclv->get_min_luminance() << "\n"; + } + if (m_tmap_cclv->max_luminance_is_valid()) + { + sstr << indent << "tmap_cclv.ccv_max_luminance_value: " << m_tmap_cclv->get_max_luminance() << "\n"; + } + if (m_tmap_cclv->avg_luminance_is_valid()) + { + sstr << indent << "tmap_cclv.ccv_avg_luminance_value: " << m_tmap_cclv->get_avg_luminance() << "\n"; + } + } + if (m_tmap_amve_flag) + { + sstr << indent << "tmap_amve.ambient_illumination: " << m_tmap_amve->amve.ambient_illumination << "\n"; + sstr << indent << "tmap_amve.ambient_light_x: " << m_tmap_amve->amve.ambient_light_x << "\n"; + sstr << indent << "tmap_amve.ambient_light_y: " << m_tmap_amve->amve.ambient_light_y << "\n"; + } + if (m_tmap_reve_flag) + { + // TODO - this isn't published yet + } + if (m_tmap_ndwt_flag) + { + // TODO - this isn't published yet + } + } + } + + if (m_alpha_flag && (m_alpha_item_data_size > 0) && (m_alpha_item_codec_config.size() > 0)) + { + sstr << "alpha_item_code_config size: " << m_alpha_item_codec_config.size() << "\n"; + } + if (m_hdr_flag && m_gainmap_flag && m_gainmap_item_codec_config.size() > 0) + { + sstr << "gainmap_item_codec_config size: " << m_gainmap_item_codec_config.size() << "\n"; + } + if (m_main_item_codec_config.size() > 0) + { + sstr << "main_item_code_config size: " << m_main_item_codec_config.size() << "\n"; + } + + if (m_icc_flag) + { + sstr << "icc_data size: " << m_icc_data.size() << "\n"; + } + if (m_hdr_flag && m_gainmap_flag && m_tmap_icc_flag) + { + sstr << "tmap_icc_data size: " << m_tmap_icc_data.size() << "\n"; + } + if (m_hdr_flag && m_gainmap_flag && m_gainmap_metadata.size() > 0) + { + sstr << "gainmap_metadata size: " << m_gainmap_metadata.size() << "\n"; + } + + if (m_alpha_flag && (m_alpha_item_data_size > 0)) + { + sstr << "alpha_item_data offset: " << m_alpha_item_data_offset << ", size: " << m_alpha_item_data_size << "\n"; + } + if (m_hdr_flag && m_gainmap_flag && (m_gainmap_item_data_size > 0)) + { + sstr << "gainmap_item_data offset: " << m_gainmap_item_data_offset << ", size: " << m_gainmap_item_data_size << "\n"; + } + + sstr << "main_item_data offset: " << m_main_item_data_offset << ", size: " << m_main_item_data_size << "\n"; + + if (m_exif_flag) + { + sstr << "exif_data offset: " << m_exif_item_data_offset << ", size: " << m_exif_item_data_size << "\n"; + } + if (m_xmp_flag) + { + sstr << "xmp_data offset: " << m_xmp_item_data_offset << ", size: " << m_xmp_item_data_size << "\n"; + } + return sstr.str(); +} diff --git a/libheif/mini.h b/libheif/mini.h new file mode 100644 index 0000000000..ef676949bc --- /dev/null +++ b/libheif/mini.h @@ -0,0 +1,161 @@ +/* + * HEIF codec. + * Copyright (c) 2024 Brad Hards + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#ifndef LIBHEIF_MINI_H +#define LIBHEIF_MINI_H + +#include "libheif/heif.h" +#include "box.h" + +#include +#include +#include + +class Box_mini : public Box +{ +public: + Box_mini() + { + set_short_type(fourcc("mini")); + } + + bool get_icc_flag() const { return m_icc_flag; } + bool get_exif_flag() const { return m_exif_flag; } + bool get_xmp_flag() const { return m_xmp_flag; } + + uint32_t get_width() const { return m_width; } + uint32_t get_height() const { return m_height; } + + uint8_t get_bit_depth() const { return m_bit_depth; } + + std::vector get_main_item_codec_config() const { return m_main_item_codec_config; } + std::vector get_alpha_item_codec_config() const { return m_alpha_item_codec_config; } + std::vector get_icc_data() const { return m_icc_data; } + + uint64_t get_main_item_data_offset() const { return m_main_item_data_offset; } + uint32_t get_main_item_data_size() const { return m_main_item_data_size; } + uint64_t get_alpha_item_data_offset() const { return m_alpha_item_data_offset; } + uint32_t get_alpha_item_data_size() const { return m_alpha_item_data_size; } + uint64_t get_exif_item_data_offset() const { return m_exif_item_data_offset; } + uint32_t get_exif_item_data_size() const { return m_exif_item_data_size; } + uint64_t get_xmp_item_data_offset() const { return m_xmp_item_data_offset; } + uint32_t get_xmp_item_data_size() const { return m_xmp_item_data_size; } + + uint16_t get_colour_primaries() const { return m_colour_primaries; } + uint16_t get_transfer_characteristics() const { return m_transfer_characteristics; } + uint16_t get_matrix_coefficients() const { return m_matrix_coefficients; } + bool get_full_range_flag() const { return m_full_range_flag; } + + std::string dump(Indent &) const override; + +protected: + Error parse(BitstreamRange &range, const heif_security_limits *limits) override; + +private: + uint8_t m_version; + bool m_explicit_codec_types_flag; + bool m_float_flag; + bool m_full_range_flag; + bool m_alpha_flag; + bool m_explicit_cicp_flag; + bool m_hdr_flag; + bool m_icc_flag; + bool m_exif_flag; + bool m_xmp_flag; + uint8_t m_chroma_subsampling; + uint8_t m_orientation; + + uint32_t m_width; + uint32_t m_height; + uint8_t m_bit_depth = 8; + bool m_chroma_is_horizontally_centred = false; + bool m_chroma_is_vertically_centred = false; + bool m_alpha_is_premultiplied = false; + uint16_t m_colour_primaries; + uint16_t m_transfer_characteristics; + uint16_t m_matrix_coefficients; + + uint32_t m_infe_type; + uint32_t m_codec_config_type; + + bool m_gainmap_flag = false; + + uint32_t m_gainmap_width; + uint32_t m_gainmap_height; + uint8_t m_gainmap_matrix_coefficients; + bool m_gainmap_full_range_flag; + uint8_t m_gainmap_chroma_subsampling; + bool m_gainmap_chroma_is_horizontally_centred = false; + bool m_gainmap_chroma_is_vertically_centred = false; + bool m_gainmap_float_flag; + uint8_t m_gainmap_bit_depth = 8; + bool m_tmap_icc_flag = false; + bool m_tmap_explicit_cicp_flag = false; + uint16_t m_tmap_colour_primaries; + uint16_t m_tmap_transfer_characteristics; + uint16_t m_tmap_matrix_coefficients; + bool m_tmap_full_range_flag; + + bool m_clli_flag = false; + bool m_mdcv_flag = false; + bool m_cclv_flag = false; + bool m_amve_flag = false; + bool m_reve_flag = false; + bool m_ndwt_flag = false; + std::shared_ptr m_clli; + std::shared_ptr m_mdcv; + std::shared_ptr m_cclv; + std::shared_ptr m_amve; + // std::shared_ptr m_reve; + // std::shared_ptr m_ndwt; + + bool m_tmap_clli_flag = false; + bool m_tmap_mdcv_flag = false; + bool m_tmap_cclv_flag = false; + bool m_tmap_amve_flag = false; + bool m_tmap_reve_flag = false; + bool m_tmap_ndwt_flag = false; + std::shared_ptr m_tmap_clli; + std::shared_ptr m_tmap_mdcv; + std::shared_ptr m_tmap_cclv; + std::shared_ptr m_tmap_amve; + // std::shared_ptr m_tmap_reve; + // std::shared_ptr m_tmap_ndwt; + + std::vector m_alpha_item_codec_config; + std::vector m_gainmap_item_codec_config; + std::vector m_main_item_codec_config; + std::vector m_icc_data; + std::vector m_tmap_icc_data; + std::vector m_gainmap_metadata; + + uint64_t m_alpha_item_data_offset; + uint32_t m_alpha_item_data_size = 0; + uint64_t m_main_item_data_offset; + uint32_t m_main_item_data_size = 0; + uint64_t m_gainmap_item_data_offset; + uint32_t m_gainmap_item_data_size = 0; + uint64_t m_exif_item_data_offset; + uint32_t m_exif_item_data_size = 0; + uint64_t m_xmp_item_data_offset; + uint32_t m_xmp_item_data_size = 0; +}; + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a2f17668c1..1aa35e7448 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -62,6 +62,13 @@ else() message(INFO "Disabling JPEG 2000 encoder tests because no JPEG 2000 codec is enabled") endif() +if (ENABLE_EXPERIMENTAL_MINI_FORMAT) + if (NOT WITH_REDUCED_VISIBILITY) + add_libheif_test(mini_box) + endif() + add_libheif_test(mini_decode) +endif() + if (WITH_UNCOMPRESSED_CODEC) add_libheif_test(uncompressed_decode) add_libheif_test(uncompressed_decode_generic_compression) diff --git a/tests/data/simple_osm_tile_alpha.avif b/tests/data/simple_osm_tile_alpha.avif new file mode 100644 index 0000000000..66de47e74f Binary files /dev/null and b/tests/data/simple_osm_tile_alpha.avif differ diff --git a/tests/data/simple_osm_tile_meta.avif b/tests/data/simple_osm_tile_meta.avif new file mode 100644 index 0000000000..9d974a6f3e Binary files /dev/null and b/tests/data/simple_osm_tile_meta.avif differ diff --git a/tests/mini_box.cc b/tests/mini_box.cc new file mode 100644 index 0000000000..7b6f361a37 --- /dev/null +++ b/tests/mini_box.cc @@ -0,0 +1,226 @@ +/* + libheif AVC (H.264) unit tests + + MIT License + + Copyright (c) 2023 Brad Hards + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "catch.hpp" +#include +#include +#include "error.h" +#include +#include +#include +#include +#include +#include +#include "test_utils.h" +#include "test-config.h" +#include + +TEST_CASE("mini") +{ + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x10, 0x66, 0x74, 0x79, 0x70, + 0x6d, 0x69, 0x66, 0x33, 0x61, 0x76, 0x69, 0x66, + 0x00, 0x00, 0x00, 0x4a, 0x6d, 0x69, 0x6e, 0x69, + 0x08, 0x18, 0x00, 0xff, 0x01, 0xfe, 0xe0, 0x03, + 0x40, 0x81, 0x20, 0x00, 0x00, 0x12, 0x00, 0x0a, + 0x09, 0x38, 0x1d, 0xff, 0xff, 0xd8, 0x40, 0x43, + 0x41, 0xa4, 0x32, 0x26, 0x11, 0x90, 0x01, 0x86, + 0x18, 0x61, 0x00, 0xb4, 0x83, 0x5a, 0x70, 0x50, + 0x8b, 0xe5, 0x7d, 0xf5, 0xc7, 0xd3, 0x6e, 0x92, + 0xea, 0x80, 0x01, 0x50, 0x91, 0xc4, 0x06, 0xa3, + 0xe1, 0xca, 0x44, 0x43, 0xe7, 0xb8, 0x67, 0x43, + 0xea, 0x80}; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("ftyp")); + REQUIRE(box->get_type_string() == "ftyp"); + std::shared_ptr ftyp = std::dynamic_pointer_cast(box); + REQUIRE(ftyp->get_major_brand() == fourcc("mif3")); + REQUIRE(ftyp->get_minor_version() == fourcc("avif")); + REQUIRE(ftyp->list_brands().size() == 0); + Indent indent; + std::string dumpResult = box->dump(indent); + REQUIRE(dumpResult == "Box: ftyp -----\n" + "size: 16 (header size: 8)\n" + "major brand: mif3\n" + "minor version: avif\n" + "compatible brands: \n"); + + error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("mini")); + REQUIRE(box->get_type_string() == "mini"); + std::shared_ptr mini = std::dynamic_pointer_cast(box); + REQUIRE(mini->get_exif_flag() == false); + REQUIRE(mini->get_xmp_flag() == false); + REQUIRE(mini->get_bit_depth() == 8); + REQUIRE(mini->get_colour_primaries() == 1); + REQUIRE(mini->get_transfer_characteristics() == 13); + REQUIRE(mini->get_matrix_coefficients() == 6); + REQUIRE(mini->get_width() == 256); + REQUIRE(mini->get_height() == 256); + REQUIRE(mini->get_main_item_codec_config().size() == 4); + REQUIRE((int)(mini->get_main_item_codec_config().data()[0]) == 0x81); + REQUIRE((int)(mini->get_main_item_codec_config().data()[1]) == 0x20); + REQUIRE((int)(mini->get_main_item_codec_config().data()[2]) == 0x00); + REQUIRE((int)(mini->get_main_item_codec_config().data()[3]) == 0x00); + dumpResult = box->dump(indent); + REQUIRE(dumpResult == "Box: mini -----\n" + "size: 74 (header size: 8)\n" + "version: 0\n" + "explicit_codec_types_flag: 0\n" + "float_flag: 0\n" + "full_range_flag: 1\n" + "alpha_flag: 0\n" + "explicit_cicp_flag: 0\n" + "hdr_flag: 0\n" + "icc_flag: 0\n" + "exif_flag: 0\n" + "xmp_flag: 0\n" + "chroma_subsampling: 3\n" + "orientation: 1\n" + "width: 256\n" + "height: 256\n" + "bit_depth: 8\n" + "colour_primaries: 1\n" + "transfer_characteristics: 13\n" + "matrix_coefficients: 6\n" + "main_item_code_config size: 4\n" + "main_item_data offset: 37, size: 53\n"); +} + +TEST_CASE("check mini+alpha version") +{ + auto istr = std::unique_ptr(new std::ifstream(tests_data_directory + "/simple_osm_tile_alpha.avif", std::ios::binary)); + auto reader = std::make_shared(std::move(istr)); + FileLayout file; + Error err = file.read(reader, heif_get_global_security_limits()); + REQUIRE(err.error_code == heif_error_Ok); + + std::shared_ptr mini = file.get_mini_box(); + REQUIRE(mini->get_exif_flag() == false); + REQUIRE(mini->get_xmp_flag() == false); + REQUIRE(mini->get_bit_depth() == 8); + REQUIRE(mini->get_colour_primaries() == 2); + REQUIRE(mini->get_transfer_characteristics() == 2); + REQUIRE(mini->get_matrix_coefficients() == 6); + REQUIRE(mini->get_width() == 256); + REQUIRE(mini->get_height() == 256); + REQUIRE(mini->get_main_item_codec_config().size() == 4); + REQUIRE((int)(mini->get_main_item_codec_config().data()[0]) == 0x81); + REQUIRE((int)(mini->get_main_item_codec_config().data()[1]) == 0x20); + REQUIRE((int)(mini->get_main_item_codec_config().data()[2]) == 0x00); + REQUIRE((int)(mini->get_main_item_codec_config().data()[3]) == 0x00); + Indent indent; + std::string dumpResult = mini->dump(indent); + REQUIRE(dumpResult == "Box: mini -----\n" + "size: 788 (header size: 8)\n" + "version: 0\n" + "explicit_codec_types_flag: 0\n" + "float_flag: 0\n" + "full_range_flag: 1\n" + "alpha_flag: 1\n" + "explicit_cicp_flag: 0\n" + "hdr_flag: 0\n" + "icc_flag: 1\n" + "exif_flag: 0\n" + "xmp_flag: 0\n" + "chroma_subsampling: 3\n" + "orientation: 1\n" + "width: 256\n" + "height: 256\n" + "bit_depth: 8\n" + "alpha_is_premultiplied: 0\n" + "colour_primaries: 2\n" + "transfer_characteristics: 2\n" + "matrix_coefficients: 6\n" + "alpha_item_code_config size: 4\n" + "main_item_code_config size: 4\n" + "icc_data size: 672\n" + "alpha_item_data offset: 717, size: 34\n" + "main_item_data offset: 751, size: 53\n"); +} + +TEST_CASE("check mini+exif+xmp version") +{ + auto istr = std::unique_ptr(new std::ifstream(tests_data_directory + "/simple_osm_tile_meta.avif", std::ios::binary)); + auto reader = std::make_shared(std::move(istr)); + FileLayout file; + Error err = file.read(reader, heif_get_global_security_limits()); + REQUIRE(err.error_code == heif_error_Ok); + + std::shared_ptr mini = file.get_mini_box(); + REQUIRE(mini->get_exif_flag() == true); + REQUIRE(mini->get_xmp_flag() == true); + REQUIRE(mini->get_bit_depth() == 8); + REQUIRE(mini->get_colour_primaries() == 2); + REQUIRE(mini->get_transfer_characteristics() == 2); + REQUIRE(mini->get_matrix_coefficients() == 6); + REQUIRE(mini->get_width() == 256); + REQUIRE(mini->get_height() == 256); + REQUIRE(mini->get_main_item_codec_config().size() == 4); + REQUIRE((int)(mini->get_main_item_codec_config().data()[0]) == 0x81); + REQUIRE((int)(mini->get_main_item_codec_config().data()[1]) == 0x20); + REQUIRE((int)(mini->get_main_item_codec_config().data()[2]) == 0x00); + REQUIRE((int)(mini->get_main_item_codec_config().data()[3]) == 0x00); + Indent indent; + std::string dumpResult = mini->dump(indent); + REQUIRE(dumpResult == "Box: mini -----\n" + "size: 4388 (header size: 8)\n" + "version: 0\n" + "explicit_codec_types_flag: 0\n" + "float_flag: 0\n" + "full_range_flag: 1\n" + "alpha_flag: 0\n" + "explicit_cicp_flag: 0\n" + "hdr_flag: 0\n" + "icc_flag: 1\n" + "exif_flag: 1\n" + "xmp_flag: 1\n" + "chroma_subsampling: 3\n" + "orientation: 1\n" + "width: 256\n" + "height: 256\n" + "bit_depth: 8\n" + "colour_primaries: 2\n" + "transfer_characteristics: 2\n" + "matrix_coefficients: 6\n" + "main_item_code_config size: 4\n" + "icc_data size: 672\n" + "main_item_data offset: 717, size: 53\n" + "exif_data offset: 770, size: 208\n" + "xmp_data offset: 978, size: 3426\n"); +} diff --git a/tests/mini_decode.cc b/tests/mini_decode.cc new file mode 100644 index 0000000000..12112d031d --- /dev/null +++ b/tests/mini_decode.cc @@ -0,0 +1,58 @@ +/* + libheif integration tests for uncompressed decoder + + MIT License + + Copyright (c) 2023 Brad Hards + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "catch.hpp" +#include "libheif/heif.h" +#include "libheif/api_structs.h" +#include +#include +#include "test_utils.h" +#include + +#define MINI_FILES \ + "simple_osm_tile_alpha.avif", "simple_osm_tile_meta.avif" + +void check_image_handle_size(struct heif_context *&context) { + heif_image_handle *handle = get_primary_image_handle(context); + int ispe_width = heif_image_handle_get_ispe_width(handle); + REQUIRE(ispe_width == 256); + int ispe_height = heif_image_handle_get_ispe_height(handle); + REQUIRE(ispe_height == 256); + int width = heif_image_handle_get_width(handle); + REQUIRE(width == 256); + int height = heif_image_handle_get_height(handle); + REQUIRE(height == 256); + + heif_image_handle_release(handle); +} + +TEST_CASE("check image handle size") { + auto file = GENERATE(MINI_FILES); + auto context = get_context_for_test_file(file); + INFO("file name: " << file); + check_image_handle_size(context); + heif_context_free(context); +} +