Commit c1bf8685 for libheif
commit c1bf86853efe4ba07986b143a7ce240296076791
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sun Feb 8 20:49:39 2026 +0100
unci: add missing unc_decoder_legacybase class
diff --git a/libheif/codecs/uncompressed/unc_decoder_legacybase.cc b/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
new file mode 100644
index 00000000..a3d177b9
--- /dev/null
+++ b/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
@@ -0,0 +1,337 @@
+/*
+ * 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 <cstring>
+#include <algorithm>
+#include <iostream>
+#include <cassert>
+#include <utility>
+
+#if ((defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__PGI)) && __GNUC__ < 9) || (defined(__clang__) && __clang_major__ < 10)
+#include <type_traits>
+#else
+#include <bit>
+#endif
+
+#include "common_utils.h"
+#include "context.h"
+#include "compression.h"
+#include "error.h"
+#include "libheif/heif.h"
+#include "unc_types.h"
+#include "unc_boxes.h"
+#include "unc_codec.h"
+#include "unc_decoder_legacybase.h"
+#include "codecs/decoder.h"
+
+
+unc_decoder_legacybase::unc_decoder_legacybase(uint32_t width, uint32_t height,
+ const std::shared_ptr<const Box_cmpd>& cmpd,
+ const std::shared_ptr<const Box_uncC>& uncC)
+ : unc_decoder(width, height, cmpd, uncC)
+{
+}
+
+void unc_decoder_legacybase::ensureChannelList(std::shared_ptr<HeifPixelImage>& img)
+{
+ if (channelList.empty()) {
+ buildChannelList(img);
+ }
+}
+
+void unc_decoder_legacybase::buildChannelList(std::shared_ptr<HeifPixelImage>& img)
+{
+ for (Box_uncC::Component component : m_uncC->get_components()) {
+ ChannelListEntry entry = buildChannelListEntry(component, img);
+ channelList.push_back(entry);
+ }
+}
+
+void unc_decoder_legacybase::memcpy_to_native_endian(uint8_t* dst, uint32_t value, uint32_t bytes_per_sample)
+{
+ // TODO: this assumes that the file endianness is always big-endian. The endianness flags in the uncC header are not taken into account yet.
+
+ if (bytes_per_sample==1) {
+ *dst = static_cast<uint8_t>(value);
+ return;
+ }
+ else if (std::endian::native == std::endian::big) {
+ for (uint32_t i = 0; i < bytes_per_sample; i++) {
+ dst[bytes_per_sample - 1 - i] = static_cast<uint8_t>((value >> (i * 8)) & 0xFF);
+ }
+ }
+ else {
+ for (uint32_t i = 0; i < bytes_per_sample; i++) {
+ dst[i] = static_cast<uint8_t>((value >> (i * 8)) & 0xFF);
+ }
+ }
+}
+
+void unc_decoder_legacybase::processComponentSample(UncompressedBitReader& srcBits, const ChannelListEntry& entry, uint64_t dst_row_offset, uint32_t tile_column, uint32_t tile_x)
+{
+ uint64_t dst_col_number = static_cast<uint64_t>(tile_column) * entry.tile_width + tile_x;
+ uint64_t dst_column_offset = dst_col_number * entry.bytes_per_component_sample;
+ int val = srcBits.get_bits(entry.bits_per_component_sample); // get_bits() reads input in big-endian order
+ memcpy_to_native_endian(entry.dst_plane + dst_row_offset + dst_column_offset, val, entry.bytes_per_component_sample);
+}
+
+// Handles the case where a row consists of a single component type
+// Not valid for Pixel interleave
+// Not valid for the Cb/Cr channels in Mixed Interleave
+// Not valid for multi-Y pixel interleave
+void unc_decoder_legacybase::processComponentRow(ChannelListEntry& entry, UncompressedBitReader& srcBits, uint64_t dst_row_offset, uint32_t tile_column)
+{
+ for (uint32_t tile_x = 0; tile_x < entry.tile_width; tile_x++) {
+ if (entry.component_alignment != 0) {
+ srcBits.skip_to_byte_boundary();
+ int numPadBits = (entry.component_alignment * 8) - entry.bits_per_component_sample;
+ srcBits.skip_bits(numPadBits);
+ }
+ processComponentSample(srcBits, entry, dst_row_offset, tile_column, tile_x);
+ }
+ srcBits.skip_to_byte_boundary();
+}
+
+void unc_decoder_legacybase::processComponentTileSample(UncompressedBitReader& srcBits, const ChannelListEntry& entry, uint64_t dst_offset, uint32_t tile_x)
+{
+ uint64_t dst_sample_offset = uint64_t{tile_x} * entry.bytes_per_component_sample;
+ int val = srcBits.get_bits(entry.bits_per_component_sample);
+ memcpy_to_native_endian(entry.dst_plane + dst_offset + dst_sample_offset, val, entry.bytes_per_component_sample);
+}
+
+// Handles the case where a row consists of a single component type
+// Not valid for Pixel interleave
+// Not valid for the Cb/Cr channels in Mixed Interleave
+// Not valid for multi-Y pixel interleave
+void unc_decoder_legacybase::processComponentTileRow(ChannelListEntry& entry, UncompressedBitReader& srcBits, uint64_t dst_offset)
+{
+ for (uint32_t tile_x = 0; tile_x < entry.tile_width; tile_x++) {
+ if (entry.component_alignment != 0) {
+ srcBits.skip_to_byte_boundary();
+ int numPadBits = (entry.component_alignment * 8) - entry.bits_per_component_sample;
+ srcBits.skip_bits(numPadBits);
+ }
+ processComponentTileSample(srcBits, entry, dst_offset, tile_x);
+ }
+ srcBits.skip_to_byte_boundary();
+}
+
+
+unc_decoder_legacybase::ChannelListEntry unc_decoder_legacybase::buildChannelListEntry(Box_uncC::Component component,
+ std::shared_ptr<HeifPixelImage>& img)
+{
+ ChannelListEntry entry;
+ entry.use_channel = map_uncompressed_component_to_channel(m_cmpd, m_uncC, component, &(entry.channel));
+ entry.dst_plane = img->get_plane(entry.channel, &(entry.dst_plane_stride));
+ entry.tile_width = m_tile_width;
+ entry.tile_height = m_tile_height;
+ entry.other_chroma_dst_plane_stride = 0; // will be overwritten below if used
+ if ((entry.channel == heif_channel_Cb) || (entry.channel == heif_channel_Cr)) {
+ if (m_uncC->get_sampling_type() == sampling_mode_422) {
+ entry.tile_width /= 2;
+ }
+ else if (m_uncC->get_sampling_type() == sampling_mode_420) {
+ entry.tile_width /= 2;
+ entry.tile_height /= 2;
+ }
+ if (entry.channel == heif_channel_Cb) {
+ entry.other_chroma_dst_plane = img->get_plane(heif_channel_Cr, &(entry.other_chroma_dst_plane_stride));
+ }
+ else if (entry.channel == heif_channel_Cr) {
+ entry.other_chroma_dst_plane = img->get_plane(heif_channel_Cb, &(entry.other_chroma_dst_plane_stride));
+ }
+ }
+ entry.bits_per_component_sample = component.component_bit_depth;
+ entry.component_alignment = component.component_align_size;
+ entry.bytes_per_component_sample = (component.component_bit_depth + 7) / 8;
+ entry.bytes_per_tile_row_src = entry.tile_width * entry.bytes_per_component_sample;
+ return entry;
+}
+
+
+const Error unc_decoder_legacybase::get_compressed_image_data_uncompressed(const DataExtent& dataExtent,
+ const UncompressedImageCodec::unci_properties& properties,
+ std::vector<uint8_t>* data,
+ uint64_t range_start_offset, uint64_t range_size,
+ uint32_t tile_idx,
+ const Box_iloc::Item* item) const
+{
+ // --- get codec configuration
+
+ std::shared_ptr<const Box_cmpC> cmpC_box = properties.cmpC;
+ std::shared_ptr<const Box_icef> icef_box = properties.icef;
+
+ if (!cmpC_box) {
+ // assume no generic compression
+ auto readResult = dataExtent.read_data(range_start_offset, range_size);
+ if (!readResult) {
+ return readResult.error();
+ }
+
+ data->insert(data->end(), readResult->begin(), readResult->end());
+
+ return Error::Ok;
+ }
+
+ if (icef_box && cmpC_box->get_compressed_unit_type() == heif_cmpC_compressed_unit_type_image_tile) {
+ const auto& units = icef_box->get_units();
+ if (tile_idx >= units.size()) {
+ return {heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "no icef-box entry for tile index"};
+ }
+
+ const auto unit = units[tile_idx];
+
+ // get data needed for one tile
+ Result<std::vector<uint8_t>> readingResult = dataExtent.read_data(unit.unit_offset, unit.unit_size);
+ if (!readingResult) {
+ return readingResult.error();
+ }
+
+ const std::vector<uint8_t>& compressed_bytes = *readingResult;
+
+ // decompress only the unit
+ auto dataResult = do_decompress_data(cmpC_box, compressed_bytes);
+ if (!dataResult) {
+ return dataResult.error();
+ }
+
+ *data = std::move(*dataResult);
+ }
+ else if (icef_box) {
+ // get all data and decode all
+ Result<std::vector<uint8_t>*> readResult = dataExtent.read_data();
+ if (!readResult) {
+ return readResult.error();
+ }
+
+ const std::vector<uint8_t> compressed_bytes = std::move(**readResult);
+
+ for (Box_icef::CompressedUnitInfo unit_info : icef_box->get_units()) {
+ if (unit_info.unit_offset + unit_info.unit_size > compressed_bytes.size()) {
+ return Error{
+ heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "incomplete data in unci image"
+ };
+ }
+
+ auto unit_start = compressed_bytes.begin() + unit_info.unit_offset;
+ auto unit_end = unit_start + unit_info.unit_size;
+ std::vector<uint8_t> compressed_unit_data = std::vector<uint8_t>(unit_start, unit_end);
+
+ auto dataResult = do_decompress_data(cmpC_box, std::move(compressed_unit_data));
+ if (!dataResult) {
+ return dataResult.error();
+ }
+
+ const std::vector<uint8_t> uncompressed_unit_data = std::move(*dataResult);
+ data->insert(data->end(), uncompressed_unit_data.data(), uncompressed_unit_data.data() + uncompressed_unit_data.size());
+ }
+
+ if (range_start_offset + range_size > data->size()) {
+ return {heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Data range out of existing range"};
+ }
+
+ // cut out the range that we actually need
+ memcpy(data->data(), data->data() + range_start_offset, range_size);
+ data->resize(range_size);
+ }
+ else {
+ // get all data and decode all
+ Result<std::vector<uint8_t>*> readResult = dataExtent.read_data();
+ if (!readResult) {
+ return readResult.error();
+ }
+
+ std::vector<uint8_t> compressed_bytes = std::move(**readResult);
+
+ // Decode as a single blob
+ auto dataResult = do_decompress_data(cmpC_box, compressed_bytes);
+ if (!dataResult) {
+ return dataResult.error();
+ }
+
+ *data = std::move(*dataResult);
+
+ if (range_start_offset + range_size > data->size()) {
+ return {heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Data range out of existing range"};
+ }
+
+ // cut out the range that we actually need
+ memcpy(data->data(), data->data() + range_start_offset, range_size);
+ data->resize(range_size);
+ }
+
+ return Error::Ok;
+}
+
+
+Result<std::vector<uint8_t>> unc_decoder_legacybase::do_decompress_data(std::shared_ptr<const Box_cmpC>& cmpC_box,
+ std::vector<uint8_t> compressed_data) const
+{
+ if (cmpC_box->get_compression_type() == fourcc("brot")) {
+#if HAVE_BROTLI
+ return decompress_brotli(compressed_data);
+#else
+ std::stringstream sstr;
+ sstr << "cannot decode unci item with brotli compression - not enabled" << std::endl;
+ return Error(heif_error_Unsupported_feature,
+ heif_suberror_Unsupported_generic_compression_method,
+ sstr.str());
+#endif
+ }
+ else if (cmpC_box->get_compression_type() == fourcc("zlib")) {
+#if HAVE_ZLIB
+ return decompress_zlib(compressed_data);
+#else
+ std::stringstream sstr;
+ sstr << "cannot decode unci item with zlib compression - not enabled" << std::endl;
+ return Error(heif_error_Unsupported_feature,
+ heif_suberror_Unsupported_generic_compression_method,
+ sstr.str());
+#endif
+ }
+ else if (cmpC_box->get_compression_type() == fourcc("defl")) {
+#if HAVE_ZLIB
+ return decompress_deflate(compressed_data);
+#else
+ std::stringstream sstr;
+ sstr << "cannot decode unci item with deflate compression - not enabled" << std::endl;
+ return Error(heif_error_Unsupported_feature,
+ heif_suberror_Unsupported_generic_compression_method,
+ sstr.str());
+#endif
+ }
+ else {
+ std::stringstream sstr;
+ sstr << "cannot decode unci item with unsupported compression type: " << cmpC_box->get_compression_type() << std::endl;
+ return Error(heif_error_Unsupported_feature,
+ heif_suberror_Unsupported_generic_compression_method,
+ sstr.str());
+ }
+}
+
+
diff --git a/libheif/codecs/uncompressed/unc_decoder_legacybase.h b/libheif/codecs/uncompressed/unc_decoder_legacybase.h
new file mode 100644
index 00000000..4489b36f
--- /dev/null
+++ b/libheif/codecs/uncompressed/unc_decoder_legacybase.h
@@ -0,0 +1,210 @@
+/*
+ * 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/>.
+ */
+
+#ifndef LIBHEIF_UNC_DECODER_LEGACYBASE_H
+#define LIBHEIF_UNC_DECODER_LEGACYBASE_H
+
+#include <cstdint>
+#include <cstring>
+#include <algorithm>
+#include <map>
+#include <iostream>
+#include <cassert>
+#include <utility>
+#include <vector>
+#include <memory>
+
+#include "common_utils.h"
+#include "context.h"
+#include "compression.h"
+#include "error.h"
+#include "libheif/heif.h"
+#include "unc_types.h"
+#include "unc_boxes.h"
+#include "unc_codec.h"
+#include "unc_decoder.h"
+
+
+class UncompressedBitReader : public BitReader
+{
+public:
+ UncompressedBitReader(const std::vector<uint8_t>& data) : BitReader(data.data(), (int) data.size()) {}
+
+ void markPixelStart()
+ {
+ m_pixelStartOffset = get_current_byte_index();
+ }
+
+ void markRowStart()
+ {
+ m_rowStartOffset = get_current_byte_index();
+ }
+
+ void markTileStart()
+ {
+ m_tileStartOffset = get_current_byte_index();
+ }
+
+ inline Error handlePixelAlignment(uint32_t pixel_size)
+ {
+ if (pixel_size != 0) {
+ uint32_t bytes_in_pixel = get_current_byte_index() - m_pixelStartOffset;
+ if (pixel_size > bytes_in_pixel) {
+ uint32_t padding = pixel_size - bytes_in_pixel;
+ skip_bytes(padding);
+ }
+ else {
+ return {
+ heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Uncompressed image: invalid 'pixel_size'"
+ };
+ }
+ }
+
+ return {};
+ }
+
+ void handleRowAlignment(uint32_t alignment)
+ {
+ skip_to_byte_boundary();
+ if (alignment != 0) {
+ uint32_t bytes_in_row = get_current_byte_index() - m_rowStartOffset;
+ uint32_t residual = bytes_in_row % alignment;
+ if (residual != 0) {
+ uint32_t padding = alignment - residual;
+ skip_bytes(padding);
+ }
+ }
+ }
+
+ void handleTileAlignment(uint32_t alignment)
+ {
+ if (alignment != 0) {
+ uint32_t bytes_in_tile = get_current_byte_index() - m_tileStartOffset;
+ uint32_t residual = bytes_in_tile % alignment;
+ if (residual != 0) {
+ uint32_t tile_padding = alignment - residual;
+ skip_bytes(tile_padding);
+ }
+ }
+ }
+
+private:
+ int m_pixelStartOffset = 0;
+ int m_rowStartOffset = 0;
+ int m_tileStartOffset = 0;
+};
+
+
+template<typename T> void skip_to_alignment(T& position, uint32_t alignment)
+{
+ if (alignment == 0) {
+ return;
+ }
+
+ T residual = position % alignment;
+ if (residual == 0) {
+ return;
+ }
+
+ position += alignment - residual;
+}
+
+
+class unc_decoder_legacybase : public unc_decoder
+{
+public:
+ ~unc_decoder_legacybase() override = default;
+
+protected:
+ unc_decoder_legacybase(uint32_t width, uint32_t height,
+ const std::shared_ptr<const Box_cmpd>& cmpd,
+ const std::shared_ptr<const Box_uncC>& uncC);
+
+ class ChannelListEntry
+ {
+ public:
+ uint32_t get_bytes_per_tile() const
+ {
+ return bytes_per_tile_row_src * tile_height;
+ }
+
+ inline uint64_t getDestinationRowOffset(uint32_t tile_row, uint32_t tile_y) const
+ {
+ uint64_t dst_row_number = uint64_t{tile_row} * tile_height + tile_y;
+ return dst_row_number * dst_plane_stride;
+ }
+
+ heif_channel channel = heif_channel_Y;
+ uint8_t* dst_plane = nullptr;
+ uint8_t* other_chroma_dst_plane = nullptr;
+ size_t dst_plane_stride;
+ size_t other_chroma_dst_plane_stride;
+ uint32_t tile_width;
+ uint32_t tile_height;
+ uint32_t bytes_per_component_sample;
+ uint16_t bits_per_component_sample;
+ uint8_t component_alignment;
+ uint32_t bytes_per_tile_row_src;
+ bool use_channel;
+ };
+
+ std::vector<ChannelListEntry> channelList;
+
+ // TODO: refactor so that the channel list is passed explicitly instead of being lazily initialized as member state.
+ void ensureChannelList(std::shared_ptr<HeifPixelImage>& img);
+
+ void processComponentSample(UncompressedBitReader& srcBits, const ChannelListEntry& entry, uint64_t dst_row_offset, uint32_t tile_column, uint32_t tile_x);
+
+ // Handles the case where a row consists of a single component type
+ // Not valid for Pixel interleave
+ // Not valid for the Cb/Cr channels in Mixed Interleave
+ // Not valid for multi-Y pixel interleave
+ void processComponentRow(ChannelListEntry& entry, UncompressedBitReader& srcBits, uint64_t dst_row_offset, uint32_t tile_column);
+
+ void processComponentTileSample(UncompressedBitReader& srcBits, const ChannelListEntry& entry, uint64_t dst_offset, uint32_t tile_x);
+
+ // Handles the case where a row consists of a single component type
+ // Not valid for Pixel interleave
+ // Not valid for the Cb/Cr channels in Mixed Interleave
+ // Not valid for multi-Y pixel interleave
+ void processComponentTileRow(ChannelListEntry& entry, UncompressedBitReader& srcBits, uint64_t dst_offset);
+
+ // generic compression and uncompressed, per 23001-17
+ const Error get_compressed_image_data_uncompressed(const DataExtent& dataExtent,
+ const UncompressedImageCodec::unci_properties& properties,
+ std::vector<uint8_t>* data,
+ uint64_t range_start_offset, uint64_t range_size,
+ uint32_t tile_idx,
+ const Box_iloc::Item* item) const;
+
+ Result<std::vector<uint8_t>> do_decompress_data(std::shared_ptr<const Box_cmpC>& cmpC_box,
+ std::vector<uint8_t> compressed_data) const;
+
+protected:
+ void memcpy_to_native_endian(uint8_t* dst, uint32_t value, uint32_t bytes_per_sample);
+
+private:
+ void buildChannelList(std::shared_ptr<HeifPixelImage>& img);
+ ChannelListEntry buildChannelListEntry(Box_uncC::Component component, std::shared_ptr<HeifPixelImage>& img);
+};
+
+#endif