From 4df3995050a1f0506e34eca0c3bc30a59ce65380 Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Tue, 16 Jan 2024 11:38:04 +0100 Subject: [PATCH 1/5] cmake: support coverage analysis --- CMakeLists.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c911c2dc0..eb71b9e6d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -393,6 +393,24 @@ endif (DOXYGEN_FOUND) # --- Testing +option(ENABLE_COVERAGE "" OFF) +if(ENABLE_COVERAGE) + # set compiler flags + set(CMAKE_CXX_FLAGS "-O0 -coverage") + + # find required tools + find_program(LCOV lcov REQUIRED) + find_program(GENHTML genhtml REQUIRED) + + # add coverage target + add_custom_target(coverage + # gather data + COMMAND ${LCOV} --directory . --capture --output-file coverage.info + # generate report + COMMAND ${GENHTML} --demangle-cpp -o coverage coverage.info + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +endif() + option(BUILD_TESTING "" ON) include(CTest) if(BUILD_TESTING) From b8c32c28d7b64760e68751217b4732b0dcb66bb3 Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Tue, 16 Jan 2024 11:38:50 +0100 Subject: [PATCH 2/5] jpeg2000: enhance parsing of JPEG 2000 codestream main header (PR#1110) --- go/heif/heif.go | 2 + libheif/context.cc | 8 +- libheif/error.cc | 2 + libheif/file.cc | 17 +- libheif/heif.h | 3 + libheif/heif_emscripten.h | 1 + libheif/jpeg2000.cc | 202 +++++++++----- libheif/jpeg2000.h | 181 ++++++++++++- tests/jpeg2000.cc | 536 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 865 insertions(+), 87 deletions(-) diff --git a/go/heif/heif.go b/go/heif/heif.go index 0c01f362f0..2015ffa8f4 100644 --- a/go/heif/heif.go +++ b/go/heif/heif.go @@ -308,6 +308,8 @@ const ( SuberrorUnknownNCLXMatrixCoefficients = C.heif_suberror_Unknown_NCLX_matrix_coefficients + SuberrorInvalidJPEG2000Codestream = C.heif_suberror_Invalid_J2K_codestream + // --- Unsupported_feature --- // Image was coded with an unsupported compression method. diff --git a/libheif/context.cc b/libheif/context.cc index 8ab7fa221f..fdbe07d0ec 100644 --- a/libheif/context.cc +++ b/libheif/context.cc @@ -1205,8 +1205,12 @@ Error HeifContext::Image::get_preferred_decoding_colorspace(heif_colorspace* out *out_chroma = (heif_chroma)(av1C->get_configuration().get_heif_chroma()); } else if (auto j2kH = m_heif_context->m_heif_file->get_property(id)) { - JPEG2000_SIZ_segment siz = jpeg2000_get_SIZ_segment(*m_heif_context->m_heif_file, id); - *out_chroma = siz.get_chroma_format(); + JPEG2000MainHeader jpeg2000Header; + err = jpeg2000Header.parseHeader(*m_heif_context->m_heif_file, id); + if (err) { + return err; + } + *out_chroma = jpeg2000Header.get_chroma_format(); } return err; diff --git a/libheif/error.cc b/libheif/error.cc index b7e654151e..db8d6eaaa0 100644 --- a/libheif/error.cc +++ b/libheif/error.cc @@ -158,6 +158,8 @@ const char* Error::get_error_string(heif_suberror_code err) return "Unknown NCLX matrix coefficients"; case heif_suberror_Invalid_region_data: return "Invalid region item data"; + case heif_suberror_Invalid_J2K_codestream: + return "Invalid JPEG 2000 codestream"; // --- Memory_allocation_error --- diff --git a/libheif/file.cc b/libheif/file.cc index 07a727322f..a65c7df5ef 100644 --- a/libheif/file.cc +++ b/libheif/file.cc @@ -594,12 +594,12 @@ int HeifFile::get_luma_bits_per_pixel_from_configuration(heif_item_id imageID) c // JPEG 2000 if (image_type == "j2k1") { - auto siz = jpeg2000_get_SIZ_segment(*this, imageID); - if (siz.components.empty()) { + JPEG2000MainHeader header; + Error err = header.parseHeader(*this, imageID); + if (err) { return -1; } - - return siz.components[0].precision; + return header.get_precision(0); } #if WITH_UNCOMPRESSED_CODEC @@ -657,13 +657,12 @@ int HeifFile::get_chroma_bits_per_pixel_from_configuration(heif_item_id imageID) // JPEG 2000 if (image_type == "j2k1") { - auto siz = jpeg2000_get_SIZ_segment(*this, imageID); - if (siz.components.size() <= 1) { + JPEG2000MainHeader header; + Error err = header.parseHeader(*this, imageID); + if (err) { return -1; } - - // TODO: this is a quick hack. It is more complicated for JPEG2000 because these can be any kind of colorspace (e.g. RGB). - return siz.components[1].precision; + return header.get_precision(1); } return -1; diff --git a/libheif/heif.h b/libheif/heif.h index bc78458247..3e79cf60d1 100644 --- a/libheif/heif.h +++ b/libheif/heif.h @@ -229,6 +229,9 @@ enum heif_suberror_code // Invalid specification of region item heif_suberror_Invalid_region_data = 136, + // Invalid JPEG 2000 codestream - usually a missing marker + heif_suberror_Invalid_J2K_codestream = 140, + // --- Memory_allocation_error --- diff --git a/libheif/heif_emscripten.h b/libheif/heif_emscripten.h index 395e1a7566..52e85a6373 100644 --- a/libheif/heif_emscripten.h +++ b/libheif/heif_emscripten.h @@ -353,6 +353,7 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_Item_reference_cycle", heif_suberror_Item_reference_cycle) .value("heif_suberror_Invalid_pixi_box", heif_suberror_Invalid_pixi_box) .value("heif_suberror_Invalid_region_data", heif_suberror_Invalid_region_data) + .value("heif_suberror_Invalid_J2K_codestream", heif_suberror_Invalid_J2K_codestream) .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) diff --git a/libheif/jpeg2000.cc b/libheif/jpeg2000.cc index dc96c2cc16..a916f352ee 100644 --- a/libheif/jpeg2000.cc +++ b/libheif/jpeg2000.cc @@ -20,8 +20,14 @@ #include "jpeg2000.h" #include +#include #include +static const uint16_t JPEG2000_CAP_MARKER = 0xFF50; +static const uint16_t JPEG2000_SIZ_MARKER = 0xFF51; +static const uint16_t JPEG2000_SOC_MARKER = 0xFF4F; + + Error Box_cdef::parse(BitstreamRange& range) { int channel_count = range.read16(); @@ -305,98 +311,170 @@ std::string Box_j2kH::dump(Indent& indent) const } -int read16(const std::vector& data, int offset) +Error JPEG2000MainHeader::parseHeader(const HeifFile& file, const heif_item_id imageID) { - return (data[offset] << 8) | data[offset + 1]; + Error err = file.get_compressed_image_data(imageID, &headerData); + if (err) { + return err; + } + return doParse(); } - -int read32(const std::vector& data, int offset) +Error JPEG2000MainHeader::doParse() { - return (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]; + cursor = 0; + Error err = parse_SOC_segment(); + if (err) { + return err; + } + err = parse_SIZ_segment(); + if (err) { + return err; + } + if (cursor < headerData.size() - MARKER_LEN) { + uint16_t marker = read16(); + if (marker == JPEG2000_CAP_MARKER) { + return parse_CAP_segment_body(); + } + return Error::Ok; + } + // we should have at least COD and QCD, so this is probably broken. + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream, + std::string("Missing required header marker(s)")); } - -JPEG2000_SIZ_segment jpeg2000_get_SIZ_segment(const HeifFile& file, heif_item_id imageID) +Error JPEG2000MainHeader::parse_SOC_segment() { - std::vector data; - Error err = file.get_compressed_image_data(imageID, &data); - if (err) { - return {}; + const size_t REQUIRED_BYTES = MARKER_LEN; + if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream); } + uint16_t marker = read16(); + if (marker == JPEG2000_SOC_MARKER) { + return Error::Ok; + } + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream, + std::string("Missing required SOC Marker")); +} - for (size_t i = 0; i + 1 < data.size(); i++) { - if (data[i] == 0xFF && data[i + 1] & 0x51) { - - JPEG2000_SIZ_segment siz; - - // space for full header and one component - if (i + 2 + 40 + 3 > data.size()) { - return {}; - } - - // read number of components - - int nComponents = read16(data, 40); +Error JPEG2000MainHeader::parse_SIZ_segment() +{ + size_t REQUIRED_BYTES = MARKER_LEN + 38 + 3 * 1; + if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream); + } - if (i + 2 + 40 + nComponents * 3 > data.size()) { - return {}; - } + uint16_t marker = read16(); + if (marker != JPEG2000_SIZ_MARKER) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream, + std::string("Missing required SIZ Marker")); + } + uint16_t lsiz = read16(); + if ((lsiz < 41) || (lsiz > 49190)) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream, + std::string("Out of range Lsiz value")); + } + siz.decoder_capabilities = read16(); + siz.reference_grid_width = read32(); + siz.reference_grid_height = read32(); + siz.image_horizontal_offset = read32(); + siz.image_vertical_offset = read32(); + siz.tile_width = read32(); + siz.tile_height = read32(); + siz.tile_offset_x = read32(); + siz.tile_offset_y = read32(); + u_int16_t csiz = read16(); + if ((csiz < 1) || (csiz > 16384)) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream, + std::string("Out of range Csiz value")); + } + if (cursor > headerData.size() - (3 * csiz)) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream); + } + // TODO: consider checking for Lsiz consistent with Csiz + for (uint16_t c = 0; c < csiz; c++) { + JPEG2000_SIZ_segment::component comp; + uint8_t ssiz = read8(); + comp.is_signed = (ssiz & 0x80); + comp.precision = uint8_t((ssiz & 0x7F) + 1); + comp.h_separation = read8(); + comp.v_separation = read8(); + siz.components.push_back(comp); + } + return Error::Ok; +} - siz.decoder_capabilities = read16(data, 6); - siz.width = read32(data, 8); - siz.height = read32(data, 12); - siz.x0 = read32(data, 16); - siz.y0 = read32(data, 20); - siz.tile_width = read32(data, 24); - siz.tile_height = read32(data, 28); - siz.tile_x0 = read32(data, 32); - siz.tile_y0 = read32(data, 36); - - for (int c = 0; c < nComponents; c++) { - JPEG2000_SIZ_segment::component comp; - comp.precision = data[42 + c * 3]; - comp.is_signed = (comp.precision & 0x80); - comp.precision = uint8_t((comp.precision & 0x7F) + 1); - comp.h_separation = data[43 + c * 3]; - comp.v_separation = data[44 + c * 3]; - siz.components.push_back(comp); +Error JPEG2000MainHeader::parse_CAP_segment_body() +{ + if (cursor > headerData.size() - 8) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream); + } + uint16_t lcap = read16(); + if ((lcap < 8) || (lcap > 70)) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_J2K_codestream, + std::string("Out of range Lcap value")); + } + uint32_t pcap = read32(); + for (uint8_t i = 2; i <= 32; i++) { + if (pcap & (1 << (32 - i))) { + switch (i) { + case JPEG2000_Extension_Capability_HT::IDENT: + parse_Ccap15(); + break; + default: + std::cout << "unhandled extended capabilities value: " << (int)i << std::endl; + read16(); } - - return siz; } } - - return {}; + return Error::Ok; } +void JPEG2000MainHeader::parse_Ccap15() +{ + uint16_t val = read16(); + JPEG2000_Extension_Capability_HT ccap; + // We could parse more here, but we don't need that yet. + ccap.setValue(val); + cap.push_back(ccap); +} -heif_chroma JPEG2000_SIZ_segment::get_chroma_format() const +heif_chroma JPEG2000MainHeader::get_chroma_format() const { - if (components.size() == 1) { + // Y-plane must be full resolution + if (siz.components[0].h_separation != 1 || siz.components[0].v_separation != 1) { + return heif_chroma_undefined; + } + + if (siz.components.size() == 1) { return heif_chroma_monochrome; } - else if (components.size() == 3) { + else if (siz.components.size() == 3) { // TODO: we should map channels through `cdef` ? - // Y-plane must be full resolution - if (components[0].h_separation != 1 || components[0].v_separation != 1) { - return heif_chroma_undefined; - } - // both chroma components must have the same sampling - if (components[1].h_separation != components[2].h_separation || - components[1].v_separation != components[2].v_separation) { + if (siz.components[1].h_separation != siz.components[2].h_separation || + siz.components[1].v_separation != siz.components[2].v_separation) { return heif_chroma_undefined; } - if (components[1].h_separation == 2 && components[1].v_separation==2) { + if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==2) { return heif_chroma_420; } - if (components[1].h_separation == 2 && components[1].v_separation==1) { + if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==1) { return heif_chroma_422; } - if (components[1].h_separation == 1 && components[1].v_separation==1) { + if (siz.components[1].h_separation == 1 && siz.components[1].v_separation==1) { return heif_chroma_444; } } diff --git a/libheif/jpeg2000.h b/libheif/jpeg2000.h index 8a65129f1b..e21992041a 100644 --- a/libheif/jpeg2000.h +++ b/libheif/jpeg2000.h @@ -340,26 +340,179 @@ class Jpeg2000ImageCodec struct JPEG2000_SIZ_segment { - int x0 = 0, y0 = 0; - int width = 0, height = 0; + /** + * Decoder capabilities bitmap (Rsiz). + */ + uint16_t decoder_capabilities = 0; + /** + * Width of the reference grid (Xsiz). + */ + uint32_t reference_grid_width = 0; + /** + * Height of the reference grid (Ysiz). + */ + uint32_t reference_grid_height = 0; + /** + * Horizontal offset from reference grid origin to image left side (XOsiz). + */ + uint32_t image_horizontal_offset = 0; + /** + * Vertical offset from reference grid origin to image top size (YOsiz). + */ + uint32_t image_vertical_offset = 0; + /** + * Width of one reference tile with respect to the reference grid (XTsiz). + */ + uint32_t tile_width = 0; + /** + * Height of one reference tile with respect to the reference grid (YTsiz). + */ + uint32_t tile_height = 0; + /** + * Horizontal offset from the origin of the reference grid to left side of first tile (XTOsiz). + */ + uint32_t tile_offset_x = 0; + /** + * Vertical offset from the origin of the reference grid to top side of first tile (YTOsiz). + */ + uint32_t tile_offset_y = 0; + + struct component + { + uint8_t h_separation, v_separation; + uint8_t precision; + bool is_signed; + }; + + std::vector components; +}; + +class JPEG2000_Extension_Capability { +public: + JPEG2000_Extension_Capability(const uint8_t i) : ident(i) {} - int tile_x0 = 0, tile_y0 = 0; - int tile_width = 0, tile_height = 0; + uint8_t getIdent() const { + return ident; + } - int decoder_capabilities = 0; + uint16_t getValue() const { + return value; + } - struct component - { - uint8_t h_separation, v_separation; - uint8_t precision; - bool is_signed; - }; + void setValue(uint16_t val) { + value = val; + } - std::vector components; +private: + const uint8_t ident; + uint16_t value; +}; + +class JPEG2000_Extension_Capability_HT : public JPEG2000_Extension_Capability +{ +public: + static const int IDENT = 15; + JPEG2000_Extension_Capability_HT(): JPEG2000_Extension_Capability(IDENT) + {}; +}; + +class JPEG2000_CAP_segment +{ +public: + void push_back(JPEG2000_Extension_Capability ccap) { + extensions.push_back(ccap); + } + bool hasHighThroughputExtension() const { + for (auto &extension : extensions) { + if (extension.getIdent() == 15) { + return true; + } + } + return false; + } +private: + std::vector extensions; - heif_chroma get_chroma_format() const; }; -JPEG2000_SIZ_segment jpeg2000_get_SIZ_segment(const HeifFile& file, heif_item_id imageID); +struct JPEG2000MainHeader +{ +public: + JPEG2000MainHeader() {} + + Error parseHeader(const HeifFile& file, const heif_item_id imageID); + + // Use parseHeader instead - these are mainly for unit testing + Error doParse(); + void setHeaderData(std::vector data) + { + headerData = data; + } + + heif_chroma get_chroma_format() const; + + int get_precision(uint32_t index) const + { + if (index >= siz.components.size()) { + return -1; + } + // TODO: this is a quick hack. It is more complicated for JPEG2000 because these can be any kind of colorspace (e.g. RGB). + return siz.components[index].precision; + } + + bool hasHighThroughputExtension() { + return cap.hasHighThroughputExtension(); + } + + uint32_t getXSize() const + { + return siz.reference_grid_width; + } + + uint32_t getYSize() const + { + return siz.reference_grid_height; + } + + const JPEG2000_SIZ_segment get_SIZ() + { + return siz; + } + +private: + Error parse_SOC_segment(); + Error parse_SIZ_segment(); + Error parse_CAP_segment_body(); + void parse_Ccap15(); + + static const int MARKER_LEN = 2; + + JPEG2000_SIZ_segment siz; + JPEG2000_CAP_segment cap; + + uint8_t read8() + { + uint8_t res = headerData[cursor]; + cursor += 1; + return res; + } + + uint16_t read16() + { + uint16_t res = (headerData[cursor] << 8) | headerData[cursor + 1]; + cursor += 2; + return res; + } + + uint32_t read32() + { + uint32_t res = (headerData[cursor] << 24) | (headerData[cursor + 1] << 16) | (headerData[cursor + 2] << 8) | headerData[cursor + 3]; + cursor += 4; + return res; + } + + std::vector headerData; + size_t cursor; +}; #endif // LIBHEIF_JPEG2000_H diff --git a/tests/jpeg2000.cc b/tests/jpeg2000.cc index d74b571648..d6a1155711 100644 --- a/tests/jpeg2000.cc +++ b/tests/jpeg2000.cc @@ -191,3 +191,539 @@ TEST_CASE( "j2kL" ) std::string dump_output = j2kL->dump(indent); REQUIRE(dump_output == "Box: j2kL -----\nsize: 0 (header size: 0)\nlayer_id: 1, discard_levels: 2, decode_layers: 3\n"); } + + +TEST_CASE( "codestream too short for SOC" ) +{ + std::vector data = {0xFF}; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + + +TEST_CASE( "codestream missing SOC" ) +{ + std::vector data = {0xFF, 0x4E}; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + +TEST_CASE( "codestream too short for SIZ body" ) +{ + std::vector data = {0xFF, 0x4F, 0xFF, 0x51}; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + +TEST_CASE( "codestream - COD + SIZ" ) +{ + // This data is a subset of the example in ISO/IEC 15444-1:2019 Section J.10.1 "Main header" + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().reference_grid_width == 1); + REQUIRE(uut.get_SIZ().reference_grid_height == 9); + REQUIRE(uut.get_SIZ().image_horizontal_offset == 0); + REQUIRE(uut.get_SIZ().image_vertical_offset == 0); + REQUIRE(uut.get_SIZ().tile_width == 1); + REQUIRE(uut.get_SIZ().tile_height == 9); + REQUIRE(uut.get_SIZ().tile_offset_x == 0); + REQUIRE(uut.get_SIZ().tile_offset_y == 0); + REQUIRE(uut.get_SIZ().components.size() == 1); + REQUIRE(uut.get_SIZ().components[0].precision == 8); + REQUIRE(uut.get_SIZ().components[0].is_signed == false); + REQUIRE(uut.get_SIZ().components[0].h_separation == 1); + REQUIRE(uut.get_SIZ().components[0].v_separation == 1); + REQUIRE(uut.get_chroma_format() == heif_chroma_monochrome); + REQUIRE(uut.get_precision(0) == 8); + REQUIRE(uut.hasHighThroughputExtension() == false); +} + +TEST_CASE( "codestream - COD + SIZ first plane subsampled" ) +{ + // This data is a subset of the example in ISO/IEC 15444-1:2019 Section J.10.1 "Main header" + // with modifications + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x02, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().reference_grid_width == 1); + REQUIRE(uut.get_SIZ().reference_grid_height == 9); + REQUIRE(uut.get_SIZ().image_horizontal_offset == 0); + REQUIRE(uut.get_SIZ().image_vertical_offset == 0); + REQUIRE(uut.get_SIZ().tile_width == 1); + REQUIRE(uut.get_SIZ().tile_height == 9); + REQUIRE(uut.get_SIZ().tile_offset_x == 0); + REQUIRE(uut.get_SIZ().tile_offset_y == 0); + REQUIRE(uut.get_SIZ().components.size() == 1); + REQUIRE(uut.get_SIZ().components[0].precision == 8); + REQUIRE(uut.get_SIZ().components[0].is_signed == false); + REQUIRE(uut.get_SIZ().components[0].h_separation == 1); + REQUIRE(uut.get_SIZ().components[0].v_separation == 2); + REQUIRE(uut.get_chroma_format() == heif_chroma_undefined); + REQUIRE(uut.get_precision(0) == 8); + REQUIRE(uut.hasHighThroughputExtension() == false); +} + +TEST_CASE( "codestream - COD + SIZ 4:4:4" ) +{ + // This data is a subset of the example in ISO/IEC 15444-1:2019 Section J.10.1 "Main header" + // with modifications + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x01, 0x01, 0x06, 0x01, 0x01, + 0x05, 0x01, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().reference_grid_width == 1); + REQUIRE(uut.get_SIZ().reference_grid_height == 9); + REQUIRE(uut.get_SIZ().image_horizontal_offset == 0); + REQUIRE(uut.get_SIZ().image_vertical_offset == 0); + REQUIRE(uut.get_SIZ().tile_width == 1); + REQUIRE(uut.get_SIZ().tile_height == 9); + REQUIRE(uut.get_SIZ().tile_offset_x == 0); + REQUIRE(uut.get_SIZ().tile_offset_y == 0); + REQUIRE(uut.get_SIZ().components.size() == 3); + REQUIRE(uut.get_SIZ().components[0].precision == 8); + REQUIRE(uut.get_SIZ().components[0].is_signed == false); + REQUIRE(uut.get_SIZ().components[0].h_separation == 1); + REQUIRE(uut.get_SIZ().components[0].v_separation == 1); + REQUIRE(uut.get_SIZ().components[1].precision == 7); + REQUIRE(uut.get_SIZ().components[1].is_signed == false); + REQUIRE(uut.get_SIZ().components[1].h_separation == 1); + REQUIRE(uut.get_SIZ().components[1].v_separation == 1); + REQUIRE(uut.get_SIZ().components[2].precision == 6); + REQUIRE(uut.get_SIZ().components[2].is_signed == false); + REQUIRE(uut.get_SIZ().components[2].h_separation == 1); + REQUIRE(uut.get_SIZ().components[2].v_separation == 1); + REQUIRE(uut.get_chroma_format() == heif_chroma_444); + REQUIRE(uut.get_precision(0) == 8); + REQUIRE(uut.get_precision(1) == 7); + REQUIRE(uut.get_precision(2) == 6); + REQUIRE(uut.hasHighThroughputExtension() == false); +} + +TEST_CASE( "codestream - COD + SIZ 4:2:2" ) +{ + // This data is a subset of the example in ISO/IEC 15444-1:2019 Section J.10.1 "Main header" + // with modifications + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x01, 0x01, 0x07, 0x02, 0x01, + 0x07, 0x02, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().reference_grid_width == 1); + REQUIRE(uut.get_SIZ().reference_grid_height == 9); + REQUIRE(uut.get_SIZ().image_horizontal_offset == 0); + REQUIRE(uut.get_SIZ().image_vertical_offset == 0); + REQUIRE(uut.get_SIZ().tile_width == 1); + REQUIRE(uut.get_SIZ().tile_height == 9); + REQUIRE(uut.get_SIZ().tile_offset_x == 0); + REQUIRE(uut.get_SIZ().tile_offset_y == 0); + REQUIRE(uut.get_SIZ().components.size() == 3); + REQUIRE(uut.get_SIZ().components[0].precision == 8); + REQUIRE(uut.get_SIZ().components[0].is_signed == false); + REQUIRE(uut.get_SIZ().components[0].h_separation == 1); + REQUIRE(uut.get_SIZ().components[0].v_separation == 1); + REQUIRE(uut.get_SIZ().components[1].precision == 8); + REQUIRE(uut.get_SIZ().components[1].is_signed == false); + REQUIRE(uut.get_SIZ().components[1].h_separation == 2); + REQUIRE(uut.get_SIZ().components[1].v_separation == 1); + REQUIRE(uut.get_SIZ().components[2].precision == 8); + REQUIRE(uut.get_SIZ().components[2].is_signed == false); + REQUIRE(uut.get_SIZ().components[2].h_separation == 2); + REQUIRE(uut.get_SIZ().components[2].v_separation == 1); + REQUIRE(uut.get_chroma_format() == heif_chroma_422); + REQUIRE(uut.get_precision(0) == 8); + REQUIRE(uut.get_precision(1) == 8); + REQUIRE(uut.get_precision(2) == 8); + REQUIRE(uut.hasHighThroughputExtension() == false); +} + +TEST_CASE( "codestream - COD + SIZ 4:2:0" ) +{ + // This data is a subset of the example in ISO/IEC 15444-1:2019 Section J.10.1 "Main header" + // with modifications + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x01, 0x01, 0x07, 0x02, 0x02, + 0x07, 0x02, 0x02, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().reference_grid_width == 1); + REQUIRE(uut.get_SIZ().reference_grid_height == 9); + REQUIRE(uut.get_SIZ().image_horizontal_offset == 0); + REQUIRE(uut.get_SIZ().image_vertical_offset == 0); + REQUIRE(uut.get_SIZ().tile_width == 1); + REQUIRE(uut.get_SIZ().tile_height == 9); + REQUIRE(uut.get_SIZ().tile_offset_x == 0); + REQUIRE(uut.get_SIZ().tile_offset_y == 0); + REQUIRE(uut.get_SIZ().components.size() == 3); + REQUIRE(uut.get_SIZ().components[0].precision == 8); + REQUIRE(uut.get_SIZ().components[0].is_signed == false); + REQUIRE(uut.get_SIZ().components[0].h_separation == 1); + REQUIRE(uut.get_SIZ().components[0].v_separation == 1); + REQUIRE(uut.get_SIZ().components[1].precision == 8); + REQUIRE(uut.get_SIZ().components[1].is_signed == false); + REQUIRE(uut.get_SIZ().components[1].h_separation == 2); + REQUIRE(uut.get_SIZ().components[1].v_separation == 2); + REQUIRE(uut.get_SIZ().components[2].precision == 8); + REQUIRE(uut.get_SIZ().components[2].is_signed == false); + REQUIRE(uut.get_SIZ().components[2].h_separation == 2); + REQUIRE(uut.get_SIZ().components[2].v_separation == 2); + REQUIRE(uut.get_chroma_format() == heif_chroma_420); + REQUIRE(uut.get_precision(0) == 8); + REQUIRE(uut.get_precision(1) == 8); + REQUIRE(uut.get_precision(2) == 8); + REQUIRE(uut.hasHighThroughputExtension() == false); +} + + +TEST_CASE( "codestream - COD + SIZ mismatched v subsampling" ) +{ + // This data is a subset of the example in ISO/IEC 15444-1:2019 Section J.10.1 "Main header" + // with modifications + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x01, 0x01, 0x07, 0x02, 0x02, + 0x07, 0x02, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().components.size() == 3); + REQUIRE(uut.get_SIZ().components[1].h_separation == 2); + REQUIRE(uut.get_SIZ().components[1].v_separation == 2); + REQUIRE(uut.get_SIZ().components[2].h_separation == 2); + REQUIRE(uut.get_SIZ().components[2].v_separation == 1); + REQUIRE(uut.get_chroma_format() == heif_chroma_undefined); +} + +TEST_CASE( "codestream - COD + SIZ mismatched h subsampling" ) +{ + // This data is a subset of the example in ISO/IEC 15444-1:2019 Section J.10.1 "Main header" + // with modifications + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x01, 0x01, 0x07, 0x01, 0x02, + 0x07, 0x02, 0x02, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().components.size() == 3); + REQUIRE(uut.get_SIZ().components[1].h_separation == 1); + REQUIRE(uut.get_SIZ().components[1].v_separation == 2); + REQUIRE(uut.get_SIZ().components[2].h_separation == 2); + REQUIRE(uut.get_SIZ().components[2].v_separation == 2); + REQUIRE(uut.get_chroma_format() == heif_chroma_undefined); +} + +TEST_CASE( "codestream - COD + SIZ unsupported subsampling" ) +{ + // This data is a subset of the example in ISO/IEC 15444-1:2019 Section J.10.1 "Main header" + // with modifications + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x01, 0x01, 0x07, 0x04, 0x01, + 0x07, 0x04, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().components.size() == 3); + REQUIRE(uut.get_SIZ().components[1].h_separation == 4); + REQUIRE(uut.get_SIZ().components[1].v_separation == 1); + REQUIRE(uut.get_SIZ().components[2].h_separation == 4); + REQUIRE(uut.get_SIZ().components[2].v_separation == 1); + REQUIRE(uut.get_chroma_format() == heif_chroma_undefined); +} + + +TEST_CASE( "codestream wrong marker SIZ" ) +{ + std::vector data = { + 0xFF, 0x4F, 0xFF, 0xEF, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + +TEST_CASE( "codestream Lsiz too small" ) +{ + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + +TEST_CASE( "codestream Lsiz too large" ) +{ + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0xC0, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + +TEST_CASE( "codestream Csiz too small" ) +{ + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x01, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + +TEST_CASE( "codestream Csiz too large" ) +{ + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x07, 0x01, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + + +TEST_CASE( "codestream bad Csiz" ) +{ + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x07, 0x01, 0x01, 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + + +TEST_CASE( "codestream missing segments" ) +{ + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01 + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + +TEST_CASE( "codestream - COD + SIZ + CAP" ) +{ + // This data is a modified version of subset of the example in ISO/IEC 15444-1:2019 Section + // J.10.1 "Main header" + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, + 0xFF, 0x50, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, + 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().reference_grid_width == 1); + REQUIRE(uut.get_SIZ().reference_grid_height == 9); + REQUIRE(uut.get_SIZ().image_horizontal_offset == 0); + REQUIRE(uut.get_SIZ().image_vertical_offset == 0); + REQUIRE(uut.get_SIZ().tile_width == 1); + REQUIRE(uut.get_SIZ().tile_height == 9); + REQUIRE(uut.get_SIZ().tile_offset_x == 0); + REQUIRE(uut.get_SIZ().tile_offset_y == 0); + REQUIRE(uut.get_SIZ().components.size() == 1); + REQUIRE(uut.get_SIZ().components[0].precision == 8); + REQUIRE(uut.get_SIZ().components[0].is_signed == false); + REQUIRE(uut.get_SIZ().components[0].h_separation == 1); + REQUIRE(uut.get_SIZ().components[0].v_separation == 1); + REQUIRE(uut.get_chroma_format() == heif_chroma_monochrome); + REQUIRE(uut.get_precision(0) == 8); + REQUIRE(uut.hasHighThroughputExtension() == true); +} + +TEST_CASE( "codestream CAP short" ) +{ + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, + 0xFF, 0x50, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00 + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + +TEST_CASE( "codestream Lcap short" ) +{ +std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, + 0xFF, 0x50, 0x00, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, + 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + +TEST_CASE( "codestream Lcap long" ) +{ +std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, + 0xFF, 0x50, 0x00, 0x47, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, + 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Invalid_input); + REQUIRE(err.sub_error_code == heif_suberror_Invalid_J2K_codestream); +} + + +TEST_CASE( "codestream - COD + SIZ + CAP multiple" ) +{ + // This data is a modified version of subset of the example in ISO/IEC 15444-1:2019 Section + // J.10.1 "Main header" + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, + 0xFF, 0x50, 0x00, 0x0A, 0x00, 0x12, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x22, + 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().reference_grid_width == 1); + REQUIRE(uut.get_SIZ().reference_grid_height == 9); + REQUIRE(uut.get_SIZ().image_horizontal_offset == 0); + REQUIRE(uut.get_SIZ().image_vertical_offset == 0); + REQUIRE(uut.get_SIZ().tile_width == 1); + REQUIRE(uut.get_SIZ().tile_height == 9); + REQUIRE(uut.get_SIZ().tile_offset_x == 0); + REQUIRE(uut.get_SIZ().tile_offset_y == 0); + REQUIRE(uut.get_SIZ().components.size() == 1); + REQUIRE(uut.get_SIZ().components[0].precision == 8); + REQUIRE(uut.get_SIZ().components[0].is_signed == false); + REQUIRE(uut.get_SIZ().components[0].h_separation == 1); + REQUIRE(uut.get_SIZ().components[0].v_separation == 1); + REQUIRE(uut.get_chroma_format() == heif_chroma_monochrome); + REQUIRE(uut.get_precision(0) == 8); + REQUIRE(uut.hasHighThroughputExtension() == true); +} + + +TEST_CASE( "codestream - COD + SIZ + CAP other" ) +{ + // This data is a modified version of subset of the example in ISO/IEC 15444-1:2019 Section + // J.10.1 "Main header" + // Note that the ccap2 value may not be valid + std::vector data = { + 0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, + 0xFF, 0x50, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00, 0x00, 0x22, + 0xFF, 0x5C, 0x00, + }; + JPEG2000MainHeader uut; + uut.setHeaderData(data); + Error err = uut.doParse(); + REQUIRE(err.error_code == heif_error_Ok); + REQUIRE(uut.get_SIZ().reference_grid_width == 1); + REQUIRE(uut.get_SIZ().reference_grid_height == 9); + REQUIRE(uut.get_SIZ().image_horizontal_offset == 0); + REQUIRE(uut.get_SIZ().image_vertical_offset == 0); + REQUIRE(uut.get_SIZ().tile_width == 1); + REQUIRE(uut.get_SIZ().tile_height == 9); + REQUIRE(uut.get_SIZ().tile_offset_x == 0); + REQUIRE(uut.get_SIZ().tile_offset_y == 0); + REQUIRE(uut.get_SIZ().components.size() == 1); + REQUIRE(uut.get_SIZ().components[0].precision == 8); + REQUIRE(uut.get_SIZ().components[0].is_signed == false); + REQUIRE(uut.get_SIZ().components[0].h_separation == 1); + REQUIRE(uut.get_SIZ().components[0].v_separation == 1); + REQUIRE(uut.get_chroma_format() == heif_chroma_monochrome); + REQUIRE(uut.get_precision(0) == 8); + REQUIRE(uut.hasHighThroughputExtension() == false); +} From efcad14d3824d36424102e264f80b9dadb65aa4d Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Tue, 16 Jan 2024 11:41:26 +0100 Subject: [PATCH 3/5] fix uint16_t datatype (#1110) --- libheif/jpeg2000.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libheif/jpeg2000.cc b/libheif/jpeg2000.cc index a916f352ee..03dee77df7 100644 --- a/libheif/jpeg2000.cc +++ b/libheif/jpeg2000.cc @@ -389,7 +389,7 @@ Error JPEG2000MainHeader::parse_SIZ_segment() siz.tile_height = read32(); siz.tile_offset_x = read32(); siz.tile_offset_y = read32(); - u_int16_t csiz = read16(); + uint16_t csiz = read16(); if ((csiz < 1) || (csiz > 16384)) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream, From d9524118779e093667263480c4d50bb1c0e84719 Mon Sep 17 00:00:00 2001 From: Brad Hards Date: Tue, 16 Jan 2024 11:51:44 +0100 Subject: [PATCH 4/5] openjpeg: add support for setting lossless mode #1071 --- libheif/plugins/encoder_openjpeg.cc | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/libheif/plugins/encoder_openjpeg.cc b/libheif/plugins/encoder_openjpeg.cc index b8cdc4fc74..d69904d326 100644 --- a/libheif/plugins/encoder_openjpeg.cc +++ b/libheif/plugins/encoder_openjpeg.cc @@ -40,6 +40,7 @@ struct encoder_struct_opj { int quality = 70; heif_chroma chroma = heif_chroma_undefined; + opj_cparameters_t parameters; // --- output @@ -124,6 +125,7 @@ struct heif_error opj_new_encoder(void** encoder_out) *encoder_out = encoder; opj_set_default_parameters(encoder); + opj_set_default_encoder_parameters(&(encoder->parameters)); return heif_error_ok; } @@ -156,13 +158,17 @@ struct heif_error opj_get_parameter_quality(void* encoder_raw, int* quality) return heif_error_ok; } -struct heif_error opj_set_parameter_lossless(void* encoder, int lossless) +struct heif_error opj_set_parameter_lossless(void* encoder_raw, int lossless) { + auto* encoder = (struct encoder_struct_opj*) encoder_raw; + encoder->parameters.irreversible = lossless ? 0 : 1; return heif_error_ok; } -struct heif_error opj_get_parameter_lossless(void* encoder, int* lossless) +struct heif_error opj_get_parameter_lossless(void* encoder_raw, int* lossless) { + auto* encoder = (struct encoder_struct_opj*) encoder_raw; + *lossless = (encoder->parameters.irreversible == 0); return heif_error_ok; } @@ -358,12 +364,10 @@ static heif_error generate_codestream(opj_image_t* image, struct encoder_struct_ { heif_error error; OPJ_BOOL success; - opj_cparameters_t parameters; - opj_set_default_encoder_parameters(¶meters); - parameters.cp_disto_alloc = 1; - parameters.tcp_numlayers = 1; - parameters.tcp_rates[0] = (float)(1 + (100 - encoder->quality)/2); + encoder->parameters.cp_disto_alloc = 1; + encoder->parameters.tcp_numlayers = 1; + encoder->parameters.tcp_rates[0] = (float)(1 + (100 - encoder->quality)/2); #if 0 //Insert a human readable comment into the codestream @@ -382,7 +386,7 @@ static heif_error generate_codestream(opj_image_t* image, struct encoder_struct_ //OPJ_CODEC_JP2 - Generate the entire jp2 file (which contains a codestream) OPJ_CODEC_FORMAT codec_format = OPJ_CODEC_J2K; opj_codec_t* codec = opj_create_compress(codec_format); - success = opj_setup_encoder(codec, ¶meters, image); + success = opj_setup_encoder(codec, &(encoder->parameters), image); if (!success) { error = {heif_error_Encoding_error, heif_suberror_Unspecified, "Failed to setup OpenJPEG encoder"}; return error; From e4ddcc68acb1dab6e155546a1c85e6dfd379d537 Mon Sep 17 00:00:00 2001 From: Brad Hards Date: Tue, 16 Jan 2024 11:52:17 +0100 Subject: [PATCH 5/5] reorganize jpeg2000 tests #1071 --- tests/CMakeLists.txt | 6 +++ tests/test_utils.cc | 72 +++++++++++++++++++++++++++++++++++- tests/test_utils.h | 4 +- tests/uncompressed_encode.cc | 72 ------------------------------------ 4 files changed, 80 insertions(+), 74 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 50ea308956..62fcf79954 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,12 @@ endif() add_libheif_test(encode) add_libheif_test(region) +if (WITH_OpenJPEG_ENCODER) + add_libheif_test(encode_jpeg2000) +else() + message(INFO "Disabling JPEG 2000 encoder tests because no JPEG 2000 codec is enabled") +endif() + if (WITH_UNCOMPRESSED_CODEC) add_libheif_test(uncompressed_decode) add_libheif_test(uncompressed_decode_mono) diff --git a/tests/test_utils.cc b/tests/test_utils.cc index 04f045a9ac..08ac28e286 100644 --- a/tests/test_utils.cc +++ b/tests/test_utils.cc @@ -97,4 +97,74 @@ void fill_new_plane(heif_image* img, heif_channel channel, int w, int h) for (int y = 0; y < h; y++) { memset(p + y * stride, 128, w); } -} \ No newline at end of file +} + +struct heif_image * createImage_RGB_planar() +{ + struct heif_image *image; + struct heif_error err; + int w = 1024; + int h = 768; + err = heif_image_create(w, h, heif_colorspace_RGB, + heif_chroma_444, &image); + if (err.code) { + return nullptr; + } + + err = heif_image_add_plane(image, heif_channel_R, w, h, 8); + REQUIRE(err.code == heif_error_Ok); + err = heif_image_add_plane(image, heif_channel_G, w, h, 8); + REQUIRE(err.code == heif_error_Ok); + err = heif_image_add_plane(image, heif_channel_B, w, h, 8); + REQUIRE(err.code == heif_error_Ok); + + + int stride; + uint8_t *r = heif_image_get_plane(image, heif_channel_R, &stride); + uint8_t *g = heif_image_get_plane(image, heif_channel_G, &stride); + uint8_t *b = heif_image_get_plane(image, heif_channel_B, &stride); + + int y = 0; + for (; y < h / 2; y++) { + int x = 0; + for (; x < w / 3; x++) { + r[y * stride + x] = 1; + g[y * stride + x] = 255; + b[y * stride + x] = 2; + } + for (; x < 2 * w / 3; x++) { + r[y * stride + x] = 4; + g[y * stride + x] = 5; + b[y * stride + x] = 255; + } + for (; x < w; x++) { + r[y * stride + x] = 255; + g[y * stride + x] = 6; + b[y * stride + x] = 7; + } + } + for (; y < h; y++) { + int x = 0; + for (; x < w / 3; x++) { + r[y * stride + x]= 8; + g[y * stride + x] = 9; + b[y * stride + x] = 255; + } + for (; x < 2 * w / 3; x++) { + r[y * stride + x] = 253; + g[y * stride + x] = 10; + b[y * stride + x] = 11; + } + for (; x < w; x++) { + r[y * stride + x] = 13; + g[y * stride + x] = 252; + b[y * stride + x] = 12; + } + } + if (err.code) { + heif_image_release(image); + return nullptr; + } + + return image; +} diff --git a/tests/test_utils.h b/tests/test_utils.h index a99c1c6133..35353255ce 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -36,4 +36,6 @@ struct heif_image * get_primary_image(heif_image_handle * handle); struct heif_image * get_primary_image_mono(heif_image_handle * handle); struct heif_image * get_primary_image_ycbcr(heif_image_handle * handle, heif_chroma chroma); -void fill_new_plane(heif_image* img, heif_channel channel, int w, int h); \ No newline at end of file +void fill_new_plane(heif_image* img, heif_channel channel, int w, int h); + +struct heif_image * createImage_RGB_planar(); \ No newline at end of file diff --git a/tests/uncompressed_encode.cc b/tests/uncompressed_encode.cc index 14933a67d8..fbec7d2376 100644 --- a/tests/uncompressed_encode.cc +++ b/tests/uncompressed_encode.cc @@ -486,78 +486,6 @@ struct heif_image *createImage_RGBA_interleaved() return image; } - -struct heif_image *createImage_RGB_planar() -{ - struct heif_image *image; - struct heif_error err; - int w = 1024; - int h = 768; - err = heif_image_create(w, h, heif_colorspace_RGB, - heif_chroma_444, &image); - if (err.code) { - return nullptr; - } - - err = heif_image_add_plane(image, heif_channel_R, w, h, 8); - REQUIRE(err.code == heif_error_Ok); - err = heif_image_add_plane(image, heif_channel_G, w, h, 8); - REQUIRE(err.code == heif_error_Ok); - err = heif_image_add_plane(image, heif_channel_B, w, h, 8); - REQUIRE(err.code == heif_error_Ok); - - - int stride; - uint8_t *r = heif_image_get_plane(image, heif_channel_R, &stride); - uint8_t *g = heif_image_get_plane(image, heif_channel_G, &stride); - uint8_t *b = heif_image_get_plane(image, heif_channel_B, &stride); - - int y = 0; - for (; y < h / 2; y++) { - int x = 0; - for (; x < w / 3; x++) { - r[y * stride + x] = 1; - g[y * stride + x] = 255; - b[y * stride + x] = 2; - } - for (; x < 2 * w / 3; x++) { - r[y * stride + x] = 4; - g[y * stride + x] = 5; - b[y * stride + x] = 255; - } - for (; x < w; x++) { - r[y * stride + x] = 255; - g[y * stride + x] = 6; - b[y * stride + x] = 7; - } - } - for (; y < h; y++) { - int x = 0; - for (; x < w / 3; x++) { - r[y * stride + x]= 8; - g[y * stride + x] = 9; - b[y * stride + x] = 255; - } - for (; x < 2 * w / 3; x++) { - r[y * stride + x] = 253; - g[y * stride + x] = 10; - b[y * stride + x] = 11; - } - for (; x < w; x++) { - r[y * stride + x] = 13; - g[y * stride + x] = 252; - b[y * stride + x] = 12; - } - } - if (err.code) { - heif_image_release(image); - return nullptr; - } - - return image; -} - - struct heif_image *createImage_RGBA_planar() { struct heif_image *image;