diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt index 4530e93705..1918b7f33a 100644 --- a/libheif/CMakeLists.txt +++ b/libheif/CMakeLists.txt @@ -87,6 +87,8 @@ set(libheif_sources codecs/avc_boxes.cc codecs/avc_dec.h codecs/avc_dec.cc + codecs/evc_boxes.cc + codecs/evc_boxes.h image-items/mask_image.h image-items/mask_image.cc image-items/image_item.h diff --git a/libheif/box.cc b/libheif/box.cc index dc7b440596..2a2a42e6cc 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -31,6 +31,7 @@ #include "codecs/avc_boxes.h" #include "codecs/avif_boxes.h" #include "image-items/tiled.h" +#include "codecs/evc_boxes.h" #include #include @@ -690,6 +691,12 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result, const heif_ box = std::make_shared(); break; + // --- EVC + + case fourcc("evcC"): + box = std::make_shared(); + break; + #if WITH_EXPERIMENTAL_FEATURES case fourcc("tilC"): box = std::make_shared(); diff --git a/libheif/codecs/evc_boxes.cc b/libheif/codecs/evc_boxes.cc new file mode 100644 index 0000000000..569cefdbae --- /dev/null +++ b/libheif/codecs/evc_boxes.cc @@ -0,0 +1,168 @@ +/* + * HEIF EVC codec. + * Copyright (c) 2024 Brad Hards + * + * 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 . + */ + +#include "evc_boxes.h" +#include "bitstream.h" +#include "error.h" +#include "file.h" + +#include +#include + + + +Error Box_evcC::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + m_configuration.configurationVersion = range.read8(); + m_configuration.profile_idc = range.read8(); + m_configuration.level_idc = range.read8(); + m_configuration.toolset_idc_h = range.read32(); + m_configuration.toolset_idc_l = range.read32(); + uint8_t b = range.read8(); + m_configuration.chroma_format_idc = (b >> 6) & 0b11; + uint8_t bit_depth_luma_minus8 = (b >> 3) & 0b111; + m_configuration.bit_depth_luma = bit_depth_luma_minus8 + 8; + uint8_t bit_depth_chroma_minus8 = b & 0b111; + m_configuration.bit_depth_chroma = bit_depth_chroma_minus8 + 8; + m_configuration.pic_width_in_luma_samples = range.read16(); + m_configuration.pic_height_in_luma_samples = range.read16(); + b = range.read8(); + uint8_t lengthSizeMinus1 = b & 0b11; + m_configuration.lengthSize = lengthSizeMinus1 + 1; + uint8_t num_of_arrays = range.read8(); + for (uint8_t j = 0; j < num_of_arrays && !range.error(); j++) { + NalArray array; + b = range.read8(); + array.array_completeness = ((b & 0x80) == 0x80); + array.NAL_unit_type = (b & 0b00111111); + uint16_t num_nalus = range.read16(); + for (int i = 0; i < num_nalus && !range.error(); i++) { + uint16_t nal_unit_length = range.read16(); + if (nal_unit_length == 0) { + // Ignore empty NAL units. + continue; + } + std::vector nal_unit; + if (range.prepare_read(nal_unit_length)) { + nal_unit.resize(nal_unit_length); + bool success = range.get_istream()->read((char*) nal_unit.data(), nal_unit_length); + if (!success) { + return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while reading evcC box"}; + } + } + + array.nal_units.push_back(std::move(nal_unit)); + } + m_nal_array.push_back(array); + } + + return range.get_error(); +} + + +std::string Box_evcC::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + // TODO: decode more of this + sstr << indent << "configurationVersion: " << ((int)m_configuration.configurationVersion) << "\n"; + sstr << indent << "profile_idc: " << ((int)m_configuration.profile_idc) << "\n"; + sstr << indent << "level_idc: " << ((int)m_configuration.level_idc) << "\n"; + sstr << indent << "toolset_idc_h: " << m_configuration.toolset_idc_h << "\n"; + sstr << indent << "toolset_idc_l: " << m_configuration.toolset_idc_l << "\n"; + sstr << indent << "chroma_format_idc: " << ((int)m_configuration.chroma_format_idc) + << " (" << get_chroma_format_as_text() << ")\n"; + sstr << indent << "bit_depth_luma: " << ((int)m_configuration.bit_depth_luma) << "\n"; + sstr << indent << "bit_depth_chroma: " << ((int)m_configuration.bit_depth_chroma) << "\n"; + sstr << indent << "pic_width_in_luma_samples: " << m_configuration.pic_width_in_luma_samples << "\n"; + sstr << indent << "pic_height_in_luma_samples: " << m_configuration.pic_height_in_luma_samples << "\n"; + sstr << indent << "length_size: " << ((int)m_configuration.lengthSize) << "\n"; + for (const auto &array : m_nal_array) + { + sstr << indent << "\n"; + + indent++; + sstr << indent << "array_completeness: " << (array.array_completeness ? "true" : "false") << "\n" + << indent << "NAL_unit_type: " << ((int) array.NAL_unit_type) << "\n"; + + for (const auto& unit : array.nal_units) { + sstr << indent; + for (uint8_t b : unit) { + sstr << std::setfill('0') << std::setw(2) << std::hex << ((int) b) << " "; + } + sstr << "\n"; + sstr << std::dec; + } + + indent--; + } + return sstr.str(); +} + +std::string Box_evcC::get_chroma_format_as_text() const +{ + switch (m_configuration.chroma_format_idc) + { + case 0: + return std::string("Monochrome"); + case 1: + return std::string("4:2:0"); + case 2: + return std::string("4:2:2"); + case 3: + return std::string("4:4:4"); + default: + return std::string("Invalid"); + } +} + +Error Box_evcC::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + writer.write8(m_configuration.configurationVersion); + writer.write8(m_configuration.profile_idc); + writer.write8(m_configuration.level_idc); + writer.write32(m_configuration.toolset_idc_h); + writer.write32(m_configuration.toolset_idc_l); + uint8_t bit_depth_luma_minus8 = ((m_configuration.bit_depth_luma - 8) & 0b111); + uint8_t bit_depth_chroma_minus8 = ((m_configuration.bit_depth_chroma - 8) & 0b111); + uint8_t b = (m_configuration.chroma_format_idc << 6) | (bit_depth_luma_minus8 << 3) | bit_depth_chroma_minus8; + writer.write8(b); + writer.write16(m_configuration.pic_width_in_luma_samples); + writer.write16(m_configuration.pic_height_in_luma_samples); + writer.write8(m_configuration.lengthSize - 1); + writer.write8((uint8_t)m_nal_array.size()); + for (const NalArray& array : m_nal_array) { + + writer.write8((uint8_t) ((array.array_completeness? 0x80: 0x00) | + (array.NAL_unit_type & 0b00111111))); + + writer.write16((uint16_t)array.nal_units.size()); + + for (const std::vector& nal_unit : array.nal_units) { + writer.write16((uint16_t) nal_unit.size()); + writer.write(nal_unit); + } + } + prepend_header(writer, box_start); + + return Error::Ok; +} diff --git a/libheif/codecs/evc_boxes.h b/libheif/codecs/evc_boxes.h new file mode 100644 index 0000000000..6322390b16 --- /dev/null +++ b/libheif/codecs/evc_boxes.h @@ -0,0 +1,85 @@ +/* + * HEIF EVC codec. + * Copyright (c) 2024 Brad Hards + * + * 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 . + */ + +#ifndef HEIF_EVC_BOXES_H +#define HEIF_EVC_BOXES_H + +#include "box.h" +#include "error.h" +// #include +// #include +// #include +// #include +// #include "image-items/image_item.h" + + +class Box_evcC : public Box { +public: + Box_evcC() { set_short_type(fourcc("evcC")); } + + bool is_essential() const override { return true; } + + struct configuration { + uint8_t configurationVersion = 1; + uint8_t profile_idc; + uint8_t level_idc; + uint32_t toolset_idc_h; + uint32_t toolset_idc_l; + uint8_t chroma_format_idc; + uint8_t bit_depth_luma; + uint8_t bit_depth_chroma; + uint16_t pic_width_in_luma_samples; + uint16_t pic_height_in_luma_samples; + uint8_t lengthSize = 0; + }; + + void set_configuration(const configuration& config) + { + m_configuration = config; + } + + const configuration& get_configuration() const + { + return m_configuration; + } + + std::string dump(Indent &) const override; + + Error write(StreamWriter &writer) const override; + +protected: + Error parse(BitstreamRange &range, const heif_security_limits* limits) override; + +private: + configuration m_configuration; + struct NalArray + { + bool array_completeness; + uint8_t NAL_unit_type; + + std::vector > nal_units; + }; + + std::vector m_nal_array; + + std::string get_chroma_format_as_text() const; +}; + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1aa35e7448..b61036c7d4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,6 +34,7 @@ else() add_libheif_test(jpeg2000) add_libheif_test(avc_box) add_libheif_test(file_layout) + add_libheif_test(evc_box) endif() if (WITH_EXPERIMENTAL_FEATURS AND WITH_REDUCED_VISIBILITY) diff --git a/tests/evc_box.cc b/tests/evc_box.cc new file mode 100644 index 0000000000..2acc9dbbcb --- /dev/null +++ b/tests/evc_box.cc @@ -0,0 +1,98 @@ +/* + libheif EVC unit tests + + MIT License + + Copyright (c) 2024 Brad Hards + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "catch.hpp" +#include "codecs/evc_boxes.h" +#include "error.h" +#include +#include +#include + +TEST_CASE("evcC") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x3d, 0x65, 0x76, 0x63, 0x43, + 0x01, 0x00, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x52, 0x01, 0x40, 0x00, 0xf0, + 0x03, 0x02, 0x98, 0x00, 0x01, 0x00, 0x15, 0x32, + 0x00, 0x80, 0x6b, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x0a, 0x08, 0x0f, 0x16, + 0xc0, 0x00, 0x54, 0x00, 0x99, 0x00, 0x01, 0x00, + 0x04, 0x34, 0x00, 0xfb, 0x00}; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("evcC")); + REQUIRE(box->get_type_string() == "evcC"); + std::shared_ptr evcC = std::dynamic_pointer_cast(box); + Box_evcC::configuration configuration = evcC->get_configuration(); + REQUIRE(configuration.configurationVersion == 1); + REQUIRE(configuration.profile_idc == 0); + REQUIRE(configuration.level_idc == 215); + REQUIRE(configuration.toolset_idc_h == 0); + REQUIRE(configuration.toolset_idc_l == 0); + REQUIRE(configuration.chroma_format_idc == 1); + REQUIRE(configuration.bit_depth_luma == 10); + REQUIRE(configuration.bit_depth_chroma == 10); + REQUIRE(configuration.pic_width_in_luma_samples == 320); + REQUIRE(configuration.pic_height_in_luma_samples == 240); + REQUIRE(configuration.lengthSize == 4); + Indent indent; + std::string dumpResult = box->dump(indent); + REQUIRE(dumpResult == "Box: evcC -----\n" + "size: 61 (header size: 8)\n" + "configurationVersion: 1\n" + "profile_idc: 0\n" + "level_idc: 215\n" + "toolset_idc_h: 0\n" + "toolset_idc_l: 0\n" + "chroma_format_idc: 1 (4:2:0)\n" + "bit_depth_luma: 10\n" + "bit_depth_chroma: 10\n" + "pic_width_in_luma_samples: 320\n" + "pic_height_in_luma_samples: 240\n" + "length_size: 4\n" + "\n" + "| array_completeness: true\n" + "| NAL_unit_type: 24\n" + "| 32 00 80 6b 80 00 00 00 00 00 00 00 20 0a 08 0f 16 c0 00 54 00 \n" + "\n" + "| array_completeness: true\n" + "| NAL_unit_type: 25\n" + "| 34 00 fb 00 \n"); + + StreamWriter writer; + Error err = evcC->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector bytes = writer.get_data(); + REQUIRE(bytes == byteArray); +}