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;
}