libheif/box.cc (3,554 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 "libheif/heif.h" #include <cstddef> #include <cstdint> #include "box.h" #include "security_limits.h" #include "nclx.h" #include "codecs/jpeg.h" #include "codecs/jpeg2000.h" #include "codecs/hevc.h" #include "codecs/mask_image.h" #include "codecs/vvc.h" #include "codecs/avc.h" #include <iomanip> #include <utility> #include <iostream> #include <algorithm> #include <cstring> #include <set> #include <cassert> #include <array> #if WITH_UNCOMPRESSED_CODEC #include "codecs/uncompressed_box.h" #endif #ifndef M_PI #define M_PI 3.14159265358979323846 #endif Fraction::Fraction(int32_t num, int32_t den) { // Reduce resolution of fraction until we are in a safe range. // We need this as adding fractions may lead to very large denominators // (e.g. 0x10000 * 0x10000 > 0x100000000 -> overflow, leading to integer 0) numerator = num; denominator = den; while (denominator > MAX_FRACTION_VALUE || denominator < -MAX_FRACTION_VALUE) { numerator /= 2; denominator /= 2; } while (denominator > 1 && (numerator > MAX_FRACTION_VALUE || numerator < -MAX_FRACTION_VALUE)) { numerator /= 2; denominator /= 2; } } Fraction::Fraction(uint32_t num, uint32_t den) { assert(num <= (uint32_t) std::numeric_limits<int32_t>::max()); assert(den <= (uint32_t) std::numeric_limits<int32_t>::max()); *this = Fraction(int32_t(num), int32_t(den)); } Fraction::Fraction(int64_t num, int64_t den) { while (num < std::numeric_limits<int32_t>::min() || num > std::numeric_limits<int32_t>::max() || den < std::numeric_limits<int32_t>::min() || den > std::numeric_limits<int32_t>::max()) { num = (num + (num>=0 ? 1 : -1)) / 2; den = (den + (den>=0 ? 1 : -1)) / 2; } numerator = static_cast<int32_t>(num); denominator = static_cast<int32_t>(den); } Fraction Fraction::operator+(const Fraction& b) const { if (denominator == b.denominator) { int64_t n = int64_t{numerator} + b.numerator; int64_t d = denominator; return Fraction{n,d}; } else { int64_t n = int64_t{numerator} * b.denominator + int64_t{b.numerator} * denominator; int64_t d = int64_t{denominator} * b.denominator; return Fraction{n,d}; } } Fraction Fraction::operator-(const Fraction& b) const { if (denominator == b.denominator) { int64_t n = int64_t{numerator} - b.numerator; int64_t d = denominator; return Fraction{n,d}; } else { int64_t n = int64_t{numerator} * b.denominator - int64_t{b.numerator} * denominator; int64_t d = int64_t{denominator} * b.denominator; return Fraction{n,d}; } } Fraction Fraction::operator+(int v) const { return Fraction{numerator + v * int64_t(denominator), int64_t(denominator)}; } Fraction Fraction::operator-(int v) const { return Fraction{numerator - v * int64_t(denominator), int64_t(denominator)}; } Fraction Fraction::operator/(int v) const { return Fraction{int64_t(numerator), int64_t(denominator) * v}; } int32_t Fraction::round_down() const { return numerator / denominator; } int32_t Fraction::round_up() const { return int32_t((numerator + int64_t(denominator) - 1) / denominator); } int32_t Fraction::round() const { return int32_t((numerator + int64_t(denominator) / 2) / denominator); } bool Fraction::is_valid() const { return denominator != 0; } uint32_t from_fourcc(const char* string) { return ((string[0] << 24) | (string[1] << 16) | (string[2] << 8) | (string[3])); } std::string to_fourcc(uint32_t code) { std::string str(" "); str[0] = static_cast<char>((code >> 24) & 0xFF); str[1] = static_cast<char>((code >> 16) & 0xFF); str[2] = static_cast<char>((code >> 8) & 0xFF); str[3] = static_cast<char>((code >> 0) & 0xFF); return str; } BoxHeader::BoxHeader() = default; std::vector<uint8_t> BoxHeader::get_type() const { if (m_type == fourcc("uuid")) { return m_uuid_type; } else { std::vector<uint8_t> type(4); type[0] = static_cast<uint8_t>((m_type >> 24) & 0xFF); type[1] = static_cast<uint8_t>((m_type >> 16) & 0xFF); type[2] = static_cast<uint8_t>((m_type >> 8) & 0xFF); type[3] = static_cast<uint8_t>((m_type >> 0) & 0xFF); return type; } } std::string BoxHeader::get_type_string() const { if (m_type == fourcc("uuid")) { // 8-4-4-4-12 std::ostringstream sstr; sstr << std::hex; sstr << std::setfill('0'); for (int i = 0; i < 16; i++) { if (i == 4 || i == 6 || i == 8 || i == 10) { sstr << '-'; } sstr << std::setw(2); sstr << ((int) m_uuid_type[i]); } return sstr.str(); } else { return to_fourcc(m_type); } } std::vector<uint8_t> BoxHeader::get_uuid_type() const { if (m_type != fourcc("uuid")) { return {}; } return m_uuid_type; } void BoxHeader::set_uuid_type(const std::vector<uint8_t>& type) { m_type = fourcc("uuid"); m_uuid_type = type; } Error BoxHeader::parse_header(BitstreamRange& range) { StreamReader::grow_status status; status = range.wait_for_available_bytes(8); if (status != StreamReader::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); } m_size = range.read32(); m_type = range.read32(); m_header_size = 8; if (m_size == 1) { status = range.wait_for_available_bytes(8); if (status != StreamReader::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); } uint64_t high = range.read32(); uint64_t low = range.read32(); m_size = (high << 32) | low; m_header_size += 8; std::stringstream sstr; sstr << "Box size " << m_size << " exceeds security limit."; if (m_size > MAX_LARGE_BOX_SIZE) { return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, sstr.str()); } } if (m_type == fourcc("uuid")) { status = range.wait_for_available_bytes(16); if (status != StreamReader::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); } if (range.prepare_read(16)) { m_uuid_type.resize(16); bool success = range.get_istream()->read((char*) m_uuid_type.data(), 16); assert(success); (void) success; } m_header_size += 16; } return range.get_error(); } int Box::calculate_header_size(bool data64bit) const { int header_size = 8; // does not include "FullBox" fields. if (get_short_type() == fourcc("uuid")) { header_size += 16; } if (data64bit) { header_size += 8; } return header_size; } size_t Box::reserve_box_header_space(StreamWriter& writer, bool data64bit) const { size_t start_pos = writer.get_position(); int header_size = calculate_header_size(data64bit); writer.skip(header_size); return start_pos; } size_t FullBox::reserve_box_header_space(StreamWriter& writer, bool data64bit) const { size_t start_pos = Box::reserve_box_header_space(writer, data64bit); writer.skip(4); return start_pos; } Error FullBox::write_header(StreamWriter& writer, size_t total_size, bool data64bit) const { auto err = Box::write_header(writer, total_size, data64bit); if (err) { return err; } assert((get_flags() & ~0x00FFFFFFU) == 0); writer.write32((get_version() << 24) | get_flags()); return Error::Ok; } Error Box::prepend_header(StreamWriter& writer, size_t box_start, bool data64bit) const { size_t total_size = writer.data_size() - box_start; writer.set_position(box_start); auto err = write_header(writer, total_size, data64bit); writer.set_position_to_end(); // Note: should we move to the end of the box after writing the header? return err; } Error Box::write_header(StreamWriter& writer, size_t total_size, bool data64bit) const { bool large_size = (total_size > 0xFFFFFFFF); // --- write header if (large_size && !data64bit) { // Note: as an alternative, we could return an error here. If it fails, the user has to try again with 64 bit. writer.insert(8); } if (large_size) { writer.write32(1); } else { assert(total_size <= 0xFFFFFFFF); writer.write32((uint32_t) total_size); } writer.write32(get_short_type()); if (large_size) { writer.write64(total_size); } if (get_short_type() == fourcc("uuid")) { assert(get_type().size() == 16); writer.write(get_type()); } return Error::Ok; } std::string BoxHeader::dump(Indent& indent) const { std::ostringstream sstr; sstr << indent << "Box: " << get_type_string() << " -----\n"; sstr << indent << "size: " << get_box_size() << " (header size: " << get_header_size() << ")\n"; return sstr.str(); } Error Box::parse(BitstreamRange& range) { // skip box if (get_box_size() == size_until_end_of_file) { range.skip_to_end_of_file(); } else { uint64_t content_size = get_box_size() - get_header_size(); if (range.prepare_read(content_size)) { if (content_size > MAX_BOX_SIZE) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_box_size); } range.get_istream()->seek_cur(get_box_size() - get_header_size()); } } // Note: seekg() clears the eof flag and it will not be set again afterwards, // hence we have to test for the fail flag. return range.get_error(); } Error FullBox::parse_full_box_header(BitstreamRange& range) { uint32_t data = range.read32(); m_version = static_cast<uint8_t>(data >> 24); m_flags = data & 0x00FFFFFF; //m_is_full_box = true; m_header_size += 4; return range.get_error(); } Error Box::read(BitstreamRange& range, std::shared_ptr<Box>* result) { BoxHeader hdr; Error err = hdr.parse_header(range); if (err) { return err; } if (range.error()) { return range.get_error(); } std::shared_ptr<Box> box; switch (hdr.get_short_type()) { case fourcc("ftyp"): box = std::make_shared<Box_ftyp>(); break; case fourcc("meta"): box = std::make_shared<Box_meta>(); break; case fourcc("hdlr"): box = std::make_shared<Box_hdlr>(); break; case fourcc("pitm"): box = std::make_shared<Box_pitm>(); break; case fourcc("iloc"): box = std::make_shared<Box_iloc>(); break; case fourcc("iinf"): box = std::make_shared<Box_iinf>(); break; case fourcc("infe"): box = std::make_shared<Box_infe>(); break; case fourcc("iprp"): box = std::make_shared<Box_iprp>(); break; case fourcc("ipco"): box = std::make_shared<Box_ipco>(); break; case fourcc("ipma"): box = std::make_shared<Box_ipma>(); break; case fourcc("ispe"): box = std::make_shared<Box_ispe>(); break; case fourcc("auxC"): box = std::make_shared<Box_auxC>(); break; case fourcc("irot"): box = std::make_shared<Box_irot>(); break; case fourcc("imir"): box = std::make_shared<Box_imir>(); break; case fourcc("clap"): box = std::make_shared<Box_clap>(); break; case fourcc("iref"): box = std::make_shared<Box_iref>(); break; case fourcc("hvcC"): box = std::make_shared<Box_hvcC>(); break; case fourcc("av1C"): box = std::make_shared<Box_av1C>(); break; case fourcc("vvcC"): box = std::make_shared<Box_vvcC>(); break; case fourcc("idat"): box = std::make_shared<Box_idat>(); break; case fourcc("grpl"): box = std::make_shared<Box_grpl>(); break; case fourcc("pymd"): box = std::make_shared<Box_pymd>(); break; case fourcc("altr"): box = std::make_shared<Box_EntityToGroup>(); break; case fourcc("ster"): box = std::make_shared<Box_ster>(); break; case fourcc("dinf"): box = std::make_shared<Box_dinf>(); break; case fourcc("dref"): box = std::make_shared<Box_dref>(); break; case fourcc("url "): box = std::make_shared<Box_url>(); break; case fourcc("colr"): box = std::make_shared<Box_colr>(); break; case fourcc("pixi"): box = std::make_shared<Box_pixi>(); break; case fourcc("pasp"): box = std::make_shared<Box_pasp>(); break; case fourcc("lsel"): box = std::make_shared<Box_lsel>(); break; case fourcc("a1op"): box = std::make_shared<Box_a1op>(); break; case fourcc("a1lx"): box = std::make_shared<Box_a1lx>(); break; case fourcc("clli"): box = std::make_shared<Box_clli>(); break; case fourcc("mdcv"): box = std::make_shared<Box_mdcv>(); break; case fourcc("cmin"): box = std::make_shared<Box_cmin>(); break; case fourcc("cmex"): box = std::make_shared<Box_cmex>(); break; case fourcc("udes"): box = std::make_shared<Box_udes>(); break; case fourcc("jpgC"): box = std::make_shared<Box_jpgC>(); break; #if WITH_UNCOMPRESSED_CODEC case fourcc("cmpd"): box = std::make_shared<Box_cmpd>(); break; case fourcc("uncC"): box = std::make_shared<Box_uncC>(); break; case fourcc("cmpC"): box = std::make_shared<Box_cmpC>(); break; case fourcc("icbr"): box = std::make_shared<Box_icbr>(); break; #endif // --- JPEG 2000 case fourcc("j2kH"): box = std::make_shared<Box_j2kH>(); break; case fourcc("cdef"): box = std::make_shared<Box_cdef>(); break; case fourcc("cmap"): box = std::make_shared<Box_cmap>(); break; case fourcc("pclr"): box = std::make_shared<Box_pclr>(); break; case fourcc("j2kL"): box = std::make_shared<Box_j2kL>(); break; // --- mski case fourcc("mskC"): box = std::make_shared<Box_mskC>(); break; // --- AVC (H.264) case fourcc("avcC"): box = std::make_shared<Box_avcC>(); break; case fourcc("mdat"): // avoid generating a 'Box_other' box = std::make_shared<Box>(); break; case fourcc("uuid"): if (hdr.get_uuid_type() == std::vector<uint8_t>{0x22, 0xcc, 0x04, 0xc7, 0xd6, 0xd9, 0x4e, 0x07, 0x9d, 0x90, 0x4e, 0xb6, 0xec, 0xba, 0xf3, 0xa3}) { box = std::make_shared<Box_cmin>(); } else if (hdr.get_uuid_type() == std::vector<uint8_t>{0x43, 0x63, 0xe9, 0x14, 0x5b, 0x7d, 0x4a, 0xab, 0x97, 0xae, 0xbe, 0xa6, 0x98, 0x03, 0xb4, 0x34}) { box = std::make_shared<Box_cmex>(); } else { box = std::make_shared<Box_other>(hdr.get_short_type()); } break; case fourcc("moov"): box = std::make_shared<Box_moov>(); break; case fourcc("mvhd"): box = std::make_shared<Box_mvhd>(); break; case fourcc("trak"): box = std::make_shared<Box_trak>(); break; case fourcc("tkhd"): box = std::make_shared<Box_tkhd>(); break; case fourcc("mdia"): box = std::make_shared<Box_mdia>(); break; case fourcc("mdhd"): box = std::make_shared<Box_mdhd>(); break; case fourcc("minf"): box = std::make_shared<Box_minf>(); break; case fourcc("vmhd"): box = std::make_shared<Box_vmhd>(); break; case fourcc("stbl"): box = std::make_shared<Box_stbl>(); break; case fourcc("stsd"): box = std::make_shared<Box_stsd>(); break; case fourcc("hvc1"): box = std::make_shared<Box_hvc1>(); break; case fourcc("ccst"): box = std::make_shared<Box_ccst>(); break; case fourcc("stsz"): box = std::make_shared<Box_stsz>(); break; case fourcc("stts"): box = std::make_shared<Box_stts>(); break; case fourcc("stsc"): box = std::make_shared<Box_stsc>(); break; case fourcc("stco"): box = std::make_shared<Box_stco>(); break; case fourcc("stss"): box = std::make_shared<Box_stss>(); break; default: box = std::make_shared<Box_other>(hdr.get_short_type()); break; } box->set_short_header(hdr); if (hdr.has_fixed_box_size() && hdr.get_box_size() < hdr.get_header_size()) { std::stringstream sstr; sstr << "Box size (" << hdr.get_box_size() << " bytes) smaller than header size (" << hdr.get_header_size() << " bytes)"; // Sanity check. return Error(heif_error_Invalid_input, heif_suberror_Invalid_box_size, sstr.str()); } if (range.get_nesting_level() > MAX_BOX_NESTING_LEVEL) { return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, "Security limit for maximum nesting of boxes has been exceeded"); } if (hdr.has_fixed_box_size()) { auto status = range.wait_for_available_bytes(hdr.get_box_size() - hdr.get_header_size()); if (status != StreamReader::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); } } // Security check: make sure that box size does not exceed int64 size. if (hdr.get_box_size() > (uint64_t) std::numeric_limits<int64_t>::max()) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_box_size); } int64_t box_size = static_cast<int64_t>(hdr.get_box_size()); int64_t box_size_without_header = hdr.has_fixed_box_size() ? (box_size - hdr.get_header_size()) : (int64_t)range.get_remaining_bytes(); // Box size may not be larger than remaining bytes in parent box. if ((int64_t)range.get_remaining_bytes() < box_size_without_header) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_box_size); } // Create child bitstream range and read box from that range. BitstreamRange boxrange(range.get_istream(), box_size_without_header, &range); err = box->parse(boxrange); if (err == Error::Ok) { *result = std::move(box); } boxrange.skip_to_end_of_box(); return err; } std::string Box::dump(Indent& indent) const { std::ostringstream sstr; sstr << BoxHeader::dump(indent); return sstr.str(); } std::string FullBox::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "version: " << ((int) m_version) << "\n" << indent << "flags: " << std::hex << m_flags << "\n"; return sstr.str(); } Error Box::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); Error err = write_children(writer); prepend_header(writer, box_start); return err; } std::shared_ptr<Box> Box::get_child_box(uint32_t short_type) const { for (auto& box : m_children) { if (box->get_short_type() == short_type) { return box; } } return nullptr; } std::vector<std::shared_ptr<Box>> Box::get_child_boxes(uint32_t short_type) const { std::vector<std::shared_ptr<Box>> result; for (auto& box : m_children) { if (box->get_short_type() == short_type) { result.push_back(box); } } return result; } bool Box::operator==(const Box& other) const { if (this->get_short_type() != other.get_short_type()) { return false; } StreamWriter writer1; StreamWriter writer2; this->write(writer1); other.write(writer2); return writer1.get_data() == writer2.get_data(); } bool Box::equal(const std::shared_ptr<Box>& box1, const std::shared_ptr<Box>& box2) { if (!box1 || !box2) { return false; } return *box1 == *box2; } Error Box::read_children(BitstreamRange& range, int max_number) { int count = 0; while (!range.eof() && !range.error()) { std::shared_ptr<Box> box; Error error = Box::read(range, &box); if (error != Error::Ok) { return error; } if (m_children.size() > MAX_CHILDREN_PER_BOX) { std::stringstream sstr; sstr << "Maximum number of child boxes " << MAX_CHILDREN_PER_BOX << " exceeded."; // Sanity check. return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, sstr.str()); } m_children.push_back(std::move(box)); // count the new child and end reading new children when we reached the expected number count++; if (max_number != READ_CHILDREN_ALL && count == max_number) { break; } } return range.get_error(); } Error Box::write_children(StreamWriter& writer) const { for (const auto& child : m_children) { Error err = child->write(writer); if (err) { return err; } } return Error::Ok; } std::string Box::dump_children(Indent& indent) const { std::ostringstream sstr; bool first = true; indent++; for (const auto& childBox : m_children) { if (first) { first = false; } else { sstr << indent << "\n"; } sstr << childBox->dump(indent); } indent--; return sstr.str(); } void Box::derive_box_version_recursive() { derive_box_version(); for (auto& child : m_children) { child->derive_box_version_recursive(); } } Error Box_other::parse(BitstreamRange& range) { if (has_fixed_box_size()) { size_t len; if (get_box_size() >= get_header_size()) { len = get_box_size() - get_header_size(); m_data.resize(len); range.read(m_data.data(), len); } else { return Error(heif_error_Invalid_input, heif_suberror_Invalid_box_size); } } else { // TODO: boxes until end of file (we will probably never need this) } return range.get_error(); } Error Box_other::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); if (get_box_size() >= get_header_size()) { writer.write(m_data); prepend_header(writer, box_start); return Error::Ok; } else { return Error(heif_error_Invalid_input, heif_suberror_Invalid_box_size); } } std::string Box_other::dump(Indent& indent) const { std::ostringstream sstr; sstr << BoxHeader::dump(indent); // --- show raw box content size_t len = 0; if (get_box_size() >= get_header_size()) { len = get_box_size() - get_header_size(); } else { sstr << indent << "invalid box size " << get_box_size() << " (smaller than header)\n"; return sstr.str(); } sstr << write_raw_data_as_hex(m_data.data(), len, "data: ", " "); return sstr.str(); } Error Box_ftyp::parse(BitstreamRange& range) { m_major_brand = range.read32(); m_minor_version = range.read32(); if (get_box_size() <= get_header_size() + 8) { // Sanity check. return Error(heif_error_Invalid_input, heif_suberror_Invalid_box_size, "ftyp box too small (less than 8 bytes)"); } uint64_t n_minor_brands = (get_box_size() - get_header_size() - 8) / 4; for (uint64_t i = 0; i < n_minor_brands && !range.error(); i++) { m_compatible_brands.push_back(range.read32()); } return range.get_error(); } bool Box_ftyp::has_compatible_brand(heif_brand2 brand) const { return std::find(m_compatible_brands.begin(), m_compatible_brands.end(), brand) != m_compatible_brands.end(); } std::string Box_ftyp::dump(Indent& indent) const { std::ostringstream sstr; sstr << BoxHeader::dump(indent); sstr << indent << "major brand: " << to_fourcc(m_major_brand) << "\n" << indent << "minor version: " << m_minor_version << "\n" << indent << "compatible brands: "; bool first = true; for (uint32_t brand : m_compatible_brands) { if (first) { first = false; } else { sstr << ','; } sstr << to_fourcc(brand); } sstr << "\n"; return sstr.str(); } void Box_ftyp::add_compatible_brand(heif_brand2 brand) { if (!has_compatible_brand(brand)) { m_compatible_brands.push_back(brand); } } Error Box_ftyp::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(m_major_brand); writer.write32(m_minor_version); for (uint32_t b : m_compatible_brands) { writer.write32(b); } prepend_header(writer, box_start); return Error::Ok; } Error Box_meta::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() != 0) { return unsupported_version_error("meta"); } /* uint64_t boxSizeLimit; if (get_box_size() == BoxHeader::size_until_end_of_file) { boxSizeLimit = sizeLimit; } else { boxSizeLimit = get_box_size() - get_header_size(); } */ return read_children(range); } std::string Box_meta::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } Error Box_moov::parse(BitstreamRange& range) { //parse_full_box_header(range); return read_children(range); } std::string Box_moov::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } void Box_mvhd::derive_box_version() { set_version(1); } Error Box_mvhd::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write64(m_creation_time); writer.write64(m_modification_time); writer.write32(m_timescale); writer.write64(m_duration); writer.write32(0x00010000); writer.write16(0x0100); writer.write16(m_reserved0); for (int i = 0; i < 2; i++) { writer.write32(m_reserved1[i]); } for (int i = 0; i < 9; i++) { writer.write32(m_matrix[i]); } for (int i = 0; i < 6; i++) { writer.write32(pre_defined[i]); } writer.write32(m_next_track_ID); prepend_header(writer, box_start); return Error::Ok; } Error Box_mvhd::parse(BitstreamRange& range) { parse_full_box_header(range); if(get_version()==1) { m_creation_time = range.read64(); m_modification_time = range.read64(); m_timescale = range.read32(); m_duration = range.read64(); } else { m_creation_time = range.read32(); m_modification_time = range.read32(); m_timescale = range.read32(); m_duration = range.read32(); } m_rate = range.read32(); m_volume = range.read16(); m_reserved0 = range.read16(); for (int i = 0; i < 2; i++) { m_reserved1[i] = range.read32(); } for (int i = 0; i < 9; i++) { m_matrix[i] = range.read32(); } for (int i = 0; i < 6; i++) { pre_defined[i] = range.read32(); } m_next_track_ID = range.read32(); return range.get_error(); } std::string Box_mvhd::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "creation_time: " << m_creation_time << "\n" << indent << "modification_time: " << m_modification_time << "\n" << indent << "timescale: " << m_timescale << "\n" << indent << "duration: " << m_duration << "\n" << indent << "rate: " << m_rate << "\n" << indent << "volume: " << m_volume << "\n" << indent << "reserved: " << m_reserved0 << "\n" << indent << "reserved: " << m_reserved1[0] << " " << m_reserved1[1] << "\n" << indent << "matrix: " << m_matrix[0] << " " << m_matrix[1] << " " << m_matrix[2] << " " << m_matrix[3] << " " << m_matrix[4] << " " << m_matrix[5] << " " << m_matrix[6] << " " << m_matrix[7] << " " << m_matrix[8] << "\n" << indent << "pre_defined: " << pre_defined[0] << " " << pre_defined[1] << " " << pre_defined[2] << " " << pre_defined[3] << " " << pre_defined[4] << " " << pre_defined[5] << "\n" << indent << "next_track_ID: " << m_next_track_ID << "\n"; return sstr.str(); } Error Box_trak::parse(BitstreamRange& range) { //parse_full_box_header(range); return read_children(range); } std::string Box_trak::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } void Box_tkhd::derive_box_version() { set_version(1); set_flags(1); } Error Box_tkhd::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write64(m_creation_time); writer.write64(m_modification_time); writer.write32(m_track_ID); writer.write32(m_reserved0); writer.write64(m_duration); for (int i = 0; i < 2; i++) { writer.write32(m_reserved1[i]); } writer.write16(m_layer); writer.write16(m_alternate_group); writer.write16(m_volume); writer.write16(m_reserved2); for (int i = 0; i < 9; i++) { writer.write32(m_matrix[i]); } writer.write32(m_width); writer.write32(m_height); prepend_header(writer, box_start); return Error::Ok; } Error Box_tkhd::parse(BitstreamRange& range) { parse_full_box_header(range); if(get_version()==1) { m_creation_time = range.read64(); m_modification_time = range.read64(); m_track_ID = range.read32(); m_reserved0 = range.read32(); m_duration = range.read64(); } else { m_creation_time = range.read32(); m_modification_time = range.read32(); m_track_ID = range.read32(); m_reserved0 = range.read32(); m_duration = range.read32(); } for (int i = 0; i < 2; i++) { m_reserved1[i] = range.read32(); } m_layer = range.read16(); m_alternate_group = range.read16(); m_volume = range.read16(); m_reserved2 = range.read16(); for (int i = 0; i < 9; i++) { m_matrix[i] = range.read32(); } m_width = range.read32(); m_height = range.read32(); return range.get_error(); } std::string Box_tkhd::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "creation_time: " << m_creation_time << "\n" << indent << "modification_time: " << m_modification_time << "\n" << indent << "track_ID: " << m_track_ID << "\n" << indent << "reserved: " << m_reserved0 << "\n" << indent << "duration: " << m_duration << "\n" << indent << "reserved: " << m_reserved1[0] << " " << m_reserved1[1] << "\n" << indent << "layer: " << m_layer << "\n" << indent << "alternate_group: " << m_alternate_group << "\n" << indent << "volume: " << m_volume << "\n" << indent << "reserved: " << m_reserved2 << "\n" << indent << "matrix: " << m_matrix[0] << " " << m_matrix[1] << " " << m_matrix[2] << " " << m_matrix[3] << " " << m_matrix[4] << " " << m_matrix[5] << " " << m_matrix[6] << " " << m_matrix[7] << " " << m_matrix[8] << "\n" << indent << "width: " << m_width << "\n" << indent << "height: " << m_height << "\n"; return sstr.str(); } Error Box_mdia::parse(BitstreamRange& range) { //parse_full_box_header(range); return read_children(range); } std::string Box_mdia::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } void Box_mdhd::derive_box_version() { set_version(1); set_flags(0); } Error Box_mdhd::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write64(m_creation_time); writer.write64(m_modification_time); writer.write32(m_timescale); writer.write64(m_duration); writer.write16(m_language); writer.write16(m_pre_defined); \ prepend_header(writer, box_start); return Error::Ok; } Error Box_mdhd::parse(BitstreamRange& range) { parse_full_box_header(range); if(get_version()==1) { m_creation_time = range.read64(); m_modification_time = range.read64(); m_timescale = range.read32(); m_duration = range.read64(); } else { m_creation_time = range.read32(); m_modification_time = range.read32(); m_timescale = range.read32(); m_duration = range.read32(); } m_language = range.read16(); m_pre_defined = range.read16(); return range.get_error(); } std::string Box_mdhd::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "creation_time: " << m_creation_time << "\n" << indent << "modification_time: " << m_modification_time << "\n" << indent << "timescale: " << m_timescale << "\n" << indent << "duration: " << m_duration << "\n" << indent << "language: " << m_language << "\n" << indent << "pre_defined: " << m_pre_defined << "\n"; return sstr.str(); } Error Box_minf::parse(BitstreamRange& range) { //parse_full_box_header(range); return read_children(range); } std::string Box_minf::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } void Box_vmhd::derive_box_version() { set_version(0); set_flags(1); } Error Box_vmhd::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write16(m_graphicsmode); for (int i = 0; i < 3; i++) { writer.write16(m_opcolor[i]); } prepend_header(writer, box_start); return Error::Ok; } Error Box_vmhd::parse(BitstreamRange& range) { parse_full_box_header(range); m_graphicsmode = range.read16(); for (int i = 0; i < 3; i++) { m_opcolor[i] = range.read16(); } return range.get_error(); } std::string Box_vmhd::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "graphicsmode: " << m_graphicsmode << "\n" << indent << "opcolor: " << m_opcolor[0] << " " << m_opcolor[1] << " " << m_opcolor[2] << "\n"; return sstr.str(); } Error Box_stbl::parse(BitstreamRange& range) { //parse_full_box_header(range); return read_children(range); } std::string Box_stbl::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } void Box_stsd::derive_box_version() { set_version(0); set_flags(0); } std::string Box_stsd::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "entry_count: " << m_entry_count << "\n"; sstr << dump_children(indent); return sstr.str(); } Error Box_stsd::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(1); Error err = write_children(writer); prepend_header(writer, box_start); return err; } Error Box_stsd::parse(BitstreamRange& range) { parse_full_box_header(range); m_entry_count = range.read32(); return read_children(range); } void Box_hvc1::set_hvc1_data(uint16_t img_src_width, uint16_t img_src_height, uint16_t frame_count) { m_width = img_src_width; m_height = img_src_height; m_frame_count = frame_count; } Error Box_hvc1::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); for (int i = 0; i < 6; i++) { writer.write8(m_reserved0[i]); } writer.write16(m_data_reference_index); writer.write16(m_pre_defined0); writer.write16(m_reserved1); for (int i = 0; i < 3; i++) { writer.write32(m_pre_defined1[i]); } writer.write16(m_width); writer.write16(m_height); writer.write32(m_horizresolution); writer.write32(m_vertresolution); writer.write32(m_reserved2); writer.write16(m_frame_count); std::string compressorname = "HEVC Coding"; writer.write(compressorname); writer.write(32-compressorname.size()-1, 0); writer.write16(m_depth); writer.write16(m_pre_defined2); Error err = write_children(writer); prepend_header(writer, box_start); return err; } std::string IntToString(int & i) { std::string s; std::stringstream ss(s); ss<<i; return ss.str(); } Error Box_hvc1::parse(BitstreamRange& range) { //parse_full_box_header(range); uint8_t data[32]; for (int i = 0; i < 6; i++) { m_reserved0[i] = range.read8(); } m_data_reference_index = range.read16(); m_pre_defined0 = range.read16(); m_reserved1 = range.read16(); for (int i = 0; i < 3; i++) { m_pre_defined1[i] = range.read32(); } m_width = range.read16(); m_height = range.read16(); m_horizresolution = range.read32(); m_vertresolution = range.read32(); m_reserved2 = range.read32(); m_frame_count = range.read16(); m_compressorname = range.read_string(); // printf("%s\n",m_compressorname); // int xxx = m_compressorname.size(); range.read(data, 32-m_compressorname.size()-1); //range.read(data, 32); m_depth = range.read16(); m_pre_defined2 = range.read16(); //printf("%s\n", strs); return read_children(range); } std::string Box_hvc1::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "data_reference_index: " << m_data_reference_index << "\n" << indent << "width: " << m_width << "\n" << indent << "height: " << m_height << "\n" << indent << "horizresolution: " << m_horizresolution << "\n" << indent << "vertresolution: " << m_vertresolution << "\n" << indent << "frame_count: " << m_frame_count << "\n" << indent << "compressorname: " << m_compressorname << "\n" << indent << "depth: " << m_depth << "\n"; sstr << dump_children(indent); return sstr.str(); } void Box_ccst::derive_box_version() { set_version(0); set_flags(0); } Error Box_ccst::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); const uint8_t ccstValue = (1 << 7) | // unsigned int(1) all_ref_pics_intra; (1 << 6) | // unsigned int(1) intra_pred_used; (15 << 2); // unsigned int(4) max_ref_per_pic; writer.write8(ccstValue); for (int i = 0; i < 3; i++) { writer.write8(m_reserved[i]); } prepend_header(writer, box_start); return Error::Ok; } Error Box_ccst::parse(BitstreamRange& range) { parse_full_box_header(range); m_ccst_data = range.read8(); for (int i = 0; i < 3; i++) { m_reserved[i] = range.read8(); } return range.get_error(); } std::string Box_ccst::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "ccst_data: " << (uint16_t)m_ccst_data << "\n"; return sstr.str(); } void Box_stsz::derive_box_version() { set_version(0); set_flags(0); } uint32_t Box_stsz::get_sample_size(uint32_t ID) { if(m_sample_size) { return m_sample_size; } else { return entry_sizes[ID]; } } uint32_t Box_stsz::get_sample_offset(uint32_t ID) { uint32_t offset = 0; for (size_t i = 0; i < ID; i++) { if(m_sample_size) { offset += m_sample_size; } else { offset += entry_sizes[i]; } } return offset; } Error Box_stsz::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); uint32_t sample_count = entry_sizes.size(); writer.write32(m_sample_size); writer.write32(sample_count); for(auto& i : entry_sizes) { writer.write32(i); } prepend_header(writer, box_start); return Error::Ok; } Error Box_stsz::parse(BitstreamRange& range) { uint32_t sample_count = 0; parse_full_box_header(range); m_sample_size = range.read32(); sample_count = range.read32(); if(m_sample_size==0) { for (uint32_t i = 0; i < sample_count; i++) { uint32_t entry_size = range.read32(); entry_sizes.push_back(entry_size); } } return range.get_error(); } std::string Box_stsz::dump(Indent& indent) const { int idx = 0; std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "sample_size: " << m_sample_size << "\n"; for(auto& i : entry_sizes) { sstr << indent << "entry_size" << idx << ": " << i << "\n"; idx++; } return sstr.str(); } void Box_stts::derive_box_version() { set_version(0); set_flags(0); } void Box_stts::calc_stts_data() { m_entry_count = 0; for (uint32_t sampleCount = 0, frameIndex = 0; frameIndex < frame_durationinTimeScales.size(); frameIndex++) { ++sampleCount; if (frameIndex < (frame_durationinTimeScales.size() - 1)) { if (frame_durationinTimeScales[frameIndex] == frame_durationinTimeScales[frameIndex+1]) { continue; } } sample_info tmp; tmp.m_sample_count = sampleCount; tmp.m_sample_delta = (uint32_t)frame_durationinTimeScales[frameIndex]; m_samples.push_back(tmp); ++m_entry_count; } } Error Box_stts::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(m_entry_count); for(auto& i : m_samples) { writer.write32(i.m_sample_count); writer.write32(i.m_sample_delta); } prepend_header(writer, box_start); return Error::Ok; } Error Box_stts::parse(BitstreamRange& range) { parse_full_box_header(range); m_entry_count = range.read32(); for (int i = 0; i < m_entry_count; i++) { sample_info tmp; tmp.m_sample_count= range.read32(); tmp.m_sample_delta = range.read32(); m_samples.push_back(tmp); } return range.get_error(); } std::string Box_stts::dump(Indent& indent) const { int idx = 0; std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "entry_count: " << m_entry_count << "\n"; for(auto& i : m_samples) { sstr << indent << "sample_count" << idx << ": " << i.m_sample_count << "\n"; sstr << indent << "sample_delta" << idx << ": " << i.m_sample_delta << "\n"; idx++; } return sstr.str(); } void Box_stsc::derive_box_version() { set_version(0); set_flags(0); } Error Box_stsc::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(m_entry_count); writer.write32(m_first_chunk); writer.write32(m_samples_per_chunk); writer.write32(m_sample_description_index); prepend_header(writer, box_start); return Error::Ok; } Error Box_stsc::parse(BitstreamRange& range) { parse_full_box_header(range); m_entry_count = range.read32(); for (int i = 0; i < m_entry_count; i++) { sampleofchunk sample_chunk; sample_chunk.m_first_chunk = range.read32(); sample_chunk.m_samples_per_chunk = range.read32(); sample_chunk.m_sample_description_index = range.read32(); m_chunk_samples.push_back(sample_chunk); } return range.get_error(); } std::string Box_stsc::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "entry_count: " << m_entry_count << "\n"; for(auto& i : m_chunk_samples) { sstr << indent << "first_chunk: " << i.m_first_chunk << "\n"; sstr << indent << "samples_per_chunk: " << i.m_samples_per_chunk << "\n"; sstr << indent << "sample_description_index: " << i.m_sample_description_index << "\n"; } return sstr.str(); } void Box_stco::derive_box_version() { set_version(0); set_flags(0); } Error Box_stco::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(m_entry_count); m_stco_box_start = writer.get_position(); writer.skip(4); prepend_header(writer, box_start); return Error::Ok; } Error Box_stco::parse(BitstreamRange& range) { parse_full_box_header(range); m_entry_count = range.read32(); m_stco_mdat_offset = range.read32(); return range.get_error(); } std::string Box_stco::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "entry_count: " << m_entry_count << "\n" << indent << "mdat_offset: " << m_stco_mdat_offset << "\n"; return sstr.str(); } void Box_stco::patch_iloc_header(StreamWriter& writer, uint64_t mdate_offset) { size_t old_pos = writer.get_position(); writer.set_position(m_stco_box_start); writer.write32(mdate_offset); writer.set_position(old_pos); } void Box_stss::derive_box_version() { set_version(0); set_flags(0); } Error Box_stss::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(m_entry_count); for (uint32_t i = 0; i < m_entry_count; i++) { writer.write32(m_sample_number[i]); } prepend_header(writer, box_start); return Error::Ok; } Error Box_stss::parse(BitstreamRange& range) { parse_full_box_header(range); m_entry_count = range.read32(); for (uint32_t i = 0; i < m_entry_count; i++) { m_sample_number.push_back(range.read32()); } return range.get_error(); } std::string Box_stss::dump(Indent& indent) const { int idx = 0; std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "entry_count: " << m_entry_count << "\n"; for(auto& i : m_sample_number) { sstr << indent << "sample_number" << idx << ": " << i << "\n"; idx++; } return sstr.str(); } void Box_stss::record_sync_data(uint8_t nal_type) { if(nal_type == 19 || nal_type==20) { m_sync_flags.push_back(1); } else { m_sync_flags.push_back(0); } } void Box_stss::set_stss_data() { for (uint32_t i = 0; i < m_sync_flags.size(); i++) { if(m_sync_flags[i]) { m_entry_count++; m_sample_number.push_back(i+1); } } } Error FullBox::unsupported_version_error(const char* box) const { std::stringstream sstr; sstr << box << " box data version " << ((int) m_version) << " is not implemented yet"; return {heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()}; } Error Box_hdlr::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() != 0) { return unsupported_version_error("hdlr"); } m_pre_defined = range.read32(); m_handler_type = range.read32(); for (int i = 0; i < 3; i++) { m_reserved[i] = range.read32(); } m_name = range.read_string(); return range.get_error(); } std::string Box_hdlr::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "pre_defined: " << m_pre_defined << "\n" << indent << "handler_type: " << to_fourcc(m_handler_type) << "\n" << indent << "name: " << m_name << "\n"; return sstr.str(); } Error Box_hdlr::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(m_pre_defined); writer.write32(m_handler_type); for (int i = 0; i < 3; i++) { writer.write32(m_reserved[i]); } writer.write(m_name); prepend_header(writer, box_start); return Error::Ok; } Error Box_pitm::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() > 1) { return unsupported_version_error("pitm"); } if (get_version() == 0) { m_item_ID = range.read16(); } else { m_item_ID = range.read32(); } return range.get_error(); } std::string Box_pitm::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "item_ID: " << m_item_ID << "\n"; return sstr.str(); } void Box_pitm::derive_box_version() { if (m_item_ID <= 0xFFFF) { set_version(0); } else { set_version(1); } } Error Box_pitm::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); if (get_version() == 0) { assert(m_item_ID <= 0xFFFF); writer.write16((uint16_t) m_item_ID); } else { writer.write32(m_item_ID); } prepend_header(writer, box_start); return Error::Ok; } Error Box_iloc::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() > 2) { return unsupported_version_error("iloc"); } const int version = get_version(); uint16_t values4 = range.read16(); int offset_size = (values4 >> 12) & 0xF; int length_size = (values4 >> 8) & 0xF; int base_offset_size = (values4 >> 4) & 0xF; int index_size = 0; if (version == 1 || version == 2) { index_size = (values4 & 0xF); } uint32_t item_count = 0; if (version < 2) { item_count = range.read16(); } else if (version == 2) { item_count = range.read32(); } // Sanity check. if (item_count > MAX_ILOC_ITEMS) { std::stringstream sstr; sstr << "iloc box contains " << item_count << " items, which exceeds the security limit of " << MAX_ILOC_ITEMS << " items."; return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, sstr.str()); } for (uint32_t i = 0; i < item_count; i++) { Item item; if (version < 2) { item.item_ID = range.read16(); } else if (version == 2) { item.item_ID = range.read32(); } if (version >= 1) { values4 = range.read16(); item.construction_method = (values4 & 0xF); } item.data_reference_index = range.read16(); item.base_offset = 0; if (base_offset_size == 4) { item.base_offset = range.read32(); } else if (base_offset_size == 8) { item.base_offset = ((uint64_t) range.read32()) << 32; item.base_offset |= range.read32(); } int extent_count = range.read16(); // Sanity check. if (extent_count > MAX_ILOC_EXTENTS_PER_ITEM) { std::stringstream sstr; sstr << "Number of extents in iloc box (" << extent_count << ") exceeds security limit (" << MAX_ILOC_EXTENTS_PER_ITEM << ")\n"; return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, sstr.str()); } for (int e = 0; e < extent_count; e++) { Extent extent; if ((version == 1 || version == 2) && index_size > 0) { if (index_size == 4) { extent.index = range.read32(); } else if (index_size == 8) { extent.index = ((uint64_t) range.read32()) << 32; extent.index |= range.read32(); } } extent.offset = 0; if (offset_size == 4) { extent.offset = range.read32(); } else if (offset_size == 8) { extent.offset = ((uint64_t) range.read32()) << 32; extent.offset |= range.read32(); } extent.length = 0; if (length_size == 4) { extent.length = range.read32(); } else if (length_size == 8) { extent.length = ((uint64_t) range.read32()) << 32; extent.length |= range.read32(); } item.extents.push_back(extent); } if (!range.error()) { m_items.push_back(item); } } return range.get_error(); } std::string Box_iloc::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); for (const Item& item : m_items) { sstr << indent << "item ID: " << item.item_ID << "\n" << indent << " construction method: " << ((int) item.construction_method) << "\n" << indent << " data_reference_index: " << std::hex << item.data_reference_index << std::dec << "\n" << indent << " base_offset: " << item.base_offset << "\n"; sstr << indent << " extents: "; for (const Extent& extent : item.extents) { sstr << extent.offset << "," << extent.length; if (extent.index != 0) { sstr << ";index=" << extent.index; } sstr << " "; } sstr << "\n"; } return sstr.str(); } Error Box_iloc::read_data(const Item& item, const std::shared_ptr<StreamReader>& istr, const std::shared_ptr<Box_idat>& idat, std::vector<uint8_t>* dest) const { // TODO: this function should always append the data to the output vector as this is used when // the image data is concatenated with data in a configuration box. However, it seems that // this function clears the array in some cases. This should be corrected. for (const auto& extent : item.extents) { if (item.construction_method == 0) { // --- security check that we do not allocate too much memory size_t old_size = dest->size(); if (MAX_MEMORY_BLOCK_SIZE - old_size < extent.length) { std::stringstream sstr; sstr << "iloc box contained " << extent.length << " bytes, total memory size would be " << (old_size + extent.length) << " 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()); } // --- make sure that all data is available if (extent.offset > MAX_FILE_POS || item.base_offset > MAX_FILE_POS || extent.length > MAX_FILE_POS) { return Error(heif_error_Invalid_input, heif_suberror_Security_limit_exceeded, "iloc data pointers out of allowed range"); } StreamReader::grow_status status = istr->wait_for_file_size(extent.offset + item.base_offset + extent.length); 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 " << extent.offset + item.base_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(extent.offset + item.base_offset); assert(success); (void) success; // --- read data dest->resize(static_cast<size_t>(old_size + extent.length)); success = istr->read((char*) dest->data() + old_size, static_cast<size_t>(extent.length)); assert(success); (void) success; } else if (item.construction_method == 1) { if (!idat) { return Error(heif_error_Invalid_input, heif_suberror_No_idat_box, "idat box referenced in iref box is not present in file"); } idat->read_data(istr, extent.offset + item.base_offset, extent.length, *dest); } else { std::stringstream sstr; sstr << "Item construction method " << (int) item.construction_method << " not implemented"; return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_item_construction_method, sstr.str()); } } return Error::Ok; } Error Box_iloc::append_data(heif_item_id item_ID, const std::vector<uint8_t>& data, uint8_t construction_method) { // check whether this item ID already exists size_t idx; for (idx = 0; idx < m_items.size(); idx++) { if (m_items[idx].item_ID == item_ID) { break; } } // item does not exist -> add a new one to the end if (idx == m_items.size()) { Item item; item.item_ID = item_ID; item.construction_method = construction_method; m_items.push_back(item); } if (m_items[idx].construction_method != construction_method) { // TODO: return error: construction methods do not match } Extent extent; extent.data = data; if (construction_method == 1) { extent.offset = m_idat_offset; extent.length = data.size(); m_idat_offset += (int) data.size(); } m_items[idx].extents.push_back(std::move(extent)); return Error::Ok; } void Box_iloc::derive_box_version() { int min_version = m_user_defined_min_version; if (m_items.size() > 0xFFFF) { min_version = std::max(min_version, 2); } m_offset_size = 0; m_length_size = 0; m_base_offset_size = 0; m_index_size = 0; for (const auto& item : m_items) { // check item_ID size if (item.item_ID > 0xFFFF) { min_version = std::max(min_version, 2); } // check construction method if (item.construction_method != 0) { min_version = std::max(min_version, 1); } // base offset size /* if (item.base_offset > 0xFFFFFFFF) { m_base_offset_size = 8; } else if (item.base_offset > 0) { m_base_offset_size = 4; } */ /* for (const auto& extent : item.extents) { // extent index size if (extent.index != 0) { min_version = std::max(min_version, 1); m_index_size = 4; } if (extent.index > 0xFFFFFFFF) { m_index_size = 8; } // extent offset size if (extent.offset > 0xFFFFFFFF) { m_offset_size = 8; } else { m_offset_size = 4; } // extent length size if (extent.length > 0xFFFFFFFF) { m_length_size = 8; } else { m_length_size = 4; } } */ } m_offset_size = 4; m_length_size = 4; m_base_offset_size = 4; // TODO: or could be 8 if we write >4GB files m_index_size = 0; set_version((uint8_t) min_version); } Error Box_iloc::write(StreamWriter& writer) const { // --- write idat size_t sum_idat_size = 0; for (const auto& item : m_items) { if (item.construction_method == 1) { for (const auto& extent : item.extents) { sum_idat_size += extent.data.size(); } } } if (sum_idat_size > 0) { writer.write32((uint32_t) (sum_idat_size + 8)); writer.write32(fourcc("idat")); for (auto& item : m_items) { if (item.construction_method == 1) { for (auto& extent : item.extents) { writer.write(extent.data); } } } } // --- write iloc box size_t box_start = reserve_box_header_space(writer); m_iloc_box_start = writer.get_position(); int nSkip = 0; nSkip += 2; nSkip += (get_version() < 2) ? 2 : 4; // item_count for (const auto& item : m_items) { if(item.item_ID!=m_items[0].item_ID && moov_flag) continue; nSkip += (get_version() < 2) ? 2 : 4; // item_ID nSkip += (get_version() >= 1) ? 2 : 0; // construction method nSkip += 4 + m_base_offset_size; for (const auto& extent : item.extents) { (void) extent; if (get_version() >= 1) { nSkip += m_index_size; } nSkip += m_offset_size + m_length_size; } } writer.skip(nSkip); prepend_header(writer, box_start); return Error::Ok; } Error Box_iloc::write_mdat_after_iloc(StreamWriter& writer) { // --- compute sum of all mdat data size_t sum_mdat_size = 0; for (const auto& item : m_items) { if (item.construction_method == 0) { for (const auto& extent : item.extents) { sum_mdat_size += extent.data.size(); } } } if (sum_mdat_size > 0xFFFFFFFF) { // TODO: box size > 4 GB } // --- write mdat box writer.write32((uint32_t) (sum_mdat_size + 8)); writer.write32(fourcc("mdat")); for (auto& item : m_items) { if (item.construction_method == 0) { item.base_offset = writer.get_position(); for (auto& extent : item.extents) { extent.offset = writer.get_position() - item.base_offset; extent.length = extent.data.size(); writer.write(extent.data); } } } // --- patch iloc box patch_iloc_header(writer); return Error::Ok; } void Box_iloc::patch_iloc_header(StreamWriter& writer) const { size_t old_pos = writer.get_position(); writer.set_position(m_iloc_box_start); writer.write8((uint8_t) ((m_offset_size << 4) | (m_length_size))); writer.write8((uint8_t) ((m_base_offset_size << 4) | (m_index_size))); if (moov_flag) { if (get_version() < 2) { writer.write16(1); } else { writer.write32(1); } } else { if (get_version() < 2) { writer.write16((uint16_t) m_items.size()); } else { writer.write32((uint32_t) m_items.size()); } } for (const auto& item : m_items) { if(item.item_ID!=m_items[0].item_ID && moov_flag) continue; if (get_version() < 2) { writer.write16((uint16_t) item.item_ID); } else { writer.write32((uint32_t) item.item_ID); } if (get_version() >= 1) { writer.write16(item.construction_method); } writer.write16(item.data_reference_index); writer.write(m_base_offset_size, item.base_offset); writer.write16((uint16_t) item.extents.size()); for (const auto& extent : item.extents) { if (get_version() >= 1 && m_index_size > 0) { writer.write(m_index_size, extent.index); } writer.write(m_offset_size, extent.offset); writer.write(m_length_size, extent.length); } } writer.set_position(old_pos); } /* * version <= 1 version 2 version > 2 mime uri * ----------------------------------------------------------------------------------------------- * item id 16 16 32 16/32 16/32 * protection index 16 16 16 16 16 * item type - yes yes yes yes * item name yes yes yes yes yes * content type yes - - yes - * content encoding yes - - yes - * hidden item - yes yes yes yes * item uri type - - - - yes * * Note: HEIF does not allow version 0 and version 1 boxes ! (see 23008-12, 10.2.1) */ Error Box_infe::parse(BitstreamRange& range) { parse_full_box_header(range); // only versions 2,3 are required by HEIF if (get_version() > 3) { return unsupported_version_error("infe"); } if (get_version() <= 1) { m_item_ID = range.read16(); m_item_protection_index = range.read16(); m_item_name = range.read_string(); m_content_type = range.read_string(); m_content_encoding = range.read_string(); } if (get_version() >= 2) { m_hidden_item = (get_flags() & 1); if (get_version() == 2) { m_item_ID = range.read16(); } else { m_item_ID = range.read32(); } m_item_protection_index = range.read16(); uint32_t item_type = range.read32(); if (item_type != 0) { m_item_type = to_fourcc(item_type); } m_item_name = range.read_string(); if (item_type == fourcc("mime")) { m_content_type = range.read_string(); m_content_encoding = range.read_string(); } else if (item_type == fourcc("uri ")) { m_item_uri_type = range.read_string(); } } return range.get_error(); } void Box_infe::derive_box_version() { int min_version = 0; if (m_hidden_item) { min_version = std::max(min_version, 2); } if (m_item_ID > 0xFFFF) { min_version = std::max(min_version, 3); } if (m_item_type != "") { min_version = std::max(min_version, 2); } set_version((uint8_t) min_version); } void Box_infe::set_hidden_item(bool hidden) { m_hidden_item = hidden; if (m_hidden_item) { set_flags(get_flags() | 1U); } else { set_flags(get_flags() & ~1U); } } Error Box_infe::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); if (get_version() <= 1) { writer.write16((uint16_t) m_item_ID); writer.write16(m_item_protection_index); writer.write(m_item_name); writer.write(m_content_type); writer.write(m_content_encoding); } if (get_version() >= 2) { if (get_version() == 2) { writer.write16((uint16_t) m_item_ID); } else if (get_version() == 3) { writer.write32(m_item_ID); } writer.write16(m_item_protection_index); if (m_item_type.empty()) { writer.write32(0); } else { writer.write32(from_fourcc(m_item_type.c_str())); } writer.write(m_item_name); if (m_item_type == "mime") { writer.write(m_content_type); writer.write(m_content_encoding); } else if (m_item_type == "uri ") { writer.write(m_item_uri_type); } } prepend_header(writer, box_start); return Error::Ok; } std::string Box_infe::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "item_ID: " << m_item_ID << "\n" << indent << "item_protection_index: " << m_item_protection_index << "\n" << indent << "item_type: " << m_item_type << "\n" << indent << "item_name: " << m_item_name << "\n"; if (m_item_type == "mime") { sstr << indent << "content_type: " << m_content_type << "\n" << indent << "content_encoding: " << m_content_encoding << "\n"; } if (m_item_type == "uri ") { sstr << indent << "item uri type: " << m_item_uri_type << "\n"; } sstr << indent << "hidden item: " << std::boolalpha << m_hidden_item << "\n"; return sstr.str(); } Error Box_iinf::parse(BitstreamRange& range) { parse_full_box_header(range); // TODO: there are several images in circulation that have an iinf version=2. We should not enforce this with a hard error. if (false && get_version() > 1) { return unsupported_version_error("iinf"); } int nEntries_size = (get_version() > 0) ? 4 : 2; uint32_t item_count; if (nEntries_size == 2) { item_count = range.read16(); } else { item_count = range.read32(); } if (item_count == 0) { return Error::Ok; } // TODO: Only try to read "item_count" children. return read_children(range); } std::string Box_iinf::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } Error Box_iprp::parse(BitstreamRange& range) { //parse_full_box_header(range); return read_children(range); } void Box_iinf::derive_box_version() { if (m_children.size() > 0xFFFF) { set_version(1); } else { set_version(0); } } Error Box_iinf::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); int nEntries_size = (get_version() > 0) ? 4 : 2; writer.write(nEntries_size, m_children.size()); Error err = write_children(writer); prepend_header(writer, box_start); return err; } std::string Box_iprp::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } int Box_ipco::find_or_append_child_box(const std::shared_ptr<Box>& box) { for (int i = 0; i < (int) m_children.size(); i++) { if (Box::equal(m_children[i], box)) { return i; } } return append_child_box(box); } Error Box_ipco::parse(BitstreamRange& range) { //parse_full_box_header(range); return read_children(range); } std::string Box_ipco::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } Error Box_pixi::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() != 0) { return unsupported_version_error("pixi"); } StreamReader::grow_status status; uint8_t num_channels = range.read8(); status = range.wait_for_available_bytes(num_channels); if (status != StreamReader::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); } m_bits_per_channel.resize(num_channels); for (int i = 0; i < num_channels; i++) { m_bits_per_channel[i] = range.read8(); } return range.get_error(); } std::string Box_pixi::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "bits_per_channel: "; for (size_t i = 0; i < m_bits_per_channel.size(); i++) { if (i > 0) sstr << ","; sstr << ((int) m_bits_per_channel[i]); } sstr << "\n"; return sstr.str(); } Error Box_pixi::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); if (m_bits_per_channel.size() > 255 || m_bits_per_channel.empty()) { // TODO: error assert(false); } writer.write8((uint8_t) (m_bits_per_channel.size())); for (size_t i = 0; i < m_bits_per_channel.size(); i++) { writer.write8(m_bits_per_channel[i]); } prepend_header(writer, box_start); return Error::Ok; } Error Box_pasp::parse(BitstreamRange& range) { //parse_full_box_header(range); hSpacing = range.read32(); vSpacing = range.read32(); return range.get_error(); } std::string Box_pasp::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "hSpacing: " << hSpacing << "\n"; sstr << indent << "vSpacing: " << vSpacing << "\n"; return sstr.str(); } Error Box_pasp::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(hSpacing); writer.write32(vSpacing); prepend_header(writer, box_start); return Error::Ok; } Error Box_lsel::parse(BitstreamRange& range) { layer_id = range.read16(); return range.get_error(); } std::string Box_lsel::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "layer_id: " << layer_id << "\n"; return sstr.str(); } Error Box_lsel::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write16(layer_id); prepend_header(writer, box_start); return Error::Ok; } Error Box_clli::parse(BitstreamRange& range) { //parse_full_box_header(range); clli.max_content_light_level = range.read16(); clli.max_pic_average_light_level = range.read16(); return range.get_error(); } std::string Box_clli::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "max_content_light_level: " << clli.max_content_light_level << "\n"; sstr << indent << "max_pic_average_light_level: " << clli.max_pic_average_light_level << "\n"; return sstr.str(); } Error Box_clli::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write16(clli.max_content_light_level); writer.write16(clli.max_pic_average_light_level); prepend_header(writer, box_start); return Error::Ok; } Box_mdcv::Box_mdcv() { set_short_type(fourcc("mdcv")); memset(&mdcv, 0, sizeof(heif_mastering_display_colour_volume)); } Error Box_mdcv::parse(BitstreamRange& range) { //parse_full_box_header(range); for (int c = 0; c < 3; c++) { mdcv.display_primaries_x[c] = range.read16(); mdcv.display_primaries_y[c] = range.read16(); } mdcv.white_point_x = range.read16(); mdcv.white_point_y = range.read16(); mdcv.max_display_mastering_luminance = range.read32(); mdcv.min_display_mastering_luminance = range.read32(); return range.get_error(); } std::string Box_mdcv::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "display_primaries (x,y): "; sstr << "(" << mdcv.display_primaries_x[0] << ";" << mdcv.display_primaries_y[0] << "), "; sstr << "(" << mdcv.display_primaries_x[1] << ";" << mdcv.display_primaries_y[1] << "), "; sstr << "(" << mdcv.display_primaries_x[2] << ";" << mdcv.display_primaries_y[2] << ")\n"; sstr << indent << "white point (x,y): (" << mdcv.white_point_x << ";" << mdcv.white_point_y << ")\n"; sstr << indent << "max display mastering luminance: " << mdcv.max_display_mastering_luminance << "\n"; sstr << indent << "min display mastering luminance: " << mdcv.min_display_mastering_luminance << "\n"; return sstr.str(); } Error Box_mdcv::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); for (int c = 0; c < 3; c++) { writer.write16(mdcv.display_primaries_x[c]); writer.write16(mdcv.display_primaries_y[c]); } writer.write16(mdcv.white_point_x); writer.write16(mdcv.white_point_y); writer.write32(mdcv.max_display_mastering_luminance); writer.write32(mdcv.min_display_mastering_luminance); prepend_header(writer, box_start); return Error::Ok; } Error Box_ipco::get_properties_for_item_ID(uint32_t itemID, const std::shared_ptr<class Box_ipma>& ipma, std::vector<std::shared_ptr<Box>>& out_properties) const { const std::vector<Box_ipma::PropertyAssociation>* property_assoc = ipma->get_properties_for_item_ID(itemID); if (property_assoc == nullptr) { std::stringstream sstr; sstr << "Item (ID=" << itemID << ") has no properties assigned to it in ipma box"; return Error(heif_error_Invalid_input, heif_suberror_No_properties_assigned_to_item, sstr.str()); } const auto& allProperties = get_all_child_boxes(); for (const Box_ipma::PropertyAssociation& assoc : *property_assoc) { if (assoc.property_index > allProperties.size()) { std::stringstream sstr; sstr << "Nonexisting property (index=" << assoc.property_index << ") for item " << " ID=" << itemID << " referenced in ipma box"; return Error(heif_error_Invalid_input, heif_suberror_Ipma_box_references_nonexisting_property, sstr.str()); } if (assoc.property_index > 0) { out_properties.push_back(allProperties[assoc.property_index - 1]); } } return Error::Ok; } std::shared_ptr<Box> Box_ipco::get_property_for_item_ID(heif_item_id itemID, const std::shared_ptr<class Box_ipma>& ipma, uint32_t box_type) const { const std::vector<Box_ipma::PropertyAssociation>* property_assoc = ipma->get_properties_for_item_ID(itemID); if (property_assoc == nullptr) { return nullptr; } const auto& allProperties = get_all_child_boxes(); for (const Box_ipma::PropertyAssociation& assoc : *property_assoc) { if (assoc.property_index > allProperties.size() || assoc.property_index == 0) { return nullptr; } const auto& property = allProperties[assoc.property_index - 1]; if (property->get_short_type() == box_type) { return property; } } return nullptr; } bool Box_ipco::is_property_essential_for_item(heif_item_id itemId, const std::shared_ptr<const class Box>& property, const std::shared_ptr<class Box_ipma>& ipma) const { // find property index for (int i=0;i<(int)m_children.size();i++) { if (m_children[i] == property) { return ipma->is_property_essential_for_item(itemId, i); } } assert(false); // non-existing property return false; } Error Box_ispe::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() != 0) { return unsupported_version_error("ispe"); } m_image_width = range.read32(); m_image_height = range.read32(); return range.get_error(); } std::string Box_ispe::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "image width: " << m_image_width << "\n" << indent << "image height: " << m_image_height << "\n"; return sstr.str(); } Error Box_ispe::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(m_image_width); writer.write32(m_image_height); prepend_header(writer, box_start); return Error::Ok; } bool Box_ispe::operator==(const Box& other) const { const auto* other_ispe = dynamic_cast<const Box_ispe*>(&other); if (other_ispe == nullptr) { return false; } return (m_image_width == other_ispe->m_image_width && m_image_height == other_ispe->m_image_height); } Error Box_ipma::parse(BitstreamRange& range) { parse_full_box_header(range); // TODO: is there any specification of allowed values for the ipma version in the HEIF standards? if (get_version() > 1) { return unsupported_version_error("ipma"); } uint32_t entry_cnt = range.read32(); for (uint32_t i = 0; i < entry_cnt && !range.error() && !range.eof(); i++) { Entry entry; if (get_version() < 1) { entry.item_ID = range.read16(); } else { entry.item_ID = range.read32(); } int assoc_cnt = range.read8(); for (int k = 0; k < assoc_cnt; k++) { PropertyAssociation association; uint16_t index; if (get_flags() & 1) { index = range.read16(); association.essential = !!(index & 0x8000); association.property_index = (index & 0x7fff); } else { index = range.read8(); association.essential = !!(index & 0x80); association.property_index = (index & 0x7f); } entry.associations.push_back(association); } m_entries.push_back(entry); } return range.get_error(); } const std::vector<Box_ipma::PropertyAssociation>* Box_ipma::get_properties_for_item_ID(uint32_t itemID) const { for (const auto& entry : m_entries) { if (entry.item_ID == itemID) { return &entry.associations; } } return nullptr; } bool Box_ipma::is_property_essential_for_item(heif_item_id itemId, int propertyIndex) const { for (const auto& entry : m_entries) { if (entry.item_ID == itemId) { for (const auto& assoc : entry.associations) { if (assoc.property_index == propertyIndex) { return assoc.essential; } } } } assert(false); return false; } void Box_ipma::add_property_for_item_ID(heif_item_id itemID, PropertyAssociation assoc) { size_t idx; for (idx = 0; idx < m_entries.size(); idx++) { if (m_entries[idx].item_ID == itemID) { break; } } // if itemID does not exist, add a new entry if (idx == m_entries.size()) { Entry entry; entry.item_ID = itemID; m_entries.push_back(entry); } // add the property association m_entries[idx].associations.push_back(assoc); } std::string Box_ipma::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); for (const Entry& entry : m_entries) { sstr << indent << "associations for item ID: " << entry.item_ID << "\n"; indent++; for (const auto& assoc : entry.associations) { sstr << indent << "property index: " << assoc.property_index << " (essential: " << std::boolalpha << assoc.essential << ")\n"; } indent--; } return sstr.str(); } void Box_ipma::derive_box_version() { int version = 0; bool large_property_indices = false; for (const Entry& entry : m_entries) { if (entry.item_ID > 0xFFFF) { version = 1; } for (const auto& assoc : entry.associations) { if (assoc.property_index > 0x7F) { large_property_indices = true; } } } set_version((uint8_t) version); set_flags(large_property_indices ? 1 : 0); } Error Box_ipma::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); size_t entry_cnt = m_entries.size(); writer.write32((uint32_t) entry_cnt); for (const Entry& entry : m_entries) { if (get_version() < 1) { writer.write16((uint16_t) entry.item_ID); } else { writer.write32(entry.item_ID); } size_t assoc_cnt = entry.associations.size(); if (assoc_cnt > 0xFF) { // TODO: error, too many associations } writer.write8((uint8_t) assoc_cnt); for (const PropertyAssociation& association : entry.associations) { if (get_flags() & 1) { writer.write16((uint16_t) ((association.essential ? 0x8000 : 0) | (association.property_index & 0x7FFF))); } else { writer.write8((uint8_t) ((association.essential ? 0x80 : 0) | (association.property_index & 0x7F))); } } } prepend_header(writer, box_start); return Error::Ok; } void Box_ipma::insert_entries_from_other_ipma_box(const Box_ipma& b) { m_entries.insert(m_entries.end(), b.m_entries.begin(), b.m_entries.end()); } Error Box_auxC::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() != 0) { return unsupported_version_error("auxC"); } m_aux_type = range.read_string(); while (!range.eof()) { m_aux_subtypes.push_back(range.read8()); } return range.get_error(); } Error Box_auxC::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write(m_aux_type); for (uint8_t subtype : m_aux_subtypes) { writer.write8(subtype); } prepend_header(writer, box_start); return Error::Ok; } std::string Box_auxC::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "aux type: " << m_aux_type << "\n" << indent << "aux subtypes: "; for (uint8_t subtype : m_aux_subtypes) { sstr << std::hex << std::setw(2) << std::setfill('0') << ((int) subtype) << " "; } sstr << "\n"; return sstr.str(); } Error Box_irot::parse(BitstreamRange& range) { //parse_full_box_header(range); uint16_t rotation = range.read8(); rotation &= 0x03; m_rotation = rotation * 90; return range.get_error(); } Error Box_irot::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write8((uint8_t) (m_rotation / 90)); prepend_header(writer, box_start); return Error::Ok; } std::string Box_irot::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "rotation: " << m_rotation << " degrees (CCW)\n"; return sstr.str(); } Error Box_imir::parse(BitstreamRange& range) { //parse_full_box_header(range); uint8_t axis = range.read8(); if (axis & 1) { m_axis = heif_transform_mirror_direction_horizontal; } else { m_axis = heif_transform_mirror_direction_vertical; } return range.get_error(); } Error Box_imir::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write8(m_axis); prepend_header(writer, box_start); return Error::Ok; } std::string Box_imir::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "mirror direction: "; switch (m_axis) { case heif_transform_mirror_direction_vertical: sstr << "vertical\n"; break; case heif_transform_mirror_direction_horizontal: sstr << "horizontal\n"; break; case heif_transform_mirror_direction_invalid: sstr << "invalid\n"; break; } return sstr.str(); } Error Box_clap::parse(BitstreamRange& range) { //parse_full_box_header(range); uint32_t clean_aperture_width_num = range.read32(); uint32_t clean_aperture_width_den = range.read32(); uint32_t clean_aperture_height_num = range.read32(); uint32_t clean_aperture_height_den = range.read32(); // Note: in the standard document 14496-12(2015), it says that the offset values should also be unsigned integers, // but this is obviously an error. Even the accompanying standard text says that offsets may be negative. int32_t horizontal_offset_num = (int32_t) range.read32(); uint32_t horizontal_offset_den = (uint32_t) range.read32(); int32_t vertical_offset_num = (int32_t) range.read32(); uint32_t vertical_offset_den = (uint32_t) range.read32(); if (clean_aperture_width_num > (uint32_t) std::numeric_limits<int32_t>::max() || clean_aperture_width_den > (uint32_t) std::numeric_limits<int32_t>::max() || clean_aperture_height_num > (uint32_t) std::numeric_limits<int32_t>::max() || clean_aperture_height_den > (uint32_t) std::numeric_limits<int32_t>::max() || horizontal_offset_den > (uint32_t) std::numeric_limits<int32_t>::max() || vertical_offset_den > (uint32_t) std::numeric_limits<int32_t>::max()) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_fractional_number, "Exceeded supported value range."); } m_clean_aperture_width = Fraction(clean_aperture_width_num, clean_aperture_width_den); m_clean_aperture_height = Fraction(clean_aperture_height_num, clean_aperture_height_den); m_horizontal_offset = Fraction(horizontal_offset_num, (int32_t) horizontal_offset_den); m_vertical_offset = Fraction(vertical_offset_num, (int32_t) vertical_offset_den); if (!m_clean_aperture_width.is_valid() || !m_clean_aperture_height.is_valid() || !m_horizontal_offset.is_valid() || !m_vertical_offset.is_valid()) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_fractional_number); } return range.get_error(); } Error Box_clap::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(m_clean_aperture_width.numerator); writer.write32(m_clean_aperture_width.denominator); writer.write32(m_clean_aperture_height.numerator); writer.write32(m_clean_aperture_height.denominator); writer.write32(m_horizontal_offset.numerator); writer.write32(m_horizontal_offset.denominator); writer.write32(m_vertical_offset.numerator); writer.write32(m_vertical_offset.denominator); prepend_header(writer, box_start); return Error::Ok; } std::string Box_clap::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "clean_aperture: " << m_clean_aperture_width.numerator << "/" << m_clean_aperture_width.denominator << " x " << m_clean_aperture_height.numerator << "/" << m_clean_aperture_height.denominator << "\n"; sstr << indent << "offset: " << m_horizontal_offset.numerator << "/" << m_horizontal_offset.denominator << " ; " << m_vertical_offset.numerator << "/" << m_vertical_offset.denominator << "\n"; return sstr.str(); } double Box_clap::left(int image_width) const { Fraction pcX = m_horizontal_offset + Fraction(image_width - 1, 2); Fraction left = pcX - (m_clean_aperture_width - 1) / 2; return left.to_double(); } double Box_clap::top(int image_height) const { Fraction pcY = m_vertical_offset + Fraction(image_height - 1, 2); Fraction top = pcY - (m_clean_aperture_height - 1) / 2; return top.to_double(); } int Box_clap::left_rounded(int image_width) const { // pcX = horizOff + (width - 1)/2 // pcX ± (cleanApertureWidth - 1)/2 // left = horizOff + (width-1)/2 - (clapWidth-1)/2 Fraction pcX = m_horizontal_offset + Fraction(image_width - 1, 2); Fraction left = pcX - (m_clean_aperture_width - 1) / 2; return left.round_down(); } int Box_clap::right_rounded(int image_width) const { Fraction right = m_clean_aperture_width - 1 + left_rounded(image_width); return right.round(); } int Box_clap::top_rounded(int image_height) const { Fraction pcY = m_vertical_offset + Fraction(image_height - 1, 2); Fraction top = pcY - (m_clean_aperture_height - 1) / 2; return top.round(); } int Box_clap::bottom_rounded(int image_height) const { Fraction bottom = m_clean_aperture_height - 1 + top_rounded(image_height); return bottom.round(); } int Box_clap::get_width_rounded() const { return m_clean_aperture_width.round(); } int Box_clap::get_height_rounded() const { return m_clean_aperture_height.round(); } void Box_clap::set(uint32_t clap_width, uint32_t clap_height, uint32_t image_width, uint32_t image_height) { assert(image_width >= clap_width); assert(image_height >= clap_height); m_clean_aperture_width = Fraction(clap_width, 1U); m_clean_aperture_height = Fraction(clap_height, 1U); m_horizontal_offset = Fraction(-(int32_t) (image_width - clap_width), 2); m_vertical_offset = Fraction(-(int32_t) (image_height - clap_height), 2); } Error Box_iref::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() > 1) { return unsupported_version_error("iref"); } while (!range.eof()) { Reference ref; Error err = ref.header.parse_header(range); if (err != Error::Ok) { return err; } if (get_version() == 0) { ref.from_item_ID = range.read16(); int nRefs = range.read16(); for (int i = 0; i < nRefs; i++) { ref.to_item_ID.push_back(range.read16()); if (range.eof()) { break; } } } else { ref.from_item_ID = range.read32(); int nRefs = range.read16(); for (int i = 0; i < nRefs; i++) { ref.to_item_ID.push_back(range.read32()); if (range.eof()) { break; } } } m_references.push_back(ref); } // --- check number of total refs size_t nTotalRefs = 0; for (const auto& ref : m_references) { nTotalRefs += ref.to_item_ID.size(); } if (nTotalRefs > MAX_IREF_REFERENCES) { return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, "Number of iref references exceeds security limit."); } // --- check for duplicate references for (const auto& ref : m_references) { std::set<heif_item_id> to_ids; for (const auto to_id : ref.to_item_ID) { if (to_ids.find(to_id) == to_ids.end()) { to_ids.insert(to_id); } else { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "'iref' has double references"); } } } #if 0 // Note: This input sanity check does not work as expected. // Its idea was to prevent infinite recursions while decoding when the input file // contains cyclic references. However, apparently there are cases where cyclic // references are actually allowed, like with images that have premultiplied alpha channels: // | Box: iref ----- // | size: 40 (header size: 12) // | reference with type 'auxl' from ID: 2 to IDs: 1 // | reference with type 'prem' from ID: 1 to IDs: 2 // // TODO: implement the infinite recursion detection in a different way. E.g. by passing down // the already processed item-ids while decoding an image and checking whether the current // item has already been decoded before. // --- check for cyclic references for (const auto& ref : m_references) { std::set<heif_item_id> reached_ids; // IDs that we have already reached in the DAG std::set<heif_item_id> todo; // IDs that still need to be followed todo.insert(ref.from_item_ID); // start at base item while (!todo.empty()) { // transfer ID into set of reached IDs auto id = *todo.begin(); todo.erase(id); reached_ids.insert(id); // if this ID refers to another 'iref', follow it for (const auto& succ_ref : m_references) { if (succ_ref.from_item_ID == id) { // Check whether any successor IDs has been visited yet, which would be an error. // Otherwise, put that ID into the 'todo' set. for (const auto& succ_ref_id : succ_ref.to_item_ID) { if (reached_ids.find(succ_ref_id) != reached_ids.end()) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "'iref' has cyclic references"); } todo.insert(succ_ref_id); } } } } } #endif return range.get_error(); } void Box_iref::derive_box_version() { uint8_t version = 0; for (const auto& ref : m_references) { if (ref.from_item_ID > 0xFFFF) { version = 1; break; } for (uint32_t r : ref.to_item_ID) { if (r > 0xFFFF) { version = 1; break; } } } set_version(version); } Error Box_iref::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); int id_size = ((get_version() == 0) ? 2 : 4); for (const auto& ref : m_references) { uint32_t box_size = uint32_t(4 + 4 + 2 + id_size * (1 + ref.to_item_ID.size())); // we write the BoxHeader ourselves since it is very simple writer.write32(box_size); writer.write32(ref.header.get_short_type()); writer.write(id_size, ref.from_item_ID); writer.write16((uint16_t) ref.to_item_ID.size()); for (uint32_t r : ref.to_item_ID) { writer.write(id_size, r); } } prepend_header(writer, box_start); return Error::Ok; } std::string Box_iref::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); for (const auto& ref : m_references) { sstr << indent << "reference with type '" << ref.header.get_type_string() << "'" << " from ID: " << ref.from_item_ID << " to IDs: "; for (uint32_t id : ref.to_item_ID) { sstr << id << " "; } sstr << "\n"; } return sstr.str(); } bool Box_iref::has_references(uint32_t itemID) const { for (const Reference& ref : m_references) { if (ref.from_item_ID == itemID) { return true; } } return false; } std::vector<Box_iref::Reference> Box_iref::get_references_from(heif_item_id itemID) const { std::vector<Reference> references; for (const Reference& ref : m_references) { if (ref.from_item_ID == itemID) { references.push_back(ref); } } return references; } std::vector<uint32_t> Box_iref::get_references(uint32_t itemID, uint32_t ref_type) const { for (const Reference& ref : m_references) { if (ref.from_item_ID == itemID && ref.header.get_short_type() == ref_type) { return ref.to_item_ID; } } return std::vector<uint32_t>(); } void Box_iref::add_references(heif_item_id from_id, uint32_t type, const std::vector<heif_item_id>& to_ids) { Reference ref; ref.header.set_short_type(type); ref.from_item_ID = from_id; ref.to_item_ID = to_ids; m_references.push_back(ref); } Error Box_idat::parse(BitstreamRange& range) { //parse_full_box_header(range); m_data_start_pos = range.get_istream()->get_position(); return range.get_error(); } Error Box_idat::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write(m_data_for_writing); prepend_header(writer, box_start); return Error::Ok; } std::string Box_idat::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); if (get_box_size() >= get_header_size()) { sstr << indent << "number of data bytes: " << get_box_size() - get_header_size() << "\n"; } else { sstr << indent << "number of data bytes is invalid\n"; } return sstr.str(); } Error Box_idat::read_data(const std::shared_ptr<StreamReader>& istr, uint64_t start, uint64_t length, std::vector<uint8_t>& out_data) const { // --- security check that we do not allocate too much data auto curr_size = out_data.size(); if (MAX_MEMORY_BLOCK_SIZE - curr_size < length) { std::stringstream sstr; sstr << "idat box contained " << length << " bytes, total memory size would be " << (curr_size + length) << " 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()); } // move to start of data if (start > (uint64_t) m_data_start_pos + get_box_size()) { return Error(heif_error_Invalid_input, heif_suberror_End_of_data); } else if (length > get_box_size() || start + length > get_box_size()) { return Error(heif_error_Invalid_input, heif_suberror_End_of_data); } StreamReader::grow_status status = istr->wait_for_file_size((int64_t) m_data_start_pos + start + length); if (status == StreamReader::size_beyond_eof || 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); } bool success; success = istr->seek(m_data_start_pos + (std::streampos) start); assert(success); (void) success; if (length > 0) { // reserve space for the data in the output array out_data.resize(static_cast<size_t>(curr_size + length)); uint8_t* data = &out_data[curr_size]; success = istr->read((char*) data, static_cast<size_t>(length)); assert(success); (void) success; } return Error::Ok; } Error Box_grpl::parse(BitstreamRange& range) { //parse_full_box_header(range); return read_children(range); // should we pass the parsing context 'grpl' or are the box types unique? } std::string Box_grpl::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } Error Box_EntityToGroup::parse(BitstreamRange& range) { Error err = parse_full_box_header(range); if (err != Error::Ok) { return err; } group_id = range.read32(); uint32_t nEntities = range.read32(); for (uint32_t i = 0; i < nEntities; i++) { if (range.eof()) { break; } entity_ids.push_back(range.read32()); } return Error::Ok; } std::string Box_EntityToGroup::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "group id: " << group_id << "\n" << indent << "entity IDs: "; bool first = true; for (uint32_t id : entity_ids) { if (first) { first = false; } else { sstr << ' '; } sstr << id; } sstr << "\n"; return sstr.str(); } Error Box_ster::parse(BitstreamRange& range) { Error err = Box_EntityToGroup::parse(range); if (err) { return err; } if (entity_ids.size() != 2) { return {heif_error_Invalid_input, heif_suberror_Invalid_box_size, "'ster' entity group does not exists of exactly two images"}; } return Error::Ok; } std::string Box_ster::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "group id: " << group_id << "\n" << indent << "left image ID: " << entity_ids[0] << "\n" << indent << "right image ID: " << entity_ids[1] << "\n"; return sstr.str(); } Error Box_pymd::parse(BitstreamRange& range) { Error err = Box_EntityToGroup::parse(range); if (err) { return err; } tile_size_x = range.read16(); tile_size_y = range.read16(); for (size_t i = 0; i < entity_ids.size(); i++) { LayerInfo layer{}; layer.layer_binning = range.read16(); layer.tiles_in_layer_row_minus1 = range.read16(); layer.tiles_in_layer_column_minus1 = range.read16(); m_layer_infos.push_back(layer); } return Error::Ok; } std::string Box_pymd::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box_EntityToGroup::dump(indent); sstr << indent << "tile size: " << tile_size_x << "x" << tile_size_y << "\n"; int layerNr = 0; for (const auto& layer : m_layer_infos) { sstr << indent << "layer " << layerNr << ":\n" << indent << "| binning: " << layer.layer_binning << "\n" << indent << "| tiles: " << (layer.tiles_in_layer_row_minus1 + 1) << "x" << (layer.tiles_in_layer_column_minus1 + 1) << "\n"; layerNr++; } return sstr.str(); } Error Box_dinf::parse(BitstreamRange& range) { //parse_full_box_header(range); return read_children(range); } std::string Box_dinf::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } Error Box_dref::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write32(1); Error err = write_children(writer); prepend_header(writer, box_start); return err; } Error Box_dref::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() != 0) { return unsupported_version_error("dref"); } uint32_t nEntities = range.read32(); /* for (int i=0;i<nEntities;i++) { if (range.eof()) { break; } } */ if (nEntities > (uint32_t)std::numeric_limits<int>::max()) { return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, "Too many entities in dref box."); } Error err = read_children(range, (int)nEntities); if (err) { return err; } if (m_children.size() != nEntities) { // TODO return Error( } return err; } std::string Box_dref::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } void Box_url::derive_box_version() { set_version(0); set_flags(1); } Error Box_url::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() > 0) { return unsupported_version_error("url"); } m_location = range.read_string(); return range.get_error(); } std::string Box_url::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); //sstr << dump_children(indent); sstr << indent << "location: " << m_location << "\n"; return sstr.str(); } Error Box_udes::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() > 0) { return unsupported_version_error("udes"); } m_lang = range.read_string(); m_name = range.read_string(); m_description = range.read_string(); m_tags = range.read_string(); return range.get_error(); } std::string Box_udes::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "lang: " << m_lang << "\n"; sstr << indent << "name: " << m_name << "\n"; sstr << indent << "description: " << m_description << "\n"; sstr << indent << "tags: " << m_lang << "\n"; return sstr.str(); } Error Box_udes::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write(m_lang); writer.write(m_name); writer.write(m_description); writer.write(m_tags); prepend_header(writer, box_start); return Error::Ok; } void Box_cmin::RelativeIntrinsicMatrix::compute_focal_length(int image_width, int image_height, double& out_focal_length_x, double& out_focal_length_y) const { out_focal_length_x = focal_length_x * image_width; if (is_anisotropic) { out_focal_length_y = focal_length_y * image_height; } else { out_focal_length_y = out_focal_length_x; } } void Box_cmin::RelativeIntrinsicMatrix::compute_principal_point(int image_width, int image_height, double& out_principal_point_x, double& out_principal_point_y) const { out_principal_point_x = principal_point_x * image_width; out_principal_point_y = principal_point_y * image_height; } Box_cmin::AbsoluteIntrinsicMatrix Box_cmin::RelativeIntrinsicMatrix::to_absolute(int image_width, int image_height) const { AbsoluteIntrinsicMatrix m{}; compute_focal_length(image_width, image_height, m.focal_length_x, m.focal_length_y); compute_principal_point(image_width, image_height, m.principal_point_x, m.principal_point_y); m.skew = skew; return m; } std::string Box_cmin::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "principal-point: " << m_matrix.principal_point_x << ", " << m_matrix.principal_point_y << "\n"; if (m_matrix.is_anisotropic) { sstr << indent << "focal-length: " << m_matrix.focal_length_x << ", " << m_matrix.focal_length_y << "\n"; sstr << indent << "skew: " << m_matrix.skew << "\n"; } else { sstr << indent << "focal-length: " << m_matrix.focal_length_x << "\n"; sstr << indent << "no skew\n"; } return sstr.str(); } Error Box_cmin::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() > 0) { return unsupported_version_error("cmin"); } m_denominatorShift = (get_flags() & 0x1F00) >> 8; uint32_t denominator = (1U << m_denominatorShift); m_matrix.focal_length_x = range.read32s() / (double)denominator; m_matrix.principal_point_x = range.read32s() / (double)denominator; m_matrix.principal_point_y = range.read32s() / (double)denominator; if (get_flags() & 1) { m_skewDenominatorShift = ((get_flags()) & 0x1F0000) >> 16; uint32_t skewDenominator = (1U << m_skewDenominatorShift); m_matrix.focal_length_y = range.read32s() / (double)denominator; m_matrix.skew = range.read32s() / (double)skewDenominator; m_matrix.is_anisotropic = true; } else { m_matrix.is_anisotropic = false; m_matrix.focal_length_y = 0; m_matrix.skew = 0; } return range.get_error(); } static uint32_t get_signed_fixed_point_shift(double v) { if (v==0) { return 31; } v = std::abs(v); uint32_t shift = 0; while (v < (1<<30)) { v *= 2; shift++; if (shift==31) { return shift; } } return shift; } void Box_cmin::set_intrinsic_matrix(RelativeIntrinsicMatrix matrix) { m_matrix = matrix; uint32_t flags = 0; flags |= matrix.is_anisotropic ? 1 : 0; uint32_t shift_fx = get_signed_fixed_point_shift(matrix.focal_length_x); uint32_t shift_px = get_signed_fixed_point_shift(matrix.principal_point_x); uint32_t shift_py = get_signed_fixed_point_shift(matrix.principal_point_y); m_denominatorShift = std::min(std::min(shift_fx, shift_px), shift_py); if (matrix.is_anisotropic) { uint32_t shift_fy = get_signed_fixed_point_shift(matrix.focal_length_y); m_denominatorShift = std::min(m_denominatorShift, shift_fy); m_skewDenominatorShift = get_signed_fixed_point_shift(matrix.skew); } else { m_skewDenominatorShift = 0; } flags |= (m_denominatorShift << 8); flags |= (m_skewDenominatorShift << 16); set_flags(flags); } Error Box_cmin::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); uint32_t denominator = (1U << m_denominatorShift); writer.write32s(static_cast<int32_t>(m_matrix.focal_length_x * denominator)); writer.write32s(static_cast<int32_t>(m_matrix.principal_point_x * denominator)); writer.write32s(static_cast<int32_t>(m_matrix.principal_point_y * denominator)); if (get_flags() & 1) { writer.write32s(static_cast<int32_t>(m_matrix.focal_length_y * denominator)); uint32_t skewDenominator = (1U << m_skewDenominatorShift); writer.write32s(static_cast<int32_t>(m_matrix.skew * skewDenominator)); } prepend_header(writer, box_start); return Error::Ok; } std::array<double,9> mul(const std::array<double,9>& a, const std::array<double,9>& b) { std::array<double,9> m; m[0] = a[0]*b[0] + a[1]*b[3] + a[2]*b[6]; m[1] = a[0]*b[1] + a[1]*b[4] + a[2]*b[7]; m[2] = a[0]*b[2] + a[1]*b[5] + a[2]*b[8]; m[3] = a[3]*b[0] + a[4]*b[3] + a[5]*b[6]; m[4] = a[3]*b[1] + a[4]*b[4] + a[5]*b[7]; m[5] = a[3]*b[2] + a[4]*b[5] + a[5]*b[8]; m[6] = a[6]*b[0] + a[7]*b[3] + a[8]*b[6]; m[7] = a[6]*b[1] + a[7]*b[4] + a[8]*b[7]; m[8] = a[6]*b[2] + a[7]*b[5] + a[8]*b[8]; return m; } std::array<double,9> Box_cmex::ExtrinsicMatrix::calculate_rotation_matrix() const { std::array<double,9> m{}; if (rotation_as_quaternions) { double qx = quaternion_x; double qy = quaternion_y; double qz = quaternion_z; double qw = quaternion_w; m[0] = 1-2*(qy*qy+qz*qz); m[1] = 2*(qx*qy-qz*qw); m[2] = 2*(qx*qz+qy*qw); m[3] = 2*(qx*qy+qz*qw); m[4] = 1-2*(qx*qx+qz*qz); m[5] = 2*(qy*qz-qx*qw); m[6] = 2*(qx*qz-qy*qw); m[7] = 2*(qy*qz+qx*qw); m[8] = 1-2*(qx*qx+qy*qy); } else { // This rotation order fits the conformance data // https://github.com/MPEGGroup/FileFormatConformance // branch m62054_extrinsics : FileFormatConformance/data/file_features/under_consideration/ex_in_trinsics/extrinsic_rotation std::array<double,9> m_yaw{}; // Z std::array<double,9> m_pitch{}; // Y std::array<double,9> m_roll{}; // X const double d2r = M_PI/180; double x = d2r * rotation_roll; double y = d2r * rotation_pitch; double z = d2r * rotation_yaw; // X m_roll[0] = 1; m_roll[4] = m_roll[8] = cos(x); m_roll[5] = -sin(x); m_roll[7] = sin(x); // Y m_pitch[4] = 1; m_pitch[0] = m_pitch[8] = cos(y); m_pitch[6] = -sin(y); m_pitch[2] = sin(y); // Z m_yaw[8] = 1; m_yaw[0] = m_yaw[4] = cos(z); m_yaw[1] = -sin(z); m_yaw[3] = sin(z); m = mul(m_yaw, mul(m_pitch, m_roll)); } return m; } Error Box_cmex::parse(BitstreamRange& range) { parse_full_box_header(range); if (get_version() > 0) { return unsupported_version_error("cmex"); } m_matrix = ExtrinsicMatrix{}; if (get_flags() & pos_x_present) { m_has_pos_x = true; m_matrix.pos_x = range.read32s(); } if (get_flags() & pos_y_present) { m_has_pos_y = true; m_matrix.pos_y = range.read32s(); } if (get_flags() & pos_z_present) { m_has_pos_z = true; m_matrix.pos_z = range.read32s(); } if (get_flags() & orientation_present) { m_has_orientation = true; if (get_version() == 0) { bool use32bit = (get_flags() & rot_large_field_size); int32_t quat_x = use32bit ? range.read32s() : range.read16s(); int32_t quat_y = use32bit ? range.read32s() : range.read16s(); int32_t quat_z = use32bit ? range.read32s() : range.read16s(); uint32_t div = 1U << (14 + (use32bit ? 16 : 0)); m_matrix.rotation_as_quaternions = true; m_matrix.quaternion_x = quat_x / (double)div; m_matrix.quaternion_y = quat_y / (double)div; m_matrix.quaternion_z = quat_z / (double)div; double q_sum = (m_matrix.quaternion_x * m_matrix.quaternion_x + m_matrix.quaternion_y * m_matrix.quaternion_y + m_matrix.quaternion_z * m_matrix.quaternion_z); if (q_sum > 1.0) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "Invalid quaternion in extrinsic rotation matrix"); } m_matrix.quaternion_w = sqrt(1 - q_sum); } else if (get_version() == 1) { uint32_t div = 1<<16; m_matrix.rotation_yaw = range.read32s() / (double)div; m_matrix.rotation_pitch = range.read32s() / (double)div; m_matrix.rotation_roll = range.read32s() / (double)div; } } if (get_flags() & id_present) { m_has_world_coordinate_system_id = true; m_matrix.world_coordinate_system_id = range.read32(); } return range.get_error(); } std::string Box_cmex::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "camera position (um): "; sstr << m_matrix.pos_x << " ; "; sstr << m_matrix.pos_y << " ; "; sstr << m_matrix.pos_z << "\n"; sstr << indent << "orientation "; if (m_matrix.rotation_as_quaternions) { sstr << "(quaterion)\n"; sstr << indent << " q = [" << m_matrix.quaternion_x << ";" << m_matrix.quaternion_y << ";" << m_matrix.quaternion_z << ";" << m_matrix.quaternion_w << "]\n"; } else { sstr << "(angles)\n"; sstr << indent << " yaw: " << m_matrix.rotation_yaw << "\n"; sstr << indent << " pitch: " << m_matrix.rotation_pitch << "\n"; sstr << indent << " roll: " << m_matrix.rotation_roll << "\n"; } sstr << indent << "world coordinate system id: " << m_matrix.world_coordinate_system_id << "\n"; return sstr.str(); } Error Box_cmex::set_extrinsic_matrix(ExtrinsicMatrix matrix) { m_matrix = matrix; uint32_t flags = 0; m_has_pos_x = (matrix.pos_x != 0); m_has_pos_y = (matrix.pos_y != 0); m_has_pos_z = (matrix.pos_z != 0); if (m_has_pos_x) { flags |= pos_x_present; } if (m_has_pos_y) { flags |= pos_y_present; } if (m_has_pos_z) { flags |= pos_z_present; } if (matrix.rotation_as_quaternions) { if (matrix.quaternion_x != 0 || matrix.quaternion_y != 0 || matrix.quaternion_z != 0) { flags |= orientation_present; double q_sum = (m_matrix.quaternion_x * m_matrix.quaternion_x + m_matrix.quaternion_y * m_matrix.quaternion_y + m_matrix.quaternion_z * m_matrix.quaternion_z); if (q_sum > 1.0) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "Invalid quaternion in extrinsic rotation matrix"); } if (matrix.quaternion_w < 0) { matrix.quaternion_x = -matrix.quaternion_x; matrix.quaternion_y = -matrix.quaternion_y; matrix.quaternion_z = -matrix.quaternion_z; matrix.quaternion_w = -matrix.quaternion_w; } } } else { if (matrix.rotation_yaw != 0 || matrix.rotation_pitch != 0 || matrix.rotation_roll != 0) { flags |= orientation_present; if (matrix.rotation_yaw < -180.0 || matrix.rotation_yaw >= 180.0) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "Invalid yaw angle"); } if (matrix.rotation_pitch < -90.0 || matrix.rotation_pitch > 90.0) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "Invalid pitch angle"); } if (matrix.rotation_roll < -180.0 || matrix.rotation_roll >= 180.0) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "Invalid roll angle"); } } } if (matrix.orientation_is_32bit) { flags |= rot_large_field_size; } if (matrix.world_coordinate_system_id != 0) { flags |= id_present; } set_flags(flags); set_version(m_matrix.rotation_as_quaternions ? 0 : 1); return Error::Ok; } Error Box_cmex::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); if (m_has_pos_x) { writer.write32s(m_matrix.pos_x); } if (m_has_pos_y) { writer.write32s(m_matrix.pos_y); } if (m_has_pos_z) { writer.write32s(m_matrix.pos_z); } if (m_has_orientation) { if (m_matrix.rotation_as_quaternions) { if (m_matrix.orientation_is_32bit) { writer.write32s(static_cast<int32_t>(m_matrix.quaternion_x * (1<<30))); writer.write32s(static_cast<int32_t>(m_matrix.quaternion_y * (1<<30))); writer.write32s(static_cast<int32_t>(m_matrix.quaternion_z * (1<<30))); } else { writer.write16s(static_cast<int16_t>(m_matrix.quaternion_x * (1<<14))); writer.write16s(static_cast<int16_t>(m_matrix.quaternion_y * (1<<14))); writer.write16s(static_cast<int16_t>(m_matrix.quaternion_z * (1<<14))); } } else { writer.write32s(static_cast<int32_t>(m_matrix.rotation_yaw * (1<<16))); writer.write32s(static_cast<int32_t>(m_matrix.rotation_pitch * (1<<16))); writer.write32s(static_cast<int32_t>(m_matrix.rotation_roll * (1<<16))); } } if (m_has_world_coordinate_system_id) { writer.write32(m_matrix.world_coordinate_system_id); } prepend_header(writer, box_start); return Error::Ok; }