libheif/file.cc (1,648 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 "file.h"
#include "box.h"
#include "libheif/heif.h"
#include "libheif/heif_properties.h"
#include "compression.h"
#include "codecs/jpeg2000.h"
#include "codecs/jpeg.h"
#include "codecs/vvc.h"
#include "codecs/uncompressed_box.h"
#include <cstdint>
#include <fstream>
#include <limits>
#include <sstream>
#include <utility>
#include <cstring>
#include <cassert>
#include <algorithm>
#include "heif_limits.h"
#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER)
#ifndef NOMINMAX
#define NOMINMAX 1
#endif
#include <windows.h>
#endif
#if WITH_UNCOMPRESSED_CODEC
#include "codecs/uncompressed_image.h"
#endif
// TODO: make this a decoder option
#define STRICT_PARSING false
HeifFile::HeifFile() = default;
HeifFile::~HeifFile() = default;
std::vector<heif_item_id> HeifFile::get_item_IDs() const
{
std::vector<heif_item_id> IDs;
for (const auto& infe : m_infe_boxes) {
IDs.push_back(infe.second->get_item_ID());
}
return IDs;
}
std::shared_ptr<const Box_infe> HeifFile::get_infe_box(heif_item_id ID) const
{
auto iter = m_infe_boxes.find(ID);
if (iter == m_infe_boxes.end()) {
return nullptr;
}
return iter->second;
}
std::shared_ptr<Box_infe> HeifFile::get_infe_box(heif_item_id ID)
{
auto iter = m_infe_boxes.find(ID);
if (iter == m_infe_boxes.end()) {
return nullptr;
}
return iter->second;
}
Error HeifFile::read_from_file(const char* input_filename)
{
#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER)
auto input_stream_istr = std::unique_ptr<std::istream>(new std::ifstream(convert_utf8_path_to_utf16(input_filename).c_str(), std::ios_base::binary));
#else
auto input_stream_istr = std::unique_ptr<std::istream>(new std::ifstream(input_filename, std::ios_base::binary));
#endif
if (!input_stream_istr->good()) {
std::stringstream sstr;
sstr << "Error opening file: " << strerror(errno) << " (" << errno << ")\n";
return Error(heif_error_Input_does_not_exist, heif_suberror_Unspecified, sstr.str());
}
auto input_stream = std::make_shared<StreamReader_istream>(std::move(input_stream_istr));
return read(input_stream);
}
Error HeifFile::read_from_memory(const void* data, size_t size, bool copy)
{
auto input_stream = std::make_shared<StreamReader_memory>((const uint8_t*) data, size, copy);
return read(input_stream);
}
Error HeifFile::read(const std::shared_ptr<StreamReader>& reader)
{
m_input_stream = reader;
uint64_t maxSize = std::numeric_limits<int64_t>::max();
BitstreamRange range(m_input_stream, maxSize);
Error error = parse_heif_file(range);
return error;
}
void HeifFile::add_movie_box()
{
m_moov_box = std::make_shared<Box_moov>();
m_mvhd_box = std::make_shared<Box_mvhd>();
m_trak_box = std::make_shared<Box_trak>();
m_tkhd_box = std::make_shared<Box_tkhd>();
m_mdia_box = std::make_shared<Box_mdia>();
m_mdhd_box = std::make_shared<Box_mdhd>();
m_minf_box = std::make_shared<Box_minf>();
m_vmhd_box = std::make_shared<Box_vmhd>();
m_dinf_box = std::make_shared<Box_dinf>();
m_dref_box = std::make_shared<Box_dref>();
m_url_box = std::make_shared<Box_url>();
m_stbl_box = std::make_shared<Box_stbl>();
m_stsd_box = std::make_shared<Box_stsd>();
m_hvc1_box = std::make_shared<Box_hvc1>();
m_hvcC_box = std::make_shared<Box_hvcC>();
m_ccst_box = std::make_shared<Box_ccst>();
m_stsz_box = std::make_shared<Box_stsz>();
m_stts_box = std::make_shared<Box_stts>();
m_stsc_box = std::make_shared<Box_stsc>();
m_stco_box = std::make_shared<Box_stco>();
m_stss_box = std::make_shared<Box_stss>();
m_top_level_boxes.push_back(m_moov_box);
m_moov_box->append_child_box(m_mvhd_box);
m_moov_box->append_child_box(m_trak_box);
m_trak_box->append_child_box(m_tkhd_box);
m_trak_box->append_child_box(m_mdia_box);
m_mdia_box->append_child_box(m_mdhd_box);
m_mdia_box->append_child_box(m_hdlr_box);
m_mdia_box->append_child_box(m_minf_box);
m_minf_box->append_child_box(m_vmhd_box);
m_minf_box->append_child_box(m_dinf_box);
m_dinf_box->append_child_box(m_dref_box);
m_dref_box->append_child_box(m_url_box);
m_minf_box->append_child_box(m_stbl_box);
m_stbl_box->append_child_box(m_stsd_box);
m_stsd_box->append_child_box(m_hvc1_box);
m_hvc1_box->append_child_box(m_hvcC_box);
m_hvc1_box->append_child_box(m_ccst_box);
m_stbl_box->append_child_box(m_stsz_box);
m_stbl_box->append_child_box(m_stts_box);
m_stbl_box->append_child_box(m_stsc_box);
m_stbl_box->append_child_box(m_stco_box);
m_stbl_box->append_child_box(m_stss_box);
m_iloc_box->set_moov_flag(true);
}
void HeifFile::new_empty_file()
{
m_input_stream.reset();
m_top_level_boxes.clear();
m_ftyp_box = std::make_shared<Box_ftyp>();
m_hdlr_box = std::make_shared<Box_hdlr>();
m_meta_box = std::make_shared<Box_meta>();
m_ipco_box = std::make_shared<Box_ipco>();
m_ipma_box = std::make_shared<Box_ipma>();
m_iloc_box = std::make_shared<Box_iloc>();
m_iinf_box = std::make_shared<Box_iinf>();
m_iprp_box = std::make_shared<Box_iprp>();
m_pitm_box = std::make_shared<Box_pitm>();
m_meta_box->append_child_box(m_hdlr_box);
m_meta_box->append_child_box(m_pitm_box);
m_meta_box->append_child_box(m_iloc_box);
m_meta_box->append_child_box(m_iinf_box);
m_meta_box->append_child_box(m_iprp_box);
m_iprp_box->append_child_box(m_ipco_box);
m_iprp_box->append_child_box(m_ipma_box);
m_infe_boxes.clear();
m_top_level_boxes.push_back(m_ftyp_box);
m_top_level_boxes.push_back(m_meta_box);
}
void HeifFile::set_brand(heif_compression_format format, bool miaf_compatible, bool moov_flag)
{
// Note: major brand should be repeated in the compatible brands, according to this:
// ISOBMFF (ISO/IEC 14496-12:2020) § K.4:
// NOTE This document requires that the major brand be repeated in the compatible-brands,
// but this requirement is relaxed in the 'profiles' parameter for compactness.
// See https://github.com/strukturag/libheif/issues/478
if(m_ftyp_box->get_compatiable_bands_avaliable()==false)
{
switch (format) {
case heif_compression_HEVC:
if(moov_flag)
{
m_ftyp_box->set_major_brand(fourcc("mif1"));
m_ftyp_box->set_minor_version(0);
m_ftyp_box->add_compatible_brand(fourcc("msf1"));
m_ftyp_box->add_compatible_brand(fourcc("hevc"));
m_ftyp_box->add_compatible_brand(fourcc("mif1"));
m_ftyp_box->add_compatible_brand(fourcc("heic"));
}
else
{
m_ftyp_box->set_major_brand(heif_brand2_heic);
m_ftyp_box->set_minor_version(0);
m_ftyp_box->add_compatible_brand(heif_brand2_mif1);
m_ftyp_box->add_compatible_brand(heif_brand2_heic);
}
break;
case heif_compression_AV1:
m_ftyp_box->set_major_brand(heif_brand2_avif);
m_ftyp_box->set_minor_version(0);
m_ftyp_box->add_compatible_brand(heif_brand2_avif);
m_ftyp_box->add_compatible_brand(heif_brand2_mif1);
break;
case heif_compression_VVC:
m_ftyp_box->set_major_brand(heif_brand2_vvic);
m_ftyp_box->set_minor_version(0);
m_ftyp_box->add_compatible_brand(heif_brand2_mif1);
m_ftyp_box->add_compatible_brand(heif_brand2_vvic);
break;
case heif_compression_JPEG:
m_ftyp_box->set_major_brand(heif_brand2_jpeg);
m_ftyp_box->set_minor_version(0);
m_ftyp_box->add_compatible_brand(heif_brand2_jpeg);
m_ftyp_box->add_compatible_brand(heif_brand2_mif1);
break;
case heif_compression_uncompressed:
// Not clear what the correct major brand should be
m_ftyp_box->set_major_brand(heif_brand2_mif2);
m_ftyp_box->set_minor_version(0);
m_ftyp_box->add_compatible_brand(heif_brand2_mif1);
break;
case heif_compression_JPEG2000:
case heif_compression_HTJ2K:
m_ftyp_box->set_major_brand(fourcc("j2ki"));
m_ftyp_box->set_minor_version(0);
m_ftyp_box->add_compatible_brand(fourcc("mif1"));
m_ftyp_box->add_compatible_brand(fourcc("j2ki"));
break;
default:
break;
}
if (miaf_compatible) {
m_ftyp_box->add_compatible_brand(heif_brand2_miaf);
}
}
if(m_ftyp_box->get_compatiable_bands_avaliable()==false)
{
m_ftyp_box->set_compatiable_bands_avaliable(true);
}
#if 0
// Temporarily disabled, pending resolution of
// https://github.com/strukturag/libheif/issues/888
if (get_num_images() == 1) {
// This could be overly conservative, but is safe
m_ftyp_box->add_compatible_brand(heif_brand2_1pic);
}
#endif
}
void HeifFile::write(StreamWriter& writer)
{
for (auto& box : m_top_level_boxes) {
box->derive_box_version_recursive();
box->write(writer);
}
m_iloc_box->write_mdat_after_iloc(writer);
}
void HeifFile::patch_stco_data(StreamWriter& writer)
{
uint64_t m_stco_mdat_offset;
auto items = m_iloc_box->get_items();
m_stco_mdat_offset = items[0].base_offset;
m_stco_box->patch_iloc_header(writer, m_stco_mdat_offset);
}
std::string HeifFile::debug_dump_boxes() const
{
std::stringstream sstr;
bool first = true;
for (const auto& box : m_top_level_boxes) {
// dump box content for debugging
if (first) {
first = false;
}
else {
sstr << "\n";
}
Indent indent;
sstr << box->dump(indent);
}
return sstr.str();
}
Error HeifFile::parse_heif_file(BitstreamRange& range)
{
// --- read all top-level boxes
for (;;) {
std::shared_ptr<Box> box;
Error error = Box::read(range, &box);
if (range.error() || range.eof()) {
break;
}
// When an EOF error is returned, this is not really a fatal exception,
// but simply the indication that we reached the end of the file.
// TODO: this design should be cleaned up
if (error.error_code == heif_error_Invalid_input && error.sub_error_code == heif_suberror_End_of_data) {
break;
}
if (error != Error::Ok) {
return error;
}
m_top_level_boxes.push_back(box);
// extract relevant boxes (ftyp, meta)
if (box->get_short_type() == fourcc("meta")) {
m_meta_box = std::dynamic_pointer_cast<Box_meta>(box);
}
if (box->get_short_type() == fourcc("ftyp")) {
m_ftyp_box = std::dynamic_pointer_cast<Box_ftyp>(box);
}
if (box->get_short_type() == fourcc("moov")) {
m_moov_box = std::dynamic_pointer_cast<Box_moov>(box);
}
}
// --- check whether this is a HEIF file and its structural format
if (!m_ftyp_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_ftyp_box);
}
if (
!m_ftyp_box->has_compatible_brand(fourcc("hevc")) &&
!m_ftyp_box->has_compatible_brand(fourcc("hevx")) &&
!m_ftyp_box->has_compatible_brand(fourcc("msf1")) &&
!m_ftyp_box->has_compatible_brand(heif_brand2_heic) &&
!m_ftyp_box->has_compatible_brand(heif_brand2_heix) &&
!m_ftyp_box->has_compatible_brand(heif_brand2_mif1) &&
!m_ftyp_box->has_compatible_brand(heif_brand2_avif) &&
!m_ftyp_box->has_compatible_brand(heif_brand2_1pic) &&
!m_ftyp_box->has_compatible_brand(heif_brand2_jpeg)) {
std::stringstream sstr;
sstr << "File does not include any supported brands.\n";
return Error(heif_error_Unsupported_filetype,
heif_suberror_Unspecified,
sstr.str());
}
if((m_ftyp_box->has_compatible_brand(fourcc("hevc"))||m_ftyp_box->has_compatible_brand(fourcc("hevx"))) && m_moov_box)
{
// if(m_tkhd_box && (m_tkhd_box->get_flags()&0x01))
set_moov_flag(true);
// else
// set_moov_flag(false);
}
else
{
set_moov_flag(false);
}
if (!m_meta_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_meta_box);
}
m_hdlr_box = std::dynamic_pointer_cast<Box_hdlr>(m_meta_box->get_child_box(fourcc("hdlr")));
if (STRICT_PARSING && !m_hdlr_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_hdlr_box);
}
if (m_hdlr_box &&
m_hdlr_box->get_handler_type() != fourcc("pict")) {
return Error(heif_error_Invalid_input,
heif_suberror_No_pict_handler);
}
// --- find mandatory boxes needed for image decoding
m_pitm_box = std::dynamic_pointer_cast<Box_pitm>(m_meta_box->get_child_box(fourcc("pitm")));
if (!m_pitm_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_pitm_box);
}
m_iprp_box = std::dynamic_pointer_cast<Box_iprp>(m_meta_box->get_child_box(fourcc("iprp")));
if (!m_iprp_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_iprp_box);
}
m_ipco_box = std::dynamic_pointer_cast<Box_ipco>(m_iprp_box->get_child_box(fourcc("ipco")));
if (!m_ipco_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_ipco_box);
}
auto ipma_boxes = m_iprp_box->get_typed_child_boxes<Box_ipma>(fourcc("ipma"));
if (ipma_boxes.empty()) {
return Error(heif_error_Invalid_input,
heif_suberror_No_ipma_box);
}
for (size_t i=1;i<ipma_boxes.size();i++) {
ipma_boxes[0]->insert_entries_from_other_ipma_box(*ipma_boxes[i]);
}
m_ipma_box = ipma_boxes[0];
m_iloc_box = std::dynamic_pointer_cast<Box_iloc>(m_meta_box->get_child_box(fourcc("iloc")));
if (!m_iloc_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_iloc_box);
}
m_idat_box = std::dynamic_pointer_cast<Box_idat>(m_meta_box->get_child_box(fourcc("idat")));
m_iref_box = std::dynamic_pointer_cast<Box_iref>(m_meta_box->get_child_box(fourcc("iref")));
if (m_iref_box) {
Error error = check_for_ref_cycle(get_primary_image_ID(), m_iref_box);
if (error) {
return error;
}
}
m_iinf_box = std::dynamic_pointer_cast<Box_iinf>(m_meta_box->get_child_box(fourcc("iinf")));
if (!m_iinf_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_iinf_box);
}
if(m_moov_box)
{
m_mvhd_box = std::dynamic_pointer_cast<Box_mvhd>(m_moov_box->get_child_box(fourcc("mvhd")));
if (!m_mvhd_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_mvhd_box);
}
m_trak_box = std::dynamic_pointer_cast<Box_trak>(m_moov_box->get_child_box(fourcc("trak")));
if (!m_mvhd_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_mvhd_box);
}
m_tkhd_box = std::dynamic_pointer_cast<Box_tkhd>(m_trak_box->get_child_box(fourcc("tkhd")));
if (!m_tkhd_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_tkhd_box);
}
m_mdia_box = std::dynamic_pointer_cast<Box_mdia>(m_trak_box->get_child_box(fourcc("mdia")));
if (!m_mdia_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_mdia_box);
}
m_mdhd_box = std::dynamic_pointer_cast<Box_mdhd>(m_mdia_box->get_child_box(fourcc("mdhd")));
if (!m_mdhd_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_mdhd_box);
}
m_minf_box = std::dynamic_pointer_cast<Box_minf>(m_mdia_box->get_child_box(fourcc("minf")));
if (!m_minf_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_minf_box);
}
m_vmhd_box = std::dynamic_pointer_cast<Box_vmhd>(m_minf_box->get_child_box(fourcc("vmhd")));
if (!m_vmhd_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_vmhd_box);
}
m_stbl_box = std::dynamic_pointer_cast<Box_stbl>(m_minf_box->get_child_box(fourcc("stbl")));
if (!m_stbl_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_stbl_box);
}
m_stsd_box = std::dynamic_pointer_cast<Box_stsd>(m_stbl_box->get_child_box(fourcc("stsd")));
if (!m_stsd_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_stbl_box);
}
m_hvc1_box = std::dynamic_pointer_cast<Box_hvc1>(m_stsd_box->get_child_box(fourcc("hvc1")));
if (!m_hvc1_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_hvc1_box);
}
m_hvcC_box = std::dynamic_pointer_cast<Box_hvcC>(m_hvc1_box->get_child_box(fourcc("hvcC")));
if (!m_hvcC_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_hvcC_moov_box);
}
m_ccst_box = std::dynamic_pointer_cast<Box_ccst>(m_hvc1_box->get_child_box(fourcc("ccst")));
if (!m_ccst_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_ccst_box);
}
m_stsz_box = std::dynamic_pointer_cast<Box_stsz>(m_stbl_box->get_child_box(fourcc("stsz")));
if (!m_stsz_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_stsz_box);
}
m_stts_box = std::dynamic_pointer_cast<Box_stts>(m_stbl_box->get_child_box(fourcc("stts")));
if (!m_stts_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_stts_box);
}
m_stsc_box = std::dynamic_pointer_cast<Box_stsc>(m_stbl_box->get_child_box(fourcc("stsc")));
if (!m_stsc_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_stsc_box);
}
m_stco_box = std::dynamic_pointer_cast<Box_stco>(m_stbl_box->get_child_box(fourcc("stco")));
if (!m_stco_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_stco_box);
}
m_stss_box = std::dynamic_pointer_cast<Box_stss>(m_stbl_box->get_child_box(fourcc("stss")));
if (!m_stss_box) {
return Error(heif_error_Invalid_input, heif_suberror_No_stss_box);
}
}
// --- build list of images
std::vector<std::shared_ptr<Box>> infe_boxes = m_iinf_box->get_child_boxes(fourcc("infe"));
for (auto& box : infe_boxes) {
std::shared_ptr<Box_infe> infe_box = std::dynamic_pointer_cast<Box_infe>(box);
if (!infe_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_infe_box);
}
m_infe_boxes.insert(std::make_pair(infe_box->get_item_ID(), infe_box));
}
return Error::Ok;
}
Error HeifFile::check_for_ref_cycle(heif_item_id ID,
const std::shared_ptr<Box_iref>& iref_box) const
{
std::unordered_set<heif_item_id> parent_items;
return check_for_ref_cycle_recursion(ID, iref_box, parent_items);
}
Error HeifFile::check_for_ref_cycle_recursion(heif_item_id ID,
const std::shared_ptr<Box_iref>& iref_box,
std::unordered_set<heif_item_id>& parent_items) const {
if (parent_items.find(ID) != parent_items.end()) {
return Error(heif_error_Invalid_input,
heif_suberror_Item_reference_cycle,
"Image reference cycle");
}
parent_items.insert(ID);
std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg"));
for (heif_item_id reference_idx : image_references) {
Error error = check_for_ref_cycle_recursion(reference_idx, iref_box, parent_items);
if (error) {
return error;
}
}
parent_items.erase(ID);
return Error::Ok;
}
bool HeifFile::image_exists(heif_item_id ID) const
{
auto image_iter = m_infe_boxes.find(ID);
return image_iter != m_infe_boxes.end();
}
bool HeifFile::has_item_with_id(heif_item_id ID) const
{
auto infe_box = get_infe_box(ID);
return infe_box != nullptr;
}
std::string HeifFile::get_item_type(heif_item_id ID) const
{
auto infe_box = get_infe_box(ID);
if (!infe_box) {
return "";
}
return infe_box->get_item_type();
}
std::string HeifFile::get_content_type(heif_item_id ID) const
{
auto infe_box = get_infe_box(ID);
if (!infe_box) {
return "";
}
return infe_box->get_content_type();
}
std::string HeifFile::get_item_uri_type(heif_item_id ID) const
{
auto infe_box = get_infe_box(ID);
if (!infe_box) {
return "";
}
return infe_box->get_item_uri_type();
}
Error HeifFile::get_properties(heif_item_id imageID,
std::vector<std::shared_ptr<Box>>& properties) const
{
if (!m_ipco_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_ipco_box);
}
else if (!m_ipma_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_ipma_box);
}
return m_ipco_box->get_properties_for_item_ID(imageID, m_ipma_box, properties);
}
heif_chroma HeifFile::get_image_chroma_from_configuration(heif_item_id imageID) const
{
// HEVC
auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("hvcC"));
std::shared_ptr<Box_hvcC> hvcC_box = std::dynamic_pointer_cast<Box_hvcC>(box);
if (hvcC_box) {
return (heif_chroma) (hvcC_box->get_configuration().chroma_format);
}
// VVC
box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC"));
std::shared_ptr<Box_vvcC> vvcC_box = std::dynamic_pointer_cast<Box_vvcC>(box);
if (vvcC_box) {
return (heif_chroma) (vvcC_box->get_configuration().chroma_format_idc);
}
// AV1
box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("av1C"));
std::shared_ptr<Box_av1C> av1C_box = std::dynamic_pointer_cast<Box_av1C>(box);
if (av1C_box) {
Box_av1C::configuration config = av1C_box->get_configuration();
if (config.chroma_subsampling_x == 1 &&
config.chroma_subsampling_y == 1) {
return heif_chroma_420;
}
else if (config.chroma_subsampling_x == 1 &&
config.chroma_subsampling_y == 0) {
return heif_chroma_422;
}
else if (config.chroma_subsampling_x == 0 &&
config.chroma_subsampling_y == 0) {
return heif_chroma_444;
}
else {
return heif_chroma_undefined;
}
}
assert(false);
return heif_chroma_undefined;
}
int HeifFile::get_luma_bits_per_pixel_from_configuration() const
{
if(m_hvc1_box)
{
if (m_hvcC_box) {
return m_hvcC_box->get_configuration().bit_depth_luma;
}
}
return -1;
}
int HeifFile::get_luma_bits_per_pixel_from_configuration(heif_item_id imageID) const
{
std::string image_type = get_item_type(imageID);
// HEVC
if (image_type == "hvc1") {
auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("hvcC"));
std::shared_ptr<Box_hvcC> hvcC_box = std::dynamic_pointer_cast<Box_hvcC>(box);
if (hvcC_box) {
return hvcC_box->get_configuration().bit_depth_luma;
}
}
// VVC
if (image_type == "vvc1") {
auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC"));
std::shared_ptr<Box_vvcC> vvcC_box = std::dynamic_pointer_cast<Box_vvcC>(box);
if (vvcC_box) {
Box_vvcC::configuration config = vvcC_box->get_configuration();
if (config.bit_depth_present_flag) {
return config.bit_depth;
}
else {
return 8; // TODO: what shall we do if the bit-depth is unknown? Use PIXI?
}
}
}
// AV1
if (image_type == "av01") {
auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("av1C"));
std::shared_ptr<Box_av1C> av1C_box = std::dynamic_pointer_cast<Box_av1C>(box);
if (av1C_box) {
Box_av1C::configuration config = av1C_box->get_configuration();
if (!config.high_bitdepth) {
return 8;
}
else if (config.twelve_bit) {
return 12;
}
else {
return 10;
}
}
}
// JPEG
if (image_type == "jpeg" || (image_type=="mime" && get_content_type(imageID)=="image/jpeg")) {
return jpeg_get_bits_per_pixel(imageID);
}
// JPEG 2000
if (image_type == "j2k1") {
JPEG2000MainHeader header;
Error err = header.parseHeader(*this, imageID);
if (err) {
return -1;
}
return header.get_precision(0);
}
#if WITH_UNCOMPRESSED_CODEC
// Uncompressed
if (image_type == "unci") {
int bpp = UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci(*this, imageID);
return bpp;
}
#endif
return -1;
}
int HeifFile::get_chroma_bits_per_pixel_from_configuration(heif_item_id imageID) const
{
std::string image_type = get_item_type(imageID);
// HEVC
if (image_type == "hvc1") {
auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("hvcC"));
std::shared_ptr<Box_hvcC> hvcC_box = std::dynamic_pointer_cast<Box_hvcC>(box);
if (hvcC_box) {
return hvcC_box->get_configuration().bit_depth_chroma;
}
}
// VVC
if (image_type == "vvc1") {
auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC"));
std::shared_ptr<Box_vvcC> vvcC_box = std::dynamic_pointer_cast<Box_vvcC>(box);
if (vvcC_box) {
Box_vvcC::configuration config = vvcC_box->get_configuration();
if (config.bit_depth_present_flag) {
return config.bit_depth;
}
else {
return 8; // TODO: what shall we do if the bit-depth is unknown? Use PIXI?
}
}
}
// AV1
if (image_type == "av01") {
auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("av1C"));
std::shared_ptr<Box_av1C> av1C_box = std::dynamic_pointer_cast<Box_av1C>(box);
if (av1C_box) {
Box_av1C::configuration config = av1C_box->get_configuration();
if (!config.high_bitdepth) {
return 8;
}
else if (config.twelve_bit) {
return 12;
}
else {
return 10;
}
}
}
// JPEG
if (image_type == "jpeg" || (image_type=="mime" && get_content_type(imageID)=="image/jpeg")) {
return jpeg_get_bits_per_pixel(imageID);
}
// JPEG 2000
if (image_type == "j2k1") {
JPEG2000MainHeader header;
Error err = header.parseHeader(*this, imageID);
if (err) {
return -1;
}
return header.get_precision(1);
}
return -1;
}
// This checks whether a start code FFCx with nibble 'x' is a SOF marker.
// E.g. FFC0-FFC3 are, while FFC4 is not.
static bool isSOF[16] = { 1,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1 };
int HeifFile::jpeg_get_bits_per_pixel(heif_item_id imageID) const
{
std::vector<uint8_t> data;
Error err = get_compressed_image_data(imageID, &data);
if (err) {
return -1;
}
for (size_t i = 0; i + 1 < data.size(); i++) {
if (data[i] == 0xFF && (data[i+1] & 0xF0) == 0xC0 && isSOF[data[i+1] & 0x0F]) {
i += 4;
if (i < data.size()) {
return data[i];
}
else {
return -1;
}
}
}
return -1;
}
Error HeifFile::get_image_data_for_moov(heif_item_id ID, const std::shared_ptr<StreamReader>& istr, std::vector<uint8_t>* dest) const
{
uint32_t base_offset = m_stco_box->get_base_offset();
uint32_t sample_size = m_stsz_box->get_sample_size(ID-1);
uint32_t sample_offset = m_stsz_box->get_sample_offset(ID-1);
size_t old_size = dest->size();
if (MAX_MEMORY_BLOCK_SIZE - old_size < sample_size)
{
std::stringstream sstr;
sstr << "iloc box contained " << sample_size << " bytes, total memory size would be "
<< (old_size + sample_size) << " bytes, exceeding the security limit of "
<< MAX_MEMORY_BLOCK_SIZE << " bytes";
return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, sstr.str());
}
if ((base_offset + sample_offset) > MAX_FILE_POS )
{
return Error(heif_error_Invalid_input, heif_suberror_Security_limit_exceeded, "sample data pointers out of allowed range");
}
StreamReader::grow_status status = istr->wait_for_file_size(base_offset + sample_offset + sample_size);
if (status == StreamReader::size_beyond_eof)
{
// Out-of-bounds
// TODO: I think we should not clear this. Maybe we want to try reading again later and
// hence should not lose the data already read.
dest->clear();
std::stringstream sstr;
sstr << "Extent in iloc box references data outside of file bounds " << "(points to file position " << base_offset + sample_offset << ")\n";
return Error(heif_error_Invalid_input, heif_suberror_End_of_data, sstr.str());
}
else if (status == StreamReader::timeout) {
// TODO: maybe we should introduce some 'Recoverable error' instead of 'Invalid input'
return Error(heif_error_Invalid_input, heif_suberror_End_of_data);
}
// --- move file pointer to start of data
bool success = istr->seek(base_offset + sample_offset);
assert(success);
(void) success;
// --- read data
dest->resize(static_cast<size_t>(old_size + sample_size));
success = istr->read((char*) dest->data() + old_size, static_cast<size_t>(sample_size));
assert(success);
(void) success;
return Error::Ok;
}
Error HeifFile::get_compressed_image_data_for_moov(heif_item_id ID, std::vector<uint8_t>* data) const
{
#if ENABLE_PARALLEL_TILE_DECODING
std::lock_guard<std::mutex> guard(m_read_mutex);
#endif
Error error = Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_codec);
if(m_hvc1_box)
{
if (!m_hvcC_box) {
// Should always have an hvcC box, because we are checking this in
// heif_context::interpret_heif_file()
assert(false);
return Error(heif_error_Invalid_input, heif_suberror_No_hvcC_box);
}
else if (!m_hvcC_box->get_header(ID-1, data)) {
return Error(heif_error_Invalid_input, heif_suberror_No_hvcC_box);
}
error = get_image_data_for_moov(ID, m_input_stream, data);
}
else
{
std::stringstream sstr;
sstr << "Current avif moov is not supported";
return Error(heif_error_Invalid_input, heif_suberror_Unsupported_image_type, sstr.str());
}
if (error != Error::Ok) {
return error;
}
return Error::Ok;
}
Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector<uint8_t>* data) const
{
#if ENABLE_PARALLEL_TILE_DECODING
std::lock_guard<std::mutex> guard(m_read_mutex);
#endif
if (!image_exists(ID)) {
return Error(heif_error_Usage_error,
heif_suberror_Nonexisting_item_referenced);
}
auto infe_box = get_infe_box(ID);
if (!infe_box) {
return Error(heif_error_Usage_error,
heif_suberror_Nonexisting_item_referenced);
}
std::string item_type = infe_box->get_item_type();
std::string content_type = infe_box->get_content_type();
// --- get coded image data pointers
auto items = m_iloc_box->get_items();
const Box_iloc::Item* item = nullptr;
for (const auto& i : items) {
if (i.item_ID == ID) {
item = &i;
break;
}
}
if (!item) {
std::stringstream sstr;
sstr << "Item with ID " << ID << " has no compressed data";
return Error(heif_error_Invalid_input,
heif_suberror_No_item_data,
sstr.str());
}
if (item_type == "hvc1") {
// --- --- --- HEVC
return get_compressed_image_data_hvc1(ID, data, item);
}
else if (item_type == "vvc1") {
// --- --- --- VVC
return get_compressed_image_data_vvc(ID, data, item);
}
else if (item_type == "av01") {
return get_compressed_image_data_av1(ID, data, item);
}
else if (item_type == "jpeg" ||
(item_type == "mime" && get_content_type(ID) == "image/jpeg")) {
return get_compressed_image_data_jpeg(ID, data, item);
}
else if (item_type == "j2k1") {
return get_compressed_image_data_jpeg2000(ID, item, data);
}
#if WITH_UNCOMPRESSED_CODEC
else if (item_type == "unci") {
return get_compressed_image_data_uncompressed(ID, data, item);
}
#endif
else if (true || // fallback case for all kinds of generic metadata (e.g. 'iptc')
item_type == "grid" ||
item_type == "iovl" ||
item_type == "Exif" ||
(item_type == "mime" && content_type == "application/rdf+xml")) {
Error error;
bool read_uncompressed = true;
if (item_type == "mime") {
std::string encoding = infe_box->get_content_encoding();
if (encoding == "compress_zlib") {
#if HAVE_ZLIB
read_uncompressed = false;
std::vector<uint8_t> compressed_data;
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data);
if (error) {
return error;
}
error = decompress_zlib(compressed_data, data);
if (error) {
return error;
}
#else
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_header_compression_method,
encoding);
#endif
}
else if (encoding == "deflate") {
#if HAVE_ZLIB
read_uncompressed = false;
std::vector<uint8_t> compressed_data;
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data);
if (error) {
return error;
}
error = decompress_deflate(compressed_data, data);
if (error) {
return error;
}
#else
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_header_compression_method,
encoding);
#endif
}
else if (encoding == "br") {
#if HAVE_BROTLI
read_uncompressed = false;
std::vector<uint8_t> compressed_data;
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data);
if (error) {
return error;
}
error = decompress_brotli(compressed_data, data);
if (error) {
return error;
}
#else
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_header_compression_method,
encoding);
#endif
}
}
if (read_uncompressed) {
return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
}
}
return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_codec);
}
#if WITH_UNCOMPRESSED_CODEC
// generic compression and uncompressed, per 23001-17
const Error HeifFile::get_compressed_image_data_uncompressed(heif_item_id ID, std::vector<uint8_t> *data, const Box_iloc::Item *item) const
{
std::vector<std::shared_ptr<Box>> properties;
Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties);
if (err) {
return err;
}
// --- get codec configuration
std::shared_ptr<Box_cmpC> cmpC_box;
std::shared_ptr<Box_icbr> icbr_box;
for (auto& prop : properties) {
if (prop->get_short_type() == fourcc("cmpC")) {
cmpC_box = std::dynamic_pointer_cast<Box_cmpC>(prop);
}
if (prop->get_short_type() == fourcc("icbr")) {
icbr_box = std::dynamic_pointer_cast<Box_icbr>(prop);
}
if (cmpC_box && icbr_box) {
break;
}
}
if (!cmpC_box) {
// assume no generic compression
return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
}
if (!cmpC_box->get_must_decompress_individual_entities()) {
std::vector<uint8_t> compressed_data;
m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data);
return do_decompress_data(cmpC_box, compressed_data, data);
} else {
if (!icbr_box) {
std::stringstream sstr;
sstr << "cannot decode unci item requiring entity decompression without icbr box" << std::endl;
return Error(heif_error_Invalid_input,
heif_suberror_No_icbr_box,
sstr.str());
}
if (item->construction_method == 0) {
for (Box_icbr::ByteRange range: icbr_box->get_ranges()) {
// TODO: check errors
bool success = m_input_stream->seek(range.range_offset);
if (!success) {
return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while seeking to generically compressed data"};
}
std::vector<uint8_t> compressed_range_bytes(range.range_size);
success = m_input_stream->read((char*) compressed_range_bytes.data(), static_cast<size_t>(compressed_range_bytes.size()));
if (!success) {
return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while reading generically compressed data"};
}
std::vector<uint8_t> uncompressed_range_data;
Error err = do_decompress_data(cmpC_box, compressed_range_bytes, &uncompressed_range_data);
if (err) {
return err;
}
data->insert(data->end(), uncompressed_range_data.data(), uncompressed_range_data.data() + uncompressed_range_data.size());
}
return Error::Ok;
} else {
// TODO: implement...
std::stringstream sstr;
sstr << "cannot decode unci item from idat yet" << std::endl;
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_data_version,
sstr.str());
}
}
return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_codec);
}
const Error HeifFile::do_decompress_data(std::shared_ptr<Box_cmpC> &cmpC_box, std::vector<uint8_t> compressed_data, std::vector<uint8_t> *data) const
{
if (cmpC_box->get_compression_type() == fourcc("brot")) {
#if HAVE_BROTLI
return decompress_brotli(compressed_data, data);
#else
std::stringstream sstr;
sstr << "cannot decode unci item with brotli compression - not enabled" << std::endl;
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_generic_compression_method,
sstr.str());
#endif
} else if (cmpC_box->get_compression_type() == fourcc("zlib")) {
#if HAVE_ZLIB
return decompress_zlib(compressed_data, data);
#else
std::stringstream sstr;
sstr << "cannot decode unci item with zlib compression - not enabled" << std::endl;
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_generic_compression_method,
sstr.str());
#endif
} else if (cmpC_box->get_compression_type() == fourcc("defl")) {
#if HAVE_ZLIB
return decompress_deflate(compressed_data, data);
#else
std::stringstream sstr;
sstr << "cannot decode unci item with deflate compression - not enabled" << std::endl;
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_generic_compression_method,
sstr.str());
#endif
} else {
std::stringstream sstr;
sstr << "cannot decode unci item with unsupported compression type: " << cmpC_box->get_compression_type() << std::endl;
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_generic_compression_method,
sstr.str());
}
}
#endif
const Error HeifFile::get_compressed_image_data_hvc1(heif_item_id ID, std::vector<uint8_t> *data, const Box_iloc::Item *item) const
{
// --- get properties for this image
std::vector<std::shared_ptr<Box>> properties;
Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties);
if (err)
{
return err;
}
// --- get codec configuration
std::shared_ptr<Box_hvcC> hvcC_box;
for (auto &prop : properties)
{
if (prop->get_short_type() == fourcc("hvcC"))
{
hvcC_box = std::dynamic_pointer_cast<Box_hvcC>(prop);
if (hvcC_box)
{
break;
}
}
}
if (!hvcC_box)
{
// Should always have an hvcC box, because we are checking this in
// heif_context::interpret_heif_file()
assert(false);
return Error(heif_error_Invalid_input,
heif_suberror_No_hvcC_box);
}
else if (!hvcC_box->get_headers(data))
{
return Error(heif_error_Invalid_input,
heif_suberror_No_item_data);
}
return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
}
const Error HeifFile::get_compressed_image_data_vvc(heif_item_id ID, std::vector<uint8_t> *data, const Box_iloc::Item *item) const
{
// --- get properties for this image
std::vector<std::shared_ptr<Box>> properties;
Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties);
if (err)
{
return err;
}
// --- get codec configuration
std::shared_ptr<Box_vvcC> vvcC_box;
for (auto &prop : properties)
{
if (prop->get_short_type() == fourcc("vvcC"))
{
vvcC_box = std::dynamic_pointer_cast<Box_vvcC>(prop);
if (vvcC_box)
{
break;
}
}
}
if (!vvcC_box)
{
assert(false);
return Error(heif_error_Invalid_input,
heif_suberror_No_vvcC_box);
}
else if (!vvcC_box->get_headers(data))
{
return Error(heif_error_Invalid_input,
heif_suberror_No_item_data);
}
return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
}
const Error HeifFile::get_compressed_image_data_av1(heif_item_id ID, std::vector<uint8_t> *data, const Box_iloc::Item *item) const
{
// --- --- --- AV1
// --- get properties for this image
std::vector<std::shared_ptr<Box>> properties;
Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties);
if (err)
{
return err;
}
// --- get codec configuration
std::shared_ptr<Box_av1C> av1C_box;
for (auto &prop : properties)
{
if (prop->get_short_type() == fourcc("av1C"))
{
av1C_box = std::dynamic_pointer_cast<Box_av1C>(prop);
if (av1C_box)
{
break;
}
}
}
if (!av1C_box)
{
return Error(heif_error_Invalid_input,
heif_suberror_No_av1C_box);
}
else if (!av1C_box->get_headers(data))
{
return Error(heif_error_Invalid_input,
heif_suberror_No_item_data);
}
return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
}
const Error HeifFile::get_compressed_image_data_jpeg2000(heif_item_id ID, const Box_iloc::Item *item, std::vector<uint8_t> *data) const
{
std::vector<std::shared_ptr<Box>> properties;
Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties);
if (err)
{
return err;
}
// --- get codec configuration
std::shared_ptr<Box_j2kH> j2kH_box;
for (auto &prop : properties)
{
if (prop->get_short_type() == fourcc("j2kH"))
{
j2kH_box = std::dynamic_pointer_cast<Box_j2kH>(prop);
if (j2kH_box)
{
break;
}
}
}
if (!j2kH_box)
{
// TODO - Correctly Find the j2kH box
// return Error(heif_error_Invalid_input,
// heif_suberror_Unspecified);
}
// else if (!j2kH_box->get_headers(data)) {
// return Error(heif_error_Invalid_input,
// heif_suberror_No_item_data);
// }
return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
}
const Error HeifFile::get_compressed_image_data_jpeg(heif_item_id ID, std::vector<uint8_t> * data, const Box_iloc::Item *item) const
{
// --- check if 'jpgC' is present
std::vector<std::shared_ptr<Box>> properties;
Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties);
if (err)
{
return err;
}
// --- get codec configuration
std::shared_ptr<Box_jpgC> jpgC_box;
for (auto &prop : properties)
{
if (prop->get_short_type() == fourcc("jpgC"))
{
jpgC_box = std::dynamic_pointer_cast<Box_jpgC>(prop);
if (jpgC_box)
{
*data = jpgC_box->get_data();
break;
}
}
}
return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data);
}
Error HeifFile::get_item_data(heif_item_id ID, std::vector<uint8_t>* out_data, heif_metadata_compression* out_compression) const
{
Error error;
auto infe_box = get_infe_box(ID);
if (!infe_box) {
return {heif_error_Usage_error,
heif_suberror_Nonexisting_item_referenced};
}
std::string item_type = infe_box->get_item_type();
std::string content_type = infe_box->get_content_type();
// --- get item
auto items = m_iloc_box->get_items();
const Box_iloc::Item* item = nullptr;
for (const auto& i : items) {
if (i.item_ID == ID) {
item = &i;
break;
}
}
if (!item) {
std::stringstream sstr;
sstr << "Item with ID " << ID << " has no data";
return {heif_error_Invalid_input,
heif_suberror_No_item_data,
sstr.str()};
}
// --- non 'mime' data (uncompressed)
if (item_type != "mime") {
if (out_compression) {
*out_compression = heif_metadata_compression_off;
}
return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, out_data);
}
// --- mime data
std::string encoding = infe_box->get_content_encoding();
heif_metadata_compression compression;
if (encoding.empty()) {
// shortcut for case of uncompressed mime data
if (out_compression) {
*out_compression = heif_metadata_compression_off;
}
return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, out_data);
}
else if (encoding == "compress_zlib") {
compression = heif_metadata_compression_zlib;
}
else if (encoding == "deflate") {
compression = heif_metadata_compression_deflate;
}
else if (encoding == "br") {
compression = heif_metadata_compression_brotli;
}
else {
compression = heif_metadata_compression_unknown;
}
// read compressed data
std::vector<uint8_t> compressed_data;
error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data);
if (error) {
return error;
}
// return compressed data, if we do not want to have it uncompressed
const bool do_decode = (out_compression == nullptr);
if (!do_decode) {
*out_compression = compression;
*out_data = std::move(compressed_data);
return Error::Ok;
}
// decompress the data
switch (compression) {
#if HAVE_ZLIB
case heif_metadata_compression_zlib:
return decompress_zlib(compressed_data, out_data);
case heif_metadata_compression_deflate:
return decompress_deflate(compressed_data, out_data);
#endif
#if HAVE_BROTLI
case heif_metadata_compression_brotli:
return decompress_brotli(compressed_data, out_data);
#endif
default:
return {heif_error_Unsupported_filetype, heif_suberror_Unsupported_header_compression_method};
}
}
heif_item_id HeifFile::get_unused_item_id() const
{
for (heif_item_id id = 1;;
id++) {
bool id_exists = false;
for (const auto& infe : m_infe_boxes) {
if (infe.second->get_item_ID() == id) {
id_exists = true;
break;
}
}
if (!id_exists) {
return id;
}
}
assert(false); // should never be reached
return 0;
}
heif_item_id HeifFile::add_new_image(const char* item_type)
{
heif_item_id id = add_new_infe_box_returnID(item_type);
return id;
}
heif_item_id HeifFile::add_new_infe_box_returnID(const char* item_type)
{
heif_item_id id = get_unused_item_id();
if(m_moov_flag && id==1)
{
auto infe = std::make_shared<Box_infe>();
infe->set_item_ID(id);
infe->set_hidden_item(false);
infe->set_item_type(item_type);
m_infe_boxes[id] = infe;
m_iinf_box->append_child_box(infe);
}
else if(!m_moov_flag)
{
auto infe = std::make_shared<Box_infe>();
infe->set_item_ID(id);
infe->set_hidden_item(false);
infe->set_item_type(item_type);
m_infe_boxes[id] = infe;
m_iinf_box->append_child_box(infe);
}
return id;
}
heif_item_id HeifFile::add_new_hidden_image(const char* item_type)
{
auto box = add_new_infe_box(item_type);
box->set_hidden_item(true);
return box->get_item_ID();
}
std::shared_ptr<Box_infe> HeifFile::add_new_infe_box(const char* item_type)
{
heif_item_id id = get_unused_item_id();
auto infe = std::make_shared<Box_infe>();
infe->set_item_ID(id);
infe->set_hidden_item(false);
infe->set_item_type(item_type);
m_infe_boxes[id] = infe;
m_iinf_box->append_child_box(infe);
return infe;
}
void HeifFile::add_ispe_property(heif_item_id id, uint32_t width, uint32_t height)
{
auto ispe = std::make_shared<Box_ispe>();
ispe->set_size(width, height);
int index = m_ipco_box->find_or_append_child_box(ispe);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)});
}
void HeifFile::add_clap_property(heif_item_id id, uint32_t clap_width, uint32_t clap_height,
uint32_t image_width, uint32_t image_height)
{
auto clap = std::make_shared<Box_clap>();
clap->set(clap_width, clap_height, image_width, image_height);
int index = m_ipco_box->find_or_append_child_box(clap);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
}
heif_property_id HeifFile::add_property(heif_item_id id, const std::shared_ptr<Box>& property, bool essential)
{
int index = m_ipco_box->find_or_append_child_box(property);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
return index + 1;
}
void HeifFile::add_orientation_properties(heif_item_id id, heif_orientation orientation)
{
// Note: ISO/IEC 23000-22:2019(E) (MIAF) 7.3.6.7 requires the following order:
// clean aperture first, then rotation, then mirror
int rotation_ccw = 0;
heif_transform_mirror_direction mirror;
bool has_mirror = false;
switch (orientation) {
case heif_orientation_normal:
break;
case heif_orientation_flip_horizontally:
mirror = heif_transform_mirror_direction_horizontal;
has_mirror = true;
break;
case heif_orientation_rotate_180:
rotation_ccw = 180;
break;
case heif_orientation_flip_vertically:
mirror = heif_transform_mirror_direction_vertical;
has_mirror = true;
break;
case heif_orientation_rotate_90_cw_then_flip_horizontally:
rotation_ccw = 270;
mirror = heif_transform_mirror_direction_horizontal;
has_mirror = true;
break;
case heif_orientation_rotate_90_cw:
rotation_ccw = 270;
break;
case heif_orientation_rotate_90_cw_then_flip_vertically:
rotation_ccw = 270;
mirror = heif_transform_mirror_direction_vertical;
has_mirror = true;
break;
case heif_orientation_rotate_270_cw:
rotation_ccw = 90;
break;
}
// omit rotation when angle is 0
if (rotation_ccw!=0) {
auto irot = std::make_shared<Box_irot>();
irot->set_rotation_ccw(rotation_ccw);
int index = m_ipco_box->find_or_append_child_box(irot);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)});
}
if (has_mirror) {
auto imir = std::make_shared<Box_imir>();
imir->set_mirror_direction(mirror);
int index = m_ipco_box->find_or_append_child_box(imir);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)});
}
}
void HeifFile::add_pixi_property(heif_item_id id, uint8_t c1, uint8_t c2, uint8_t c3)
{
auto pixi = std::make_shared<Box_pixi>();
pixi->add_channel_bits(c1);
if (c2 || c3) {
pixi->add_channel_bits(c2);
pixi->add_channel_bits(c3);
}
int index = m_ipco_box->find_or_append_child_box(pixi);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)});
}
void HeifFile::add_vvcC_property(heif_item_id id)
{
auto vvcC = std::make_shared<Box_vvcC>();
int index = m_ipco_box->append_child_box(vvcC);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
}
Error HeifFile::append_vvcC_nal_data(heif_item_id id, const std::vector<uint8_t>& nal_data)
{
auto vvcC = std::dynamic_pointer_cast<Box_vvcC>(m_ipco_box->get_property_for_item_ID(id,
m_ipma_box,
fourcc("vvcC")));
if (vvcC) {
vvcC->append_nal_data(nal_data);
return Error::Ok;
}
else {
// Should always have an vvcC box, because we are checking this in
// heif_context::interpret_heif_file()
assert(false);
return Error(heif_error_Usage_error,
heif_suberror_No_vvcC_box);
}
}
Error HeifFile::set_vvcC_configuration(heif_item_id id, const Box_vvcC::configuration& config)
{
auto vvcC = std::dynamic_pointer_cast<Box_vvcC>(m_ipco_box->get_property_for_item_ID(id,
m_ipma_box,
fourcc("vvcC")));
if (vvcC) {
vvcC->set_configuration(config);
return Error::Ok;
}
else {
return Error(heif_error_Usage_error,
heif_suberror_No_vvcC_box);
}
}
Error HeifFile::append_vvcC_nal_data(heif_item_id id, const uint8_t* data, size_t size)
{
std::vector<std::shared_ptr<Box>> properties;
auto vvcC = std::dynamic_pointer_cast<Box_vvcC>(m_ipco_box->get_property_for_item_ID(id,
m_ipma_box,
fourcc("vvcC")));
if (vvcC) {
vvcC->append_nal_data(data, size);
return Error::Ok;
}
else {
return Error(heif_error_Usage_error,
heif_suberror_No_vvcC_box);
}
}
void HeifFile::add_hvcC_property(heif_item_id id)
{
auto hvcC = std::make_shared<Box_hvcC>();
int index = m_ipco_box->append_child_box(hvcC);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
}
Error HeifFile::append_hvcC_nal_data(heif_item_id id, const std::vector<uint8_t>& nal_data)
{
auto hvcC = std::dynamic_pointer_cast<Box_hvcC>(m_ipco_box->get_property_for_item_ID(id,
m_ipma_box,
fourcc("hvcC")));
if (hvcC) {
hvcC->append_nal_data(nal_data);
return Error::Ok;
}
else {
// Should always have an hvcC box, because we are checking this in
// heif_context::interpret_heif_file()
assert(false);
return Error(heif_error_Usage_error,
heif_suberror_No_hvcC_box);
}
}
Error HeifFile::set_hvcC_configuration(heif_item_id id, const Box_hvcC::configuration& config)
{
auto hvcC = std::dynamic_pointer_cast<Box_hvcC>(m_ipco_box->get_property_for_item_ID(id,
m_ipma_box,
fourcc("hvcC")));
if (hvcC) {
hvcC->set_configuration(config);
return Error::Ok;
}
else {
return Error(heif_error_Usage_error,
heif_suberror_No_hvcC_box);
}
}
Error HeifFile::set_hvcC_configuration(const Box_hvcC::configuration& config)
{
m_hvcC_box->set_configuration(config);
return Error::Ok;
}
Error HeifFile::append_hvcC_nal_data(const uint8_t* data, size_t size)
{
m_hvcC_box->append_nal_data_for_movie(data, size);
return Error::Ok;
}
Error HeifFile::append_sample_entry_size(uint32_t size)
{
m_stsz_box->add_entry_size(size);
return Error::Ok;
}
Error HeifFile::record_sync_data_in_stss(uint8_t nal_type)
{
m_stss_box->record_sync_data(nal_type);
return Error::Ok;
}
Error HeifFile::add_frame_duration_in_TimeScale(uint64_t duration)
{
m_stts_box->set_frame_durationinTimeScale(duration);
return Error::Ok;
}
Error HeifFile::add_frame_sample_in_chunk()
{
m_stsc_box->add_chunk_sample();
return Error::Ok;
}
Error HeifFile::append_hvcC_nal_data(heif_item_id id, const uint8_t* data, size_t size)
{
std::vector<std::shared_ptr<Box>> properties;
auto hvcC = std::dynamic_pointer_cast<Box_hvcC>(m_ipco_box->get_property_for_item_ID(id,
m_ipma_box,
fourcc("hvcC")));
if (hvcC) {
hvcC->append_nal_data(data, size);
return Error::Ok;
}
else {
return Error(heif_error_Usage_error,
heif_suberror_No_hvcC_box);
}
}
void HeifFile::add_av1C_property(heif_item_id id, const Box_av1C::configuration& config)
{
auto av1C = std::make_shared<Box_av1C>();
av1C->set_configuration(config);
add_property(id, av1C, true);
}
std::shared_ptr<Box_j2kH> HeifFile::add_j2kH_property(heif_item_id id)
{
auto j2kH = std::make_shared<Box_j2kH>();
int index = m_ipco_box->append_child_box(j2kH); // do not deduplicate because this can have a child box
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
return j2kH;
}
Result<heif_item_id> HeifFile::add_infe(const char* item_type, const uint8_t* data, size_t size)
{
Result<heif_item_id> result;
// create an infe box describing what kind of data we are storing (this also creates a new ID)
auto infe_box = add_new_infe_box(item_type);
infe_box->set_hidden_item(true);
heif_item_id metadata_id = infe_box->get_item_ID();
result.value = metadata_id;
set_item_data(infe_box, data, size, heif_metadata_compression_off);
return result;
}
Result<heif_item_id> HeifFile::add_infe_mime(const char* content_type, heif_metadata_compression content_encoding, const uint8_t* data, size_t size)
{
Result<heif_item_id> result;
// create an infe box describing what kind of data we are storing (this also creates a new ID)
auto infe_box = add_new_infe_box("mime");
infe_box->set_hidden_item(true);
infe_box->set_content_type(content_type);
heif_item_id metadata_id = infe_box->get_item_ID();
result.value = metadata_id;
set_item_data(infe_box, data, size, content_encoding);
return result;
}
Result<heif_item_id> HeifFile::add_precompressed_infe_mime(const char* content_type, std::string content_encoding, const uint8_t* data, size_t size)
{
Result<heif_item_id> result;
// create an infe box describing what kind of data we are storing (this also creates a new ID)
auto infe_box = add_new_infe_box("mime");
infe_box->set_hidden_item(true);
infe_box->set_content_type(content_type);
heif_item_id metadata_id = infe_box->get_item_ID();
result.value = metadata_id;
set_precompressed_item_data(infe_box, data, size, content_encoding);
return result;
}
Result<heif_item_id> HeifFile::add_infe_uri(const char* item_uri_type, const uint8_t* data, size_t size)
{
Result<heif_item_id> result;
// create an infe box describing what kind of data we are storing (this also creates a new ID)
auto infe_box = add_new_infe_box("uri ");
infe_box->set_hidden_item(true);
infe_box->set_item_uri_type(item_uri_type);
heif_item_id metadata_id = infe_box->get_item_ID();
result.value = metadata_id;
set_item_data(infe_box, data, size, heif_metadata_compression_off);
return result;
}
Error HeifFile::set_item_data(const std::shared_ptr<Box_infe>& item, const uint8_t* data, size_t size, heif_metadata_compression compression)
{
// --- 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 &&
item->get_item_type() != "mime") {
// 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);
item->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_deflate((const uint8_t*) data, size);
item->set_content_encoding("compress_zlib");
#else
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_header_compression_method);
#endif
}
// TODO: brotli
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
append_iloc_data(item->get_item_ID(), data_array);
return Error::Ok;
}
Error HeifFile::set_precompressed_item_data(const std::shared_ptr<Box_infe>& item, const uint8_t* data, size_t size, std::string content_encoding)
{
// only set metadata compression for MIME type data which has 'content_encoding' field
if (!content_encoding.empty() &&
item->get_item_type() != "mime") {
// TODO: error, compression not supported
}
std::vector<uint8_t> data_array;
data_array.resize(size);
memcpy(data_array.data(), data, size);
item->set_content_encoding(content_encoding);
// copy the data into the file, store the pointer to it in an iloc box entry
append_iloc_data(item->get_item_ID(), data_array);
return Error::Ok;
}
void HeifFile::append_iloc_data(heif_item_id id, const std::vector<uint8_t>& nal_packets, uint8_t construction_method)
{
m_iloc_box->append_data(id, nal_packets, construction_method);
}
void HeifFile::append_iloc_data_with_4byte_size(heif_item_id id, const uint8_t* data, size_t size)
{
std::vector<uint8_t> nal;
nal.resize(size + 4);
nal[0] = (uint8_t) ((size >> 24) & 0xFF);
nal[1] = (uint8_t) ((size >> 16) & 0xFF);
nal[2] = (uint8_t) ((size >> 8) & 0xFF);
nal[3] = (uint8_t) ((size >> 0) & 0xFF);
memcpy(nal.data() + 4, data, size);
append_iloc_data(id, nal);
}
void HeifFile::set_primary_item_id(heif_item_id id)
{
m_pitm_box->set_item_ID(id);
}
void HeifFile::add_iref_reference(heif_item_id from, uint32_t type,
const std::vector<heif_item_id>& to)
{
if (!m_iref_box) {
m_iref_box = std::make_shared<Box_iref>();
m_meta_box->append_child_box(m_iref_box);
}
m_iref_box->add_references(from, type, to);
}
void HeifFile::set_auxC_property(heif_item_id id, const std::string& type)
{
auto auxC = std::make_shared<Box_auxC>();
auxC->set_aux_type(type);
int index = m_ipco_box->find_or_append_child_box(auxC);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
}
void HeifFile::set_color_profile(heif_item_id id, const std::shared_ptr<const color_profile>& profile)
{
auto colr = std::make_shared<Box_colr>();
colr->set_color_profile(profile);
int index = m_ipco_box->find_or_append_child_box(colr);
m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)});
}
// TODO: the hdlr box is probably not the right place for this. Into which box should we write comments?
void HeifFile::set_hdlr_library_info(const std::string& encoder_plugin_version)
{
std::stringstream sstr;
sstr << "libheif (" << LIBHEIF_VERSION << ") / " << encoder_plugin_version;
m_hdlr_box->set_name(sstr.str());
}
#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER)
std::wstring HeifFile::convert_utf8_path_to_utf16(std::string str)
{
std::wstring ret;
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), NULL, 0);
if (len > 0)
{
ret.resize(len);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), &ret[0], len);
}
return ret;
}
#endif