From 4cc0ea89c69ade4a54dabb6d694fc7e1378eff3b Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Mon, 2 Sep 2024 20:47:02 +0200 Subject: [PATCH] implement tild item type with tilC configuration box --- libheif/box.cc | 5 + libheif/codecs/tild.cc | 395 ++++++++++++++++++++++------------------- libheif/codecs/tild.h | 62 +++++-- libheif/file.h | 1 + 4 files changed, 263 insertions(+), 200 deletions(-) diff --git a/libheif/box.cc b/libheif/box.cc index 121ba767b4..746e9cd720 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -29,6 +29,7 @@ #include "codecs/mask_image.h" #include "codecs/vvc.h" #include "codecs/avc.h" +#include "codecs/tild.h" #include #include @@ -673,6 +674,10 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) box = std::make_shared(); break; + case fourcc("tilC"): + box = std::make_shared(); + break; + case fourcc("mdat"): // avoid generating a 'Box_other' box = std::make_shared(); diff --git a/libheif/codecs/tild.cc b/libheif/codecs/tild.cc index 8adfbc9913..d24a3fff4b 100644 --- a/libheif/codecs/tild.cc +++ b/libheif/codecs/tild.cc @@ -37,58 +37,157 @@ static uint64_t readvec(const std::vector& data, size_t& ptr, int len) } -void TildHeader::set_parameters(const heif_tild_image_parameters& params) +uint64_t number_of_tiles(const heif_tild_image_parameters& params) { - m_parameters = params; + uint64_t nTiles = nTiles_h(params) * nTiles_v(params); - m_offsets.resize(number_of_tiles()); + for (int i = 0; i < params.number_of_extra_dimensions; i++) { + // We only support up to 8 extra dimensions + if (i == 8) { + break; + } - for (auto& tile : m_offsets) { - tile.offset = TILD_OFFSET_NOT_AVAILABLE; + nTiles *= params.extra_dimensions[i]; } + + return nTiles; } +uint64_t nTiles_h(const heif_tild_image_parameters& params) +{ + return (params.image_width + params.tile_width - 1) / params.tile_width; +} + -Error TildHeader::parse(const std::shared_ptr& file, heif_item_id tild_id) +uint64_t nTiles_v(const heif_tild_image_parameters& params) { - const Error eofError(heif_error_Invalid_input, - heif_suberror_Unspecified, - "Tild header data incomplete"); + return (params.image_height + params.tile_height - 1) / params.tile_height; +} - std::vector data; -#if 0 - const uint64_t APPROX_TILD_HEADER_SIZE = 1024; - uint64_t maxDataLen = file->request_iloc_data(tild_id, 0, APPROX_TILD_HEADER_SIZE); -#endif +bool dimensions_64bit(const heif_tild_image_parameters& params) +{ + return (params.image_width > 0xFFFF || params.image_height > 0xFFFF); +} - Error err; - err = file->append_data_from_iloc(tild_id, data, 0, 3); - if (err) { - return err; + +void Box_tilC::derive_box_version() +{ + set_version(1); + + uint8_t flags = 0; + + if (dimensions_64bit(m_parameters)) { + flags |= 0x20; } - assert(data.size() == 3); + switch (m_parameters.offset_field_length) { + case 32: + flags |= 0; + break; + case 40: + flags |= 0x01; + break; + case 48: + flags |= 0x02; + break; + case 64: + flags |= 0x03; + break; + default: + assert(false); // TODO: return error + } -#if 0 - if (data.size() < 2 + 1 + 2 * 4 + 2 * 4 + 4) { - return eofError; + switch (m_parameters.size_field_length) { + case 0: + flags |= 0; + break; + case 24: + flags |= 0x04; + break; + case 32: + flags |= 0x08; + break; + case 64: + flags |= 0x0c; + break; + default: + assert(false); // TODO: return error } -#endif - size_t idx = 0; - version = data[idx++]; - if (version != 1) { + // printf("> %d %d -> %d\n", m_parameters.offset_field_length, m_parameters.size_field_length, (int)flags); + + if (m_parameters.tiles_are_sequential) { + flags |= 0x10; + } + + set_flags(flags); +} + + +Error Box_tilC::write(StreamWriter& writer) const +{ + assert(m_parameters.version == 1); + + size_t box_start = reserve_box_header_space(writer); + + bool dimensions_are_64bit = dimensions_64bit(m_parameters); + + if (m_parameters.number_of_extra_dimensions > 8) { + assert(false); // currently not supported + } + + writer.write8(m_parameters.number_of_extra_dimensions); + + writer.write(dimensions_are_64bit ? 8 : 4, m_parameters.image_width); + writer.write(dimensions_are_64bit ? 8 : 4, m_parameters.image_height); + + for (int i = 0; i < m_parameters.number_of_extra_dimensions; i++) { + writer.write(dimensions_are_64bit ? 8 : 4, m_parameters.extra_dimensions[i]); + } + + writer.write32(m_parameters.tile_width); + writer.write32(m_parameters.tile_height); + writer.write32(m_parameters.compression_type_fourcc); + + prepend_header(writer, box_start); + + return Error::Ok; +} + + +std::string Box_tilC::dump(Indent& indent) const +{ + std::ostringstream sstr; + + sstr << BoxHeader::dump(indent); + + sstr << indent << "version: " << ((int) get_version()) << "\n" + << indent << "image size: " << m_parameters.image_width << "x" << m_parameters.image_height << "\n" + << indent << "tile size: " << m_parameters.tile_width << "x" << m_parameters.tile_height << "\n"; + + return sstr.str(); + +} + + +Error Box_tilC::parse(BitstreamRange& range) +{ + parse_full_box_header(range); + + if (get_version() != 1) { std::stringstream sstr; - sstr << "'tild' image version " << ((int) version) << " is not implemented yet"; + sstr << "'tild' image version " << ((int) get_version()) << " is not implemented yet"; return {heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()}; } - int flags = data[idx++]; + m_parameters.version = get_version(); + + uint32_t flags = get_flags(); switch (flags & 0x03) { case 0: @@ -123,14 +222,7 @@ Error TildHeader::parse(const std::shared_ptr& file, heif_item_id tild m_parameters.tiles_are_sequential = !!(flags % 0x10); bool dimensions_are_64bit = (flags & 0x20); - m_parameters.number_of_extra_dimensions = data[idx++]; - - size_t size_of_header_without_offsets = 3 + (2 + m_parameters.number_of_extra_dimensions) * (dimensions_are_64bit ? 8 : 4) + 3 * 4; - - err = file->append_data_from_iloc(tild_id, data, 3, size_of_header_without_offsets - 3); - if (err) { - return err; - } + m_parameters.number_of_extra_dimensions = range.read8(); #if 0 if (data.size() < idx + 2 * (dimensions_are_64bit ? 8 : 4)) { @@ -142,8 +234,8 @@ Error TildHeader::parse(const std::shared_ptr& file, heif_item_id tild } #endif - m_parameters.image_width = readvec(data, idx, dimensions_are_64bit ? 8 : 4); - m_parameters.image_height = readvec(data, idx, dimensions_are_64bit ? 8 : 4); + m_parameters.image_width = (dimensions_are_64bit ? range.read64() : range.read32()); + m_parameters.image_height = (dimensions_are_64bit ? range.read64() : range.read32()); if (m_parameters.image_width == 0 || m_parameters.image_height == 0) { return {heif_error_Invalid_input, @@ -152,7 +244,7 @@ Error TildHeader::parse(const std::shared_ptr& file, heif_item_id tild } for (int i = 0; i < m_parameters.number_of_extra_dimensions; i++) { - uint64_t size = readvec(data, idx, dimensions_are_64bit ? 8 : 4); + uint64_t size = (dimensions_are_64bit ? range.read64() : range.read32()); if (size == 0) { return {heif_error_Invalid_input, @@ -168,10 +260,9 @@ Error TildHeader::parse(const std::shared_ptr& file, heif_item_id tild } } - m_parameters.tile_width = static_cast(readvec(data, idx, 4)); - m_parameters.tile_height = static_cast(readvec(data, idx, 4)); - - m_parameters.compression_type_fourcc = static_cast(readvec(data, idx, 4)); + m_parameters.tile_width = range.read32(); + m_parameters.tile_height = range.read32(); + m_parameters.compression_type_fourcc = range.read32(); if (m_parameters.tile_width == 0 || m_parameters.tile_height == 0) { return {heif_error_Invalid_input, @@ -179,7 +270,31 @@ Error TildHeader::parse(const std::shared_ptr& file, heif_item_id tild "Tile with zero width or height."}; } - uint64_t nTiles = number_of_tiles(); + return range.get_error(); +} + + +void TildHeader::set_parameters(const heif_tild_image_parameters& params) +{ + m_parameters = params; + + m_offsets.resize(number_of_tiles(params)); + + for (auto& tile: m_offsets) { + tile.offset = TILD_OFFSET_NOT_AVAILABLE; + } +} + + +Error TildHeader::read_full_offset_table(const std::shared_ptr& file, heif_item_id tild_id) +{ + const Error eofError(heif_error_Invalid_input, + heif_suberror_Unspecified, + "Tild header data incomplete"); + + std::vector data; + + uint64_t nTiles = number_of_tiles(m_parameters); if (nTiles > MAX_TILD_TILES) { return {heif_error_Invalid_input, heif_suberror_Security_limit_exceeded, @@ -193,13 +308,14 @@ Error TildHeader::parse(const std::shared_ptr& file, heif_item_id tild size_t size_of_offset_table = nTiles * (m_parameters.offset_field_length + m_parameters.size_field_length) / 8; - err = file->append_data_from_iloc(tild_id, data, size_of_header_without_offsets, size_of_offset_table); + Error err = file->append_data_from_iloc(tild_id, data, 0, size_of_offset_table); if (err) { return err; } - for (uint64_t i=0; i& file, heif_item_id tild } -uint64_t TildHeader::number_of_tiles() const -{ - uint64_t nTiles_h = (m_parameters.image_width + m_parameters.tile_width - 1) / m_parameters.tile_width; - uint64_t nTiles_v = (m_parameters.image_height + m_parameters.tile_height - 1) / m_parameters.tile_height; - uint64_t nTiles = nTiles_h * nTiles_v; - - for (int i = 0; i < m_parameters.number_of_extra_dimensions; i++) { - // We only support up to 8 extra dimensions - if (i == 8) { - break; - } - - nTiles *= m_parameters.extra_dimensions[i]; - } - - return nTiles; -} - - -uint64_t TildHeader::nTiles_h() const -{ - return (m_parameters.image_width + m_parameters.tile_width - 1) / m_parameters.tile_width; -} - - -uint64_t TildHeader::nTiles_v() const -{ - return (m_parameters.image_height + m_parameters.tile_height - 1) / m_parameters.tile_height; -} - - size_t TildHeader::get_header_size() const { assert(m_header_size); @@ -253,7 +338,7 @@ size_t TildHeader::get_header_size() const void TildHeader::set_tild_tile_range(uint32_t tile_x, uint32_t tile_y, uint64_t offset, uint32_t size) { - uint64_t idx = tile_y * nTiles_h() + tile_x; + uint64_t idx = tile_y * nTiles_h(m_parameters) + tile_x; m_offsets[idx].offset = offset; m_offsets[idx].size = size; } @@ -270,94 +355,19 @@ void writevec(uint8_t* data, size_t& idx, I value, int len) } -std::vector TildHeader::write() +std::vector TildHeader::write_offset_table() { - assert(m_parameters.version == 1); - - uint8_t flags = 0; - bool dimensions_are_64bit = false; - - if (m_parameters.image_width > 0xFFFF || m_parameters.image_height > 0xFFFF) { - flags |= 0x20; - dimensions_are_64bit = true; - } - - switch (m_parameters.offset_field_length) { - case 32: - flags |= 0; - break; - case 40: - flags |= 0x01; - break; - case 48: - flags |= 0x02; - break; - case 64: - flags |= 0x03; - break; - default: - assert(false); // TODO: return error - } - - switch (m_parameters.size_field_length) { - case 0: - flags |= 0; - break; - case 24: - flags |= 0x04; - break; - case 32: - flags |= 0x08; - break; - case 64: - flags |= 0x0c; - break; - default: - assert(false); // TODO: return error - } - - printf("> %d %d -> %d\n", m_parameters.offset_field_length, m_parameters.size_field_length, (int)flags); - - if (m_parameters.tiles_are_sequential) { - flags |= 0x10; - } - - uint64_t nTiles = number_of_tiles(); + uint64_t nTiles = number_of_tiles(m_parameters); int offset_entry_size = (m_parameters.offset_field_length + m_parameters.size_field_length) / 8; + uint64_t size = nTiles * offset_entry_size; std::vector data; - uint64_t size = (2 + // version, flags - 1 + // number of extra dimensions - (dimensions_are_64bit ? 8 : 4) * (2 + m_parameters.number_of_extra_dimensions) + // image size - 2 * 4 + // tile size - 4 + // compression type - nTiles * offset_entry_size); // offsets - data.resize(size); - size_t idx = 0; - data[idx++] = version; - data[idx++] = flags; - if (m_parameters.number_of_extra_dimensions > 8) { - assert(false); // currently not supported - } - - data[idx++] = m_parameters.number_of_extra_dimensions; - - writevec(data.data(), idx, m_parameters.image_width, dimensions_are_64bit ? 8 : 4); - writevec(data.data(), idx, m_parameters.image_height, dimensions_are_64bit ? 8 : 4); - - for (int i = 0; i < m_parameters.number_of_extra_dimensions; i++) { - writevec(data.data(), idx, m_parameters.extra_dimensions[i], dimensions_are_64bit ? 8 : 4); - } - - writevec(data.data(), idx, m_parameters.tile_width, 4); - writevec(data.data(), idx, m_parameters.tile_height, 4); - - writevec(data.data(), idx, m_parameters.compression_type_fourcc, 4); + size_t idx = 0; - for (const auto& offset : m_offsets) { + for (const auto& offset: m_offsets) { writevec(data.data(), idx, offset.offset, m_parameters.offset_field_length / 8); if (m_parameters.size_field_length != 0) { @@ -377,14 +387,11 @@ std::string TildHeader::dump() const { std::stringstream sstr; - sstr << "version: " << ((int) m_parameters.version) << "\n" - << "image size: " << m_parameters.image_width << "x" << m_parameters.image_height << "\n" - << "tile size: " << m_parameters.tile_width << "x" << m_parameters.tile_height << "\n" - << "offsets: "; + sstr << "offsets: "; // TODO - for (const auto& offset : m_offsets) { + for (const auto& offset: m_offsets) { sstr << offset.offset << ", size: " << offset.size << "\n"; } @@ -393,13 +400,13 @@ std::string TildHeader::dump() const ImageItem_Tild::ImageItem_Tild(HeifContext* ctx) - : ImageItem(ctx) + : ImageItem(ctx) { } ImageItem_Tild::ImageItem_Tild(HeifContext* ctx, heif_item_id id) - : ImageItem(ctx, id) + : ImageItem(ctx, id) { } @@ -415,7 +422,16 @@ Error ImageItem_Tild::on_load_file() Error err; auto heif_file = get_context()->get_heif_file(); - err = m_tild_header.parse(heif_file, get_id()); + auto tilC_box = heif_file->get_property(get_id()); + if (!tilC_box) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Tiled image without 'tilC' property box."}; + } + + m_tild_header.set_parameters(tilC_box->get_parameters()); + + err = m_tild_header.read_full_offset_table(heif_file, get_id()); if (err) { return err; } @@ -424,15 +440,9 @@ Error ImageItem_Tild::on_load_file() } -Result> ImageItem_Tild::add_new_tild_item(HeifContext* ctx, const heif_tild_image_parameters* parameters) +Result> +ImageItem_Tild::add_new_tild_item(HeifContext* ctx, const heif_tild_image_parameters* parameters) { - // Create header - - TildHeader tild_header; - tild_header.set_parameters(*parameters); - - std::vector header_data = tild_header.write(); - // Create 'tild' Item auto file = ctx->get_heif_file(); @@ -441,6 +451,19 @@ Result> ImageItem_Tild::add_new_tild_item(HeifCo auto tild_image = std::make_shared(ctx, tild_id); ctx->insert_new_image(tild_id, tild_image); + // Create tilC box + + auto tilC_box = std::make_shared(); + tilC_box->set_parameters(*parameters); + ctx->get_heif_file()->add_property(tild_id, tilC_box, true); + + // Create header + offset table + + TildHeader tild_header; + tild_header.set_parameters(*parameters); + + std::vector header_data = tild_header.write_offset_table(); + const int construction_method = 0; // 0=mdat 1=idat file->append_iloc_data(tild_id, header_data, construction_method); @@ -480,26 +503,30 @@ void ImageItem_Tild::process_before_write() const int construction_method = 0; // 0=mdat 1=idat - std::vector header_data = m_tild_header.write(); + std::vector header_data = m_tild_header.write_offset_table(); get_file()->replace_iloc_data(get_id(), 0, header_data, construction_method); } -Result> ImageItem_Tild::decode_compressed_image(const struct heif_decoding_options& options, - bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const +Result> +ImageItem_Tild::decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const { if (decode_tile_only) { return decode_grid_tile(options, tile_x0, tile_y0); } else { - return Error{heif_error_Unsupported_feature, heif_suberror_Unspecified, "'tild' images can only be access per tile"}; + return Error{heif_error_Unsupported_feature, heif_suberror_Unspecified, + "'tild' images can only be access per tile"}; } } -Result> ImageItem_Tild::decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty) const +Result> +ImageItem_Tild::decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty) const { - heif_compression_format format = compression_format_from_fourcc_infe_type(m_tild_header.get_parameters().compression_type_fourcc); + heif_compression_format format = compression_format_from_fourcc_infe_type( + m_tild_header.get_parameters().compression_type_fourcc); // --- get compressed data @@ -512,7 +539,7 @@ Result> ImageItem_Tild::decode_grid_tile(const h // --- decode - uint32_t idx = (uint32_t)(ty * m_tild_header.nTiles_h() + tx); + uint32_t idx = (uint32_t) (ty * nTiles_h(m_tild_header.get_parameters()) + tx); uint64_t offset = m_tild_header.get_tile_offset(idx); uint64_t size = m_tild_header.get_tile_size(idx); @@ -528,8 +555,8 @@ heif_image_tiling ImageItem_Tild::get_heif_image_tiling() const { heif_image_tiling tiling{}; - tiling.num_columns = m_tild_header.nTiles_h(); - tiling.num_rows = m_tild_header.nTiles_v(); + tiling.num_columns = nTiles_h(m_tild_header.get_parameters()); + tiling.num_rows = nTiles_v(m_tild_header.get_parameters()); tiling.tile_width = m_tild_header.get_parameters().tile_width; tiling.tile_height = m_tild_header.get_parameters().tile_height; diff --git a/libheif/codecs/tild.h b/libheif/codecs/tild.h index 73d1a84886..20fb294873 100644 --- a/libheif/codecs/tild.h +++ b/libheif/codecs/tild.h @@ -23,11 +23,54 @@ #include +#include "box.h" #include #include #include +uint64_t number_of_tiles(const heif_tild_image_parameters& params); + +uint64_t nTiles_h(const heif_tild_image_parameters& params); + +uint64_t nTiles_v(const heif_tild_image_parameters& params); + + +class Box_tilC : public FullBox +{ + /* + * Flags: + * bit 0-1 - number of bits for offsets (0: 32, 1: 40, 2: 48, 3: 64) + * bit 2-3 - number of bits for tile size (0: 0, 1: 24; 2: 32, 3: 64) + * bit 4 - sequential ordering hint + * bit 5 - use 64 bit dimensions (currently unused because ispe is limited to 32 bit) + */ +public: + Box_tilC() + { + set_short_type(fourcc("tilC")); + } + + bool is_essential() const override { return true; } + + void derive_box_version() override; + + void set_parameters(const heif_tild_image_parameters& params) { m_parameters = params; } + + const heif_tild_image_parameters& get_parameters() const { return m_parameters; } + + Error write(StreamWriter& writer) const override; + + std::string dump(Indent&) const override; + +protected: + Error parse(BitstreamRange& range) override; + +private: + heif_tild_image_parameters m_parameters; +}; + + #define TILD_OFFSET_NOT_AVAILABLE 0 #define TILD_OFFSET_SEE_LOWER_RESOLUTION_LAYER 1 #define TILD_OFFSET_NOT_LOADED 10 @@ -39,18 +82,12 @@ class TildHeader const heif_tild_image_parameters& get_parameters() const { return m_parameters; } - Error parse(const std::shared_ptr& file, heif_item_id tild_id); + Error read_full_offset_table(const std::shared_ptr& file, heif_item_id tild_id); - std::vector write(); + std::vector write_offset_table(); std::string dump() const; - uint64_t number_of_tiles() const; - - uint64_t nTiles_h() const; - - uint64_t nTiles_v() const; - void set_tild_tile_range(uint32_t tile_x, uint32_t tile_y, uint64_t offset, uint32_t size); size_t get_header_size() const; @@ -62,13 +99,6 @@ class TildHeader private: uint8_t version = 1; - /* - * Flags: - * bit 0-1 - number of bits for offsets (0: 32, 1: 40, 2: 48, 3: 64) - * bit 2-3 - number of bits for tile size (0: 0, 1: 24; 2: 32, 3: 64) - * bit 4 - sequential ordering hint - * bit 5 - use 64 bit dimensions (currently unused because ispe is limited to 32 bit) - */ heif_tild_image_parameters m_parameters; struct TileOffset { @@ -91,7 +121,7 @@ class ImageItem_Tild : public ImageItem ImageItem_Tild(HeifContext* ctx); - const char* get_infe_type() const override { return "tild"; } + const char* get_infe_type() const override { return "tili"; } // const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } diff --git a/libheif/file.h b/libheif/file.h index b822b3184c..d05b8b4a18 100644 --- a/libheif/file.h +++ b/libheif/file.h @@ -161,6 +161,7 @@ class HeifFile void add_pixi_property(heif_item_id id, uint8_t c1, uint8_t c2 = 0, uint8_t c3 = 0); + // TODO: can be remove the 'essential' parameter and take this from the box? Or is that depending on the context? heif_property_id add_property(heif_item_id id, const std::shared_ptr& property, bool essential); Result add_infe(const char* item_type, const uint8_t* data, size_t size);