diff --git a/CMakeLists.txt b/CMakeLists.txt index 562a115d957..2a51616eecc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -395,9 +395,18 @@ endif() if (LIBSHARPYUV_FOUND) list(APPEND REQUIRES_PRIVATE "libsharpyuv") endif() -if (WITH_DEFLATE_HEADER_COMPRESSION) +if (WITH_DEFLATE_HEADER_COMPRESSION OR WITH_UNCOMPRESSED_CODEC) list(APPEND REQUIRES_PRIVATE "zlib") endif() +if (WITH_UNCOMPRESSED_CODEC) + find_package(Brotli) + if (Brotli_FOUND) + message("Brotli found") + list(APPEND REQUIRES_PRIVATE "libbrotli") + else() + message("Brotli not found") + endif() +endif() list(JOIN REQUIRES_PRIVATE " " REQUIRES_PRIVATE) diff --git a/README.md b/README.md index 677d892168e..31f177aa33c 100644 --- a/README.md +++ b/README.md @@ -159,8 +159,8 @@ The placeholder `{codec}` can have these values: `LIBDE265`, `X265`, `AOM_DECODE Further options are: -* `WITH_UNCOMPRESSED_CODEC`: enable support for uncompressed images according to ISO/IEC 23001-17:2023. This is *experimental* - and not available as a dynamic plugin. +* `WITH_UNCOMPRESSED_CODEC`: enable support for uncompressed images according to ISO/IEC 23001-17:2024. This is *experimental* + and not available as a dynamic plugin. When enabled, it adds a dependency to `zlib`, and optionally will use `brotli`. * `WITH_DEFLATE_HEADER_COMPRESSION`: enables support for compressed metadata. When enabled, it adds a dependency to `zlib`. Note that header compression is not widely supported yet. * `WITH_LIBSHARPYUV`: enables high-quality YCbCr/RGB color space conversion algorithms (requires `libsharpyuv`, @@ -170,7 +170,7 @@ Further options are: * `PLUGIN_DIRECTORY`: the directory where libheif will search for dynamic plugins when the environment variable `LIBHEIF_PLUGIN_PATH` is not set. * `WITH_REDUCED_VISIBILITY`: only export those symbols into the library that are public API. - Has to be turned off for running the tests. + Has to be turned off for running some tests. ### macOS diff --git a/cmake/modules/FindBrotli.cmake b/cmake/modules/FindBrotli.cmake new file mode 100644 index 00000000000..d0ea34c892c --- /dev/null +++ b/cmake/modules/FindBrotli.cmake @@ -0,0 +1,26 @@ +include(FindPackageHandleStandardArgs) + +find_path(BROTLI_DEC_INCLUDE_DIR "brotli/decode.h") +find_path(BROTLI_ENC_INCLUDE_DIR "brotli/encode.h") + +find_library(BROTLI_COMMON_LIB NAMES brotlicommon) +find_library(BROTLI_DEC_LIB NAMES brotlidec) +find_library(BROTLI_ENC_LIB NAMES brotlienc) + +find_package_handle_standard_args(Brotli + FOUND_VAR + Brotli_FOUND + REQUIRED_VARS + BROTLI_COMMON_LIB + BROTLI_DEC_INCLUDE_DIR + BROTLI_DEC_LIB + BROTLI_ENC_INCLUDE_DIR + BROTLI_ENC_LIB + FAIL_MESSAGE + "Did not find Brotli" +) + + +set(HAVE_BROTLI ${Brotli_FOUND}) +set(BROTLI_INCLUDE_DIRS ${BROTLI_DEC_INCLUDE_DIR} ${BROTLI_ENC_INCLUDE_DIR}) +set(BROTLI_LIBS "${BROTLICOMMON_LIBRARY}" "${BROTLI_DEC_LIB}" "${BROTLI_ENC_LIB}") \ No newline at end of file diff --git a/go/heif/heif.go b/go/heif/heif.go index 71e531e967f..b71b2eadf45 100644 --- a/go/heif/heif.go +++ b/go/heif/heif.go @@ -269,6 +269,8 @@ const ( SuberrorCameraExtrinsicMatrixUndefined = C.heif_suberror_Camera_extrinsic_matrix_undefined + SuberrorDecompressionInvalidData = C.heif_suberror_Decompression_invalid_data + // --- Memory_allocation_error --- // A security limit preventing unreasonable memory allocations was exceeded by the input file. @@ -276,6 +278,8 @@ const ( // security limits further. SuberrorSecurityLimitExceeded = C.heif_suberror_Security_limit_exceeded + CompressionInitialisationError = C.heif_suberror_Compression_initialisation_error + // --- Usage_error --- // An item ID was used that is not present in the file. @@ -331,6 +335,8 @@ const ( SuberrorUnsupportedDataVersion = C.heif_suberror_Unsupported_data_version + SuberrorUnsupportedGenericCompressionMethod = C.heif_suberror_Unsupported_generic_compression_method + // The conversion of the source image to the requested chroma / colorspace is not supported. SuberrorUnsupportedColorConversion = C.heif_suberror_Unsupported_color_conversion diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt index 4555cabc21b..401d0769a88 100644 --- a/libheif/CMakeLists.txt +++ b/libheif/CMakeLists.txt @@ -33,8 +33,9 @@ set(libheif_sources init.h logging.h logging.cc - metadata_compression.cc - metadata_compression.h + compression.h + compression_brotli.cc + compression_zlib.cc common_utils.cc common_utils.h region.cc @@ -133,11 +134,21 @@ else () message("Not compiling 'libsharpyuv'") endif () -if (WITH_DEFLATE_HEADER_COMPRESSION) +if (WITH_DEFLATE_HEADER_COMPRESSION OR WITH_UNCOMPRESSED_CODEC) find_package(ZLIB REQUIRED) target_link_libraries(heif PRIVATE ZLIB::ZLIB) + target_compile_definitions(heif PRIVATE WITH_ZLIB_COMPRESSION=1) +endif () + +if (WITH_DEFLATE_HEADER_COMPRESSION) target_compile_definitions(heif PRIVATE WITH_DEFLATE_HEADER_COMPRESSION=1) endif () + +if (HAVE_BROTLI) + target_compile_definitions(heif PUBLIC HAVE_BROTLI=1) + target_include_directories(heif PRIVATE ${BROTLI_INCLUDE_DIRS}) + target_link_libraries(heif PRIVATE ${BROTLI_LIBS}) +endif() if (ENABLE_MULTITHREADING_SUPPORT) find_package(Threads) diff --git a/libheif/api/libheif/heif.h b/libheif/api/libheif/heif.h index a560c3a77b6..b38da0a60c2 100644 --- a/libheif/api/libheif/heif.h +++ b/libheif/api/libheif/heif.h @@ -241,6 +241,11 @@ enum heif_suberror_code heif_suberror_No_vvcC_box = 141, + // icbr is only needed in some situations, this error is for those cases + heif_suberror_No_icbr_box = 142, + + // Decompressing generic compression or header compression data failed (e.g. bitstream corruption) + heif_suberror_Decompression_invalid_data = 150, // --- Memory_allocation_error --- @@ -249,6 +254,9 @@ enum heif_suberror_code // security limits further. heif_suberror_Security_limit_exceeded = 1000, + // There was an error from the underlying compression / decompression library. + // One possibility is lack of resources (e.g. memory). + heif_suberror_Compression_initialisation_error = 1001, // --- Usage_error --- @@ -297,6 +305,8 @@ enum heif_suberror_code heif_suberror_Unsupported_header_compression_method = 3005, + // Generically compressed data used an unsupported compression method + heif_suberror_Unsupported_generic_compression_method = 3006, // --- Encoder_plugin_error --- diff --git a/libheif/api/libheif/heif_emscripten.h b/libheif/api/libheif/heif_emscripten.h index a04124babbd..cc43c65c98e 100644 --- a/libheif/api/libheif/heif_emscripten.h +++ b/libheif/api/libheif/heif_emscripten.h @@ -331,6 +331,8 @@ EMSCRIPTEN_BINDINGS(libheif) { emscripten::enum_("heif_suberror_code") .value("heif_suberror_Unspecified", heif_suberror_Unspecified) .value("heif_suberror_Cannot_write_output_data", heif_suberror_Cannot_write_output_data) + .value("heif_suberror_Compression_initialisation_error", heif_suberror_Compression_initialisation_error) + .value("heif_suberror_Decompression_invalid_data", heif_suberror_Decompression_invalid_data) .value("heif_suberror_Encoder_initialization", heif_suberror_Encoder_initialization) .value("heif_suberror_Encoder_encoding", heif_suberror_Encoder_encoding) .value("heif_suberror_Encoder_cleanup", heif_suberror_Encoder_cleanup) @@ -386,6 +388,7 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_Unsupported_codec", heif_suberror_Unsupported_codec) .value("heif_suberror_Unsupported_image_type", heif_suberror_Unsupported_image_type) .value("heif_suberror_Unsupported_data_version", heif_suberror_Unsupported_data_version) + .value("heif_suberror_Unsupported_generic_compression_method", heif_suberror_Unsupported_generic_compression_method) .value("heif_suberror_Unsupported_color_conversion", heif_suberror_Unsupported_color_conversion) .value("heif_suberror_Unsupported_item_construction_method", heif_suberror_Unsupported_item_construction_method) .value("heif_suberror_Unsupported_header_compression_method", heif_suberror_Unsupported_header_compression_method) diff --git a/libheif/bitstream.cc b/libheif/bitstream.cc index 80fcd02e683..c49a3f08cf9 100644 --- a/libheif/bitstream.cc +++ b/libheif/bitstream.cc @@ -235,6 +235,31 @@ uint32_t BitstreamRange::read32() (buf[3])); } +uint64_t BitstreamRange::read64() +{ + if (!prepare_read(8)) { + return 0; + } + + uint8_t buf[8]; + + auto istr = get_istream(); + bool success = istr->read((char*) buf, 8); + + if (!success) { + set_eof_while_reading(); + return 0; + } + + return (uint64_t) (((uint64_t)buf[0] << 56) | + ((uint64_t)buf[1] << 48) | + ((uint64_t)buf[2] << 40) | + ((uint64_t)buf[3] << 32) | + (buf[4] << 24) | + (buf[5] << 16) | + (buf[6] << 8) | + (buf[7])); +} int32_t BitstreamRange::read32s() { diff --git a/libheif/bitstream.h b/libheif/bitstream.h index ed1de3d37b4..a0188ef35e1 100644 --- a/libheif/bitstream.h +++ b/libheif/bitstream.h @@ -150,6 +150,8 @@ class BitstreamRange int32_t read32s(); + uint64_t read64(); + std::string read_string(); bool read(uint8_t* data, size_t n); diff --git a/libheif/box.cc b/libheif/box.cc index 645dc4b27a7..2c18725b4fe 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -611,6 +611,14 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) case fourcc("uncC"): box = std::make_shared(); break; + + case fourcc("cmpC"): + box = std::make_shared(); + break; + + case fourcc("icbr"): + box = std::make_shared(); + break; #endif // --- JPEG 2000 diff --git a/libheif/codecs/uncompressed_box.cc b/libheif/codecs/uncompressed_box.cc index b6afa359c00..9983640e63a 100644 --- a/libheif/codecs/uncompressed_box.cc +++ b/libheif/codecs/uncompressed_box.cc @@ -312,6 +312,7 @@ std::string Box_uncC::dump(Indent& indent) const return sstr.str(); } + Error Box_uncC::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); @@ -350,3 +351,89 @@ Error Box_uncC::write(StreamWriter& writer) const return Error::Ok; } + + +Error Box_cmpC::parse(BitstreamRange& range) +{ + parse_full_box_header(range); + compression_type = range.read32(); + uint8_t v = range.read8(); + must_decompress_individual_entities = ((v & 0x80) == 0x80); + compressed_range_type = (v & 0x7f); + return range.get_error(); +} + + +std::string Box_cmpC::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "compression_type: " << to_fourcc(compression_type) << "\n"; + sstr << indent << "must_decompress_individual_entities: " << must_decompress_individual_entities << "\n"; + sstr << indent << "compressed_entity_type: " << (int)compressed_range_type << "\n"; + return sstr.str(); +} + +Error Box_cmpC::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + writer.write32(compression_type); + uint8_t v = must_decompress_individual_entities ? 0x80 : 0x00; + v |= (compressed_range_type & 0x7F); + writer.write8(v); + + prepend_header(writer, box_start); + + return Error::Ok; +} + + +Error Box_icbr::parse(BitstreamRange& range) +{ + parse_full_box_header(range); + uint32_t num_ranges = range.read32(); + for (uint32_t r = 0; r < num_ranges; r++) { + struct ByteRange byteRange; + if (get_version() == 1) { + byteRange.range_offset = range.read64(); + byteRange.range_size = range.read64(); + } else if (get_version() == 0) { + byteRange.range_offset = range.read32(); + byteRange.range_size = range.read32(); + } + m_ranges.push_back(byteRange); + } + return range.get_error(); +} + + +std::string Box_icbr::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "num_ranges: " << m_ranges.size() << "\n"; + for (ByteRange range: m_ranges) { + sstr << indent << "range_offset: " << range.range_offset << ", range_size: " << range.range_size << "\n"; + } + return sstr.str(); +} + +Error Box_icbr::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + writer.write32((uint32_t)m_ranges.size()); + for (ByteRange range: m_ranges) { + if (get_version() == 1) { + writer.write64(range.range_offset); + writer.write64(range.range_size); + } else if (get_version() == 0) { + writer.write32((uint32_t)range.range_offset); + writer.write32((uint32_t)range.range_size); + } + } + prepend_header(writer, box_start); + + return Error::Ok; +} \ No newline at end of file diff --git a/libheif/codecs/uncompressed_box.h b/libheif/codecs/uncompressed_box.h index 98ae3da60b2..39a52a6b496 100644 --- a/libheif/codecs/uncompressed_box.h +++ b/libheif/codecs/uncompressed_box.h @@ -76,7 +76,8 @@ class Box_cmpd : public Box class Box_uncC : public FullBox { public: - Box_uncC() { + Box_uncC() + { set_short_type(fourcc("uncC")); } @@ -220,4 +221,69 @@ class Box_uncC : public FullBox uint32_t m_num_tile_rows = 1; }; +/** + * Generic compression box (cmpC). + * + * This is from ISO/IEC 23001-17 Amd 2. + */ +class Box_cmpC : public FullBox +{ +public: + Box_cmpC() + { + set_short_type(fourcc("cmpC")); + } + + std::string dump(Indent&) const override; + + uint32_t get_compression_type() const { return compression_type; } + bool get_must_decompress_individual_entities() const { return must_decompress_individual_entities; } + uint8_t get_compressed_range_type() const { return compressed_range_type; } + + Error write(StreamWriter& writer) const override; + +protected: + Error parse(BitstreamRange& range) override; + + uint32_t compression_type; + bool must_decompress_individual_entities; + uint8_t compressed_range_type; +}; + +/** + * Item compressed byte range info (icbr). + * + * This is from ISO/IEC 23001-17 Amd 2. + */ +class Box_icbr : public FullBox +{ +public: + Box_icbr() + { + set_short_type(fourcc("icbr")); + } + + struct ByteRange + { + uint64_t range_offset; + uint64_t range_size; + }; + + const std::vector& get_ranges() const { return m_ranges; } + + void add_component(const ByteRange& range) + { + m_ranges.push_back(range); + } + + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + +protected: + Error parse(BitstreamRange& range) override; + + std::vector m_ranges; +}; + #endif //LIBHEIF_UNCOMPRESSED_BOX_H diff --git a/libheif/codecs/uncompressed_image.cc b/libheif/codecs/uncompressed_image.cc index ff627eeb57c..1145eb9496e 100644 --- a/libheif/codecs/uncompressed_image.cc +++ b/libheif/codecs/uncompressed_image.cc @@ -29,6 +29,7 @@ #include "common_utils.h" #include "context.h" +#include "compression.h" #include "error.h" #include "libheif/heif.h" #include "uncompressed.h" @@ -861,9 +862,9 @@ static AbstractDecoder* makeDecoder(uint32_t width, uint32_t height, const std:: Error UncompressedImageCodec::decode_uncompressed_image(const HeifContext* context, heif_item_id ID, std::shared_ptr& img, - const std::vector& uncompressed_data) + const std::vector& source_data) { - if (uncompressed_data.empty()) { + if (source_data.empty()) { return {heif_error_Invalid_input, heif_suberror_Unspecified, "Uncompressed image data is empty"}; @@ -883,6 +884,9 @@ Error UncompressedImageCodec::decode_uncompressed_image(const HeifContext* conte bool found_ispe = false; std::shared_ptr cmpd; std::shared_ptr uncC; + std::shared_ptr cmpC; + std::shared_ptr icbr; + for (const auto& prop : item_properties) { auto ispe = std::dynamic_pointer_cast(prop); if (ispe) { @@ -905,6 +909,17 @@ Error UncompressedImageCodec::decode_uncompressed_image(const HeifContext* conte if (maybe_uncC) { uncC = maybe_uncC; } + + auto maybe_cmpC = std::dynamic_pointer_cast(prop); + if (maybe_cmpC) { + cmpC = maybe_cmpC; + } + + auto maybe_icbr = std::dynamic_pointer_cast(prop); + if (maybe_icbr) { + icbr = maybe_icbr; + } + } @@ -919,6 +934,10 @@ Error UncompressedImageCodec::decode_uncompressed_image(const HeifContext* conte heif_suberror_Unsupported_data_version, "Missing required uncC box for uncompressed codec"); } + if (!cmpd) { + printf("No cmpd\n"); + } + // printf("uncC version: %d\n", uncC->get_version()); if (!cmpd && (uncC->get_version() !=1)) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, @@ -958,9 +977,9 @@ Error UncompressedImageCodec::decode_uncompressed_image(const HeifContext* conte AbstractDecoder *decoder = makeDecoder(width, height, cmpd, uncC); if (decoder != nullptr) { - Error result = decoder->decode(uncompressed_data, img); - delete decoder; - return result; + Error result = decoder->decode(source_data, img); + delete decoder; + return result; } else { printf("bad interleave mode - we should have detected this earlier: %d\n", uncC->get_interleave_type()); std::stringstream sstr; diff --git a/libheif/metadata_compression.h b/libheif/compression.h similarity index 67% rename from libheif/metadata_compression.h rename to libheif/compression.h index 3780620b925..bff089ff527 100644 --- a/libheif/metadata_compression.h +++ b/libheif/compression.h @@ -17,17 +17,28 @@ * You should have received a copy of the GNU Lesser General Public License * along with libheif. If not, see . */ -#ifndef LIBHEIF_METADATA_COMPRESSION_H -#define LIBHEIF_METADATA_COMPRESSION_H +#ifndef LIBHEIF_COMPRESSION_H +#define LIBHEIF_COMPRESSION_H #include #include #include -#if WITH_DEFLATE_HEADER_COMPRESSION +#include + +#if WITH_ZLIB_COMPRESSION std::vector deflate(const uint8_t* input, size_t size); -std::vector inflate(const std::vector&); +Error inflate_zlib(const std::vector&compressed_input, std::vector *output); + +Error inflate_deflate(const std::vector&, std::vector *output); + +#endif + +#if HAVE_BROTLI + +Error inflate_brotli(const std::vector &compressed_input, std::vector *output); + #endif -#endif //LIBHEIF_METADATA_COMPRESSION_H +#endif //LIBHEIF_COMPRESSION_H diff --git a/libheif/compression_brotli.cc b/libheif/compression_brotli.cc new file mode 100644 index 00000000000..69de5bf7673 --- /dev/null +++ b/libheif/compression_brotli.cc @@ -0,0 +1,84 @@ +/* + * 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 "compression.h" + +#if HAVE_BROTLI + +const size_t BUF_SIZE = (1 << 18); +#include +#include +#include +#include +#include + +#include "error.h" + +Error inflate_brotli(const std::vector &compressed_input, std::vector *output) +{ + BrotliDecoderResult result = BROTLI_DECODER_RESULT_ERROR; + std::vector buffer(BUF_SIZE, 0); + size_t available_in = compressed_input.size(); + const std::uint8_t *next_in = reinterpret_cast(compressed_input.data()); + size_t available_out = buffer.size(); + std::uint8_t *next_output = buffer.data(); + BrotliDecoderState *state = BrotliDecoderCreateInstance(0, 0, 0); + + while (true) + { + result = BrotliDecoderDecompressStream(state, &available_in, &next_in, &available_out, &next_output, 0); + + if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) + { + output->insert(output->end(), buffer.data(), buffer.data() + std::distance(buffer.data(), next_output)); + available_out = buffer.size(); + next_output = buffer.data(); + } + else if (result == BROTLI_DECODER_RESULT_SUCCESS) + { + output->insert(output->end(), buffer.data(), buffer.data() + std::distance(buffer.data(), next_output)); + break; + } + else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) + { + std::stringstream sstr; + sstr << "Error performing brotli inflate - insufficient data.\n"; + return Error(heif_error_Invalid_input, heif_suberror_Decompression_invalid_data, sstr.str()); + } + else if (result == BROTLI_DECODER_RESULT_ERROR) + { + const char* errorMessage = BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state)); + std::stringstream sstr; + sstr << "Error performing brotli inflate - " << errorMessage << "\n"; + return Error(heif_error_Invalid_input, heif_suberror_Decompression_invalid_data, sstr.str()); + } + else + { + const char* errorMessage = BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state)); + std::stringstream sstr; + sstr << "Unknown error performing brotli inflate - " << errorMessage << "\n"; + return Error(heif_error_Invalid_input, heif_suberror_Decompression_invalid_data, sstr.str()); + } + } + BrotliDecoderDestroyInstance(state); + return Error::Ok; +} + +#endif \ No newline at end of file diff --git a/libheif/metadata_compression.cc b/libheif/compression_zlib.cc similarity index 72% rename from libheif/metadata_compression.cc rename to libheif/compression_zlib.cc index b738d1f62db..af84ebe352a 100644 --- a/libheif/metadata_compression.cc +++ b/libheif/compression_zlib.cc @@ -19,12 +19,14 @@ */ -#include "metadata_compression.h" +#include "compression.h" -#if WITH_DEFLATE_HEADER_COMPRESSION +#if WITH_ZLIB_COMPRESSION + #include #include +#include std::vector deflate(const uint8_t* input, size_t size) { @@ -78,9 +80,8 @@ std::vector deflate(const uint8_t* input, size_t size) } -std::vector inflate(const std::vector& compressed_input) +Error do_inflate(const std::vector& compressed_input, int windowSize, std::vector *output) { - std::vector output; // decompress data with zlib @@ -102,10 +103,11 @@ std::vector inflate(const std::vector& compressed_input) int err = -1; - err = inflateInit(&strm); + err = inflateInit2(&strm, windowSize); if (err != Z_OK) { - // TODO: return error - return {}; + std::stringstream sstr; + sstr << "Error initialising zlib inflate: " << strm.msg << " (" << err << ")\n"; + return Error(heif_error_Memory_allocation_error, heif_suberror_Compression_initialisation_error, sstr.str()); } do { @@ -118,19 +120,28 @@ std::vector inflate(const std::vector& compressed_input) // -> do nothing } else if (err == Z_NEED_DICT || err == Z_DATA_ERROR || err == Z_STREAM_ERROR) { - // TODO: return error - return {}; + std::stringstream sstr; + sstr << "Error performing zlib inflate: " << strm.msg << " (" << err << ")\n"; + return Error(heif_error_Invalid_input, heif_suberror_Decompression_invalid_data, sstr.str()); } - // append decoded data to output - - output.insert(output.end(), dst, dst + outBufferSize - strm.avail_out); + output->insert(output->end(), dst, dst + outBufferSize - strm.avail_out); } while (err != Z_STREAM_END); inflateEnd(&strm); - return output; + return Error::Ok; +} + +Error inflate_zlib(const std::vector& compressed_input, std::vector *output) +{ + return do_inflate(compressed_input, 15, output); +} + +Error inflate_deflate(const std::vector& compressed_input, std::vector *output) +{ + return do_inflate(compressed_input, -15, output); } #endif diff --git a/libheif/context.cc b/libheif/context.cc index b3666d3282e..5a572c20325 100644 --- a/libheif/context.cc +++ b/libheif/context.cc @@ -40,7 +40,7 @@ #include "pixelimage.h" #include "libheif/api_structs.h" #include "security_limits.h" -#include "metadata_compression.h" +#include "compression.h" #include "color-conversion/colorconversion.h" #include "plugin_registry.h" #include "codecs/hevc.h" diff --git a/libheif/error.cc b/libheif/error.cc index 2735c129817..1caa5348fc8 100644 --- a/libheif/error.cc +++ b/libheif/error.cc @@ -168,12 +168,18 @@ const char* Error::get_error_string(heif_suberror_code err) return "Camera extrinsic matrix undefined"; case heif_suberror_Invalid_J2K_codestream: return "Invalid JPEG 2000 codestream"; + case heif_suberror_Decompression_invalid_data: + return "Invalid data in generic compression inflation"; + case heif_suberror_No_icbr_box: + return "No 'icbr' box"; // --- Memory_allocation_error --- case heif_suberror_Security_limit_exceeded: return "Security limit exceeded"; + case heif_suberror_Compression_initialisation_error: + return "Compression initialisation method error"; // --- Usage_error --- @@ -210,6 +216,8 @@ const char* Error::get_error_string(heif_suberror_code err) return "Unsupported item construction method"; case heif_suberror_Unsupported_header_compression_method: return "Unsupported header compression method"; + case heif_suberror_Unsupported_generic_compression_method: + return "Unsupported generic compression method"; // --- Encoder_plugin_error -- diff --git a/libheif/file.cc b/libheif/file.cc index ea66d5279a6..770c35e6ca0 100644 --- a/libheif/file.cc +++ b/libheif/file.cc @@ -22,13 +22,15 @@ #include "box.h" #include "libheif/heif.h" #include "libheif/heif_properties.h" -#include "metadata_compression.h" +#include "compression.h" #include "codecs/jpeg2000.h" #include "codecs/jpeg.h" #include "codecs/vvc.h" +#include "codecs/uncompressed_box.h" #include #include +#include #include #include #include @@ -49,6 +51,7 @@ #if WITH_UNCOMPRESSED_CODEC #include "codecs/uncompressed_image.h" #endif +#include // TODO: make this a decoder option #define STRICT_PARSING false @@ -780,211 +783,353 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* sstr.str()); } - Error error = Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_codec); if (item_type == "hvc1") { // --- --- --- HEVC - - // --- get properties for this image - - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } - - // --- get codec configuration - - std::shared_ptr hvcC_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("hvcC")) { - hvcC_box = std::dynamic_pointer_cast(prop); - if (hvcC_box) { - break; + return get_compressed_image_data_hvc1(ID, data, item); + } + else if (item_type == "vvc1") { + // --- --- --- VVC + return get_compressed_image_data_vvc(ID, data, item); + } + else if (item_type == "av01") { + return get_compressed_image_data_av1(ID, data, item); + } + else if (item_type == "jpeg" || + (item_type == "mime" && get_content_type(ID) == "image/jpeg")) { + return get_compressed_image_data_jpeg(ID, data, item); + } + else if (item_type == "j2k1") { + return get_compressed_image_data_jpeg2000(ID, item, data); + } +#if WITH_UNCOMPRESSED_CODEC + else if (item_type == "unci") { + return get_compressed_image_data_uncompressed(ID, data, item); + } +#endif + else if (true || // fallback case for all kinds of generic metadata (e.g. 'iptc') + item_type == "grid" || + item_type == "iovl" || + item_type == "Exif" || + (item_type == "mime" && content_type == "application/rdf+xml")) { + Error error; + bool read_uncompressed = true; + if (item_type == "mime") { + std::string encoding = infe_box->get_content_encoding(); + if (encoding == "deflate") { +#if WITH_DEFLATE_HEADER_COMPRESSION + read_uncompressed = false; + std::vector compressed_data; + error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data); + if (error) { + return error; } + error = inflate_zlib(compressed_data, data); + if (error) { + return error; + } +#else + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_header_compression_method, + encoding); +#endif } } - if (!hvcC_box) { - // Should always have an hvcC box, because we are checking this in - // heif_context::interpret_heif_file() - assert(false); - return Error(heif_error_Invalid_input, - heif_suberror_No_hvcC_box); - } - else if (!hvcC_box->get_headers(data)) { - return Error(heif_error_Invalid_input, - heif_suberror_No_item_data); + if (read_uncompressed) { + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); } + } + return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_codec); +} - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +// generic compression and uncompressed, per 23001-17 +const Error HeifFile::get_compressed_image_data_uncompressed(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const +{ + std::vector> properties; + Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); + if (err) { + return err; } - else if (item_type == "vvc1") { - // --- --- --- VVC - // --- get properties for this image + // --- get codec configuration - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; + std::shared_ptr cmpC_box; + std::shared_ptr icbr_box; + for (auto& prop : properties) { + if (prop->get_short_type() == fourcc("cmpC")) { + cmpC_box = std::dynamic_pointer_cast(prop); } - - // --- get codec configuration - - std::shared_ptr vvcC_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("vvcC")) { - vvcC_box = std::dynamic_pointer_cast(prop); - if (vvcC_box) { - break; - } - } + if (prop->get_short_type() == fourcc("icbr")) { + icbr_box = std::dynamic_pointer_cast(prop); } - - if (!vvcC_box) { - // Should always have an vvcC box, because we are checking this in - // heif_context::interpret_heif_file() - assert(false); - return Error(heif_error_Invalid_input, - heif_suberror_No_vvcC_box); + if (cmpC_box && icbr_box) { + break; } - else if (!vvcC_box->get_headers(data)) { + } + if (!cmpC_box) { + // assume no generic compression + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); + } + if (!cmpC_box->get_must_decompress_individual_entities()) { + std::vector compressed_data; + m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data); + return do_decompress_data(cmpC_box, compressed_data, data); + } else { + if (!icbr_box) { + std::stringstream sstr; + sstr << "cannot decode unci item requiring entity decompression without icbr box" << std::endl; return Error(heif_error_Invalid_input, - heif_suberror_No_item_data); + heif_suberror_No_icbr_box, + sstr.str()); + } + if (item->construction_method == 0) { + for (Box_icbr::ByteRange range: icbr_box->get_ranges()) { + // TODO: check errors + bool success = m_input_stream->seek(range.range_offset); + if (!success) { + return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while seeking to generically compressed data"}; + } + std::vector compressed_range_bytes(range.range_size); + success = m_input_stream->read((char*) compressed_range_bytes.data(), static_cast(compressed_range_bytes.size())); + if (!success) { + return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while reading generically compressed data"}; + } + std::vector uncompressed_range_data; + Error err = do_decompress_data(cmpC_box, compressed_range_bytes, &uncompressed_range_data); + if (err) { + return err; + } + data->insert(data->end(), uncompressed_range_data.data(), uncompressed_range_data.data() + uncompressed_range_data.size()); + } + return Error::Ok; + } else { + // TODO: implement... + std::stringstream sstr; + sstr << "cannot decode unci item from idat yet" << std::endl; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); } - - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); } - else if (item_type == "av01") { - // --- --- --- AV1 + return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_codec); +} - // --- get properties for this image +const Error HeifFile::do_decompress_data(std::shared_ptr &cmpC_box, std::vector compressed_data, std::vector *data) const +{ + if (cmpC_box->get_compression_type() == fourcc("brot")) { +#if HAVE_BROTLI + return inflate_brotli(compressed_data, data); +#else + std::stringstream sstr; + sstr << "cannot decode unci item with brotli compression - not enabled" << std::endl; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_generic_compression_method, + sstr.str()); +#endif + } else if (cmpC_box->get_compression_type() == fourcc("zlib")) { + return inflate_zlib(compressed_data, data); + } else if (cmpC_box->get_compression_type() == fourcc("defl")) { + return inflate_deflate(compressed_data, data); + } else { + std::stringstream sstr; + sstr << "cannot decode unci item with unsupported compression type: " << cmpC_box->get_compression_type() << std::endl; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_generic_compression_method, + sstr.str()); + } +} - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } +const Error HeifFile::get_compressed_image_data_hvc1(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const +{ + // --- get properties for this image + std::vector> properties; + Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); + if (err) + { + return err; + } - // --- get codec configuration + // --- get codec configuration - std::shared_ptr av1C_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("av1C")) { - av1C_box = std::dynamic_pointer_cast(prop); - if (av1C_box) { - break; - } + std::shared_ptr hvcC_box; + for (auto &prop : properties) + { + if (prop->get_short_type() == fourcc("hvcC")) + { + hvcC_box = std::dynamic_pointer_cast(prop); + if (hvcC_box) + { + break; } } + } - if (!av1C_box) { - // Should always have an hvcC box, because we are checking this in - // heif_context::interpret_heif_file() - return Error(heif_error_Invalid_input, - heif_suberror_No_av1C_box); - } - else if (!av1C_box->get_headers(data)) { - return Error(heif_error_Invalid_input, - heif_suberror_No_item_data); - } - - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); + if (!hvcC_box) + { + // Should always have an hvcC box, because we are checking this in + // heif_context::interpret_heif_file() + assert(false); + return Error(heif_error_Invalid_input, + heif_suberror_No_hvcC_box); + } + else if (!hvcC_box->get_headers(data)) + { + return Error(heif_error_Invalid_input, + heif_suberror_No_item_data); } - else if (item_type == "jpeg" || - (item_type == "mime" && get_content_type(ID) == "image/jpeg")) { - // --- check if 'jpgC' is present + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +} - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } +const Error HeifFile::get_compressed_image_data_vvc(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const +{ - // --- get codec configuration + // --- get properties for this image - std::shared_ptr jpgC_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("jpgC")) { - jpgC_box = std::dynamic_pointer_cast(prop); - if (jpgC_box) { - *data = jpgC_box->get_data(); - break; - } + std::vector> properties; + Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); + if (err) + { + return err; + } + + // --- get codec configuration + + std::shared_ptr vvcC_box; + for (auto &prop : properties) + { + if (prop->get_short_type() == fourcc("vvcC")) + { + vvcC_box = std::dynamic_pointer_cast(prop); + if (vvcC_box) + { + break; } } + } + + if (!vvcC_box) + { + assert(false); + return Error(heif_error_Invalid_input, + heif_suberror_No_vvcC_box); + } + else if (!vvcC_box->get_headers(data)) + { + return Error(heif_error_Invalid_input, + heif_suberror_No_item_data); + } + + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +} + +const Error HeifFile::get_compressed_image_data_av1(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const +{ + // --- --- --- AV1 + + // --- get properties for this image - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); + std::vector> properties; + Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); + if (err) + { + return err; } - else if (item_type == "j2k1") { - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } - // --- get codec configuration + // --- get codec configuration - std::shared_ptr j2kH_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("j2kH")) { - j2kH_box = std::dynamic_pointer_cast(prop); - if (j2kH_box) { - break; - } + std::shared_ptr av1C_box; + for (auto &prop : properties) + { + if (prop->get_short_type() == fourcc("av1C")) + { + av1C_box = std::dynamic_pointer_cast(prop); + if (av1C_box) + { + break; } } + } - if (!j2kH_box) { - // Should always have an j2kH box, because we are checking this in - // heif_context::interpret_heif_file() + if (!av1C_box) + { + return Error(heif_error_Invalid_input, + heif_suberror_No_av1C_box); + } + else if (!av1C_box->get_headers(data)) + { + return Error(heif_error_Invalid_input, + heif_suberror_No_item_data); + } - //TODO - Correctly Find the j2kH box - // return Error(heif_error_Invalid_input, - // heif_suberror_Unspecified); - } - // else if (!j2kH_box->get_headers(data)) { - // return Error(heif_error_Invalid_input, - // heif_suberror_No_item_data); - // } + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +} - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +const Error HeifFile::get_compressed_image_data_jpeg2000(heif_item_id ID, const Box_iloc::Item *item, std::vector *data) const +{ + std::vector> properties; + Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); + if (err) + { + return err; } - else if (true || // fallback case for all kinds of generic metadata (e.g. 'iptc') - item_type == "grid" || - item_type == "iovl" || - item_type == "Exif" || - (item_type == "mime" && content_type == "application/rdf+xml")) { - bool read_uncompressed = true; - if (item_type == "mime") { - std::string encoding = infe_box->get_content_encoding(); - if (encoding == "deflate") { -#if WITH_DEFLATE_HEADER_COMPRESSION - read_uncompressed = false; - std::vector compressed_data; - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data); - *data = inflate(compressed_data); -#else - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_header_compression_method, - encoding); -#endif + // --- get codec configuration + + std::shared_ptr j2kH_box; + for (auto &prop : properties) + { + if (prop->get_short_type() == fourcc("j2kH")) + { + j2kH_box = std::dynamic_pointer_cast(prop); + if (j2kH_box) + { + break; } } + } - if (read_uncompressed) { - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); - } + if (!j2kH_box) + { + // TODO - Correctly Find the j2kH box + // return Error(heif_error_Invalid_input, + // heif_suberror_Unspecified); } + // else if (!j2kH_box->get_headers(data)) { + // return Error(heif_error_Invalid_input, + // heif_suberror_No_item_data); + // } - if (error != Error::Ok) { - return error; + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +} + +const Error HeifFile::get_compressed_image_data_jpeg(heif_item_id ID, std::vector * data, const Box_iloc::Item *item) const +{ + // --- check if 'jpgC' is present + std::vector> properties; + Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); + if (err) + { + return err; } - return Error::Ok; + // --- get codec configuration + + std::shared_ptr jpgC_box; + for (auto &prop : properties) + { + if (prop->get_short_type() == fourcc("jpgC")) + { + jpgC_box = std::dynamic_pointer_cast(prop); + if (jpgC_box) + { + *data = jpgC_box->get_data(); + break; + } + } + } + + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); } @@ -1075,8 +1220,7 @@ Error HeifFile::get_item_data(heif_item_id ID, std::vector* out_data, h switch (compression) { #if WITH_DEFLATE_HEADER_COMPRESSION case heif_metadata_compression_deflate: - *out_data = inflate(compressed_data); - return Error::Ok; + return inflate_zlib(compressed_data, out_data); #endif default: return {heif_error_Unsupported_filetype, heif_suberror_Unsupported_header_compression_method}; diff --git a/libheif/file.h b/libheif/file.h index a489af9a879..75095c438b1 100644 --- a/libheif/file.h +++ b/libheif/file.h @@ -26,6 +26,7 @@ #include "codecs/avif.h" #include "codecs/hevc.h" #include "codecs/vvc.h" +#include "codecs/uncompressed_box.h" #include #include @@ -84,9 +85,9 @@ class HeifFile std::string get_item_uri_type(heif_item_id ID) const; - Error get_compressed_image_data(heif_item_id ID, std::vector* out_data) const; + Error get_compressed_image_data(heif_item_id ID, std::vector *out_data) const; - Error get_item_data(heif_item_id ID, std::vector* out_data, heif_metadata_compression* out_compression) const; + Error get_item_data(heif_item_id ID, std::vector *out_data, heif_metadata_compression *out_compression) const; std::shared_ptr get_ftyp_box() { return m_ftyp_box; } @@ -251,6 +252,20 @@ class HeifFile std::unordered_set& parent_items) const; int jpeg_get_bits_per_pixel(heif_item_id imageID) const; + + const Error get_compressed_image_data_hvc1(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; + + const Error get_compressed_image_data_vvc(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; + + const Error get_compressed_image_data_uncompressed(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; + + const Error do_decompress_data(std::shared_ptr &cmpC_box, std::vector compressed_data, std::vector *data) const; + + const Error get_compressed_image_data_av1(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; + + const Error get_compressed_image_data_jpeg2000(heif_item_id ID, const Box_iloc::Item *item, std::vector *data) const; + + const Error get_compressed_image_data_jpeg(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; }; #endif diff --git a/scripts/install-ci-linux.sh b/scripts/install-ci-linux.sh index 97a644eeef2..cdb5f981e9d 100755 --- a/scripts/install-ci-linux.sh +++ b/scripts/install-ci-linux.sh @@ -111,6 +111,13 @@ if [ ! -z "$WITH_GRAPHICS" ]; then " fi +if [ ! -z "$WITH_UNCOMPRESSED_CODEC" ]; then + INSTALL_PACKAGES="$INSTALL_PACKAGES \ + libbrotli-dev \ + zlib-dev \ + " +fi + if [ "$MINGW" == "32" ]; then sudo dpkg --add-architecture i386 # https://github.com/actions/runner-images/issues/4589 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 82152065b9a..afa9fbea03c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,6 +48,7 @@ endif() if (WITH_UNCOMPRESSED_CODEC) add_libheif_test(uncompressed_decode) + add_libheif_test(uncompressed_decode_generic_compression) add_libheif_test(uncompressed_decode_mono) add_libheif_test(uncompressed_decode_rgb) add_libheif_test(uncompressed_decode_rgb16) diff --git a/tests/data/rgb_generic_compressed_brotli.heif b/tests/data/rgb_generic_compressed_brotli.heif new file mode 100644 index 00000000000..8771ddc8114 Binary files /dev/null and b/tests/data/rgb_generic_compressed_brotli.heif differ diff --git a/tests/data/rgb_generic_compressed_defl.heif b/tests/data/rgb_generic_compressed_defl.heif new file mode 100644 index 00000000000..acb484a43cb Binary files /dev/null and b/tests/data/rgb_generic_compressed_defl.heif differ diff --git a/tests/data/rgb_generic_compressed_tile_deflate.heif b/tests/data/rgb_generic_compressed_tile_deflate.heif new file mode 100644 index 00000000000..86c2d3e5e66 Binary files /dev/null and b/tests/data/rgb_generic_compressed_tile_deflate.heif differ diff --git a/tests/data/rgb_generic_compressed_zlib.heif b/tests/data/rgb_generic_compressed_zlib.heif new file mode 100644 index 00000000000..98455c992de Binary files /dev/null and b/tests/data/rgb_generic_compressed_zlib.heif differ diff --git a/tests/data/rgb_generic_compressed_zlib_rows.heif b/tests/data/rgb_generic_compressed_zlib_rows.heif new file mode 100644 index 00000000000..c0507f6cd93 Binary files /dev/null and b/tests/data/rgb_generic_compressed_zlib_rows.heif differ diff --git a/tests/data/rgb_generic_compressed_zlib_tiled.heif b/tests/data/rgb_generic_compressed_zlib_tiled.heif new file mode 100644 index 00000000000..4b905aa465a Binary files /dev/null and b/tests/data/rgb_generic_compressed_zlib_tiled.heif differ diff --git a/tests/uncompressed_box.cc b/tests/uncompressed_box.cc index 630e79b1f31..4ba9809e2c1 100644 --- a/tests/uncompressed_box.cc +++ b/tests/uncompressed_box.cc @@ -216,3 +216,217 @@ TEST_CASE( "uncC" ) std::string dump_output = uncC->dump(indent); REQUIRE(dump_output == "Box: uncC -----\nsize: 0 (header size: 0)\nprofile: 1919378017 (rgba)\ncomponent_index: 0\ncomponent_bit_depth: 8\ncomponent_format: unsigned\ncomponent_align_size: 0\ncomponent_index: 1\ncomponent_bit_depth: 8\ncomponent_format: unsigned\ncomponent_align_size: 0\ncomponent_index: 2\ncomponent_bit_depth: 8\ncomponent_format: unsigned\ncomponent_align_size: 0\ncomponent_index: 3\ncomponent_bit_depth: 8\ncomponent_format: unsigned\ncomponent_align_size: 0\nsampling_type: no subsampling\ninterleave_type: pixel\nblock_size: 0\ncomponents_little_endian: 0\nblock_pad_lsb: 0\nblock_little_endian: 0\nblock_reversed: 0\npad_unknown: 0\npixel_size: 0\nrow_align_size: 0\ntile_align_size: 0\nnum_tile_cols: 1\nnum_tile_rows: 1\n"); } + + +TEST_CASE("cmpC_defl") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x11, 'c', 'm', 'p', 'C', + 0x00, 0x00, 0x00, 0x00, 'd', 'e', 'f', 'l', + 0x00 + }; + + 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); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("cmpC")); + REQUIRE(box->get_type_string() == "cmpC"); + std::shared_ptr cmpC = std::dynamic_pointer_cast(box); + REQUIRE(cmpC != nullptr); + REQUIRE(cmpC->get_compression_type() == fourcc("defl")); + REQUIRE(cmpC->get_must_decompress_individual_entities() == false); + REQUIRE(cmpC->get_compressed_range_type() == 0); + + StreamWriter writer; + Error err = cmpC->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = cmpC->dump(indent); + REQUIRE(dump_output == "Box: cmpC -----\nsize: 17 (header size: 12)\ncompression_type: defl\nmust_decompress_individual_entities: 0\ncompressed_entity_type: 0\n"); + +} + + +TEST_CASE("cmpC_zlib") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x11, 'c', 'm', 'p', 'C', + 0x00, 0x00, 0x00, 0x00, 'z', 'l', 'i', 'b', + 0x82 + }; + + 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); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("cmpC")); + REQUIRE(box->get_type_string() == "cmpC"); + std::shared_ptr cmpC = std::dynamic_pointer_cast(box); + REQUIRE(cmpC != nullptr); + REQUIRE(cmpC->get_compression_type() == fourcc("zlib")); + REQUIRE(cmpC->get_must_decompress_individual_entities() == true); + REQUIRE(cmpC->get_compressed_range_type() == 2); + + StreamWriter writer; + Error err = cmpC->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = cmpC->dump(indent); + REQUIRE(dump_output == "Box: cmpC -----\nsize: 17 (header size: 12)\ncompression_type: zlib\nmust_decompress_individual_entities: 1\ncompressed_entity_type: 2\n"); + +} + +TEST_CASE("cmpC_brot") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x11, 'c', 'm', 'p', 'C', + 0x00, 0x00, 0x00, 0x00, 'b', 'r', 'o', 't', + 0x81 + }; + + 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); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("cmpC")); + REQUIRE(box->get_type_string() == "cmpC"); + std::shared_ptr cmpC = std::dynamic_pointer_cast(box); + REQUIRE(cmpC != nullptr); + REQUIRE(cmpC->get_compression_type() == fourcc("brot")); + REQUIRE(cmpC->get_must_decompress_individual_entities() == true); + REQUIRE(cmpC->get_compressed_range_type() == 1); + + StreamWriter writer; + Error err = cmpC->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = cmpC->dump(indent); + REQUIRE(dump_output == "Box: cmpC -----\nsize: 17 (header size: 12)\ncompression_type: brot\nmust_decompress_individual_entities: 1\ncompressed_entity_type: 1\n"); + + } + +TEST_CASE("icbr_empty") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x10, 'i', 'c', 'b', 'r', + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + 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); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icbr")); + REQUIRE(box->get_type_string() == "icbr"); + std::shared_ptr icbr = std::dynamic_pointer_cast(box); + REQUIRE(icbr != nullptr); + REQUIRE(icbr->get_ranges().size() == 0); + + StreamWriter writer; + Error err = icbr->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = icbr->dump(indent); + REQUIRE(dump_output == "Box: icbr -----\nsize: 16 (header size: 12)\nnum_ranges: 0\n"); +} + +TEST_CASE("icbr_version0") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x20, 'i', 'c', 'b', 'r', + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x0a, 0x03, 0x00, 0x01, 0x02, 0x03, + 0x00, 0x02, 0x03, 0x0a, 0x00, 0x04, 0x05, 0x07 + }; + + 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); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icbr")); + REQUIRE(box->get_type_string() == "icbr"); + std::shared_ptr icbr = std::dynamic_pointer_cast(box); + REQUIRE(icbr != nullptr); + REQUIRE(icbr->get_ranges().size() == 2); + REQUIRE(icbr->get_version() == 0); + + StreamWriter writer; + Error err = icbr->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = icbr->dump(indent); + REQUIRE(dump_output == "Box: icbr -----\nsize: 32 (header size: 12)\nnum_ranges: 2\nrange_offset: 2563, range_size: 66051\nrange_offset: 131850, range_size: 263431\n"); +} + +TEST_CASE("icbr_version1") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x30, 'i', 'c', 'b', 'r', + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x0a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x05, 0x07 + }; + + 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); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icbr")); + REQUIRE(box->get_type_string() == "icbr"); + std::shared_ptr icbr = std::dynamic_pointer_cast(box); + REQUIRE(icbr != nullptr); + REQUIRE(icbr->get_ranges().size() == 2); + REQUIRE(icbr->get_version() == 1); + + StreamWriter writer; + Error err = icbr->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = icbr->dump(indent); + REQUIRE(dump_output == "Box: icbr -----\nsize: 48 (header size: 12)\nnum_ranges: 2\nrange_offset: 2563, range_size: 66051\nrange_offset: 131850, range_size: 263431\n"); +} diff --git a/tests/uncompressed_decode.h b/tests/uncompressed_decode.h index 4b124866769..48ea344e4b6 100644 --- a/tests/uncompressed_decode.h +++ b/tests/uncompressed_decode.h @@ -46,6 +46,16 @@ "uncompressed_pix_R8G8B8_bsz0_psz10_tiled.heif", \ "uncompressed_pix_R8G8B8_bsz0_psz5_tiled.heif" +#if HAVE_BROTLI + #define BROTLI_FILES "rgb_generic_compressed_brotli.heif", +#else + #define BROTLI_FILES +#endif + +#define FILES_GENERIC_COMPRESSED \ + "rgb_generic_compressed_defl.heif", BROTLI_FILES \ + "rgb_generic_compressed_tile_deflate.heif", "rgb_generic_compressed_zlib.heif", \ + "rgb_generic_compressed_zlib_rows.heif", "rgb_generic_compressed_zlib_tiled.heif" #define FILES_16BIT_RGB \ "uncompressed_comp_B16R16G16.heif", "uncompressed_comp_B16R16G16_tiled.heif", \ diff --git a/tests/uncompressed_decode_generic_compression.cc b/tests/uncompressed_decode_generic_compression.cc new file mode 100644 index 00000000000..47460ca7ae9 --- /dev/null +++ b/tests/uncompressed_decode_generic_compression.cc @@ -0,0 +1,216 @@ +/* + 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 + +#include "uncompressed_decode.h" + +void check_image_size(struct heif_context *&context) { + heif_image_handle *handle = get_primary_image_handle(context); + heif_image *img = get_primary_image(handle); + + REQUIRE(heif_image_has_channel(img, heif_channel_Y) == 0); + REQUIRE(heif_image_has_channel(img, heif_channel_Cb) == 0); + REQUIRE(heif_image_has_channel(img, heif_channel_Cr) == 0); + REQUIRE(heif_image_has_channel(img, heif_channel_R) == 1); + REQUIRE(heif_image_has_channel(img, heif_channel_G) == 1); + REQUIRE(heif_image_has_channel(img, heif_channel_B) == 1); + REQUIRE(heif_image_has_channel(img, heif_channel_Alpha) == 0); + REQUIRE(heif_image_has_channel(img, heif_channel_interleaved) == 0); + int width = heif_image_get_primary_width(img); + REQUIRE(width == 128); + int height = heif_image_get_primary_height(img); + REQUIRE(height == 72); + width = heif_image_get_width(img, heif_channel_R); + REQUIRE(width == 128); + height = heif_image_get_height(img, heif_channel_R); + REQUIRE(height == 72); + width = heif_image_get_width(img, heif_channel_G); + REQUIRE(width == 128); + height = heif_image_get_height(img, heif_channel_G); + REQUIRE(height == 72); + width = heif_image_get_width(img, heif_channel_B); + REQUIRE(width == 128); + height = heif_image_get_height(img, heif_channel_B); + REQUIRE(height == 72); + + int pixel_depth = heif_image_get_bits_per_pixel(img, heif_channel_R); + REQUIRE(pixel_depth == 8); + pixel_depth = heif_image_get_bits_per_pixel(img, heif_channel_G); + REQUIRE(pixel_depth == 8); + pixel_depth = heif_image_get_bits_per_pixel(img, heif_channel_B); + REQUIRE(pixel_depth == 8); + + int pixel_range = heif_image_get_bits_per_pixel_range(img, heif_channel_R); + REQUIRE(pixel_range == 8); + pixel_range = heif_image_get_bits_per_pixel_range(img, heif_channel_G); + REQUIRE(pixel_range == 8); + pixel_range = heif_image_get_bits_per_pixel_range(img, heif_channel_B); + REQUIRE(pixel_range == 8); + + heif_image_release(img); + heif_image_handle_release(handle); +} + +TEST_CASE("check image size") { + auto file = GENERATE(FILES_GENERIC_COMPRESSED); + auto context = get_context_for_test_file(file); + INFO("file name: " << file); + check_image_size(context); + heif_context_free(context); +} + + +void check_image_content(struct heif_context *&context) { + heif_image_handle *handle = get_primary_image_handle(context); + heif_image *img = get_primary_image(handle); + + int stride; + const uint8_t *img_plane = + heif_image_get_plane_readonly(img, heif_channel_R, &stride); + REQUIRE(stride == 128); + for (int row = 0; row < 24; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 255); + REQUIRE(((int)(img_plane[stride * row + 31])) == 255); + REQUIRE(((int)(img_plane[stride * row + 32])) == 0); + REQUIRE(((int)(img_plane[stride * row + 63])) == 0); + REQUIRE(((int)(img_plane[stride * row + 64])) == 0); + REQUIRE(((int)(img_plane[stride * row + 95])) == 0); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + for (int row = 24; row < 48; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 255); + REQUIRE(((int)(img_plane[stride * row + 31])) == 255); + REQUIRE(((int)(img_plane[stride * row + 32])) == 64); + REQUIRE(((int)(img_plane[stride * row + 63])) == 64); + REQUIRE(((int)(img_plane[stride * row + 64])) == 0); + REQUIRE(((int)(img_plane[stride * row + 95])) == 0); + REQUIRE(((int)(img_plane[stride * row + 96])) == 255); + REQUIRE(((int)(img_plane[stride * row + 127])) == 255); + } + for (int row = 48; row < 72; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 192); + REQUIRE(((int)(img_plane[stride * row + 31])) == 192); + REQUIRE(((int)(img_plane[stride * row + 32])) == 255); + REQUIRE(((int)(img_plane[stride * row + 63])) == 255); + REQUIRE(((int)(img_plane[stride * row + 64])) == 255); + REQUIRE(((int)(img_plane[stride * row + 95])) == 255); + REQUIRE(((int)(img_plane[stride * row + 96])) == 255); + REQUIRE(((int)(img_plane[stride * row + 127])) == 255); + } + + img_plane = heif_image_get_plane_readonly(img, heif_channel_G, &stride); + REQUIRE(stride == 128); + for (int row = 0; row < 24; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 0); + REQUIRE(((int)(img_plane[stride * row + 31])) == 0); + REQUIRE(((int)(img_plane[stride * row + 32])) == 255); + REQUIRE(((int)(img_plane[stride * row + 63])) == 255); + REQUIRE(((int)(img_plane[stride * row + 64])) == 0); + REQUIRE(((int)(img_plane[stride * row + 95])) == 0); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + for (int row = 24; row < 48; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 255); + REQUIRE(((int)(img_plane[stride * row + 31])) == 255); + REQUIRE(((int)(img_plane[stride * row + 32])) == 64); + REQUIRE(((int)(img_plane[stride * row + 63])) == 64); + REQUIRE(((int)(img_plane[stride * row + 64])) == 255); + REQUIRE(((int)(img_plane[stride * row + 95])) == 255); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + for (int row = 48; row < 72; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 192); + REQUIRE(((int)(img_plane[stride * row + 31])) == 192); + REQUIRE(((int)(img_plane[stride * row + 32])) == 255); + REQUIRE(((int)(img_plane[stride * row + 63])) == 255); + REQUIRE(((int)(img_plane[stride * row + 64])) == 175); + REQUIRE(((int)(img_plane[stride * row + 95])) == 175); + REQUIRE(((int)(img_plane[stride * row + 96])) == 200); + REQUIRE(((int)(img_plane[stride * row + 127])) == 200); + } + + img_plane = heif_image_get_plane_readonly(img, heif_channel_B, &stride); + REQUIRE(stride == 128); + for (int row = 0; row < 24; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 0); + REQUIRE(((int)(img_plane[stride * row + 31])) == 0); + REQUIRE(((int)(img_plane[stride * row + 32])) == 0); + REQUIRE(((int)(img_plane[stride * row + 63])) == 0); + REQUIRE(((int)(img_plane[stride * row + 64])) == 255); + REQUIRE(((int)(img_plane[stride * row + 95])) == 255); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + for (int row = 24; row < 48; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 255); + REQUIRE(((int)(img_plane[stride * row + 31])) == 255); + REQUIRE(((int)(img_plane[stride * row + 32])) == 64); + REQUIRE(((int)(img_plane[stride * row + 63])) == 64); + REQUIRE(((int)(img_plane[stride * row + 64])) == 255); + REQUIRE(((int)(img_plane[stride * row + 95])) == 255); + REQUIRE(((int)(img_plane[stride * row + 96])) == 255); + REQUIRE(((int)(img_plane[stride * row + 127])) == 255); + } + for (int row = 48; row < 72; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 192); + REQUIRE(((int)(img_plane[stride * row + 31])) == 192); + REQUIRE(((int)(img_plane[stride * row + 32])) == 0); + REQUIRE(((int)(img_plane[stride * row + 63])) == 0); + REQUIRE(((int)(img_plane[stride * row + 64])) == 175); + REQUIRE(((int)(img_plane[stride * row + 95])) == 175); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + + heif_image_release(img); + heif_image_handle_release(handle); +} + +TEST_CASE("check image content") { + auto file = GENERATE(FILES_GENERIC_COMPRESSED); + auto context = get_context_for_test_file(file); + INFO("file name: " << file); + check_image_content(context); + heif_context_free(context); +} diff --git a/tests/uncompressed_encode.cc b/tests/uncompressed_encode.cc index b3326dc4d49..c7d2e6e99b1 100644 --- a/tests/uncompressed_encode.cc +++ b/tests/uncompressed_encode.cc @@ -565,6 +565,8 @@ struct heif_image *createImage_RGBA_planar() return image; } +#include + static void do_encode(heif_image* input_image, const char* filename, bool check_decode, uint8_t prefer_uncC_short_form = 0) { REQUIRE(input_image != nullptr); @@ -607,6 +609,7 @@ static void do_encode(heif_image* input_image, const char* filename, bool check_ REQUIRE(height == heif_image_get_primary_height(input_image)); heif_image* decode_image; err = heif_decode_image(decode_image_handle, &decode_image, heif_colorspace_undefined, heif_chroma_undefined, NULL); + std::cout << "error message: " << err.message << std::endl; REQUIRE(err.code == heif_error_Ok); // REQUIRE(heif_image_has_channel(input_image, heif_channel_Y) == heif_image_has_channel(decode_image, heif_channel_Y)); REQUIRE(heif_image_has_channel(input_image, heif_channel_Cb) == heif_image_has_channel(decode_image, heif_channel_Cb));