libheif/codecs/uncompressed_box.cc (346 lines of code) (raw):
/*
* HEIF codec.
* Copyright (c) 2023 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 <cstdint>
#include <cstring>
#include <algorithm>
#include <map>
#include <iostream>
#include <cassert>
#include "libheif/heif.h"
#include "security_limits.h"
#include "uncompressed.h"
#include "uncompressed_box.h"
/**
* Check for valid component format.
*
* @param format the format value to check
* @return true if the format is a valid value, or false otherwise
*/
bool is_valid_component_format(uint8_t format)
{
return format <= component_format_max_valid;
}
static std::map<heif_uncompressed_component_format, const char*> sNames_uncompressed_component_format{
{component_format_unsigned, "unsigned"},
{component_format_float, "float"},
{component_format_complex, "complex"}
};
/**
* Check for valid interleave mode.
*
* @param interleave the interleave value to check
* @return true if the interleave mode is valid, or false otherwise
*/
bool is_valid_interleave_mode(uint8_t interleave)
{
return interleave <= interleave_mode_max_valid;
}
static std::map<heif_uncompressed_interleave_mode, const char*> sNames_uncompressed_interleave_mode{
{interleave_mode_component, "component"},
{interleave_mode_pixel, "pixel"},
{interleave_mode_mixed, "mixed"},
{interleave_mode_row, "row"},
{interleave_mode_tile_component, "tile-component"},
{interleave_mode_multi_y, "multi-y"}
};
/**
* Check for valid sampling mode.
*
* @param sampling the sampling value to check
* @return true if the sampling mode is valid, or false otherwise
*/
bool is_valid_sampling_mode(uint8_t sampling)
{
return sampling <= sampling_mode_max_valid;
}
static std::map<heif_uncompressed_sampling_mode, const char*> sNames_uncompressed_sampling_mode{
{sampling_mode_no_subsampling, "no subsampling"},
{sampling_mode_422, "4:2:2"},
{sampling_mode_420, "4:2:0"},
{sampling_mode_411, "4:1:1"}
};
bool is_predefined_component_type(uint16_t type)
{
// check whether the component type can be mapped to heif_uncompressed_component_type and we have a name defined for
// it in sNames_uncompressed_component_type.
return (type >= 0 && type <= component_type_max_valid);
}
static std::map<heif_uncompressed_component_type, const char*> sNames_uncompressed_component_type{
{component_type_monochrome, "monochrome"},
{component_type_Y, "Y"},
{component_type_Cb, "Cb"},
{component_type_Cr, "Cr"},
{component_type_red, "red"},
{component_type_green, "green"},
{component_type_blue, "blue"},
{component_type_alpha, "alpha"},
{component_type_depth, "depth"},
{component_type_disparity, "disparity"},
{component_type_palette, "palette"},
{component_type_filter_array, "filter-array"},
{component_type_padded, "padded"},
{component_type_cyan, "cyan"},
{component_type_magenta, "magenta"},
{component_type_yellow, "yellow"},
{component_type_key_black, "key (black)"}
};
template <typename T> const char* get_name(T val, const std::map<T, const char*>& table)
{
auto iter = table.find(val);
if (iter == table.end()) {
return "unknown";
}
else {
return iter->second;
}
}
Error Box_cmpd::parse(BitstreamRange& range)
{
unsigned int component_count = range.read32();
for (unsigned int i = 0; i < component_count && !range.error() && !range.eof(); i++) {
Component component;
component.component_type = range.read16();
if (component.component_type >= 0x8000) {
component.component_type_uri = range.read_string();
}
else {
component.component_type_uri = std::string();
}
m_components.push_back(component);
}
return range.get_error();
}
std::string Box_cmpd::Component::get_component_type_name(uint16_t component_type)
{
std::stringstream sstr;
if (is_predefined_component_type(component_type)) {
sstr << get_name(heif_uncompressed_component_type(component_type), sNames_uncompressed_component_type) << "\n";
}
else {
sstr << "0x" << std::hex << component_type << std::dec << "\n";
}
return sstr.str();
}
std::string Box_cmpd::dump(Indent& indent) const
{
std::ostringstream sstr;
sstr << Box::dump(indent);
for (const auto& component : m_components) {
sstr << indent << "component_type: " << component.get_component_type_name();
if (component.component_type >= 0x8000) {
sstr << indent << "| component_type_uri: " << component.component_type_uri << "\n";
}
}
return sstr.str();
}
Error Box_cmpd::write(StreamWriter& writer) const
{
size_t box_start = reserve_box_header_space(writer);
writer.write32((uint32_t) m_components.size());
for (const auto& component : m_components) {
writer.write16(component.component_type);
if (component.component_type >= 0x8000) {
writer.write(component.component_type_uri);
}
}
prepend_header(writer, box_start);
return Error::Ok;
}
Error Box_uncC::parse(BitstreamRange& range)
{
parse_full_box_header(range);
m_profile = range.read32();
if (get_version() == 1) {
if (m_profile == fourcc_to_uint32("rgb3")) {
Box_uncC::Component component0 = {0, 8, component_format_unsigned, 0};
add_component(component0);
Box_uncC::Component component1 = {1, 8, component_format_unsigned, 0};
add_component(component1);
Box_uncC::Component component2 = {2, 8, component_format_unsigned, 0};
add_component(component2);
} else if ((m_profile == fourcc_to_uint32("rgba")) || (m_profile == fourcc_to_uint32("abgr"))) {
Box_uncC::Component component0 = {0, 8, component_format_unsigned, 0};
add_component(component0);
Box_uncC::Component component1 = {1, 8, component_format_unsigned, 0};
add_component(component1);
Box_uncC::Component component2 = {2, 8, component_format_unsigned, 0};
add_component(component2);
Box_uncC::Component component3 = {3, 8, component_format_unsigned, 0};
add_component(component3);
} else {
return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid component format"};
}
} else if (get_version() == 0) {
unsigned int component_count = range.read32();
for (unsigned int i = 0; i < component_count && !range.error() && !range.eof(); i++) {
Component component;
component.component_index = range.read16();
component.component_bit_depth = uint16_t(range.read8() + 1);
component.component_format = range.read8();
component.component_align_size = range.read8();
m_components.push_back(component);
if (!is_valid_component_format(component.component_format)) {
return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid component format"};
}
}
m_sampling_type = range.read8();
if (!is_valid_sampling_mode(m_sampling_type)) {
return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid sampling mode"};
}
m_interleave_type = range.read8();
if (!is_valid_interleave_mode(m_interleave_type)) {
return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid interleave mode"};
}
m_block_size = range.read8();
uint8_t flags = range.read8();
m_components_little_endian = !!(flags & 0x80);
m_block_pad_lsb = !!(flags & 0x40);
m_block_little_endian = !!(flags & 0x20);
m_block_reversed = !!(flags & 0x10);
m_pad_unknown = !!(flags & 0x08);
m_pixel_size = range.read32();
m_row_align_size = range.read32();
m_tile_align_size = range.read32();
uint32_t num_tile_cols_minus_one = range.read32();
uint32_t num_tile_rows_minus_one = range.read32();
if ((num_tile_cols_minus_one >= UINT32_MAX) || (num_tile_rows_minus_one >= UINT32_MAX)) {
std::stringstream sstr;
sstr << "Tiling size " << ((uint64_t)num_tile_cols_minus_one + 1) << " x " << ((uint64_t)num_tile_rows_minus_one + 1) << " exceeds the maximum allowed size "
<< UINT32_MAX << " x " << UINT32_MAX;
return Error(heif_error_Memory_allocation_error,
heif_suberror_Security_limit_exceeded,
sstr.str());
}
m_num_tile_cols = num_tile_cols_minus_one + 1;
m_num_tile_rows = num_tile_rows_minus_one + 1;
}
return range.get_error();
}
std::string Box_uncC::dump(Indent& indent) const
{
std::ostringstream sstr;
sstr << Box::dump(indent);
sstr << indent << "profile: " << m_profile;
if (m_profile != 0) {
sstr << " (" << to_fourcc(m_profile) << ")";
}
sstr << "\n";
if (get_version() == 0) {
for (const auto& component : m_components) {
sstr << indent << "component_index: " << component.component_index << "\n";
sstr << indent << "component_bit_depth: " << (int) component.component_bit_depth << "\n";
sstr << indent << "component_format: " << get_name(heif_uncompressed_component_format(component.component_format), sNames_uncompressed_component_format) << "\n";
sstr << indent << "component_align_size: " << (int) component.component_align_size << "\n";
}
sstr << indent << "sampling_type: " << get_name(heif_uncompressed_sampling_mode(m_sampling_type), sNames_uncompressed_sampling_mode) << "\n";
sstr << indent << "interleave_type: " << get_name(heif_uncompressed_interleave_mode(m_interleave_type), sNames_uncompressed_interleave_mode) << "\n";
sstr << indent << "block_size: " << (int) m_block_size << "\n";
sstr << indent << "components_little_endian: " << m_components_little_endian << "\n";
sstr << indent << "block_pad_lsb: " << m_block_pad_lsb << "\n";
sstr << indent << "block_little_endian: " << m_block_little_endian << "\n";
sstr << indent << "block_reversed: " << m_block_reversed << "\n";
sstr << indent << "pad_unknown: " << m_pad_unknown << "\n";
sstr << indent << "pixel_size: " << m_pixel_size << "\n";
sstr << indent << "row_align_size: " << m_row_align_size << "\n";
sstr << indent << "tile_align_size: " << m_tile_align_size << "\n";
sstr << indent << "num_tile_cols: " << m_num_tile_cols << "\n";
sstr << indent << "num_tile_rows: " << m_num_tile_rows << "\n";
}
return sstr.str();
}
Error Box_uncC::write(StreamWriter& writer) const
{
size_t box_start = reserve_box_header_space(writer);
writer.write32(m_profile);
if (get_version() == 1) {
}
else if (get_version() == 0) {
writer.write32((uint32_t)m_components.size());
for (const auto &component : m_components) {
if (component.component_bit_depth < 1 || component.component_bit_depth > 256) {
return {heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "component bit-depth out of range [1..256]"};
}
writer.write16(component.component_index);
writer.write8(uint8_t(component.component_bit_depth - 1));
writer.write8(component.component_format);
writer.write8(component.component_align_size);
}
writer.write8(m_sampling_type);
writer.write8(m_interleave_type);
writer.write8(m_block_size);
uint8_t flags = 0;
flags |= (m_components_little_endian ? 0x80 : 0);
flags |= (m_block_pad_lsb ? 0x40 : 0);
flags |= (m_block_little_endian ? 0x20 : 0);
flags |= (m_block_reversed ? 0x10 : 0);
flags |= (m_pad_unknown ? 0x08 : 0);
writer.write8(flags);
writer.write32(m_pixel_size);
writer.write32(m_row_align_size);
writer.write32(m_tile_align_size);
writer.write32(m_num_tile_cols - 1);
writer.write32(m_num_tile_rows - 1);
}
prepend_header(writer, box_start);
return Error::Ok;
}
Error Box_cmpC::parse(BitstreamRange& range)
{
parse_full_box_header(range);
if (get_version() != 0) {
return unsupported_version_error("cmpC");
}
compression_type = range.read32();
uint8_t v = range.read8();
must_decompress_individual_entities = ((v & 0x80) == 0x80);
compressed_range_type = (v & 0x7f);
return range.get_error();
}
std::string Box_cmpC::dump(Indent& indent) const
{
std::ostringstream sstr;
sstr << Box::dump(indent);
sstr << indent << "compression_type: " << to_fourcc(compression_type) << "\n";
sstr << indent << "must_decompress_individual_entities: " << must_decompress_individual_entities << "\n";
sstr << indent << "compressed_entity_type: " << (int)compressed_range_type << "\n";
return sstr.str();
}
Error Box_cmpC::write(StreamWriter& writer) const
{
size_t box_start = reserve_box_header_space(writer);
writer.write32(compression_type);
uint8_t v = must_decompress_individual_entities ? 0x80 : 0x00;
v |= (compressed_range_type & 0x7F);
writer.write8(v);
prepend_header(writer, box_start);
return Error::Ok;
}
Error Box_icbr::parse(BitstreamRange& range)
{
parse_full_box_header(range);
if ((get_version() != 0) && (get_version() != 1)) {
return unsupported_version_error("icbr");
}
uint32_t num_ranges = range.read32();
for (uint32_t r = 0; r < num_ranges; r++) {
struct ByteRange byteRange;
if (get_version() == 1) {
byteRange.range_offset = range.read64();
byteRange.range_size = range.read64();
} else if (get_version() == 0) {
byteRange.range_offset = range.read32();
byteRange.range_size = range.read32();
} else {
return Error(heif_error_Usage_error, heif_suberror_Unsupported_parameter, "Unsupported icbr version");
}
if (range.get_error() != Error::Ok) {
return range.get_error();
}
m_ranges.push_back(byteRange);
}
return range.get_error();
}
std::string Box_icbr::dump(Indent& indent) const
{
std::ostringstream sstr;
sstr << Box::dump(indent);
sstr << indent << "num_ranges: " << m_ranges.size() << "\n";
for (ByteRange range: m_ranges) {
sstr << indent << "range_offset: " << range.range_offset << ", range_size: " << range.range_size << "\n";
}
return sstr.str();
}
Error Box_icbr::write(StreamWriter& writer) const
{
size_t box_start = reserve_box_header_space(writer);
writer.write32((uint32_t)m_ranges.size());
for (ByteRange range: m_ranges) {
if (get_version() == 1) {
writer.write64(range.range_offset);
writer.write64(range.range_size);
} else if (get_version() == 0) {
writer.write32((uint32_t)range.range_offset);
writer.write32((uint32_t)range.range_size);
}
}
prepend_header(writer, box_start);
return Error::Ok;
}