libheif/context.cc (3,069 lines of code) (raw):

/* * HEIF codec. * Copyright (c) 2017 Dirk Farin <dirk.farin@gmail.com> * * 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 <http://www.gnu.org/licenses/>. */ #include "box.h" #include "error.h" #include "libheif/heif.h" #include "region.h" #include <cstdint> #include <cassert> #include <cstring> #include <algorithm> #include <iostream> #include <limits> #include <cmath> #include <deque> #if ENABLE_PARALLEL_TILE_DECODING #include <future> #endif #include "context.h" #include "file.h" #include "pixelimage.h" #include "libheif/api_structs.h" #include "security_limits.h" #include "compression.h" #include "color-conversion/colorconversion.h" #include "plugin_registry.h" #include "codecs/hevc.h" #include "codecs/vvc.h" #include "codecs/avif.h" #include "codecs/jpeg.h" #include "codecs/mask_image.h" #include "codecs/jpeg2000.h" #if WITH_UNCOMPRESSED_CODEC #include "codecs/uncompressed_image.h" #endif heif_encoder::heif_encoder(const struct heif_encoder_plugin* _plugin) : plugin(_plugin) { } heif_encoder::~heif_encoder() { release(); } void heif_encoder::release() { if (encoder) { plugin->free_encoder(encoder); encoder = nullptr; } } struct heif_error heif_encoder::alloc() { if (encoder == nullptr) { struct heif_error error = plugin->new_encoder(&encoder); // TODO: error handling return error; } struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess}; return err; } static int32_t readvec_signed(const std::vector<uint8_t>& data, int& ptr, int len) { const uint32_t high_bit = 0x80 << ((len - 1) * 8); uint32_t val = 0; while (len--) { val <<= 8; val |= data[ptr++]; } bool negative = (val & high_bit) != 0; val &= ~high_bit; if (negative) { return -(high_bit - val); } else { return val; } return val; } static uint32_t readvec(const std::vector<uint8_t>& data, int& ptr, int len) { uint32_t val = 0; while (len--) { val <<= 8; val |= data[ptr++]; } return val; } class ImageGrid { public: Error parse(const std::vector<uint8_t>& data); std::vector<uint8_t> write() const; std::string dump() const; uint32_t get_width() const { return m_output_width; } uint32_t get_height() const { return m_output_height; } uint16_t get_rows() const { assert(m_rows <= 256); return m_rows; } uint16_t get_columns() const { assert(m_columns <= 256); return m_columns; } void set_num_tiles(uint16_t columns, uint16_t rows) { m_rows = rows; m_columns = columns; } void set_output_size(uint32_t width, uint32_t height) { m_output_width = width; m_output_height = height; } private: uint16_t m_rows = 0; uint16_t m_columns = 0; uint32_t m_output_width = 0; uint32_t m_output_height = 0; }; Error ImageGrid::parse(const std::vector<uint8_t>& data) { if (data.size() < 8) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_grid_data, "Less than 8 bytes of data"); } uint8_t version = data[0]; if (version != 0) { std::stringstream sstr; sstr << "Grid image version " << ((int)version) << " is not supported"; return {heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()}; } uint8_t flags = data[1]; int field_size = ((flags & 1) ? 32 : 16); m_rows = static_cast<uint16_t>(data[2] + 1); m_columns = static_cast<uint16_t>(data[3] + 1); if (field_size == 32) { if (data.size() < 12) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_grid_data, "Grid image data incomplete"); } m_output_width = ((data[4] << 24) | (data[5] << 16) | (data[6] << 8) | (data[7])); m_output_height = ((data[8] << 24) | (data[9] << 16) | (data[10] << 8) | (data[11])); } else { m_output_width = ((data[4] << 8) | (data[5])); m_output_height = ((data[6] << 8) | (data[7])); } return Error::Ok; } std::vector<uint8_t> ImageGrid::write() const { int field_size; if (m_output_width > 0xFFFF || m_output_height > 0xFFFF) { field_size = 32; } else { field_size = 16; } std::vector<uint8_t> data(field_size == 16 ? 8 : 12); data[0] = 0; // version uint8_t flags = 0; if (field_size == 32) { flags |= 1; } data[1] = flags; data[2] = (uint8_t) (m_rows - 1); data[3] = (uint8_t) (m_columns - 1); if (field_size == 32) { data[4] = (uint8_t) ((m_output_width >> 24) & 0xFF); data[5] = (uint8_t) ((m_output_width >> 16) & 0xFF); data[6] = (uint8_t) ((m_output_width >> 8) & 0xFF); data[7] = (uint8_t) ((m_output_width) & 0xFF); data[8] = (uint8_t) ((m_output_height >> 24) & 0xFF); data[9] = (uint8_t) ((m_output_height >> 16) & 0xFF); data[10] = (uint8_t) ((m_output_height >> 8) & 0xFF); data[11] = (uint8_t) ((m_output_height) & 0xFF); } else { data[4] = (uint8_t) ((m_output_width >> 8) & 0xFF); data[5] = (uint8_t) ((m_output_width) & 0xFF); data[6] = (uint8_t) ((m_output_height >> 8) & 0xFF); data[7] = (uint8_t) ((m_output_height) & 0xFF); } return data; } std::string ImageGrid::dump() const { std::ostringstream sstr; sstr << "rows: " << m_rows << "\n" << "columns: " << m_columns << "\n" << "output width: " << m_output_width << "\n" << "output height: " << m_output_height << "\n"; return sstr.str(); } class ImageOverlay { public: Error parse(size_t num_images, const std::vector<uint8_t>& data); std::string dump() const; void get_background_color(uint16_t col[4]) const; uint32_t get_canvas_width() const { return m_width; } uint32_t get_canvas_height() const { return m_height; } size_t get_num_offsets() const { return m_offsets.size(); } void get_offset(size_t image_index, int32_t* x, int32_t* y) const; private: uint8_t m_version; uint8_t m_flags; uint16_t m_background_color[4]; uint32_t m_width; uint32_t m_height; struct Offset { int32_t x, y; }; std::vector<Offset> m_offsets; }; Error ImageOverlay::parse(size_t num_images, const std::vector<uint8_t>& data) { Error eofError(heif_error_Invalid_input, heif_suberror_Invalid_overlay_data, "Overlay image data incomplete"); if (data.size() < 2 + 4 * 2) { return eofError; } m_version = data[0]; if (m_version != 0) { std::stringstream sstr; sstr << "Overlay image data version " << ((int) m_version) << " is not implemented yet"; return {heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()}; } m_flags = data[1]; int field_len = ((m_flags & 1) ? 4 : 2); int ptr = 2; if (ptr + 4 * 2 + 2 * field_len + num_images * 2 * field_len > data.size()) { return eofError; } for (int i = 0; i < 4; i++) { uint16_t color = static_cast<uint16_t>(readvec(data, ptr, 2)); m_background_color[i] = color; } m_width = readvec(data, ptr, field_len); m_height = readvec(data, ptr, field_len); if (m_width==0 || m_height==0) { return {heif_error_Invalid_input, heif_suberror_Invalid_overlay_data, "Overlay image with zero width or height."}; } m_offsets.resize(num_images); for (size_t i = 0; i < num_images; i++) { m_offsets[i].x = readvec_signed(data, ptr, field_len); m_offsets[i].y = readvec_signed(data, ptr, field_len); } return Error::Ok; } std::string ImageOverlay::dump() const { std::stringstream sstr; sstr << "version: " << ((int) m_version) << "\n" << "flags: " << ((int) m_flags) << "\n" << "background color: " << m_background_color[0] << ";" << m_background_color[1] << ";" << m_background_color[2] << ";" << m_background_color[3] << "\n" << "canvas size: " << m_width << "x" << m_height << "\n" << "offsets: "; for (const Offset& offset : m_offsets) { sstr << offset.x << ";" << offset.y << " "; } sstr << "\n"; return sstr.str(); } void ImageOverlay::get_background_color(uint16_t col[4]) const { for (int i = 0; i < 4; i++) { col[i] = m_background_color[i]; } } void ImageOverlay::get_offset(size_t image_index, int32_t* x, int32_t* y) const { assert(image_index < m_offsets.size()); assert(x && y); *x = m_offsets[image_index].x; *y = m_offsets[image_index].y; } HeifContext::HeifContext() { m_maximum_image_size_limit = MAX_IMAGE_SIZE; reset_to_empty_heif(); } HeifContext::~HeifContext() { // Break circular references between Images (when a faulty input image has circular image references) for (auto& it : m_all_images) { std::shared_ptr<Image> image = it.second; image->clear(); } } Error HeifContext::read(const std::shared_ptr<StreamReader>& reader) { m_heif_file = std::make_shared<HeifFile>(); Error err = m_heif_file->read(reader); if (err) { return err; } if(m_heif_file->get_moov_flag()) { err = interpret_heif_file_for_moov(); } else { err = interpret_heif_file(); } return err; } int HeifContext::read_grid_params(heif_item_id ID) { std::string image_type = m_heif_file->get_item_type(ID); if (image_type == "grid") { return 1; } else { return 0; } } Error HeifContext::read_params(libheif_parameters* params) { //m_heif_file = std::make_shared<HeifFile>(); params->movie_flag = m_heif_file->get_moov_flag(); params->frame_count = get_top_level_images().size(); if(params->movie_flag) { std::shared_ptr<Box_mvhd> mvhd = m_heif_file->get_mvhd_box(); params->movie_duration = mvhd->get_duration(); } else { params->movie_duration = 0; } for(uint32_t i = 0; i < params->frame_count; ++i) { auto& image = m_top_level_images[i]; params->img_params[i].img_width = image->get_width(); params->img_params[i].img_height = image->get_height(); params->img_params[i].img_bitdepth = image->get_luma_bits_per_pixel(); if(image->get_alpha_channel()) params->img_params[i].alpha_flag = true; else params->img_params[i].alpha_flag = false; } return Error::Ok; } Error HeifContext::read_from_file(const char* input_filename) { m_heif_file = std::make_shared<HeifFile>(); Error err = m_heif_file->read_from_file(input_filename); if (err) { return err; } if(m_heif_file->get_moov_flag()) { err = interpret_heif_file_for_moov(); } else { err = interpret_heif_file(); } return err; } Error HeifContext::read_from_memory(const void* data, size_t size, bool copy) { m_heif_file = std::make_shared<HeifFile>(); Error err = m_heif_file->read_from_memory(data, size, copy); if (err) { return err; } if(m_heif_file->get_moov_flag()) { err = interpret_heif_file_for_moov(); } else { err = interpret_heif_file(); } return err; } void HeifContext::reset_to_empty_heif() { m_heif_file = std::make_shared<HeifFile>(); m_heif_file->new_empty_file(); m_all_images.clear(); m_top_level_images.clear(); m_primary_image.reset(); } void HeifContext::patch_stco_data(StreamWriter& writer) { m_heif_file->patch_stco_data(writer); } Error HeifContext::check_resolution(uint32_t width, uint32_t height) const { // --- check whether the image size is "too large" uint32_t max_width_height = static_cast<uint32_t>(std::numeric_limits<int>::max()); if ((width > max_width_height || height > max_width_height) || (height != 0 && width > m_maximum_image_size_limit / height)) { std::stringstream sstr; sstr << "Image size " << width << "x" << height << " exceeds the maximum image size " << m_maximum_image_size_limit << "\n"; return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, sstr.str()); } if (width==0 || height==0) { return Error(heif_error_Memory_allocation_error, heif_suberror_Invalid_image_size, "zero width or height"); } return Error::Ok; } std::shared_ptr<RegionItem> HeifContext::add_region_item(uint32_t reference_width, uint32_t reference_height) { std::shared_ptr<Box_infe> box = m_heif_file->add_new_infe_box("rgan"); box->set_hidden_item(true); auto regionItem = std::make_shared<RegionItem>(box->get_item_ID(), reference_width, reference_height); add_region_item(regionItem); return regionItem; } void HeifContext::add_region_referenced_mask_ref(heif_item_id region_item_id, heif_item_id mask_item_id) { m_heif_file->add_iref_reference(region_item_id, fourcc("mask"), {mask_item_id}); } void HeifContext::write(StreamWriter& writer) { // --- serialize regions for (auto& image : m_all_images) { for (auto region : image.second->get_region_item_ids()) { m_heif_file->add_iref_reference(region, fourcc("cdsc"), {image.first}); } } for (auto& region : m_region_items) { std::vector<uint8_t> data_array; Error err = region->encode(data_array); // TODO: err m_heif_file->append_iloc_data(region->item_id, data_array); } // --- write to file m_heif_file->write(writer); } std::string HeifContext::debug_dump_boxes() const { return m_heif_file->debug_dump_boxes(); } static bool item_type_is_image(const std::string& item_type, const std::string& content_type) { return (item_type == "hvc1" || item_type == "grid" || item_type == "iden" || item_type == "iovl" || item_type == "av01" || item_type == "unci" || item_type == "vvc1" || item_type == "jpeg" || (item_type == "mime" && content_type == "image/jpeg") || item_type == "j2k1" || item_type == "mski"); } void HeifContext::remove_top_level_image(const std::shared_ptr<Image>& image) { std::vector<std::shared_ptr<Image>> new_list; for (const auto& img : m_top_level_images) { if (img != image) { new_list.push_back(img); } } m_top_level_images = new_list; } Error HeifContext::interpret_heif_file_for_moov() { m_all_images.clear(); m_top_level_images.clear(); m_primary_image.reset(); //check how many samples in the chunk std::shared_ptr<Box_stsc> stsc = m_heif_file->get_stsc_box(); uint32_t chunk_count = stsc->get_entry_count(); std::vector<sampleofchunk> m_chunk_samples; for(uint32_t i = 0; i < chunk_count; ++i) { m_chunk_samples.push_back(stsc->get_chunk_samples(i)); } if (chunk_count!=1) { return Error(heif_error_Invalid_input, heif_suberror_Unsupported_stsc_chunk_unequa1, "'stsc' box more than one chunk"); } for(heif_item_id id = 1; id <= m_chunk_samples[0].m_samples_per_chunk; ++id ) { auto image = std::make_shared<Image>(this, id); m_all_images.insert(std::make_pair(id, image)); if (id == 1) { image->set_primary(true); m_primary_image = image; } m_top_level_images.push_back(image); } std::shared_ptr<Box_tkhd> tkhd = m_heif_file->get_tkhd_box(); uint32_t width = tkhd->get_width() >> 16; uint32_t height = tkhd->get_height() >> 16; for (auto& pair : m_all_images) { auto& image = pair.second; // --- check whether the image size is "too large" uint32_t max_width_height = static_cast<uint32_t>(std::numeric_limits<int>::max()); if ((width > max_width_height || height > max_width_height) || (height != 0 && width > m_maximum_image_size_limit / height)) { std::stringstream sstr; sstr << "Image size " << width << "x" << height << " exceeds the maximum image size " << m_maximum_image_size_limit << "\n"; return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, sstr.str()); } image->set_resolution(width, height); } std::shared_ptr<Box_hvcC> hvcC = m_heif_file->get_hvcC_box(); if(!hvcC) { return Error(heif_error_Invalid_input, heif_suberror_No_hvcC_box, "No hvcC box in hvc1 type sequence"); } return Error::Ok; } Error HeifContext::interpret_heif_file() { m_all_images.clear(); m_top_level_images.clear(); m_primary_image.reset(); // --- reference all non-hidden images std::vector<heif_item_id> image_IDs = m_heif_file->get_item_IDs(); for (heif_item_id id : image_IDs) { auto infe_box = m_heif_file->get_infe_box(id); if (!infe_box) { // TODO(farindk): Should we return an error instead of skipping the invalid id? continue; } if (item_type_is_image(infe_box->get_item_type(), infe_box->get_content_type())) { auto image = std::make_shared<Image>(this, id); m_all_images.insert(std::make_pair(id, image)); if (!infe_box->is_hidden_item()) { if (id == m_heif_file->get_primary_image_ID()) { image->set_primary(true); m_primary_image = image; } m_top_level_images.push_back(image); } } } if (!m_primary_image) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "'pitm' box references a non-existing image"); } // --- read through properties for each image and extract image resolutions for (auto& pair : m_all_images) { auto& image = pair.second; std::vector<std::shared_ptr<Box>> properties; Error err = m_heif_file->get_properties(pair.first, properties); if (err) { return err; } bool ispe_read = false; for (const auto& prop : properties) { auto ispe = std::dynamic_pointer_cast<Box_ispe>(prop); if (ispe) { uint32_t width = ispe->get_width(); uint32_t height = ispe->get_height(); uint32_t max_width_height = static_cast<uint32_t>(std::numeric_limits<int>::max()); if (width >= max_width_height || height >= max_width_height) { std::stringstream sstr; sstr << "Image size " << width << "x" << height << " exceeds the maximum image size " << m_maximum_image_size_limit << "\n"; return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, sstr.str()); } image->set_resolution(width, height); ispe_read = true; } } if (!ispe_read) { return Error(heif_error_Invalid_input, heif_suberror_No_ispe_property, "Image has no 'ispe' property"); } for (const auto& prop : properties) { auto colr = std::dynamic_pointer_cast<Box_colr>(prop); if (colr) { auto profile = colr->get_color_profile(); image->set_color_profile(profile); continue; } auto cmin = std::dynamic_pointer_cast<Box_cmin>(prop); if (cmin) { image->set_intrinsic_matrix(cmin->get_intrinsic_matrix()); } auto cmex = std::dynamic_pointer_cast<Box_cmex>(prop); if (cmex) { image->set_extrinsic_matrix(cmex->get_extrinsic_matrix()); } } for (const auto& prop : properties) { auto clap = std::dynamic_pointer_cast<Box_clap>(prop); if (clap) { image->set_resolution(clap->get_width_rounded(), clap->get_height_rounded()); if (image->has_intrinsic_matrix()) { image->get_intrinsic_matrix().apply_clap(clap.get(), image->get_width(), image->get_height()); } } auto imir = std::dynamic_pointer_cast<Box_imir>(prop); if (imir) { image->get_intrinsic_matrix().apply_imir(imir.get(), image->get_width(), image->get_height()); } auto irot = std::dynamic_pointer_cast<Box_irot>(prop); if (irot) { if (irot->get_rotation() == 90 || irot->get_rotation() == 270) { // swap width and height image->set_resolution(image->get_height(), image->get_width()); } // TODO: apply irot to camera extrinsic matrix } } } // --- remove auxiliary from top-level images and assign to their respective image auto iref_box = m_heif_file->get_iref_box(); if (iref_box) { // m_top_level_images.clear(); for (auto& pair : m_all_images) { auto& image = pair.second; std::vector<Box_iref::Reference> references = iref_box->get_references_from(image->get_id()); for (const Box_iref::Reference& ref : references) { uint32_t type = ref.header.get_short_type(); if (type == fourcc("thmb")) { // --- this is a thumbnail image, attach to the main image std::vector<heif_item_id> refs = ref.to_item_ID; for (heif_item_id ref: refs) { image->set_is_thumbnail(); auto master_iter = m_all_images.find(ref); if (master_iter == m_all_images.end()) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Thumbnail references a non-existing image"); } if (master_iter->second->is_thumbnail()) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Thumbnail references another thumbnail"); } if (image.get() == master_iter->second.get()) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Recursive thumbnail image detected"); } master_iter->second->add_thumbnail(image); } remove_top_level_image(image); } else if (type == fourcc("auxl")) { // --- this is an auxiliary image // check whether it is an alpha channel and attach to the main image if yes std::vector<std::shared_ptr<Box>> properties; Error err = m_heif_file->get_properties(image->get_id(), properties); if (err) { return err; } std::shared_ptr<Box_auxC> auxC_property; for (const auto& property : properties) { auto auxC = std::dynamic_pointer_cast<Box_auxC>(property); if (auxC) { auxC_property = auxC; } } if (!auxC_property) { std::stringstream sstr; sstr << "No auxC property for image " << image->get_id(); return Error(heif_error_Invalid_input, heif_suberror_Auxiliary_image_type_unspecified, sstr.str()); } std::vector<heif_item_id> refs = ref.to_item_ID; // alpha channel if (auxC_property->get_aux_type() == "urn:mpeg:avc:2015:auxid:1" || // HEIF (avc) auxC_property->get_aux_type() == "urn:mpeg:hevc:2015:auxid:1" || // HEIF (h265) auxC_property->get_aux_type() == "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha") { // MIAF for (heif_item_id ref: refs) { auto master_iter = m_all_images.find(ref); if (master_iter == m_all_images.end()) { if (!m_heif_file->has_item_with_id(ref)) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Non-existing alpha image referenced"); } continue; } auto master_img = master_iter->second; if (image.get() == master_img.get()) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Recursive alpha image detected"); } image->set_is_alpha_channel(); master_img->set_alpha_channel(image); } } // depth channel if (auxC_property->get_aux_type() == "urn:mpeg:hevc:2015:auxid:2" || // HEIF auxC_property->get_aux_type() == "urn:mpeg:mpegB:cicp:systems:auxiliary:depth") { // AVIF image->set_is_depth_channel(); for (heif_item_id ref: refs) { auto master_iter = m_all_images.find(ref); if (master_iter == m_all_images.end()) { if (!m_heif_file->has_item_with_id(ref)) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Non-existing depth image referenced"); } continue; } if (image.get() == master_iter->second.get()) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Recursive depth image detected"); } master_iter->second->set_depth_channel(image); auto subtypes = auxC_property->get_subtypes(); std::vector<std::shared_ptr<SEIMessage>> sei_messages; err = decode_hevc_aux_sei_messages(subtypes, sei_messages); for (auto& msg : sei_messages) { auto depth_msg = std::dynamic_pointer_cast<SEIMessage_depth_representation_info>(msg); if (depth_msg) { image->set_depth_representation_info(*depth_msg); } } } } // --- generic aux image image->set_is_aux_image(auxC_property->get_aux_type()); for (heif_item_id ref: refs) { auto master_iter = m_all_images.find(ref); if (master_iter == m_all_images.end()) { if (!m_heif_file->has_item_with_id(ref)) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Non-existing aux image referenced"); } continue; } if (image.get() == master_iter->second.get()) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Recursive aux image detected"); } master_iter->second->add_aux_image(image); remove_top_level_image(image); } } else { // 'image' is a normal image, keep it as a top-level image } } } } // --- check that HEVC images have an hvcC property for (auto& pair : m_all_images) { auto& image = pair.second; std::shared_ptr<Box_infe> infe = m_heif_file->get_infe_box(image->get_id()); if (infe->get_item_type() == "hvc1") { auto ipma = m_heif_file->get_ipma_box(); auto ipco = m_heif_file->get_ipco_box(); if (!ipco->get_property_for_item_ID(image->get_id(), ipma, fourcc("hvcC"))) { return Error(heif_error_Invalid_input, heif_suberror_No_hvcC_box, "No hvcC property in hvc1 type image"); } } if (infe->get_item_type() == "vvc1") { auto ipma = m_heif_file->get_ipma_box(); auto ipco = m_heif_file->get_ipco_box(); if (!ipco->get_property_for_item_ID(image->get_id(), ipma, fourcc("vvcC"))) { return Error(heif_error_Invalid_input, heif_suberror_No_vvcC_box, "No vvcC property in vvc1 type image"); } } } // --- assign color profile from grid tiles to main image when main image has no profile assigned for (auto& pair : m_all_images) { auto& image = pair.second; auto id = pair.first; auto infe_box = m_heif_file->get_infe_box(id); if (!infe_box) { continue; } if (!iref_box) { break; } if (infe_box->get_item_type() == "grid") { std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg")); if (image_references.empty()) { continue; // TODO: can this every happen? } auto tileId = image_references.front(); auto iter = m_all_images.find(tileId); if (iter == m_all_images.end()) { continue; // invalid grid entry } auto tile_img = iter->second; if (image->get_color_profile_icc() == nullptr && tile_img->get_color_profile_icc()) { image->set_color_profile(tile_img->get_color_profile_icc()); } if (image->get_color_profile_nclx() == nullptr && tile_img->get_color_profile_nclx()) { image->set_color_profile(tile_img->get_color_profile_nclx()); } } } // --- read metadata and assign to image for (heif_item_id id : image_IDs) { std::string item_type = m_heif_file->get_item_type(id); // 'rgan': skip region annotations, handled next // 'iden': iden images are no metadata if (item_type == "rgan" || item_type == "iden") { continue; } std::string content_type = m_heif_file->get_content_type(id); std::string item_uri_type = m_heif_file->get_item_uri_type(id); // we now assign all kinds of metadata to the image, not only 'Exif' and 'XMP' std::shared_ptr<ImageMetadata> metadata = std::make_shared<ImageMetadata>(); metadata->item_id = id; metadata->item_type = item_type; metadata->content_type = content_type; metadata->item_uri_type = item_uri_type; Error err = m_heif_file->get_compressed_image_data(id, &(metadata->m_data)); if (err) { if (item_type == "Exif" || item_type == "mime") { // these item types should have data return err; } else { // anything else is probably something that we don't understand yet continue; } } // --- assign metadata to the image if (iref_box) { std::vector<Box_iref::Reference> references = iref_box->get_references_from(id); for (const auto& ref : references) { if (ref.header.get_short_type() == fourcc("cdsc")) { std::vector<uint32_t> refs = ref.to_item_ID; for(uint32_t ref: refs) { uint32_t exif_image_id = ref; auto img_iter = m_all_images.find(exif_image_id); if (img_iter == m_all_images.end()) { if (!m_heif_file->has_item_with_id(exif_image_id)) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Metadata assigned to non-existing image"); } continue; } img_iter->second->add_metadata(metadata); } } else if (ref.header.get_short_type() == fourcc("prem")) { uint32_t color_image_id = ref.from_item_ID; auto img_iter = m_all_images.find(color_image_id); if (img_iter == m_all_images.end()) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "`prem` link assigned to non-existing image"); } img_iter->second->set_is_premultiplied_alpha(true);; } } } } // --- read region item and assign to image(s) for (heif_item_id id : image_IDs) { std::string item_type = m_heif_file->get_item_type(id); if (item_type == "rgan") { std::shared_ptr<RegionItem> region_item = std::make_shared<RegionItem>(); region_item->item_id = id; std::vector<uint8_t> region_data; Error err = m_heif_file->get_compressed_image_data(id, &(region_data)); if (err) { return err; } region_item->parse(region_data); if (iref_box) { std::vector<Box_iref::Reference> references = iref_box->get_references_from(id); for (const auto& ref : references) { if (ref.header.get_short_type() == fourcc("cdsc")) { std::vector<uint32_t> refs = ref.to_item_ID; for (uint32_t ref: refs) { uint32_t image_id = ref; auto img_iter = m_all_images.find(image_id); if (img_iter == m_all_images.end()) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "Region item assigned to non-existing image"); } img_iter->second->add_region_item_id(id); m_region_items.push_back(region_item); } } /* When the geometry 'mask' of a region is represented by a mask stored in * another image item the image item containing the mask shall be identified * by an item reference of type 'mask' from the region item to the image item * containing the mask. */ if (ref.header.get_short_type() == fourcc("mask")) { std::vector<uint32_t> refs = ref.to_item_ID; size_t mask_index = 0; for (int j = 0; j < region_item->get_number_of_regions(); j++) { if (region_item->get_regions()[j]->getRegionType() == heif_region_type_referenced_mask) { std::shared_ptr<RegionGeometry_ReferencedMask> mask_geometry = std::dynamic_pointer_cast<RegionGeometry_ReferencedMask>(region_item->get_regions()[j]); if (mask_index >= refs.size()) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "Region mask reference with non-existing mask image reference"); } uint32_t mask_image_id = refs[mask_index]; if (!is_image(mask_image_id)) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "Region mask referenced item is not an image"); } auto mask_image = m_all_images.find(mask_image_id)->second; mask_geometry->referenced_item = mask_image_id; if (mask_geometry->width == 0) { mask_geometry->width = mask_image->get_ispe_width(); } if (mask_geometry->height == 0) { mask_geometry->height = mask_image->get_ispe_height(); } mask_index += 1; remove_top_level_image(mask_image); } } } } } } } return Error::Ok; } void HeifContext::set_mvhd_data(uint64_t creation_time, uint64_t modification_time, uint32_t timescale, uint32_t duration, uint32_t next_track_ID) { std::shared_ptr<Box_mvhd> mvhd = m_heif_file->get_mvhd_box(); mvhd->set_mvhd_data(creation_time, modification_time, timescale, duration, next_track_ID); } void HeifContext::ctx_set_tkhd_data(uint64_t creat_time, uint64_t modified_time, uint32_t track_id, uint64_t duration, uint32_t img_src_width, uint32_t img_src_height) { std::shared_ptr<Box_tkhd> tkhd = m_heif_file->get_tkhd_box(); tkhd->set_tkhd_data(creat_time, modified_time, track_id, duration, img_src_width, img_src_height); } void HeifContext::ctx_set_mdhd_data(uint64_t creat_time, uint64_t modified_time, uint32_t timescale, uint64_t duration, uint16_t language) { std::shared_ptr<Box_mdhd> mdhd = m_heif_file->get_mdhd_box(); mdhd->set_mdhd_data(creat_time, modified_time, timescale, duration, language); } void HeifContext::ctx_set_hvc1_data(uint16_t img_src_width, uint16_t img_src_height, uint16_t frame_count) { std::shared_ptr<Box_hvc1> hvc1 = m_heif_file->get_hvc1_box(); hvc1->set_hvc1_data(img_src_width, img_src_height, frame_count); } void HeifContext::ctx_calc_stts_data() { std::shared_ptr<Box_stts> stts = m_heif_file->get_stts_box(); stts->calc_stts_data(); } void HeifContext::ctx_set_stss_data() { std::shared_ptr<Box_stss> stss = m_heif_file->get_stss_box(); stss->set_stss_data(); } void HeifContext::ctx_add_movie_box() { m_heif_file->add_movie_box(); } HeifContext::Image::Image(HeifContext* context, heif_item_id id) : m_heif_context(context), m_id(id) { memset(&m_depth_representation_info, 0, sizeof(m_depth_representation_info)); } HeifContext::Image::~Image() = default; bool HeifContext::is_image(heif_item_id ID) const { for (const auto& img : m_all_images) { if (img.first == ID) return true; } return false; } bool HeifContext::has_alpha(heif_item_id ID) const { assert(is_image(ID)); auto img = m_all_images.find(ID)->second; // --- has the image an auxiliary alpha image? if (img->get_alpha_channel() != nullptr) { return true; } // --- if the image is a 'grid', check if there is alpha in any of the tiles std::string image_type = m_heif_file->get_item_type(ID); if (image_type == "grid") { std::vector<uint8_t> grid_data; Error error = m_heif_file->get_compressed_image_data(ID, &grid_data); if (error) { return false; } ImageGrid grid; Error err = grid.parse(grid_data); if (err) { return false; } auto iref_box = m_heif_file->get_iref_box(); if (!iref_box) { return false; } std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg")); if ((int) image_references.size() != grid.get_rows() * grid.get_columns()) { return false; } // --- check that all image IDs are valid images for (heif_item_id tile_id : image_references) { if (!is_image(tile_id)) { return false; } } // --- check whether at least one tile has an alpha channel bool has_alpha = false; for (heif_item_id tile_id : image_references) { auto iter = m_all_images.find(tile_id); if (iter == m_all_images.end()) { return false; } const std::shared_ptr<Image> tileImg = iter->second; has_alpha |= tileImg->get_alpha_channel() != nullptr; } return has_alpha; } else { // TODO: what about overlays ? return false; } } Error HeifContext::get_id_of_non_virtual_child_image(heif_item_id id, heif_item_id& out) const { std::string image_type = m_heif_file->get_item_type(id); if (image_type == "grid" || image_type == "iden" || image_type == "iovl") { auto iref_box = m_heif_file->get_iref_box(); if (!iref_box) { return Error(heif_error_Invalid_input, heif_suberror_No_item_data, "Derived image does not reference any other image items"); } std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg")); // TODO: check whether this really can be recursive (e.g. overlay of grid images) if (image_references.empty() || image_references[0] == id) { return Error(heif_error_Invalid_input, heif_suberror_No_item_data, "Derived image does not reference any other image items"); } else { return get_id_of_non_virtual_child_image(image_references[0], out); } } else { out = id; return Error::Ok; } } int HeifContext::Image::get_ispe_width() const { auto ispe = m_heif_context->m_heif_file->get_property<Box_ispe>(m_id); if (!ispe) { return 0; } else { return ispe->get_width(); } } int HeifContext::Image::get_ispe_height() const { auto ispe = m_heif_context->m_heif_file->get_property<Box_ispe>(m_id); if (!ispe) { return 0; } else { return ispe->get_height(); } } Error HeifContext::Image::get_preferred_decoding_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const { heif_item_id id; Error err = m_heif_context->get_id_of_non_virtual_child_image(m_id, id); if (err) { return err; } auto pixi = m_heif_context->m_heif_file->get_property<Box_pixi>(id); if (pixi && pixi->get_num_channels() == 1) { *out_colorspace = heif_colorspace_monochrome; *out_chroma = heif_chroma_monochrome; return err; } auto nclx = get_color_profile_nclx(); if (nclx && nclx->get_matrix_coefficients() == 0) { *out_colorspace = heif_colorspace_RGB; *out_chroma = heif_chroma_444; return err; } // TODO: this should be codec specific. JPEG 2000, for example, can use RGB internally. *out_colorspace = heif_colorspace_YCbCr; *out_chroma = heif_chroma_undefined; if (auto hvcC = m_heif_context->m_heif_file->get_property<Box_hvcC>(id)) { *out_chroma = (heif_chroma)(hvcC->get_configuration().chroma_format); } else if (auto vvcC = m_heif_context->m_heif_file->get_property<Box_vvcC>(id)) { *out_chroma = (heif_chroma)(vvcC->get_configuration().chroma_format_idc); } else if (auto av1C = m_heif_context->m_heif_file->get_property<Box_av1C>(id)) { *out_chroma = (heif_chroma)(av1C->get_configuration().get_heif_chroma()); } else if (auto j2kH = m_heif_context->m_heif_file->get_property<Box_j2kH>(id)) { JPEG2000MainHeader jpeg2000Header; err = jpeg2000Header.parseHeader(*m_heif_context->m_heif_file, id); if (err) { return err; } *out_chroma = jpeg2000Header.get_chroma_format(); } return err; } int HeifContext::Image::get_luma_bits_per_pixel() const { if(m_heif_context->m_heif_file->get_moov_flag()) { return m_heif_context->m_heif_file->get_luma_bits_per_pixel_from_configuration(); } else { heif_item_id id; Error err = m_heif_context->get_id_of_non_virtual_child_image(m_id, id); if (err) { return -1; } // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) return m_heif_context->m_heif_file->get_luma_bits_per_pixel_from_configuration(id); } } int HeifContext::Image::get_chroma_bits_per_pixel() const { heif_item_id id; Error err = m_heif_context->get_id_of_non_virtual_child_image(m_id, id); if (err) { return -1; } // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) return m_heif_context->m_heif_file->get_chroma_bits_per_pixel_from_configuration(id); } Error HeifContext::decode_image_user(heif_item_id ID, std::shared_ptr<HeifPixelImage>& img, heif_colorspace out_colorspace, heif_chroma out_chroma, int32_t out_width, int32_t out_height, bool has_alpha, const struct heif_decoding_options& options) const { Error err; if(m_heif_file->get_moov_flag()) { err = decode_image_planar_for_moov(ID, img, heif_colorspace_undefined, heif_chroma_undefined, options, false); } else { err = decode_image_planar(ID, img, heif_colorspace_undefined, heif_chroma_undefined, options, has_alpha); } if (err) { return err; } // --- convert to output chroma format heif_colorspace target_colorspace = (out_colorspace == heif_colorspace_undefined ? img->get_colorspace() : out_colorspace); heif_chroma target_chroma = (out_chroma == heif_chroma_undefined ? img->get_chroma_format() : out_chroma); bool different_chroma = (target_chroma != img->get_chroma_format()); bool different_colorspace = (target_colorspace != img->get_colorspace()); int bpp = options.convert_hdr_to_8bit ? 8 : 0; // TODO: check BPP changed if (different_chroma || different_colorspace) { if(options.ext_dst && options.ext_dst_enable) { img->set_image_external_info(true, options.ext_dst, options.ext_dst_len, options.ext_dst_stride); // setting output resolution, for external buf img->set_image_resolution(out_width, out_height); } img = convert_colorspace(img, target_colorspace, target_chroma, nullptr, bpp, options.color_conversion_options); if (!img) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } } // check external buf if(options.ext_dst && options.ext_dst_enable) { if(!img->get_image_use_external_buf() && (img->get_colorspace() == out_colorspace) && (img->get_chroma_format() == out_chroma) && (out_chroma ==heif_chroma_interleaved_RGBA )){ auto outimg = std::make_shared<HeifPixelImage>(); int width = img->get_width(); int height = img->get_height(); outimg->create(width, height, out_colorspace, out_chroma); outimg->set_image_external_info(true, options.ext_dst, options.ext_dst_len, options.ext_dst_stride); outimg->add_shared_rgba_plane(heif_channel_interleaved, width, height, 8); const uint8_t* src_p; int src_p_stride = 0 ; src_p = (uint8_t*) img->get_plane(heif_channel_interleaved, &src_p_stride); uint8_t* out_p; int out_p_stride = 0; out_p = (uint8_t*) outimg->get_plane(heif_channel_interleaved, &out_p_stride); for(long long y = 0; y < height; y++) { for(long long x = 0; x < width; x++) { memcpy(out_p+y*out_p_stride, src_p+y*src_p_stride, 4*width); } } img = outimg; } } return Error::Ok; } Error HeifContext::decode_image_planar_for_moov(heif_item_id ID, std::shared_ptr<HeifPixelImage>& img, heif_colorspace out_colorspace, heif_chroma out_chroma, const struct heif_decoding_options& options, bool alphaImage) const { std::shared_ptr<Image> imginfo; if (m_all_images.find(ID) != m_all_images.end()) { imginfo = m_all_images.find(ID)->second; } assert(imginfo); Error error; if(m_heif_file->get_hvc1_box()) { heif_compression_format compression = heif_compression_undefined; compression = heif_compression_HEVC; const struct heif_decoder_plugin* decoder_plugin = get_decoder(compression, options.decoder_id); if (!decoder_plugin) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_codec); } std::vector<uint8_t> data; error = m_heif_file->get_compressed_image_data_for_moov(ID, &data); if (error) { return error; } void* decoder; struct heif_error err = decoder_plugin->new_decoder(&decoder, m_max_decoder_threads); if (err.code != heif_error_Ok) { return Error(err.code, err.subcode, err.message); } if (decoder_plugin->plugin_api_version >= 2) { if (decoder_plugin->set_strict_decoding) { decoder_plugin->set_strict_decoding(decoder, options.strict_decoding ); } } err = decoder_plugin->push_data(decoder, data.data(), data.size()); if (err.code != heif_error_Ok) { decoder_plugin->free_decoder(decoder); return Error(err.code, err.subcode, err.message); } heif_image* decoded_img = nullptr; err = decoder_plugin->decode_image(decoder, &decoded_img); if (err.code != heif_error_Ok) { decoder_plugin->free_decoder(decoder); return Error(err.code, err.subcode, err.message); } if (!decoded_img) { // TODO(farindk): The plugin should return an error in this case. decoder_plugin->free_decoder(decoder); return Error(heif_error_Decoder_plugin_error, heif_suberror_Unspecified); } img = std::move(decoded_img->image); heif_image_release(decoded_img); decoder_plugin->free_decoder(decoder); // --- convert to output chroma format // If there is an NCLX profile in the HEIF/AVIF metadata, use this for the color conversion. // Otherwise, use the profile that is stored in the image stream itself and then set the // (non-NCLX) profile later. auto nclx = imginfo->get_color_profile_nclx(); if (nclx) { img->set_color_profile_nclx(nclx); } auto icc = imginfo->get_color_profile_icc(); if (icc) { img->set_color_profile_icc(icc); } if (alphaImage) { // no color conversion required } else { heif_colorspace target_colorspace = (out_colorspace == heif_colorspace_undefined ? img->get_colorspace() : out_colorspace); // if (!alphaImage && target_colorspace == heif_colorspace_YCbCr) { // target_colorspace = heif_colorspace_RGB; // } // heif_chroma target_chroma = (target_colorspace == heif_colorspace_monochrome ? // heif_chroma_monochrome : heif_chroma_444); heif_chroma target_chroma = out_chroma == heif_chroma_undefined ? img->get_chroma_format() : (target_colorspace == heif_colorspace_monochrome ? heif_chroma_monochrome : heif_chroma_444); bool different_chroma = (target_chroma != img->get_chroma_format()); bool different_colorspace = (target_colorspace != img->get_colorspace()); int bpp = options.convert_hdr_to_8bit ? 8 : 0; if (different_chroma || different_colorspace) { if(options.ext_dst && options.ext_dst_enable) { img->set_image_external_info(true, options.ext_dst, options.ext_dst_len, options.ext_dst_stride); } img = convert_colorspace(img, target_colorspace, target_chroma, nullptr, bpp, options.color_conversion_options); if (!img) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } } } } else { // Should not reach this, was already rejected by "get_image_data". return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_image_type); } return Error::Ok; } Error HeifContext::decode_image_planar(heif_item_id ID, std::shared_ptr<HeifPixelImage>& img, heif_colorspace out_colorspace, heif_chroma out_chroma, const struct heif_decoding_options& options, bool alphaImage) const { std::string image_type = m_heif_file->get_item_type(ID); std::shared_ptr<Image> imginfo; if (m_all_images.find(ID) != m_all_images.end()) { imginfo = m_all_images.find(ID)->second; } // Note: this may happen, for example when an 'iden' image references a non-existing image item. if (imginfo == nullptr) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced); } Error error; // --- check whether image size exceeds maximum (according to 'ispe') auto ispe = m_heif_file->get_property<Box_ispe>(ID); if (ispe) { error = check_resolution(ispe->get_width(), ispe->get_height()); if (error) { return error; } } // --- decode image, depending on its type if (image_type == "hvc1" || image_type == "vvc1" || image_type == "av01" || image_type == "j2k1" || image_type == "jpeg" || (image_type == "mime" && m_heif_file->get_content_type(ID) == "image/jpeg")) { heif_compression_format compression = heif_compression_undefined; if (image_type == "hvc1") { compression = heif_compression_HEVC; } else if (image_type == "vvc1") { compression = heif_compression_VVC; } else if (image_type == "av01") { compression = heif_compression_AV1; } else if (image_type == "jpeg" || (image_type == "mime" && m_heif_file->get_content_type(ID) == "image/jpeg")) { compression = heif_compression_JPEG; } else if (image_type == "j2k1") { compression = heif_compression_JPEG2000; } const struct heif_decoder_plugin* decoder_plugin = get_decoder(compression, options.decoder_id); if (!decoder_plugin) { return Error(heif_error_Plugin_loading_error, heif_suberror_No_matching_decoder_installed); } std::vector<uint8_t> data; error = m_heif_file->get_compressed_image_data(ID, &data); if (error) { return error; } void* decoder; struct heif_error err = decoder_plugin->new_decoder(&decoder, m_max_decoder_threads); if (err.code != heif_error_Ok) { return Error(err.code, err.subcode, err.message); } if (decoder_plugin->plugin_api_version >= 2) { if (decoder_plugin->set_strict_decoding) { decoder_plugin->set_strict_decoding(decoder, options.strict_decoding); } } err = decoder_plugin->push_data(decoder, data.data(), data.size()); if (err.code != heif_error_Ok) { decoder_plugin->free_decoder(decoder); return Error(err.code, err.subcode, err.message); } //std::shared_ptr<HeifPixelImage>* decoded_img; heif_image* decoded_img = nullptr; err = decoder_plugin->decode_image(decoder, &decoded_img); if (err.code != heif_error_Ok) { decoder_plugin->free_decoder(decoder); return Error(err.code, err.subcode, err.message); } if (!decoded_img) { // TODO(farindk): The plugin should return an error in this case. decoder_plugin->free_decoder(decoder); return Error(heif_error_Decoder_plugin_error, heif_suberror_Unspecified); } img = std::move(decoded_img->image); heif_image_release(decoded_img); decoder_plugin->free_decoder(decoder); // --- convert to output chroma format // If there is an NCLX profile in the HEIF/AVIF metadata, use this for the color conversion. // Otherwise, use the profile that is stored in the image stream itself and then set the // (non-NCLX) profile later. auto nclx = imginfo->get_color_profile_nclx(); if (nclx) { img->set_color_profile_nclx(nclx); } auto icc = imginfo->get_color_profile_icc(); if (icc) { img->set_color_profile_icc(icc); } if (alphaImage) { // no color conversion required } else { heif_colorspace target_colorspace = (out_colorspace == heif_colorspace_undefined ? img->get_colorspace() : out_colorspace); // if (!alphaImage && target_colorspace == heif_colorspace_YCbCr) { // target_colorspace = heif_colorspace_RGB; // } // heif_chroma target_chroma = (target_colorspace == heif_colorspace_monochrome ? heif_chroma_monochrome : // (out_chroma == heif_chroma_interleaved_RGBA? heif_chroma_interleaved_RGBA : heif_chroma_444)); heif_chroma target_chroma = out_chroma == heif_chroma_undefined ? img->get_chroma_format() : (target_colorspace == heif_colorspace_monochrome ? heif_chroma_monochrome : heif_chroma_444); bool different_chroma = (target_chroma != img->get_chroma_format()); bool different_colorspace = (target_colorspace != img->get_colorspace()); if (different_chroma || different_colorspace) { if(options.ext_dst && options.ext_dst_enable) { img->set_image_external_info(true, options.ext_dst, options.ext_dst_len, options.ext_dst_stride); } img = convert_colorspace(img, target_colorspace, target_chroma, nullptr, 0, options.color_conversion_options); if (!img) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } } } } else if (image_type == "grid") { std::vector<uint8_t> data; error = m_heif_file->get_compressed_image_data(ID, &data); if (error) { return error; } error = decode_full_grid_image(ID, img, data, options); if (error) { return error; } } else if (image_type == "iden") { error = decode_derived_image(ID, img, options); if (error) { return error; } } else if (image_type == "iovl") { std::vector<uint8_t> data; error = m_heif_file->get_compressed_image_data(ID, &data); if (error) { return error; } error = decode_overlay_image(ID, img, data, options); if (error) { return error; } #if WITH_UNCOMPRESSED_CODEC } else if (image_type == "unci") { std::vector<uint8_t> data; error = m_heif_file->get_compressed_image_data(ID, &data); if (error) { return error; } error = UncompressedImageCodec::decode_uncompressed_image(this, ID, img, data); if (error) { return error; } #endif } else if (image_type == "mski") { std::vector<uint8_t> data; error = m_heif_file->get_compressed_image_data(ID, &data); if (error) { std::cout << "mski error 1" << std::endl; return error; } error = MaskImageCodec::decode_mask_image(this, ID, img, data); if (error) { return error; } } else { // Should not reach this, was already rejected by "get_image_data". return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_image_type); } // --- apply image transformations if (options.ignore_transformations == false) { std::vector<std::shared_ptr<Box>> properties; auto ipco_box = m_heif_file->get_ipco_box(); auto ipma_box = m_heif_file->get_ipma_box(); error = ipco_box->get_properties_for_item_ID(ID, ipma_box, properties); for (const auto& property : properties) { if (property->get_short_type() == fourcc("irot")) { auto rot = std::dynamic_pointer_cast<Box_irot>(property); std::shared_ptr<HeifPixelImage> rotated_img; error = img->rotate_ccw(rot->get_rotation(), rotated_img); if (error) { return error; } img = rotated_img; } if (property->get_short_type() == fourcc("imir")) { auto mirror = std::dynamic_pointer_cast<Box_imir>(property); error = img->mirror_inplace(mirror->get_mirror_direction()); if (error) { return error; } } if (property->get_short_type() == fourcc("clap")) { auto clap = std::dynamic_pointer_cast<Box_clap>(property); std::shared_ptr<HeifPixelImage> clap_img; int img_width = img->get_width(); int img_height = img->get_height(); assert(img_width >= 0); assert(img_height >= 0); int left = clap->left_rounded(img_width); int right = clap->right_rounded(img_width); int top = clap->top_rounded(img_height); int bottom = clap->bottom_rounded(img_height); if (left < 0) { left = 0; } if (top < 0) { top = 0; } if (right >= img_width) { right = img_width - 1; } if (bottom >= img_height) { bottom = img_height - 1; } if (left > right || top > bottom) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_clean_aperture); } std::shared_ptr<HeifPixelImage> cropped_img; error = img->crop(left, right, top, bottom, cropped_img); if (error) { return error; } img = cropped_img; } } } // --- add alpha channel, if available // TODO: this if statement is probably wrong. When we have a tiled image with alpha // channel, then the alpha images should be associated with their respective tiles. // However, the tile images are not part of the m_all_images list. // Fix this, when we have a test image available. if (m_all_images.find(ID) != m_all_images.end()) { const auto imginfo = m_all_images.find(ID)->second; std::shared_ptr<Image> alpha_image = imginfo->get_alpha_channel(); if (alpha_image) { std::shared_ptr<HeifPixelImage> alpha; Error err = decode_image_planar(alpha_image->get_id(), alpha, heif_colorspace_undefined, heif_chroma_undefined, options, true); if (err) { return err; } // TODO: check that sizes are the same and that we have an Y channel // BUT: is there any indication in the standard that the alpha channel should have the same size? heif_channel channel; switch (alpha->get_colorspace()) { case heif_colorspace_YCbCr: case heif_colorspace_monochrome: channel = heif_channel_Y; break; case heif_colorspace_RGB: channel = heif_channel_R; break; case heif_colorspace_undefined: default: return Error(heif_error_Invalid_input, heif_suberror_Unsupported_color_conversion); } // TODO: we should include a decoding option to control whether libheif should automatically scale the alpha channel, and if so, which scaling filter (enum: Off, NN, Bilinear, ...). // It might also be that a specific output format implies that alpha is scaled (RGBA32). That would favor an enum for the scaling filter option + a bool to switch auto-filtering on. // But we can only do this when libheif itself doesn't assume anymore that the alpha channel has the same resolution. if ((alpha_image->get_width() != img->get_width()) || (alpha_image->get_height() != img->get_height())) { std::shared_ptr<HeifPixelImage> scaled_alpha; err = alpha->scale_nearest_neighbor(scaled_alpha, img->get_width(), img->get_height()); if (err) { return err; } alpha = std::move(scaled_alpha); } img->transfer_plane_from_image_as(alpha, channel, heif_channel_Alpha); if (imginfo->is_premultiplied_alpha()) { img->set_premultiplied_alpha(true); } } } // --- attach metadata to image { auto ipco_box = m_heif_file->get_ipco_box(); auto ipma_box = m_heif_file->get_ipma_box(); // CLLI auto clli_box = ipco_box->get_property_for_item_ID(ID, ipma_box, fourcc("clli")); auto clli = std::dynamic_pointer_cast<Box_clli>(clli_box); if (clli) { img->set_clli(clli->clli); } // MDCV auto mdcv_box = ipco_box->get_property_for_item_ID(ID, ipma_box, fourcc("mdcv")); auto mdcv = std::dynamic_pointer_cast<Box_mdcv>(mdcv_box); if (mdcv) { img->set_mdcv(mdcv->mdcv); } // PASP auto pasp_box = ipco_box->get_property_for_item_ID(ID, ipma_box, fourcc("pasp")); auto pasp = std::dynamic_pointer_cast<Box_pasp>(pasp_box); if (pasp) { img->set_pixel_ratio(pasp->hSpacing, pasp->vSpacing); } } return Error::Ok; } // This function only works with RGB images. Error HeifContext::decode_full_grid_image(heif_item_id ID, std::shared_ptr<HeifPixelImage>& img, const std::vector<uint8_t>& grid_data, const heif_decoding_options& options) const { ImageGrid grid; Error err = grid.parse(grid_data); if (err) { return err; } //std::cout << grid.dump(); auto iref_box = m_heif_file->get_iref_box(); if (!iref_box) { return Error(heif_error_Invalid_input, heif_suberror_No_iref_box, "No iref box available, but needed for grid image"); } std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg")); if ((int) image_references.size() != grid.get_rows() * grid.get_columns()) { std::stringstream sstr; sstr << "Tiled image with " << grid.get_rows() << "x" << grid.get_columns() << "=" << (grid.get_rows() * grid.get_columns()) << " tiles, but only " << image_references.size() << " tile images in file"; return Error(heif_error_Invalid_input, heif_suberror_Missing_grid_images, sstr.str()); } // --- check that all image IDs are valid images for (heif_item_id tile_id : image_references) { if (!is_image(tile_id)) { std::stringstream sstr; sstr << "Tile image ID=" << tile_id << " is not a proper image."; return Error(heif_error_Invalid_input, heif_suberror_Missing_grid_images, sstr.str()); } } auto ipma = m_heif_file->get_ipma_box(); auto ipco = m_heif_file->get_ipco_box(); auto pixi_box = ipco->get_property_for_item_ID(ID, ipma, fourcc("pixi")); auto pixi = std::dynamic_pointer_cast<Box_pixi>(pixi_box); const uint32_t w = grid.get_width(); const uint32_t h = grid.get_height(); // --- determine output image chroma size and make sure all tiles have same chroma assert(!image_references.empty()); // heif_chroma tile_chroma = heif_chroma_444; /* TODO: in the future, we might support RGB and mono as intermediate formats heif_chroma tile_chroma = m_heif_file->get_image_chroma_from_configuration(some_tile_id); if (tile_chroma != heif_chroma_monochrome) { tile_chroma = heif_chroma_RGB; } */ heif_chroma tile_chroma = m_heif_file->get_image_chroma_from_configuration(image_references[0]); // --- generate image of full output size err = check_resolution(w, h); if (err) { return err; } int bpp = 0; if (pixi) { if (pixi->get_num_channels() < 1) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_pixi_box, "No pixi information for luma channel."); } bpp = pixi->get_bits_per_channel(0); if (tile_chroma != heif_chroma_monochrome) { // there are broken files that save only a one-channel pixi for an RGB image (issue #283) if (pixi->get_num_channels() == 3) { int bpp_c1 = pixi->get_bits_per_channel(1); int bpp_c2 = pixi->get_bits_per_channel(2); if (bpp_c1 != bpp || bpp_c2 != bpp) { // TODO: is this really an error? Does the pixi depths refer to RGB or YCbCr? return Error(heif_error_Invalid_input, heif_suberror_Invalid_pixi_box, "Different number of bits per pixel in each channel."); } } } } else { // When there is no pixi-box, get the pixel-depth from one of the tile images heif_item_id tileID = image_references[0]; auto iter = m_all_images.find(tileID); if (iter == m_all_images.end()) { return Error(heif_error_Invalid_input, heif_suberror_Missing_grid_images, "Nonexistent grid image referenced"); } const std::shared_ptr<Image> tileImg = iter->second; bpp = tileImg->get_luma_bits_per_pixel(); } if (bpp < 8 || bpp > 16) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_pixi_box, "Invalid bits per pixel in pixi box."); } heif_chroma grid_chroma = heif_chroma_undefined; img = std::make_shared<HeifPixelImage>(); img->create(w, h, (tile_chroma == heif_chroma_monochrome) ? heif_colorspace_monochrome : heif_colorspace_YCbCr , tile_chroma ); if (tile_chroma == heif_chroma_monochrome) { img->add_plane(heif_channel_Y, w, h, bpp); } else { img->add_plane(heif_channel_Y, w, h, bpp); switch(tile_chroma ) { case heif_chroma_420 : img->add_plane(heif_channel_Cb, (w+1)/2, (h+1)/2, bpp); img->add_plane(heif_channel_Cr, (w+1)/2, (h+1)/2, bpp); break; case heif_chroma_422 : img->add_plane(heif_channel_Cb, (w+1)/2, h, bpp); img->add_plane(heif_channel_Cr, (w+1)/2, h, bpp); break; case heif_chroma_444 : default: img->add_plane(heif_channel_Cb, w, h, bpp); img->add_plane(heif_channel_Cr, w, h, bpp); break; } } uint32_t y0 = 0; int reference_idx = 0; #if ENABLE_PARALLEL_TILE_DECODING // remember which tile to put where into the image struct tile_data { heif_item_id tileID; uint32_t x_origin, y_origin; }; std::deque<tile_data> tiles; if (m_max_decoding_threads > 0) tiles.resize(grid.get_rows() * grid.get_columns()); std::deque<std::future<Error> > errs; #endif uint32_t tile_width=0; uint32_t tile_height=0; for (uint32_t y = 0; y < grid.get_rows(); y++) { uint32_t x0 = 0; for (uint32_t x = 0; x < grid.get_columns(); x++) { heif_item_id tileID = image_references[reference_idx]; auto iter = m_all_images.find(tileID); if (iter == m_all_images.end()) { return {heif_error_Invalid_input, heif_suberror_Missing_grid_images, "Nonexistent grid image referenced"}; } const std::shared_ptr<Image> tileImg = iter->second; uint32_t src_width = tileImg->get_width(); uint32_t src_height = tileImg->get_height(); err = check_resolution(src_width, src_height); if (err) { return err; } if (src_width < grid.get_width() / grid.get_columns() || src_height < grid.get_height() / grid.get_rows()) { return {heif_error_Invalid_input, heif_suberror_Invalid_grid_data, "Grid tiles do not cover whole image"}; } if (x==0 && y==0) { // remember size of first tile and compare all other tiles against this tile_width = src_width; tile_height = src_height; } else if (src_width != tile_width || src_height != tile_height) { return {heif_error_Invalid_input, heif_suberror_Invalid_grid_data, "Grid tiles have different sizes"}; } #if ENABLE_PARALLEL_TILE_DECODING if (m_max_decoding_threads > 0) tiles[x + y * grid.get_columns()] = tile_data{tileID, x0, y0}; else #else if (1) #endif { err = decode_and_paste_tile_image(tileID, img, grid_chroma, x0, y0, options); if (err) { return err; } } x0 += src_width; reference_idx++; } y0 += tile_height; } #if ENABLE_PARALLEL_TILE_DECODING if (m_max_decoding_threads > 0) { // Process all tiles in a set of background threads. // Do not start more than the maximum number of threads. while (tiles.empty()==false && (m_max_decoding_threads>0)) { // If maximum number of threads running, wait until first thread finishes if (errs.size() >= (size_t) m_max_decoding_threads) { Error e = errs.front().get(); if (e) { return e; } errs.pop_front(); } // Start a new decoding thread tile_data data = tiles.front(); tiles.pop_front(); errs.push_back(std::async(std::launch::async, &HeifContext::decode_and_paste_tile_image, this, data.tileID, img, grid_chroma, data.x_origin,data.y_origin, options) ); } // check for decoding errors in remaining tiles while (errs.empty() == false) { Error e = errs.front().get(); if (e) { return e; } errs.pop_front(); } } #endif return Error::Ok; } Error HeifContext::decode_and_paste_tile_image(heif_item_id tileID, const std::shared_ptr<HeifPixelImage>& img, heif_chroma out_chroma, uint32_t x0, uint32_t y0, const heif_decoding_options& options) const { std::shared_ptr<HeifPixelImage> tile_img; Error err = decode_image_planar(tileID, tile_img, heif_colorspace_undefined, heif_chroma_undefined, options, false); if (err != Error::Ok) { return err; } const uint32_t w = img->get_width(); const uint32_t h = img->get_height(); // --- copy tile into output image uint32_t src_width = tile_img->get_width(); uint32_t src_height = tile_img->get_height(); heif_chroma chroma = img->get_chroma_format(); if (chroma != tile_img->get_chroma_format()) { return Error(heif_error_Invalid_input, heif_suberror_Wrong_tile_image_chroma_format, "Image tile has different chroma format than combined image"); } // --- add alpha plane if we discovered a tile with alpha if (tile_img->has_alpha() && !img->has_alpha()) { #if ENABLE_PARALLEL_TILE_DECODING // The mutex should probably be a member of heif_context, but since this is so infrequently locked, it probably doesn't matter. static std::mutex m; std::lock_guard<std::mutex> lock(m); if (!img->has_channel(heif_channel_Alpha)) // check again, after locking #endif { int alpha_bpp = tile_img->get_bits_per_pixel(heif_channel_Alpha); assert(alpha_bpp <= 16); uint16_t alpha_default_value = static_cast<uint16_t>((1UL << alpha_bpp) - 1UL); img->fill_new_plane(heif_channel_Alpha, alpha_default_value, w, h, alpha_bpp); } } std::set<enum heif_channel> channels = tile_img->get_channel_set(); for (heif_channel channel : channels) { int tile_stride; uint8_t* tile_data = tile_img->get_plane(channel, &tile_stride); int out_stride; uint8_t* out_data = img->get_plane(channel, &out_stride); int channel_w, channel_h, channel_x0, channel_y0 ; if(channel == heif_channel_Cb || channel == heif_channel_Cr) { switch(chroma) { case heif_chroma_420 : channel_w = (w+1)/2; channel_h = (h+1)/2; channel_x0 = (x0+1)/2; channel_y0 = (y0+1)/2 ; break; case heif_chroma_422 : channel_w = (w+1)/2; channel_h = h; channel_x0 = (x0+1)/2; channel_y0 = y0; break; default: channel_w = w; channel_h = h; channel_x0 = x0; channel_y0 = y0 ; break; } } else { channel_w = w; channel_h = h; channel_x0 = x0; channel_y0 = y0 ; } if (channel_w <= channel_x0 || channel_h <= channel_y0) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_grid_data); } if (img->get_bits_per_pixel(channel) != tile_img->get_bits_per_pixel(channel)) { return Error(heif_error_Invalid_input, heif_suberror_Wrong_tile_image_pixel_depth); } int plane_width = tile_img->get_width(channel); int plane_height = tile_img->get_height(channel); int copy_width = std::min(plane_width, channel_w - channel_x0); int copy_height = std::min(plane_height, channel_h - channel_y0); copy_width *= tile_img->get_storage_bits_per_pixel(channel) / 8; int xs = channel_x0, ys = channel_y0; xs *= tile_img->get_storage_bits_per_pixel(channel) / 8; // deal with tile_img m_full_range_range int bpp = tile_img->get_bits_per_pixel(channel); int maxval = (1 << bpp) - 1; float limited_range_offset = static_cast<float>(16 << (bpp - 8)); auto tile_profile_nclx = tile_img->get_color_profile_nclx(); bool full_range_flag = (tile_profile_nclx) ? tile_profile_nclx->get_full_range_flag() : true ; int matrix_coeffs = (tile_profile_nclx ) ? tile_profile_nclx->get_matrix_coefficients() : 1; if(tile_profile_nclx && (full_range_flag != true) && (matrix_coeffs != 0)) { float convert_ratio = 0.0; if(channel == heif_channel_Cb || channel == heif_channel_Cr) { convert_ratio = 1.1429f; } else { convert_ratio = 1.1689f; } for(int py = 0; py < copy_height; py++) { for(int px = 0; px < copy_width; px++) { float limit_value = static_cast<float>(*(tile_data + py * tile_stride + px)); float full_value = (limit_value - limited_range_offset) * convert_ratio ; *(out_data + xs + (ys + py) * out_stride + px) = clip_f_u8(full_value); } } } else { for (int py = 0; py < copy_height; py++) { memcpy(out_data + xs + (ys + py) * out_stride, tile_data + py * tile_stride, copy_width); } } } return Error::Ok; } Error HeifContext::decode_derived_image(heif_item_id ID, std::shared_ptr<HeifPixelImage>& img, const heif_decoding_options& options) const { // find the ID of the image this image is derived from auto iref_box = m_heif_file->get_iref_box(); if (!iref_box) { return Error(heif_error_Invalid_input, heif_suberror_No_iref_box, "No iref box available, but needed for iden image"); } std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg")); if ((int) image_references.size() != 1) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "'iden' image with more than one reference image"); } heif_item_id reference_image_id = image_references[0]; if (reference_image_id == ID) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "'iden' image referring to itself"); } Error error = decode_image_planar(reference_image_id, img, heif_colorspace_RGB, heif_chroma_undefined, options, false); // TODO: always RGB ? return error; } Error HeifContext::decode_overlay_image(heif_item_id ID, std::shared_ptr<HeifPixelImage>& img, const std::vector<uint8_t>& overlay_data, const heif_decoding_options& options) const { // find the IDs this image is composed of auto iref_box = m_heif_file->get_iref_box(); if (!iref_box) { return Error(heif_error_Invalid_input, heif_suberror_No_iref_box, "No iref box available, but needed for iovl image"); } std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg")); /* TODO: probably, it is valid that an iovl image has no references ? if (image_references.empty()) { return Error(heif_error_Invalid_input, heif_suberror_Missing_grid_images, "'iovl' image with more than one reference image"); } */ ImageOverlay overlay; Error err = overlay.parse(image_references.size(), overlay_data); if (err) { return err; } if (image_references.size() != overlay.get_num_offsets()) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_overlay_data, "Number of image offsets does not match the number of image references"); } uint32_t w = overlay.get_canvas_width(); uint32_t h = overlay.get_canvas_height(); err = check_resolution(w, h); if (err) { return err; } // TODO: seems we always have to compose this in RGB since the background color is an RGB value img = std::make_shared<HeifPixelImage>(); img->create(w, h, heif_colorspace_RGB, heif_chroma_444); img->add_plane(heif_channel_R, w, h, 8); // TODO: other bit depths img->add_plane(heif_channel_G, w, h, 8); // TODO: other bit depths img->add_plane(heif_channel_B, w, h, 8); // TODO: other bit depths uint16_t bkg_color[4]; overlay.get_background_color(bkg_color); err = img->fill_RGB_16bit(bkg_color[0], bkg_color[1], bkg_color[2], bkg_color[3]); if (err) { return err; } for (size_t i = 0; i < image_references.size(); i++) { std::shared_ptr<HeifPixelImage> overlay_img; err = decode_image_planar(image_references[i], overlay_img, heif_colorspace_RGB, heif_chroma_undefined, options, false); // TODO: always RGB? Probably yes, because of RGB background color. if (err != Error::Ok) { return err; } overlay_img = convert_colorspace(overlay_img, heif_colorspace_RGB, heif_chroma_444, nullptr, 0, options.color_conversion_options); if (!overlay_img) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } int32_t dx, dy; overlay.get_offset(i, &dx, &dy); err = img->overlay(overlay_img, dx, dy); if (err) { if (err.error_code == heif_error_Invalid_input && err.sub_error_code == heif_suberror_Overlay_image_outside_of_canvas) { // NOP, ignore this error err = Error::Ok; } else { return err; } } } return err; } static std::shared_ptr<HeifPixelImage> create_alpha_image_from_image_alpha_channel(const std::shared_ptr<HeifPixelImage>& image) { // --- generate alpha image std::shared_ptr<HeifPixelImage> alpha_image = std::make_shared<HeifPixelImage>(); alpha_image->create(image->get_width(), image->get_height(), heif_colorspace_monochrome, heif_chroma_monochrome); alpha_image->copy_new_plane_from(image, heif_channel_Alpha, heif_channel_Y); // --- set nclx profile with full-range flag auto nclx = std::make_shared<color_profile_nclx>(); nclx->set_undefined(); nclx->set_full_range_flag(true); // this is the default, but just to be sure in case the defaults change alpha_image->set_color_profile_nclx(nclx); return alpha_image; } void HeifContext::Image::set_preencoded_hevc_image(const std::vector<uint8_t>& data) { auto hvcC = std::make_shared<Box_hvcC>(); // --- parse the h265 stream and set hvcC headers and compressed image data int state = 0; bool first = true; bool eof = false; int prev_start_code_start = -1; // init to an invalid value, will always be overwritten before use int start_code_start; int ptr = 0; for (;;) { bool dump_nal = false; uint8_t c = data[ptr++]; if (state == 3) { state = 0; } if (c == 0 && state <= 1) { state++; } else if (c == 0) { // NOP } else if (c == 1 && state == 2) { start_code_start = ptr - 3; dump_nal = true; state = 3; } else { state = 0; } if (ptr == (int) data.size()) { start_code_start = (int) data.size(); dump_nal = true; eof = true; } if (dump_nal) { if (first) { first = false; } else { std::vector<uint8_t> nal_data; size_t length = start_code_start - (prev_start_code_start + 3); nal_data.resize(length); assert(prev_start_code_start >= 0); memcpy(nal_data.data(), data.data() + prev_start_code_start + 3, length); int nal_type = (nal_data[0] >> 1); switch (nal_type) { case 0x20: case 0x21: case 0x22: hvcC->append_nal_data(nal_data); break; default: { std::vector<uint8_t> nal_data_with_size; nal_data_with_size.resize(nal_data.size() + 4); memcpy(nal_data_with_size.data() + 4, nal_data.data(), nal_data.size()); nal_data_with_size[0] = ((nal_data.size() >> 24) & 0xFF); nal_data_with_size[1] = ((nal_data.size() >> 16) & 0xFF); nal_data_with_size[2] = ((nal_data.size() >> 8) & 0xFF); nal_data_with_size[3] = ((nal_data.size() >> 0) & 0xFF); m_heif_context->m_heif_file->append_iloc_data(m_id, nal_data_with_size); } break; } } prev_start_code_start = start_code_start; } if (eof) { break; } } m_heif_context->m_heif_file->add_property(m_id, hvcC, true); } Error HeifContext::encode_image(const std::shared_ptr<HeifPixelImage>& pixel_image, struct heif_encoder* encoder, const struct heif_encoding_options& options, enum heif_image_input_class input_class, std::shared_ptr<Image>& out_image) { Error error; // TODO: the hdlr box is not the right place for comments // m_heif_file->set_hdlr_library_info(encoder->plugin->get_plugin_name()); switch (encoder->plugin->compression_format) { case heif_compression_HEVC: { error = encode_image_as_hevc(pixel_image, encoder, options, input_class, out_image); } break; case heif_compression_VVC: { error = encode_image_as_vvc(pixel_image, encoder, options, heif_image_input_class_normal, out_image); } break; case heif_compression_AV1: { error = encode_image_as_av1(pixel_image, encoder, options, input_class, out_image); } break; case heif_compression_JPEG2000: case heif_compression_HTJ2K: { error = encode_image_as_jpeg2000(pixel_image, encoder, options, input_class, out_image); } break; case heif_compression_JPEG: { error = encode_image_as_jpeg(pixel_image, encoder, options, input_class, out_image); } break; case heif_compression_uncompressed: { error = encode_image_as_uncompressed(pixel_image, encoder, options, input_class, out_image); } break; case heif_compression_mask: { error = encode_image_as_mask(pixel_image, encoder, options, input_class, out_image); } break; default: return Error(heif_error_Encoder_plugin_error, heif_suberror_Unsupported_codec); } m_heif_file->set_brand(encoder->plugin->compression_format, out_image->is_miaf_compatible(), get_movie_flag()); return error; } Error HeifContext::encode_grid(const std::vector<std::shared_ptr<HeifPixelImage>>& tiles, uint16_t rows, uint16_t columns, struct heif_encoder* encoder, const struct heif_encoding_options& options, std::shared_ptr<Image>& out_grid_image) { // Create ImageGrid ImageGrid grid; grid.set_num_tiles(columns, rows); int tile_width = tiles[0]->get_width(heif_channel_interleaved); int tile_height = tiles[0]->get_height(heif_channel_interleaved); grid.set_output_size(tile_width * columns, tile_height * rows); std::vector<uint8_t> grid_data = grid.write(); // Encode Tiles Error error; std::vector<heif_item_id> tile_ids; for (int i=0; i<rows*columns; i++) { std::shared_ptr<Image> out_tile; error = encode_image(tiles[i], encoder, options, heif_image_input_class_normal, out_tile); heif_item_id tile_id = out_tile->get_id(); m_heif_file->get_infe_box(tile_id)->set_hidden_item(true); // only show the full grid tile_ids.push_back(out_tile->get_id()); } // Create Grid Item heif_item_id grid_id = m_heif_file->add_new_image("grid"); out_grid_image = std::make_shared<Image>(this, grid_id); m_all_images.insert(std::make_pair(grid_id, out_grid_image)); const int construction_method = 1; // 0=mdat 1=idat m_heif_file->append_iloc_data(grid_id, grid_data, construction_method); // Connect tiles to grid m_heif_file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids); // Add ISPE property int image_width = tile_width * columns; int image_height = tile_height * rows; m_heif_file->add_ispe_property(grid_id, image_width, image_height); // Set Brands m_heif_file->set_brand(encoder->plugin->compression_format, out_grid_image->is_miaf_compatible(), get_movie_flag()); return error; } /* static uint32_t get_rotated_width(heif_orientation orientation, uint32_t w, uint32_t h) { return ((int)orientation) > 4 ? h : w; } static uint32_t get_rotated_height(heif_orientation orientation, uint32_t w, uint32_t h) { return ((int)orientation) > 4 ? w : h; } */ void HeifContext::write_image_metadata(std::shared_ptr<HeifPixelImage> src_image, int image_id) { auto colorspace = src_image->get_colorspace(); auto chroma = src_image->get_chroma_format(); // --- write PIXI property if(!get_movie_flag() || (get_movie_flag() && image_id == 1)) { if (colorspace == heif_colorspace_monochrome) { m_heif_file->add_pixi_property(image_id, src_image->get_bits_per_pixel(heif_channel_Y), 0, 0); } else if (colorspace == heif_colorspace_YCbCr) { m_heif_file->add_pixi_property(image_id, src_image->get_bits_per_pixel(heif_channel_Y), src_image->get_bits_per_pixel(heif_channel_Cb), src_image->get_bits_per_pixel(heif_channel_Cr)); } else if (colorspace == heif_colorspace_RGB) { if (chroma == heif_chroma_444) { m_heif_file->add_pixi_property(image_id, src_image->get_bits_per_pixel(heif_channel_R), src_image->get_bits_per_pixel(heif_channel_G), src_image->get_bits_per_pixel(heif_channel_B)); } else if (chroma == heif_chroma_interleaved_RGB || chroma == heif_chroma_interleaved_RGBA) { m_heif_file->add_pixi_property(image_id, 8, 8, 8); } } } // --- write PASP property if (src_image->has_nonsquare_pixel_ratio()) { auto pasp = std::make_shared<Box_pasp>(); src_image->get_pixel_ratio(&pasp->hSpacing, &pasp->vSpacing); int index = m_heif_file->get_ipco_box()->find_or_append_child_box(pasp); m_heif_file->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)}); } // --- write CLLI property if (src_image->has_clli()) { auto clli = std::make_shared<Box_clli>(); clli->clli = src_image->get_clli(); int index = m_heif_file->get_ipco_box()->find_or_append_child_box(clli); m_heif_file->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)}); } // --- write MDCV property if (src_image->has_mdcv()) { auto mdcv = std::make_shared<Box_mdcv>(); mdcv->mdcv = src_image->get_mdcv(); int index = m_heif_file->get_ipco_box()->find_or_append_child_box(mdcv); m_heif_file->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)}); } } static bool nclx_profile_matches_spec(heif_colorspace colorspace, std::shared_ptr<const color_profile_nclx> image_nclx, const struct heif_color_profile_nclx* spec_nclx) { if (colorspace != heif_colorspace_YCbCr) { return true; } // Do target specification -> always matches if (!spec_nclx) { return true; } if (!image_nclx) { // if no input nclx is specified, compare against default one image_nclx = std::make_shared<color_profile_nclx>(); } if (image_nclx->get_full_range_flag() != ( spec_nclx->full_range_flag == 0 ? false : true ) ) { return false; } if (image_nclx->get_matrix_coefficients() != spec_nclx->matrix_coefficients) { return false; } // TODO: are the colour primaries relevant for matrix-coefficients != 12,13 ? // If not, we should skip this test for anything else than matrix-coefficients != 12,13. if (image_nclx->get_colour_primaries() != spec_nclx->color_primaries) { return false; } return true; } static std::shared_ptr<color_profile_nclx> compute_target_nclx_profile(const std::shared_ptr<HeifPixelImage>& image, const heif_color_profile_nclx* output_nclx_profile) { auto target_nclx_profile = std::make_shared<color_profile_nclx>(); // If there is an output NCLX specified, use that. if (output_nclx_profile) { target_nclx_profile->set_from_heif_color_profile_nclx(output_nclx_profile); } // Otherwise, if there is an input NCLX, keep that. else if (auto input_nclx = image->get_color_profile_nclx()) { *target_nclx_profile = *input_nclx; } // Otherwise, just use the defaults (set below) else { target_nclx_profile->set_undefined(); } target_nclx_profile->replace_undefined_values_with_sRGB_defaults(); return target_nclx_profile; } Error HeifContext::encode_image_as_hevc(const std::shared_ptr<HeifPixelImage>& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, enum heif_image_input_class input_class, std::shared_ptr<Image>& out_image) { if(get_movie_flag()) m_heif_file->set_moov_flag(true); heif_item_id image_id = m_heif_file->add_new_image("hvc1"); out_image = std::make_shared<Image>(this, image_id); // --- check whether we have to convert the image color space heif_colorspace colorspace = image->get_colorspace(); heif_chroma chroma = image->get_chroma_format(); auto target_nclx_profile = compute_target_nclx_profile(image, options.output_nclx_profile); if (encoder->plugin->plugin_api_version >= 2) { encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); } else { encoder->plugin->query_input_colorspace(&colorspace, &chroma); } std::shared_ptr<HeifPixelImage> src_image; if (colorspace != image->get_colorspace() || chroma != image->get_chroma_format() || !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), options.output_nclx_profile)) { // @TODO: use color profile when converting int output_bpp = 0; // same as input src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, output_bpp, options.color_conversion_options); if (!src_image) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } } else { src_image = image; } int input_width = src_image->get_width(heif_channel_Y); int input_height = src_image->get_height(heif_channel_Y); out_image->set_size(input_width, input_height); // if(!get_movie_flag()) // { // m_heif_file->add_hvcC_property(image_id); // } // else if(get_movie_flag()&&image_id==1) // { // m_heif_file->add_hvcC_property(image_id); // } auto hvcC = std::make_shared<Box_hvcC>(); heif_image c_api_image; c_api_image.image = src_image; struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); if (err.code) { return Error(err.code, err.subcode, err.message); } int encoded_width = 0; int encoded_height = 0; if(get_movie_flag()) { m_heif_file->add_frame_duration_in_TimeScale(image->get_duration_in_Timescales()); m_heif_file->add_frame_sample_in_chunk(); } for (;;) { uint8_t* data; int size; encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, NULL); if (data == NULL) { break; } const uint8_t NAL_SPS = 33; if ((data[0] >> 1) == NAL_SPS) { Box_hvcC::configuration config; parse_sps_for_hvcC_configuration(data, size, &config, &encoded_width, &encoded_height); if(!get_movie_flag()) { m_heif_file->set_hvcC_configuration(image_id, config); } else if(get_movie_flag()&&image_id==1) { m_heif_file->set_hvcC_configuration(image_id, config); } if(get_movie_flag()) m_heif_file->set_hvcC_configuration(config); } switch (data[0] >> 1) { case 0x20: case 0x21: case 0x22: if(!get_movie_flag()) { m_heif_file->append_hvcC_nal_data(image_id, data, size); } else if(get_movie_flag()&&image_id==1) { m_heif_file->append_hvcC_nal_data(image_id, data, size); } if(get_movie_flag()) m_heif_file->append_hvcC_nal_data(data, size); break; default: m_heif_file->append_iloc_data_with_4byte_size(image_id, data, size); if(get_movie_flag()) { m_heif_file->append_sample_entry_size(size+4); m_heif_file->record_sync_data_in_stss(data[0] >> 1); } } } if (!encoded_width || !encoded_height) { return Error(heif_error_Encoder_plugin_error, heif_suberror_Invalid_image_size); } if(!get_movie_flag()) { m_heif_file->add_hvcC_property(image_id); } else if(get_movie_flag()&&image_id==1) { m_heif_file->add_hvcC_property(image_id); } if (encoder->plugin->plugin_api_version >= 3 && encoder->plugin->query_encoded_size != nullptr) { uint32_t check_encoded_width = input_width, check_encoded_height = input_height; encoder->plugin->query_encoded_size(encoder->encoder, input_width, input_height, &check_encoded_width, &check_encoded_height); assert((int)check_encoded_width == encoded_width); assert((int)check_encoded_height == encoded_height); } // Note: 'ispe' must be before the transformation properties if(!get_movie_flag() || (get_movie_flag() && image_id == 1)) { m_heif_file->add_ispe_property(image_id, encoded_width, encoded_height); } // if image size was rounded up to even size, add a 'clap' box to crop the // padding border away //uint32_t rotated_width = get_rotated_width(options.image_orientation, out_image->get_width(), out_image->get_height()); //uint32_t rotated_height = get_rotated_height(options.image_orientation, out_image->get_width(), out_image->get_height()); if (input_width != encoded_width || input_height != encoded_height) { m_heif_file->add_clap_property(image_id, input_width, input_height, encoded_width, encoded_height); // MIAF 7.3.6.7 // This is according to MIAF without Amd2. With Amd2, the restriction has been liften and the image is MIAF compatible. // We might remove this code at a later point in time when MIAF Amd2 is in wide use. if (!is_integer_multiple_of_chroma_size(input_width, input_height, src_image->get_chroma_format())) { out_image->mark_not_miaf_compatible(); } } m_heif_file->add_orientation_properties(image_id, options.image_orientation); // --- choose which color profile to put into 'colr' box if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { auto icc_profile = src_image->get_color_profile_icc(); if (icc_profile) { m_heif_file->set_color_profile(image_id, icc_profile); } // save nclx profile bool save_nclx_profile = (options.output_nclx_profile != nullptr); // if there is an ICC profile, only save NCLX when we chose to save both profiles if (icc_profile && !(options.version >= 3 && options.save_two_colr_boxes_when_ICC_and_nclx_available)) { save_nclx_profile = false; } // we might have turned off nclx completely because macOS/iOS cannot read it if (options.version >= 4 && options.macOS_compatibility_workaround_no_nclx_profile) { save_nclx_profile = false; } if (save_nclx_profile) { m_heif_file->set_color_profile(image_id, target_nclx_profile); } } write_image_metadata(src_image, image_id); m_top_level_images.push_back(out_image); m_all_images[image_id] = out_image; // --- If there is an alpha channel, add it as an additional image. // Save alpha after the color image because we need to know the final reference to the color image. if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { // --- generate alpha image // TODO: can we directly code a monochrome image instead of the dummy color channels? std::shared_ptr<HeifPixelImage> alpha_image; alpha_image = create_alpha_image_from_image_alpha_channel(src_image); // --- encode the alpha image std::shared_ptr<HeifContext::Image> heif_alpha_image; Error error = encode_image_as_hevc(alpha_image, encoder, options, heif_image_input_class_alpha, heif_alpha_image); if (error) { return error; } m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); if (src_image->is_premultiplied_alpha()) { m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); } // TODO: MIAF says that the *:hevc:* urn is deprecated and we should use "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha" // Is this compatible to other decoders? m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:hevc:2015:auxid:1"); } return Error::Ok; } Error HeifContext::encode_image_as_vvc(const std::shared_ptr<HeifPixelImage>& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, enum heif_image_input_class input_class, std::shared_ptr<Image>& out_image) { heif_item_id image_id = m_heif_file->add_new_image("vvc1"); out_image = std::make_shared<Image>(this, image_id); // --- check whether we have to convert the image color space heif_colorspace colorspace = image->get_colorspace(); heif_chroma chroma = image->get_chroma_format(); auto target_nclx_profile = compute_target_nclx_profile(image, options.output_nclx_profile); if (encoder->plugin->plugin_api_version >= 2) { encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); } else { encoder->plugin->query_input_colorspace(&colorspace, &chroma); } std::shared_ptr<HeifPixelImage> src_image; if (colorspace != image->get_colorspace() || chroma != image->get_chroma_format() || !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), options.output_nclx_profile)) { // @TODO: use color profile when converting int output_bpp = 0; // same as input src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, output_bpp, options.color_conversion_options); if (!src_image) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } } else { src_image = image; } int input_width = src_image->get_width(heif_channel_Y); int input_height = src_image->get_height(heif_channel_Y); out_image->set_size(input_width, input_height); m_heif_file->add_vvcC_property(image_id); heif_image c_api_image; c_api_image.image = src_image; struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); if (err.code) { return Error(err.code, err.subcode, err.message); } int encoded_width = 0; int encoded_height = 0; for (;;) { uint8_t* data; int size; encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, NULL); if (data == NULL) { break; } const uint8_t NAL_SPS = 15; uint8_t nal_type = 0; if (size>=2) { nal_type = (data[1] >> 3) & 0x1F; } if (nal_type == NAL_SPS) { Box_vvcC::configuration config; parse_sps_for_vvcC_configuration(data, size, &config, &encoded_width, &encoded_height); m_heif_file->set_vvcC_configuration(image_id, config); } switch (data[0] >> 1) { case 14: // VPS case 15: // SPS case 16: // PPS m_heif_file->append_vvcC_nal_data(image_id, data, size); break; default: m_heif_file->append_iloc_data_with_4byte_size(image_id, data, size); } } if (!encoded_width || !encoded_height) { return Error(heif_error_Encoder_plugin_error, heif_suberror_Invalid_image_size); } if (encoder->plugin->plugin_api_version >= 3 && encoder->plugin->query_encoded_size != nullptr) { uint32_t check_encoded_width = input_width, check_encoded_height = input_height; encoder->plugin->query_encoded_size(encoder->encoder, input_width, input_height, &check_encoded_width, &check_encoded_height); assert((int)check_encoded_width == encoded_width); assert((int)check_encoded_height == encoded_height); } // Note: 'ispe' must be before the transformation properties m_heif_file->add_ispe_property(image_id, encoded_width, encoded_height); // if image size was rounded up to even size, add a 'clap' box to crop the // padding border away //uint32_t rotated_width = get_rotated_width(options.image_orientation, out_image->get_width(), out_image->get_height()); //uint32_t rotated_height = get_rotated_height(options.image_orientation, out_image->get_width(), out_image->get_height()); if (input_width != encoded_width || input_height != encoded_height) { m_heif_file->add_clap_property(image_id, input_width, input_height, encoded_width, encoded_height); // MIAF 7.3.6.7 // This is according to MIAF without Amd2. With Amd2, the restriction has been liften and the image is MIAF compatible. // We might remove this code at a later point in time when MIAF Amd2 is in wide use. if (!is_integer_multiple_of_chroma_size(input_width, input_height, src_image->get_chroma_format())) { out_image->mark_not_miaf_compatible(); } } m_heif_file->add_orientation_properties(image_id, options.image_orientation); // --- choose which color profile to put into 'colr' box if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { auto icc_profile = src_image->get_color_profile_icc(); if (icc_profile) { m_heif_file->set_color_profile(image_id, icc_profile); } // save nclx profile bool save_nclx_profile = (options.output_nclx_profile != nullptr); // if there is an ICC profile, only save NCLX when we chose to save both profiles if (icc_profile && !(options.version >= 3 && options.save_two_colr_boxes_when_ICC_and_nclx_available)) { save_nclx_profile = false; } // we might have turned off nclx completely because macOS/iOS cannot read it if (options.version >= 4 && options.macOS_compatibility_workaround_no_nclx_profile) { save_nclx_profile = false; } if (save_nclx_profile) { m_heif_file->set_color_profile(image_id, target_nclx_profile); } } write_image_metadata(src_image, image_id); m_top_level_images.push_back(out_image); m_all_images[image_id] = out_image; // --- If there is an alpha channel, add it as an additional image. // Save alpha after the color image because we need to know the final reference to the color image. if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { // --- generate alpha image // TODO: can we directly code a monochrome image instead of the dummy color channels? std::shared_ptr<HeifPixelImage> alpha_image; alpha_image = create_alpha_image_from_image_alpha_channel(src_image); // --- encode the alpha image std::shared_ptr<HeifContext::Image> heif_alpha_image; Error error = encode_image_as_vvc(alpha_image, encoder, options, heif_image_input_class_alpha, heif_alpha_image); if (error) { return error; } m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); if (src_image->is_premultiplied_alpha()) { m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); } // TODO: MIAF says that the *:hevc:* urn is deprecated and we should use "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha" // Is this compatible to other decoders? m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:hevc:2015:auxid:1"); } return Error::Ok; } Error HeifContext::encode_image_as_av1(const std::shared_ptr<HeifPixelImage>& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, enum heif_image_input_class input_class, std::shared_ptr<Image>& out_image) { heif_item_id image_id = m_heif_file->add_new_image("av01"); out_image = std::make_shared<Image>(this, image_id); m_top_level_images.push_back(out_image); m_all_images[image_id] = out_image; // --- check whether we have to convert the image color space heif_colorspace colorspace = image->get_colorspace(); heif_chroma chroma = image->get_chroma_format(); auto target_nclx_profile = compute_target_nclx_profile(image, options.output_nclx_profile); if (encoder->plugin->plugin_api_version >= 2) { encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); } else { encoder->plugin->query_input_colorspace(&colorspace, &chroma); } std::shared_ptr<HeifPixelImage> src_image; if (colorspace != image->get_colorspace() || chroma != image->get_chroma_format() || !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), options.output_nclx_profile)) { // @TODO: use color profile when converting int output_bpp = 0; // same as input src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, output_bpp, options.color_conversion_options); if (!src_image) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } } else { src_image = image; } // --- choose which color profile to put into 'colr' box if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { auto icc_profile = src_image->get_color_profile_icc(); if (icc_profile) { m_heif_file->set_color_profile(image_id, icc_profile); } if (// target_nclx_profile && (!icc_profile || (options.version >= 3 && options.save_two_colr_boxes_when_ICC_and_nclx_available))) { m_heif_file->set_color_profile(image_id, target_nclx_profile); } } // --- if there is an alpha channel, add it as an additional image if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { // --- generate alpha image // TODO: can we directly code a monochrome image instead of the dummy color channels? std::shared_ptr<HeifPixelImage> alpha_image; alpha_image = create_alpha_image_from_image_alpha_channel(src_image); // --- encode the alpha image std::shared_ptr<HeifContext::Image> heif_alpha_image; Error error = encode_image_as_av1(alpha_image, encoder, options, heif_image_input_class_alpha, heif_alpha_image); if (error) { return error; } m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"); if (src_image->is_premultiplied_alpha()) { m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); } } Box_av1C::configuration config; // Fill preliminary av1C in case we cannot parse the sequence_header() correctly in the code below. // TODO: maybe we can remove this later. fill_av1C_configuration(&config, src_image); heif_image c_api_image; c_api_image.image = src_image; struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); if (err.code) { return Error(err.code, err.subcode, err.message); } for (;;) { uint8_t* data; int size; encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); bool found_config = fill_av1C_configuration_from_stream(&config, data, size); (void) found_config; if (data == nullptr) { break; } std::vector<uint8_t> vec; vec.resize(size); memcpy(vec.data(), data, size); m_heif_file->append_iloc_data(image_id, vec); } m_heif_file->add_av1C_property(image_id, config); uint32_t input_width, input_height; input_width = src_image->get_width(); input_height = src_image->get_height(); uint32_t encoded_width = input_width, encoded_height = input_height; if (encoder->plugin->plugin_api_version >= 3 && encoder->plugin->query_encoded_size != nullptr) { encoder->plugin->query_encoded_size(encoder->encoder, input_width, input_height, &encoded_width, &encoded_height); } // Note: 'ispe' must be before the transformation properties m_heif_file->add_ispe_property(image_id, encoded_width, encoded_height); if (input_width != encoded_width || input_height != encoded_height) { m_heif_file->add_clap_property(image_id, input_width, input_height, encoded_width, encoded_height); // According to MIAF without Amd2, an image is required to be cropped to multiples of the chroma format raster. // However, since AVIF is based on MIAF, the whole image would be invalid in that case. // As this restriction was lifted with MIAF-Amd2, we include the MIAF brand for all AVIF images. /* if (!is_integer_multiple_of_chroma_size(input_width, input_height, src_image->get_chroma_format())) { out_image->mark_not_miaf_compatible(); } */ m_heif_file->add_orientation_properties(image_id, options.image_orientation); } write_image_metadata(src_image, image_id); return Error::Ok; } Error HeifContext::encode_image_as_jpeg2000(const std::shared_ptr<HeifPixelImage>& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, enum heif_image_input_class input_class, std::shared_ptr<Image>& out_image) { heif_item_id image_id = m_heif_file->add_new_image("j2k1"); out_image = std::make_shared<Image>(this, image_id); m_top_level_images.push_back(out_image); // TODO: simplify the color-conversion part. It's the same for each codec. // ---begin--- heif_colorspace colorspace = image->get_colorspace(); heif_chroma chroma = image->get_chroma_format(); /* auto color_profile = image->get_color_profile_nclx(); if (!color_profile) { color_profile = std::make_shared<color_profile_nclx>(); } auto nclx_profile = std::dynamic_pointer_cast<const color_profile_nclx>(color_profile); */ auto target_nclx_profile = compute_target_nclx_profile(image, options.output_nclx_profile); if (encoder->plugin->plugin_api_version >= 2) { encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); } else { encoder->plugin->query_input_colorspace(&colorspace, &chroma); } std::shared_ptr<HeifPixelImage> src_image; if (colorspace != image->get_colorspace() || chroma != image->get_chroma_format() || !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), options.output_nclx_profile)) { int output_bpp = 0; // same as input src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, output_bpp, options.color_conversion_options); if (!src_image) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } } else { src_image = image; } // ---end--- // --- if there is an alpha channel, add it as an additional image if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { // --- generate alpha image // TODO: can we directly code a monochrome image instead of the dummy color channels? std::shared_ptr<HeifPixelImage> alpha_image; alpha_image = create_alpha_image_from_image_alpha_channel(src_image); // --- encode the alpha image std::shared_ptr<HeifContext::Image> heif_alpha_image; Error error = encode_image_as_jpeg2000(alpha_image, encoder, options, heif_image_input_class_alpha, heif_alpha_image); if (error) { return error; } m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"); if (src_image->is_premultiplied_alpha()) { m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); } } //Encode Image heif_image c_api_image; c_api_image.image = src_image; encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); //Get Compressed Data for (;;) { uint8_t* data; int size; encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); if (data == NULL) { break; } std::vector<uint8_t> vec; vec.resize(size); memcpy(vec.data(), data, size); m_heif_file->append_iloc_data(image_id, vec); } //Add 'ispe' Property m_heif_file->add_ispe_property(image_id, image->get_width(), image->get_height()); //Add 'colr' Property m_heif_file->set_color_profile(image_id, target_nclx_profile); //Add 'j2kH' Property auto j2kH = m_heif_file->add_j2kH_property(image_id); //Add 'cdef' to 'j2kH' auto cdef = std::make_shared<Box_cdef>(); cdef->set_channels(src_image->get_colorspace()); j2kH->append_child_box(cdef); write_image_metadata(src_image, image_id); return Error::Ok; } static uint8_t JPEG_SOS = 0xDA; // returns 0 if the marker_type was not found size_t find_jpeg_marker_start(const std::vector<uint8_t>& data, uint8_t marker_type) { for (size_t i = 0; i < data.size() - 1; i++) { if (data[i]==0xFF && data[i+1]==marker_type) { return i; } } return 0; } Error HeifContext::encode_image_as_jpeg(const std::shared_ptr<HeifPixelImage>& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, enum heif_image_input_class input_class, std::shared_ptr<Image>& out_image) { heif_item_id image_id = m_heif_file->add_new_image("jpeg"); out_image = std::make_shared<Image>(this, image_id); m_top_level_images.push_back(out_image); m_all_images[image_id] = out_image; // --- check whether we have to convert the image color space heif_colorspace colorspace = image->get_colorspace(); heif_chroma chroma = image->get_chroma_format(); // JPEG always uses CCIR-601 heif_color_profile_nclx target_heif_nclx; target_heif_nclx.matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6; target_heif_nclx.color_primaries = heif_color_primaries_ITU_R_BT_601_6; target_heif_nclx.transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_601_6; target_heif_nclx.full_range_flag = true; auto target_nclx_profile = std::make_shared<color_profile_nclx>(); target_nclx_profile->set_from_heif_color_profile_nclx(&target_heif_nclx); if (encoder->plugin->plugin_api_version >= 2) { encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); } else { encoder->plugin->query_input_colorspace(&colorspace, &chroma); } std::shared_ptr<HeifPixelImage> src_image; if (colorspace != image->get_colorspace() || chroma != image->get_chroma_format() || !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), &target_heif_nclx)) { // @TODO: use color profile when converting int output_bpp = 0; // same as input src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, output_bpp, options.color_conversion_options); if (!src_image) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } } else { src_image = image; } // --- choose which color profile to put into 'colr' box if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { auto icc_profile = src_image->get_color_profile_icc(); if (icc_profile) { m_heif_file->set_color_profile(image_id, icc_profile); } if (// target_nclx_profile && (!icc_profile || (options.version >= 3 && options.save_two_colr_boxes_when_ICC_and_nclx_available))) { m_heif_file->set_color_profile(image_id, target_nclx_profile); } } // --- if there is an alpha channel, add it as an additional image if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { // --- generate alpha image // TODO: can we directly code a monochrome image instead of the dummy color channels? std::shared_ptr<HeifPixelImage> alpha_image; alpha_image = create_alpha_image_from_image_alpha_channel(src_image); // --- encode the alpha image std::shared_ptr<HeifContext::Image> heif_alpha_image; Error error = encode_image_as_jpeg(alpha_image, encoder, options, heif_image_input_class_alpha, heif_alpha_image); if (error) { return error; } m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"); if (src_image->is_premultiplied_alpha()) { m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); } } heif_image c_api_image; c_api_image.image = src_image; struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); if (err.code) { return Error(err.code, err.subcode, err.message); } std::vector<uint8_t> vec; for (;;) { uint8_t* data; int size; encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); if (data == nullptr) { break; } size_t oldsize = vec.size(); vec.resize(oldsize + size); memcpy(vec.data() + oldsize, data, size); } // Optional: split the JPEG data into a jpgC box and the actual image data. // Currently disabled because not supported yet in other decoders. if (false) { size_t pos = find_jpeg_marker_start(vec, JPEG_SOS); if (pos > 0) { std::vector<uint8_t> jpgC_data(vec.begin(), vec.begin() + pos); auto jpgC = std::make_shared<Box_jpgC>(); jpgC->set_data(jpgC_data); auto ipma_box = m_heif_file->get_ipma_box(); int index = m_heif_file->get_ipco_box()->find_or_append_child_box(jpgC); ipma_box->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); std::vector<uint8_t> image_data(vec.begin() + pos, vec.end()); vec = std::move(image_data); } } m_heif_file->append_iloc_data(image_id, vec); #if 0 // TODO: extract 'jpgC' header data #endif uint32_t input_width, input_height; input_width = src_image->get_width(); input_height = src_image->get_height(); // Note: 'ispe' must be before the transformation properties m_heif_file->add_ispe_property(image_id, input_width, input_height); uint32_t encoded_width = input_width, encoded_height = input_height; if (encoder->plugin->plugin_api_version >= 3 && encoder->plugin->query_encoded_size != nullptr) { encoder->plugin->query_encoded_size(encoder->encoder, input_width, input_height, &encoded_width, &encoded_height); } if (input_width != encoded_width || input_height != encoded_height) { m_heif_file->add_clap_property(image_id, input_width, input_height, encoded_width, encoded_height); // MIAF 7.3.6.7 // This is according to MIAF without Amd2. With Amd2, the restriction has been liften and the image is MIAF compatible. // We might remove this code at a later point in time when MIAF Amd2 is in wide use. if (!is_integer_multiple_of_chroma_size(input_width, input_height, src_image->get_chroma_format())) { out_image->mark_not_miaf_compatible(); } } m_heif_file->add_orientation_properties(image_id, options.image_orientation); write_image_metadata(src_image, image_id); return Error::Ok; } Error HeifContext::encode_image_as_uncompressed(const std::shared_ptr<HeifPixelImage>& src_image, struct heif_encoder* encoder, const struct heif_encoding_options& options, enum heif_image_input_class input_class, std::shared_ptr<Image>& out_image) { #if WITH_UNCOMPRESSED_CODEC heif_item_id image_id = m_heif_file->add_new_image("unci"); out_image = std::make_shared<Image>(this, image_id); Error err = UncompressedImageCodec::encode_uncompressed_image(m_heif_file, src_image, encoder->encoder, options, out_image); m_top_level_images.push_back(out_image); m_all_images[image_id] = out_image; #endif //write_image_metadata(src_image, image_id); return Error::Ok; } Error HeifContext::encode_image_as_mask(const std::shared_ptr<HeifPixelImage>& src_image, struct heif_encoder* encoder, const struct heif_encoding_options& options, enum heif_image_input_class input_class, std::shared_ptr<Image>& out_image) { heif_item_id image_id = m_heif_file->add_new_hidden_image("mski"); out_image = std::make_shared<Image>(this, image_id); Error err = MaskImageCodec::encode_mask_image(m_heif_file, src_image, encoder->encoder, options, out_image); m_top_level_images.push_back(out_image); m_all_images[image_id] = out_image; write_image_metadata(src_image, image_id); return Error::Ok; } void HeifContext::set_primary_image(const std::shared_ptr<Image>& image) { // update heif context if (m_primary_image) { m_primary_image->set_primary(false); } image->set_primary(true); m_primary_image = image; // update pitm box in HeifFile m_heif_file->set_primary_item_id(image->get_id()); } Error HeifContext::set_primary_item(heif_item_id id) { auto iter = m_all_images.find(id); if (iter == m_all_images.end()) { return Error(heif_error_Usage_error, heif_suberror_No_or_invalid_primary_item, "Cannot set primary item as the ID does not exist."); } set_primary_image(iter->second); return Error::Ok; } Error HeifContext::assign_thumbnail(const std::shared_ptr<Image>& master_image, const std::shared_ptr<Image>& thumbnail_image) { m_heif_file->add_iref_reference(thumbnail_image->get_id(), fourcc("thmb"), {master_image->get_id()}); return Error::Ok; } Error HeifContext::encode_thumbnail(const std::shared_ptr<HeifPixelImage>& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, int bbox_size, std::shared_ptr<Image>& out_thumbnail_handle) { Error error; int orig_width = image->get_width(); int orig_height = image->get_height(); int thumb_width, thumb_height; if (orig_width <= bbox_size && orig_height <= bbox_size) { // original image is smaller than thumbnail size -> do not encode any thumbnail out_thumbnail_handle.reset(); return Error::Ok; } else if (orig_width > orig_height) { thumb_height = orig_height * bbox_size / orig_width; thumb_width = bbox_size; } else { thumb_width = orig_width * bbox_size / orig_height; thumb_height = bbox_size; } // round size to even width and height thumb_width &= ~1; thumb_height &= ~1; std::shared_ptr<HeifPixelImage> thumbnail_image; error = image->scale_nearest_neighbor(thumbnail_image, thumb_width, thumb_height); if (error) { return error; } error = encode_image(thumbnail_image, encoder, options, heif_image_input_class_thumbnail, out_thumbnail_handle); if (error) { return error; } return error; } Error HeifContext::add_exif_metadata(const std::shared_ptr<Image>& master_image, const void* data, int size) { // find location of TIFF header uint32_t offset = 0; const char* tiffmagic1 = "MM\0*"; const char* tiffmagic2 = "II*\0"; while (offset + 4 < (unsigned int) size) { if (!memcmp((uint8_t*) data + offset, tiffmagic1, 4)) break; if (!memcmp((uint8_t*) data + offset, tiffmagic2, 4)) break; offset++; } if (offset >= (unsigned int) size) { return Error(heif_error_Usage_error, heif_suberror_Invalid_parameter_value, "Could not find location of TIFF header in Exif metadata."); } std::vector<uint8_t> data_array; data_array.resize(size + 4); data_array[0] = (uint8_t) ((offset >> 24) & 0xFF); data_array[1] = (uint8_t) ((offset >> 16) & 0xFF); data_array[2] = (uint8_t) ((offset >> 8) & 0xFF); data_array[3] = (uint8_t) ((offset) & 0xFF); memcpy(data_array.data() + 4, data, size); return add_generic_metadata(master_image, data_array.data(), (int) data_array.size(), "Exif", nullptr, nullptr, heif_metadata_compression_off, nullptr); } Error HeifContext::add_XMP_metadata(const std::shared_ptr<Image>& master_image, const void* data, int size, heif_metadata_compression compression) { return add_generic_metadata(master_image, data, size, "mime", "application/rdf+xml", nullptr, compression, nullptr); } Error HeifContext::add_generic_metadata(const std::shared_ptr<Image>& master_image, const void* data, int size, const char* item_type, const char* content_type, const char* item_uri_type, heif_metadata_compression compression, heif_item_id* out_item_id) { // create an infe box describing what kind of data we are storing (this also creates a new ID) auto metadata_infe_box = m_heif_file->add_new_infe_box(item_type); metadata_infe_box->set_hidden_item(true); if (content_type != nullptr) { metadata_infe_box->set_content_type(content_type); } heif_item_id metadata_id = metadata_infe_box->get_item_ID(); if (out_item_id) { *out_item_id = metadata_id; } // we assign this data to the image m_heif_file->add_iref_reference(metadata_id, fourcc("cdsc"), {master_image->get_id()}); // --- metadata compression if (compression == heif_metadata_compression_auto) { compression = heif_metadata_compression_off; // currently, we don't use header compression by default } // only set metadata compression for MIME type data which has 'content_encoding' field if (compression != heif_metadata_compression_off && strcmp(item_type, "mime") != 0) { // TODO: error, compression not supported } std::vector<uint8_t> data_array; if (compression == heif_metadata_compression_zlib) { #if HAVE_ZLIB data_array = compress_zlib((const uint8_t*) data, size); metadata_infe_box->set_content_encoding("compress_zlib"); #else return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_header_compression_method); #endif } else if (compression == heif_metadata_compression_deflate) { #if HAVE_ZLIB data_array = compress_zlib((const uint8_t*) data, size); metadata_infe_box->set_content_encoding("deflate"); #else return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_header_compression_method); #endif } else { // uncompressed data, plain copy data_array.resize(size); memcpy(data_array.data(), data, size); } // copy the data into the file, store the pointer to it in an iloc box entry m_heif_file->append_iloc_data(metadata_id, data_array); return Error::Ok; } heif_property_id HeifContext::add_property(heif_item_id targetItem, std::shared_ptr<Box> property, bool essential) { heif_property_id id = m_heif_file->add_property(targetItem, property, essential); return id; }