This source file includes following definitions.
- release
- readvec_signed
- readvec
- get_width
- get_height
- get_rows
- get_columns
- parse
- dump
- get_canvas_width
- get_canvas_height
- get_num_offsets
- parse
- dump
- get_background_color
- get_offset
- read
- read_from_file
- read_from_memory
- reset_to_empty_heif
- write
- debug_dump_boxes
- register_decoder
- get_decoder
- item_type_is_image
- remove_top_level_image
- interpret_heif_file
- m_id
- decode_image
- decode_image
- decode_full_grid_image
- decode_and_paste_tile_image
- decode_derived_image
- decode_overlay_image
- create_alpha_image_from_image_alpha_channel
- set_preencoded_hevc_image
- encode_image
- encode_image_as_hevc
- set_primary_image
- set_primary_item
- assign_thumbnail
- encode_thumbnail
- add_exif_metadata
- add_XMP_metadata
#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif
#include <assert.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <limits>
#include <utility>
#include <math.h>
#include <deque>
#if ENABLE_PARALLEL_TILE_DECODING
#include <future>
#endif
#include "heif_context.h"
#include "heif_file.h"
#include "heif_image.h"
#include "heif_api_structs.h"
#include "heif_limits.h"
#include "heif_hevc.h"
#include "heif_plugin_registry.h"
using namespace heif;
heif_encoder::heif_encoder(std::shared_ptr<heif::HeifContext> _context,
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);
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::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 { return m_rows; }
uint16_t get_columns() const { return m_columns; }
private:
uint16_t m_rows;
uint16_t m_columns;
uint32_t m_output_width;
uint32_t m_output_height;
};
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];
(void)version;
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::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_grid_data,
"Overlay image data incomplete");
if (data.size() < 2 + 4*2) {
return eofError;
}
m_version = data[0];
m_flags = data[1];
if (m_version != 0) {
std::stringstream sstr;
sstr << "Overlay image data version " << m_version << " is not implemented yet";
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_data_version,
sstr.str());
}
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);
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>=0 && image_index<m_offsets.size());
assert(x && y);
*x = m_offsets[image_index].x;
*y = m_offsets[image_index].y;
}
HeifContext::HeifContext()
{
reset_to_empty_heif();
}
HeifContext::~HeifContext()
{
}
Error HeifContext::read(std::shared_ptr<StreamReader> reader)
{
m_heif_file = std::make_shared<HeifFile>();
Error err = m_heif_file->read(reader);
if (err) {
return err;
}
return interpret_heif_file();
}
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;
}
return interpret_heif_file();
}
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;
}
return interpret_heif_file();
}
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::write(StreamWriter& writer)
{
m_heif_file->write(writer);
}
std::string HeifContext::debug_dump_boxes() const
{
return m_heif_file->debug_dump_boxes();
}
void HeifContext::register_decoder(const heif_decoder_plugin* decoder_plugin)
{
if (decoder_plugin->init_plugin) {
(*decoder_plugin->init_plugin)();
}
m_decoder_plugins.insert(decoder_plugin);
}
const struct heif_decoder_plugin* HeifContext::get_decoder(enum heif_compression_format type) const
{
int highest_priority = 0;
const struct heif_decoder_plugin* best_plugin = nullptr;
best_plugin = heif::get_decoder(type);
if (best_plugin != nullptr) {
highest_priority = best_plugin->does_support_format(type);
}
for (const auto* plugin : m_decoder_plugins) {
int priority = plugin->does_support_format(type);
if (priority > highest_priority) {
highest_priority = priority;
best_plugin = plugin;
}
}
return best_plugin;
}
static bool item_type_is_image(const std::string& item_type)
{
return (item_type=="hvc1" ||
item_type=="grid" ||
item_type=="iden" ||
item_type=="iovl");
}
void HeifContext::remove_top_level_image(std::shared_ptr<Image> image)
{
std::vector<std::shared_ptr<Image>> new_list;
for (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()
{
m_all_images.clear();
m_top_level_images.clear();
m_primary_image.reset();
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) {
continue;
}
if (item_type_is_image(infe_box->get_item_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");
}
auto iref_box = m_heif_file->get_iref_box();
if (iref_box) {
for (auto& pair : m_all_images) {
auto& image = pair.second;
uint32_t type = iref_box->get_reference_type(image->get_id());
if (type==fourcc("thmb")) {
std::vector<heif_item_id> refs = iref_box->get_references(image->get_id());
if (refs.size() != 1) {
return Error(heif_error_Invalid_input,
heif_suberror_Unspecified,
"Too many thumbnail references");
}
image->set_is_thumbnail_of(refs[0]);
auto master_iter = m_all_images.find(refs[0]);
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");
}
master_iter->second->add_thumbnail(image);
remove_top_level_image(image);
}
else if (type==fourcc("auxl")) {
std::vector<Box_ipco::Property> 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.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 = iref_box->get_references(image->get_id());
if (refs.size() != 1) {
return Error(heif_error_Invalid_input,
heif_suberror_Unspecified,
"Too many auxiliary image references");
}
if (auxC_property->get_aux_type() == "urn:mpeg:avc:2015:auxid:1" ||
auxC_property->get_aux_type() == "urn:mpeg:hevc:2015:auxid:1") {
image->set_is_alpha_channel_of(refs[0]);
auto master_iter = m_all_images.find(refs[0]);
master_iter->second->set_alpha_channel(image);
}
if (auxC_property->get_aux_type() == "urn:mpeg:hevc:2015:auxid:2") {
image->set_is_depth_channel_of(refs[0]);
auto master_iter = m_all_images.find(refs[0]);
master_iter->second->set_depth_channel(image);
auto subtypes = auxC_property->get_subtypes();
std::vector<std::shared_ptr<SEIMessage>> sei_messages;
Error 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);
}
}
}
remove_top_level_image(image);
}
else {
}
}
}
for (auto& pair : m_all_images) {
auto& image = pair.second;
std::vector<Box_ipco::Property> 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.property);
if (ispe) {
uint32_t width = ispe->get_width();
uint32_t height = ispe->get_height();
if (width >= static_cast<uint32_t>(MAX_IMAGE_WIDTH) ||
height >= static_cast<uint32_t>(MAX_IMAGE_HEIGHT)) {
std::stringstream sstr;
sstr << "Image size " << width << "x" << height << " exceeds the maximum image size "
<< MAX_IMAGE_WIDTH << "x" << MAX_IMAGE_HEIGHT << "\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) {
auto clap = std::dynamic_pointer_cast<Box_clap>(prop.property);
if (clap) {
image->set_resolution( clap->get_width_rounded(),
clap->get_height_rounded() );
}
auto irot = std::dynamic_pointer_cast<Box_irot>(prop.property);
if (irot) {
if (irot->get_rotation()==90 ||
irot->get_rotation()==270) {
image->set_resolution( image->get_height(),
image->get_width() );
}
}
}
}
}
for (heif_item_id id : image_IDs) {
std::string item_type = m_heif_file->get_item_type(id);
std::string content_type = m_heif_file->get_content_type(id);
if (item_type == "Exif" ||
(item_type=="mime" && content_type=="application/rdf+xml")) {
std::shared_ptr<ImageMetadata> metadata = std::make_shared<ImageMetadata>();
metadata->item_id = id;
metadata->item_type = item_type;
metadata->content_type = content_type;
Error err = m_heif_file->get_compressed_image_data(id, &(metadata->m_data));
if (err) {
return err;
}
if (iref_box) {
uint32_t type = iref_box->get_reference_type(id);
if (type == fourcc("cdsc")) {
std::vector<uint32_t> refs = iref_box->get_references(id);
if (refs.size() != 1) {
return Error(heif_error_Invalid_input,
heif_suberror_Unspecified,
"Exif data not correctly assigned to image");
}
uint32_t exif_image_id = refs[0];
auto img_iter = m_all_images.find(exif_image_id);
if (img_iter == m_all_images.end()) {
return Error(heif_error_Invalid_input,
heif_suberror_Nonexisting_item_referenced,
"Exif data assigned to non-existing image");
}
img_iter->second->add_metadata(metadata);
}
}
}
}
return Error::Ok;
}
HeifContext::Image::Image(HeifContext* context, heif_item_id id)
: m_heif_context(context),
m_id(id)
{
}
HeifContext::Image::~Image()
{
}
Error HeifContext::Image::decode_image(std::shared_ptr<HeifPixelImage>& img,
heif_colorspace colorspace,
heif_chroma chroma,
const struct heif_decoding_options* options) const
{
Error err = m_heif_context->decode_image(m_id, img, options);
if (err) {
return err;
}
heif_chroma target_chroma = (chroma == heif_chroma_undefined ?
img->get_chroma_format() :
chroma);
heif_colorspace target_colorspace = (colorspace == heif_colorspace_undefined ?
img->get_colorspace() :
colorspace);
bool different_chroma = (target_chroma != img->get_chroma_format());
bool different_colorspace = (target_colorspace != img->get_colorspace());
if (different_chroma || different_colorspace) {
img = img->convert_colorspace(target_colorspace, target_chroma);
if (!img) {
return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion);
}
}
return err;
}
Error HeifContext::decode_image(heif_item_id ID,
std::shared_ptr<HeifPixelImage>& img,
const struct heif_decoding_options* options) const
{
std::string image_type = m_heif_file->get_item_type(ID);
Error error;
if (image_type == "hvc1") {
const struct heif_decoder_plugin* decoder_plugin = get_decoder(heif_compression_HEVC);
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(ID, &data);
if (error) {
return error;
}
void* decoder;
struct heif_error err = decoder_plugin->new_decoder(&decoder);
if (err.code != heif_error_Ok) {
return Error(err.code, err.subcode, err.message);
}
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) {
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);
#if 0
FILE* fh = fopen("out.bin", "wb");
fwrite(data.data(), 1, data.size(), fh);
fclose(fh);
#endif
}
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);
if (error) {
return error;
}
}
else if (image_type == "iden") {
error = decode_derived_image(ID, img);
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);
if (error) {
return error;
}
}
else {
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_image_type);
}
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 = alpha_image->decode_image(alpha);
if (err) {
return err;
}
img->transfer_plane_from_image_as(alpha, heif_channel_Y, heif_channel_Alpha);
}
}
if (!options || options->ignore_transformations == false) {
std::vector<Box_ipco::Property> 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) {
auto rot = std::dynamic_pointer_cast<Box_irot>(property.property);
if (rot) {
std::shared_ptr<HeifPixelImage> rotated_img;
error = img->rotate_ccw(rot->get_rotation(), rotated_img);
if (error) {
return error;
}
img = rotated_img;
}
auto mirror = std::dynamic_pointer_cast<Box_imir>(property.property);
if (mirror) {
error = img->mirror_inplace(mirror->get_mirror_axis() == Box_imir::MirrorAxis::Horizontal);
if (error) {
return error;
}
}
auto clap = std::dynamic_pointer_cast<Box_clap>(property.property);
if (clap) {
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;
}
}
}
return Error::Ok;
}
Error HeifContext::decode_full_grid_image(heif_item_id ID,
std::shared_ptr<HeifPixelImage>& img,
const std::vector<uint8_t>& grid_data) const
{
ImageGrid grid;
grid.parse(grid_data);
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);
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());
}
int w = grid.get_width();
int h = grid.get_height();
int bpp = 8;
if (w >= MAX_IMAGE_WIDTH || h >= MAX_IMAGE_HEIGHT) {
std::stringstream sstr;
sstr << "Image size " << w << "x" << h << " exceeds the maximum image size "
<< MAX_IMAGE_WIDTH << "x" << MAX_IMAGE_HEIGHT << "\n";
return Error(heif_error_Memory_allocation_error,
heif_suberror_Security_limit_exceeded,
sstr.str());
}
img = std::make_shared<HeifPixelImage>();
img->create(w,h,
heif_colorspace_YCbCr,
heif_chroma_420);
img->add_plane(heif_channel_Y, w,h, bpp);
img->add_plane(heif_channel_Cb, w/2,h/2, bpp);
img->add_plane(heif_channel_Cr, w/2,h/2, bpp);
int y0=0;
int reference_idx = 0;
#if ENABLE_PARALLEL_TILE_DECODING
struct tile_data {
heif_item_id tileID;
int x_origin,y_origin;
};
std::deque<tile_data> tiles;
tiles.resize(grid.get_rows() * grid.get_columns() );
std::deque<std::future<Error> > errs;
#endif
for (int y=0;y<grid.get_rows();y++) {
int x0=0;
int tile_height=0;
for (int x=0;x<grid.get_columns();x++) {
heif_item_id tileID = image_references[reference_idx];
const std::shared_ptr<Image> tileImg = m_all_images.find(tileID)->second;
int src_width = tileImg->get_width();
int src_height = tileImg->get_height();
#if ENABLE_PARALLEL_TILE_DECODING
tiles[x+y*grid.get_columns()] = tile_data { tileID, x0,y0 };
#else
Error err = decode_and_paste_tile_image(tileID, img, x0,y0);
if (err) {
return err;
}
#endif
x0 += src_width;
tile_height = src_height;
reference_idx++;
}
y0 += tile_height;
}
#if ENABLE_PARALLEL_TILE_DECODING
while (tiles.empty()==false) {
if (errs.size() >= (size_t)m_max_decoding_threads) {
Error e = errs.front().get();
if (e) {
return e;
}
errs.pop_front();
}
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, data.x_origin,data.y_origin) );
}
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,
std::shared_ptr<HeifPixelImage> img,
int x0,int y0) const
{
std::shared_ptr<HeifPixelImage> tile_img;
Error err = decode_image(tileID, tile_img);
if (err != Error::Ok) {
return err;
}
const int w = img->get_width();
const int h = img->get_height();
int src_width = tile_img->get_width();
int src_height = tile_img->get_height();
assert(src_width >= 0);
assert(src_height >= 0);
for (heif_channel channel : { heif_channel_Y, heif_channel_Cb, heif_channel_Cr }) {
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 copy_width = std::min(src_width, w - x0);
int copy_height = std::min(src_height, h - y0);
int xs=x0, ys=y0;
if (channel != heif_channel_Y) {
copy_width /= 2;
copy_height /= 2;
xs /= 2;
ys /= 2;
}
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
{
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);
if ((int)image_references.size() != 1) {
return Error(heif_error_Invalid_input,
heif_suberror_Missing_grid_images,
"'iden' image with more than one reference image");
}
heif_item_id reference_image_id = image_references[0];
Error error = decode_image(reference_image_id, img);
return error;
}
Error HeifContext::decode_overlay_image(heif_item_id ID,
std::shared_ptr<HeifPixelImage>& img,
const std::vector<uint8_t>& overlay_data) const
{
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);
ImageOverlay overlay;
overlay.parse(image_references.size(), overlay_data);
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");
}
int w = overlay.get_canvas_width();
int h = overlay.get_canvas_height();
if (w >= MAX_IMAGE_WIDTH || h >= MAX_IMAGE_HEIGHT) {
std::stringstream sstr;
sstr << "Image size " << w << "x" << h << " exceeds the maximum image size "
<< MAX_IMAGE_WIDTH << "x" << MAX_IMAGE_HEIGHT << "\n";
return Error(heif_error_Memory_allocation_error,
heif_suberror_Security_limit_exceeded,
sstr.str());
}
img = std::make_shared<HeifPixelImage>();
img->create(w,h,
heif_colorspace_RGB,
heif_chroma_444);
img->add_plane(heif_channel_R,w,h,8);
img->add_plane(heif_channel_G,w,h,8);
img->add_plane(heif_channel_B,w,h,8);
uint16_t bkg_color[4];
overlay.get_background_color(bkg_color);
Error 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(image_references[i], overlay_img);
if (err != Error::Ok) {
return err;
}
overlay_img = overlay_img->convert_colorspace(heif_colorspace_RGB, heif_chroma_444);
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) {
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)
{
int chroma_width = (image->get_width() +1)/2;
int chroma_height = (image->get_height()+1)/2;
std::shared_ptr<HeifPixelImage> alpha_image = std::make_shared<HeifPixelImage>();
alpha_image->create(image->get_width(), image->get_height(),
heif_colorspace_YCbCr, heif_chroma_420);
alpha_image->copy_new_plane_from(image, heif_channel_Alpha, heif_channel_Y);
alpha_image->fill_new_plane(heif_channel_Cb, 128, chroma_width, chroma_height);
alpha_image->fill_new_plane(heif_channel_Cr, 128, chroma_width, chroma_height);
return alpha_image;
}
void HeifContext::Image::set_preencoded_hevc_image(const std::vector<uint8_t>& data)
{
m_heif_context->m_heif_file->add_hvcC_property(m_id);
int state=0;
bool first=true;
bool eof=false;
int prev_start_code_start = -1;
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) {
}
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:
m_heif_context->m_heif_file->append_hvcC_nal_data(m_id, 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;
}
}
}
Error HeifContext::encode_image(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;
switch (encoder->plugin->compression_format) {
case heif_compression_HEVC:
{
heif_item_id image_id = m_heif_file->add_new_image("hvc1");
out_image = std::make_shared<Image>(this, image_id);
m_top_level_images.push_back(out_image);
error = out_image->encode_image_as_hevc(pixel_image,
encoder,
options,
heif_image_input_class_normal);
}
break;
default:
return Error(heif_error_Encoder_plugin_error, heif_suberror_Unsupported_codec);
}
return error;
}
Error HeifContext::Image::encode_image_as_hevc(std::shared_ptr<HeifPixelImage> image,
struct heif_encoder* encoder,
const struct heif_encoding_options* options,
enum heif_image_input_class input_class)
{
heif_colorspace colorspace = image->get_colorspace();
heif_chroma chroma = image->get_chroma_format();
encoder->plugin->query_input_colorspace(&colorspace, &chroma);
if (colorspace != image->get_colorspace() ||
chroma != image->get_chroma_format()) {
image = image->convert_colorspace(colorspace, chroma);
}
m_width = image->get_width(heif_channel_Y);
m_height = image->get_height(heif_channel_Y);
if (options->save_alpha_channel && image->has_channel(heif_channel_Alpha)) {
std::shared_ptr<HeifPixelImage> alpha_image;
alpha_image = create_alpha_image_from_image_alpha_channel(image);
heif_item_id alpha_image_id = m_heif_context->m_heif_file->add_new_image("hvc1");
std::shared_ptr<HeifContext::Image> heif_alpha_image;
heif_alpha_image = std::make_shared<Image>(m_heif_context, alpha_image_id);
Error error = heif_alpha_image->encode_image_as_hevc(alpha_image, encoder, options,
heif_image_input_class_alpha);
if (error) {
return error;
}
m_heif_context->m_heif_file->add_iref_reference(alpha_image_id, fourcc("auxl"), { m_id });
m_heif_context->m_heif_file->set_auxC_property(alpha_image_id, "urn:mpeg:hevc:2015:auxid:1");
}
m_heif_context->m_heif_file->add_hvcC_property(m_id);
heif_image c_api_image;
c_api_image.image = image;
encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class);
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) {
int width,height;
Box_hvcC::configuration config;
parse_sps_for_hvcC_configuration(data, size, &config, &width, &height);
m_heif_context->m_heif_file->set_hvcC_configuration(m_id, config);
m_heif_context->m_heif_file->add_ispe_property(m_id, width, height);
}
switch (data[0] >> 1) {
case 0x20:
case 0x21:
case 0x22:
m_heif_context->m_heif_file->append_hvcC_nal_data(m_id, data, size);
break;
default:
m_heif_context->m_heif_file->append_iloc_data_with_4byte_size(m_id, data, size);
}
}
return Error::Ok;
}
void HeifContext::set_primary_image(std::shared_ptr<Image> image)
{
if (m_primary_image) {
m_primary_image->set_primary(false);
}
image->set_primary(true);
m_primary_image = image;
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(std::shared_ptr<Image> master_image,
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(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) {
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;
}
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(std::shared_ptr<Image> master_image, const void* data, int size)
{
auto metadata_infe_box = m_heif_file->add_new_infe_box("Exif");
metadata_infe_box->set_hidden_item(true);
heif_item_id metadata_id = metadata_infe_box->get_item_ID();
m_heif_file->add_iref_reference(metadata_id,
fourcc("cdsc"), { master_image->get_id() });
std::vector<uint8_t> data_array;
data_array.resize(size+4);
data_array[0] = 0;
data_array[1] = 0;
data_array[2] = 0;
data_array[3] = 0;
memcpy(data_array.data()+4, data, size);
m_heif_file->append_iloc_data(metadata_id, data_array);
return Error::Ok;
}
Error HeifContext::add_XMP_metadata(std::shared_ptr<Image> master_image, const void* data, int size)
{
auto metadata_infe_box = m_heif_file->add_new_infe_box("mime");
metadata_infe_box->set_content_type("application/rdf+xml");
metadata_infe_box->set_hidden_item(true);
heif_item_id metadata_id = metadata_infe_box->get_item_ID();
m_heif_file->add_iref_reference(metadata_id,
fourcc("cdsc"), { master_image->get_id() });
std::vector<uint8_t> data_array;
data_array.resize(size);
memcpy(data_array.data(), data, size);
m_heif_file->append_iloc_data(metadata_id, data_array);
return Error::Ok;
}