Commit f6436f22 for libheif

commit f6436f228379d2ab8528f78a1f229cb6d9d083e5
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Sun Feb 8 20:46:58 2026 +0100

    unci: refactor unc_decoder into a clean base class and move everything else into unc_decoder_legacybase

diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt
index 6e789e0b..93843028 100644
--- a/libheif/CMakeLists.txt
+++ b/libheif/CMakeLists.txt
@@ -295,6 +295,8 @@ if (WITH_UNCOMPRESSED_CODEC)
             codecs/uncompressed/unc_enc.cc
             codecs/uncompressed/unc_decoder.h
             codecs/uncompressed/unc_decoder.cc
+            codecs/uncompressed/unc_decoder_legacybase.h
+            codecs/uncompressed/unc_decoder_legacybase.cc
             codecs/uncompressed/unc_decoder_component_interleave.h
             codecs/uncompressed/unc_decoder_component_interleave.cc
             codecs/uncompressed/unc_decoder_pixel_interleave.h
diff --git a/libheif/codecs/uncompressed/unc_codec.cc b/libheif/codecs/uncompressed/unc_codec.cc
index cde86830..fcfb03cc 100644
--- a/libheif/codecs/uncompressed/unc_codec.cc
+++ b/libheif/codecs/uncompressed/unc_codec.cc
@@ -527,8 +527,6 @@ Error UncompressedImageCodec::decode_uncompressed_image_tile(const HeifContext*

   auto& decoder = *decoderResult;

-  decoder->buildChannelList(img);
-
   DataExtent dataExtent;
   dataExtent.set_from_image_item(file, ID);

diff --git a/libheif/codecs/uncompressed/unc_decoder.cc b/libheif/codecs/uncompressed/unc_decoder.cc
index bffd52f3..2dfa9b10 100644
--- a/libheif/codecs/uncompressed/unc_decoder.cc
+++ b/libheif/codecs/uncompressed/unc_decoder.cc
@@ -18,42 +18,30 @@
  * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
  */

-#include <cstring>
-#include <algorithm>
-#include <iostream>
 #include <cassert>
-#include <utility>
+#include <sstream>

-#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.h"
 #include "unc_decoder_component_interleave.h"
 #include "unc_decoder_pixel_interleave.h"
 #include "unc_decoder_mixed_interleave.h"
 #include "unc_decoder_row_interleave.h"
 #include "unc_decoder_tile_component_interleave.h"
+#include "unc_codec.h"
+#include "unc_boxes.h"
 #include "codecs/decoder.h"
-#include "codecs/uncompressed/unc_codec.h"
 #include "security_limits.h"


-unc_decoder::unc_decoder(uint32_t width, uint32_t height, const std::shared_ptr<const Box_cmpd> cmpd, const std::shared_ptr<const Box_uncC> uncC) :
-    m_width(width),
-    m_height(height),
-    m_cmpd(std::move(cmpd)),
-    m_uncC(std::move(uncC))
+// --- unc_decoder ---
+
+unc_decoder::unc_decoder(uint32_t width, uint32_t height,
+                         const std::shared_ptr<const Box_cmpd>& cmpd,
+                         const std::shared_ptr<const Box_uncC>& uncC)
+    : m_width(width),
+      m_height(height),
+      m_cmpd(cmpd),
+      m_uncC(uncC)
 {
   m_tile_height = m_height / m_uncC->get_number_of_tile_rows();
   m_tile_width = m_width / m_uncC->get_number_of_tile_columns();
@@ -62,285 +50,28 @@ unc_decoder::unc_decoder(uint32_t width, uint32_t height, const std::shared_ptr<
   assert(m_tile_height > 0);
 }

-void unc_decoder::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::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::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::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::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::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::ChannelListEntry unc_decoder::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::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
+Error unc_decoder::decode_image(const DataExtent& extent,
+                                const UncompressedImageCodec::unci_properties& properties,
+                                std::shared_ptr<HeifPixelImage>& img)
 {
-  // --- 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);
+  uint32_t tile_width = m_width / m_uncC->get_number_of_tile_columns();
+  uint32_t tile_height = m_height / m_uncC->get_number_of_tile_rows();

-      auto dataResult = do_decompress_data(cmpC_box, std::move(compressed_unit_data));
-      if (!dataResult) {
-        return dataResult.error();
+  for (uint32_t tile_y0 = 0; tile_y0 < m_height; tile_y0 += tile_height)
+    for (uint32_t tile_x0 = 0; tile_x0 < m_width; tile_x0 += tile_width) {
+      Error error = decode_tile(extent, properties, img, tile_x0, tile_y0,
+                                m_width, m_height,
+                                tile_x0 / tile_width, tile_y0 / tile_height);
+      if (error) {
+        return 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::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());
-  }
-}
-
-
 // --- unc_decoder_factory ---

 Result<std::unique_ptr<unc_decoder>> unc_decoder_factory::get_unc_decoder(
@@ -399,8 +130,6 @@ Result<std::shared_ptr<HeifPixelImage>> unc_decoder::decode_full_image(

   auto& decoder = *decoderResult;

-  decoder->buildChannelList(img);
-
   Error error = decoder->decode_image(extent, properties, img);
   if (error) {
     return error;
@@ -408,24 +137,3 @@ Result<std::shared_ptr<HeifPixelImage>> unc_decoder::decode_full_image(

   return img;
 }
-
-
-Error unc_decoder::decode_image(const DataExtent& extent,
-                                const UncompressedImageCodec::unci_properties& properties,
-                                std::shared_ptr<HeifPixelImage>& img)
-{
-  uint32_t tile_width = m_width / m_uncC->get_number_of_tile_columns();
-  uint32_t tile_height = m_height / m_uncC->get_number_of_tile_rows();
-
-  for (uint32_t tile_y0 = 0; tile_y0 < m_height; tile_y0 += tile_height)
-    for (uint32_t tile_x0 = 0; tile_x0 < m_width; tile_x0 += tile_width) {
-      Error error = decode_tile(extent, properties, img, tile_x0, tile_y0,
-                                m_width, m_height,
-                                tile_x0 / tile_width, tile_y0 / tile_height);
-      if (error) {
-        return error;
-      }
-    }
-
-  return Error::Ok;
-}
diff --git a/libheif/codecs/uncompressed/unc_decoder.h b/libheif/codecs/uncompressed/unc_decoder.h
index 2796136a..48ee8a03 100644
--- a/libheif/codecs/uncompressed/unc_decoder.h
+++ b/libheif/codecs/uncompressed/unc_decoder.h
@@ -22,110 +22,15 @@
 #define LIBHEIF_UNC_DECODER_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_boxes.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 HeifPixelImage;
+struct DataExtent;
+struct heif_security_limits;


 class unc_decoder
@@ -140,92 +45,26 @@ public:
                             uint32_t image_width, uint32_t image_height,
                             uint32_t tile_x, uint32_t tile_y) = 0;

-  void buildChannelList(std::shared_ptr<HeifPixelImage>& img);
+  Error decode_image(const DataExtent& extent,
+                     const UncompressedImageCodec::unci_properties& properties,
+                     std::shared_ptr<HeifPixelImage>& img);

   static Result<std::shared_ptr<HeifPixelImage>> decode_full_image(
       const UncompressedImageCodec::unci_properties& properties,
       const DataExtent& extent,
       const heif_security_limits* limits);

-  Error decode_image(const DataExtent& extent,
-                     const UncompressedImageCodec::unci_properties& properties,
-                     std::shared_ptr<HeifPixelImage>& img);
-
 protected:
   unc_decoder(uint32_t width, uint32_t height,
-              const std::shared_ptr<const Box_cmpd> cmpd,
-              const std::shared_ptr<const Box_uncC> uncC);
+              const std::shared_ptr<const Box_cmpd>& cmpd,
+              const std::shared_ptr<const Box_uncC>& uncC);

   const uint32_t m_width;
   const uint32_t m_height;
   const std::shared_ptr<const Box_cmpd> m_cmpd;
   const std::shared_ptr<const Box_uncC> m_uncC;
-  // TODO: see if we can make this const
   uint32_t m_tile_height;
   uint32_t m_tile_width;
-
-  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;
-
-  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:
-  ChannelListEntry buildChannelListEntry(Box_uncC::Component component, std::shared_ptr<HeifPixelImage>& img);
 };


diff --git a/libheif/codecs/uncompressed/unc_decoder_component_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_component_interleave.cc
index b6ae03cd..f5b8ad63 100644
--- a/libheif/codecs/uncompressed/unc_decoder_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_component_interleave.cc
@@ -33,6 +33,8 @@ Error unc_decoder_component_interleave::decode_tile(const DataExtent& dataExtent
                                                      uint32_t image_width, uint32_t image_height,
                                                      uint32_t tile_x, uint32_t tile_y)
 {
+  ensureChannelList(img);
+
   if (m_tile_width == 0) {
     return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: unc_decoder_component_interleave tile_width=0"};
   }
diff --git a/libheif/codecs/uncompressed/unc_decoder_component_interleave.h b/libheif/codecs/uncompressed/unc_decoder_component_interleave.h
index 81f70121..2cd5d019 100644
--- a/libheif/codecs/uncompressed/unc_decoder_component_interleave.h
+++ b/libheif/codecs/uncompressed/unc_decoder_component_interleave.h
@@ -21,18 +21,18 @@
 #ifndef LIBHEIF_UNC_DECODER_COMPONENT_INTERLEAVE_H
 #define LIBHEIF_UNC_DECODER_COMPONENT_INTERLEAVE_H

-#include "unc_decoder.h"
+#include "unc_decoder_legacybase.h"
 #include <memory>
 #include <utility>


-class unc_decoder_component_interleave : public unc_decoder
+class unc_decoder_component_interleave : public unc_decoder_legacybase
 {
 public:
   unc_decoder_component_interleave(uint32_t width, uint32_t height,
                                     std::shared_ptr<const Box_cmpd> cmpd,
                                     std::shared_ptr<const Box_uncC> uncC) :
-      unc_decoder(width, height, std::move(cmpd), std::move(uncC)) {}
+      unc_decoder_legacybase(width, height, std::move(cmpd), std::move(uncC)) {}

   Error decode_tile(const DataExtent& dataExtent,
                     const UncompressedImageCodec::unci_properties& properties,
diff --git a/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.cc
index be117de4..dc0dec81 100644
--- a/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.cc
@@ -34,6 +34,8 @@ Error unc_decoder_mixed_interleave::decode_tile(const DataExtent& dataExtent,
                                                  uint32_t image_width, uint32_t image_height,
                                                  uint32_t tile_x, uint32_t tile_y)
 {
+  ensureChannelList(img);
+
   if (m_tile_width == 0) {
     return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: unc_decoder_mixed_interleave tile_width=0"};
   }
diff --git a/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.h b/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.h
index e9f4969d..e8a2dd4f 100644
--- a/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.h
+++ b/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.h
@@ -21,16 +21,16 @@
 #ifndef LIBHEIF_UNC_DECODER_MIXED_INTERLEAVE_H
 #define LIBHEIF_UNC_DECODER_MIXED_INTERLEAVE_H

-#include "unc_decoder.h"
+#include "unc_decoder_legacybase.h"
 #include <memory>
 #include <utility>


-class unc_decoder_mixed_interleave : public unc_decoder
+class unc_decoder_mixed_interleave : public unc_decoder_legacybase
 {
 public:
   unc_decoder_mixed_interleave(uint32_t width, uint32_t height, std::shared_ptr<const Box_cmpd> cmpd, std::shared_ptr<const Box_uncC> uncC) :
-      unc_decoder(width, height, std::move(cmpd), std::move(uncC)) {}
+      unc_decoder_legacybase(width, height, std::move(cmpd), std::move(uncC)) {}

   Error decode_tile(const DataExtent& dataExtent,
                     const UncompressedImageCodec::unci_properties& properties,
diff --git a/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.cc
index 2b1944c0..15e69e81 100644
--- a/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.cc
@@ -33,6 +33,8 @@ Error unc_decoder_pixel_interleave::decode_tile(const DataExtent& dataExtent,
                                                  uint32_t image_width, uint32_t image_height,
                                                  uint32_t tile_x, uint32_t tile_y)
 {
+  ensureChannelList(img);
+
   if (m_tile_width == 0) {
     return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: unc_decoder_pixel_interleave tile_width=0"};
   }
diff --git a/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.h b/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.h
index 0174b4ec..152e4d49 100644
--- a/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.h
+++ b/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.h
@@ -21,16 +21,16 @@
 #ifndef LIBHEIF_UNC_DECODER_PIXEL_INTERLEAVE_H
 #define LIBHEIF_UNC_DECODER_PIXEL_INTERLEAVE_H

-#include "unc_decoder.h"
+#include "unc_decoder_legacybase.h"
 #include <memory>
 #include <utility>


-class unc_decoder_pixel_interleave : public unc_decoder
+class unc_decoder_pixel_interleave : public unc_decoder_legacybase
 {
 public:
   unc_decoder_pixel_interleave(uint32_t width, uint32_t height, std::shared_ptr<const Box_cmpd> cmpd, std::shared_ptr<const Box_uncC> uncC) :
-      unc_decoder(width, height, std::move(cmpd), std::move(uncC)) {}
+      unc_decoder_legacybase(width, height, std::move(cmpd), std::move(uncC)) {}

   Error decode_tile(const DataExtent& dataExtent,
                     const UncompressedImageCodec::unci_properties& properties,
diff --git a/libheif/codecs/uncompressed/unc_decoder_row_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_row_interleave.cc
index 8851e207..5616be29 100644
--- a/libheif/codecs/uncompressed/unc_decoder_row_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_row_interleave.cc
@@ -33,6 +33,8 @@ Error unc_decoder_row_interleave::decode_tile(const DataExtent& dataExtent,
                                                uint32_t image_width, uint32_t image_height,
                                                uint32_t tile_x, uint32_t tile_y)
 {
+  ensureChannelList(img);
+
   if (m_tile_width == 0) {
     return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: unc_decoder_row_interleave tile_width=0"};
   }
diff --git a/libheif/codecs/uncompressed/unc_decoder_row_interleave.h b/libheif/codecs/uncompressed/unc_decoder_row_interleave.h
index 05bad259..898c3801 100644
--- a/libheif/codecs/uncompressed/unc_decoder_row_interleave.h
+++ b/libheif/codecs/uncompressed/unc_decoder_row_interleave.h
@@ -21,17 +21,17 @@
 #ifndef LIBHEIF_UNC_DECODER_ROW_INTERLEAVE_H
 #define LIBHEIF_UNC_DECODER_ROW_INTERLEAVE_H

-#include "unc_decoder.h"
+#include "unc_decoder_legacybase.h"
 #include <memory>
 #include <utility>


-class unc_decoder_row_interleave : public unc_decoder
+class unc_decoder_row_interleave : public unc_decoder_legacybase
 {
 public:
   unc_decoder_row_interleave(uint32_t width, uint32_t height,
                               std::shared_ptr<const Box_cmpd> cmpd, std::shared_ptr<const Box_uncC> uncC) :
-      unc_decoder(width, height, std::move(cmpd), std::move(uncC)) {}
+      unc_decoder_legacybase(width, height, std::move(cmpd), std::move(uncC)) {}


   Error decode_tile(const DataExtent& dataExtent,
diff --git a/libheif/codecs/uncompressed/unc_decoder_tile_component_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_tile_component_interleave.cc
index c7e26edc..92db8348 100644
--- a/libheif/codecs/uncompressed/unc_decoder_tile_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_tile_component_interleave.cc
@@ -35,6 +35,8 @@ Error unc_decoder_tile_component_interleave::decode_tile(const DataExtent& dataE
                                                           uint32_t image_width, uint32_t image_height,
                                                           uint32_t tile_column, uint32_t tile_row)
 {
+  ensureChannelList(img);
+
   if (m_tile_width == 0) {
     return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: unc_decoder_tile_component_interleave tile_width=0"};
   }
diff --git a/libheif/codecs/uncompressed/unc_decoder_tile_component_interleave.h b/libheif/codecs/uncompressed/unc_decoder_tile_component_interleave.h
index ed083f9e..64d4a1b4 100644
--- a/libheif/codecs/uncompressed/unc_decoder_tile_component_interleave.h
+++ b/libheif/codecs/uncompressed/unc_decoder_tile_component_interleave.h
@@ -21,17 +21,17 @@
 #ifndef LIBHEIF_UNC_DECODER_TILE_COMPONENT_INTERLEAVE_H
 #define LIBHEIF_UNC_DECODER_TILE_COMPONENT_INTERLEAVE_H

-#include "unc_decoder.h"
+#include "unc_decoder_legacybase.h"
 #include <memory>
 #include <utility>


-class unc_decoder_tile_component_interleave : public unc_decoder
+class unc_decoder_tile_component_interleave : public unc_decoder_legacybase
 {
 public:
   unc_decoder_tile_component_interleave(uint32_t width, uint32_t height,
                                          std::shared_ptr<const Box_cmpd> cmpd, std::shared_ptr<const Box_uncC> uncC) :
-      unc_decoder(width, height, std::move(cmpd), std::move(uncC)) {}
+      unc_decoder_legacybase(width, height, std::move(cmpd), std::move(uncC)) {}

   Error decode_tile(const DataExtent& dataExtent,
                     const UncompressedImageCodec::unci_properties& properties,