Commit 0023d8a4 for libheif

commit 0023d8a4af80c8423566a62b4fbec39d19a4aea6
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Sun Feb 8 21:40:27 2026 +0100

    unci: fold tile_component_interleave decoder into component_interleave decoder

diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt
index 93843028..4b02353a 100644
--- a/libheif/CMakeLists.txt
+++ b/libheif/CMakeLists.txt
@@ -305,8 +305,6 @@ if (WITH_UNCOMPRESSED_CODEC)
             codecs/uncompressed/unc_decoder_mixed_interleave.cc
             codecs/uncompressed/unc_decoder_row_interleave.h
             codecs/uncompressed/unc_decoder_row_interleave.cc
-            codecs/uncompressed/unc_decoder_tile_component_interleave.h
-            codecs/uncompressed/unc_decoder_tile_component_interleave.cc
             codecs/uncompressed/unc_encoder.h
             codecs/uncompressed/unc_encoder.cc
             codecs/uncompressed/unc_encoder_rgb3_rgba.cc
diff --git a/libheif/codecs/uncompressed/unc_codec.cc b/libheif/codecs/uncompressed/unc_codec.cc
index d9947978..89f4734f 100644
--- a/libheif/codecs/uncompressed/unc_codec.cc
+++ b/libheif/codecs/uncompressed/unc_codec.cc
@@ -530,8 +530,15 @@ Error UncompressedImageCodec::decode_uncompressed_image_tile(const HeifContext*
   DataExtent dataExtent;
   dataExtent.set_from_image_item(file, ID);

-  return decoder->decode_tile(dataExtent, properties, img, 0, 0,
-                              tile_x0, tile_y0);
+  decoder->ensure_channel_list(img);
+
+  std::vector<uint8_t> tile_data;
+  Error err = decoder->fetch_tile_data(dataExtent, properties, tile_x0, tile_y0, tile_data);
+  if (err) {
+    return err;
+  }
+
+  return decoder->decode_tile(tile_data, img, 0, 0, tile_x0, tile_y0);
 }


diff --git a/libheif/codecs/uncompressed/unc_decoder.cc b/libheif/codecs/uncompressed/unc_decoder.cc
index be4a81ca..dd461734 100644
--- a/libheif/codecs/uncompressed/unc_decoder.cc
+++ b/libheif/codecs/uncompressed/unc_decoder.cc
@@ -26,9 +26,9 @@
 #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 "compression.h"
 #include "codecs/decoder.h"
 #include "security_limits.h"

@@ -51,6 +51,175 @@ unc_decoder::unc_decoder(uint32_t width, uint32_t height,
 }


+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
+{
+  // --- 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::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());
+  }
+}
+
+
 Error unc_decoder::decode_image(const DataExtent& extent,
                                 const UncompressedImageCodec::unci_properties& properties,
                                 std::shared_ptr<HeifPixelImage>& img)
@@ -58,10 +227,20 @@ Error unc_decoder::decode_image(const DataExtent& extent,
   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();

+  ensure_channel_list(img);
+
   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,
-                                tile_x0 / tile_width, tile_y0 / tile_height);
+      uint32_t tile_x = tile_x0 / tile_width;
+      uint32_t tile_y = tile_y0 / tile_height;
+
+      std::vector<uint8_t> tile_data;
+      Error error = fetch_tile_data(extent, properties, tile_x, tile_y, tile_data);
+      if (error) {
+        return error;
+      }
+
+      error = decode_tile(tile_data, img, tile_x0, tile_y0, tile_x, tile_y);
       if (error) {
         return error;
       }
@@ -82,10 +261,9 @@ Result<std::unique_ptr<unc_decoder>> unc_decoder_factory::get_unc_decoder(
   static unc_decoder_factory_pixel_interleave dec_pixel;
   static unc_decoder_factory_mixed_interleave dec_mixed;
   static unc_decoder_factory_row_interleave dec_row;
-  static unc_decoder_factory_tile_component_interleave dec_tile_component;

   static const unc_decoder_factory* decoders[]{
-    &dec_component, &dec_pixel, &dec_mixed, &dec_row, &dec_tile_component
+    &dec_component, &dec_pixel, &dec_mixed, &dec_row
   };

   for (const unc_decoder_factory* dec : decoders) {
diff --git a/libheif/codecs/uncompressed/unc_decoder.h b/libheif/codecs/uncompressed/unc_decoder.h
index 226cc1bb..fb47dd34 100644
--- a/libheif/codecs/uncompressed/unc_decoder.h
+++ b/libheif/codecs/uncompressed/unc_decoder.h
@@ -23,6 +23,7 @@

 #include <cstdint>
 #include <memory>
+#include <vector>

 #include "error.h"
 #include "unc_codec.h"
@@ -38,8 +39,14 @@ class unc_decoder
 public:
   virtual ~unc_decoder() = default;

-  virtual Error decode_tile(const DataExtent& dataExtent,
-                            const UncompressedImageCodec::unci_properties& properties,
+  virtual void ensure_channel_list(std::shared_ptr<HeifPixelImage>& img) {}
+
+  virtual Error fetch_tile_data(const DataExtent& dataExtent,
+                                const UncompressedImageCodec::unci_properties& properties,
+                                uint32_t tile_x, uint32_t tile_y,
+                                std::vector<uint8_t>& tile_data) = 0;
+
+  virtual Error decode_tile(const std::vector<uint8_t>& tile_data,
                             std::shared_ptr<HeifPixelImage>& img,
                             uint32_t out_x0, uint32_t out_y0,
                             uint32_t tile_x, uint32_t tile_y) = 0;
@@ -58,6 +65,16 @@ protected:
               const std::shared_ptr<const Box_cmpd>& cmpd,
               const std::shared_ptr<const Box_uncC>& uncC);

+  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;
+
   const uint32_t m_width;
   const uint32_t m_height;
   const std::shared_ptr<const Box_cmpd> m_cmpd;
diff --git a/libheif/codecs/uncompressed/unc_decoder_component_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_component_interleave.cc
index 09e8be67..a86d3b6a 100644
--- a/libheif/codecs/uncompressed/unc_decoder_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_component_interleave.cc
@@ -23,22 +23,24 @@
 #include "error.h"

 #include <cassert>
+#include <map>
 #include <vector>


-Error unc_decoder_component_interleave::decode_tile(const DataExtent& dataExtent,
-                                                     const UncompressedImageCodec::unci_properties& properties,
-                                                     std::shared_ptr<HeifPixelImage>& img,
-                                                     uint32_t out_x0, uint32_t out_y0,
-                                                     uint32_t tile_x, uint32_t tile_y)
+Error unc_decoder_component_interleave::fetch_tile_data(const DataExtent& dataExtent,
+                                                         const UncompressedImageCodec::unci_properties& properties,
+                                                         uint32_t tile_x, uint32_t tile_y,
+                                                         std::vector<uint8_t>& tile_data)
 {
-  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"};
   }

-  // --- compute which file range we need to read for the tile
+  if (m_uncC->get_interleave_type() == interleave_mode_tile_component) {
+    return fetch_tile_data_tile_component(dataExtent, properties, tile_x, tile_y, tile_data);
+  }
+
+  // --- interleave_mode_component: single contiguous read

   uint64_t total_tile_size = 0;

@@ -64,21 +66,88 @@ Error unc_decoder_component_interleave::decode_tile(const DataExtent& dataExtent
   uint32_t tileIdx = tile_x + tile_y * (m_width / m_tile_width);
   uint64_t tile_start_offset = total_tile_size * tileIdx;

+  return get_compressed_image_data_uncompressed(dataExtent, properties, &tile_data, tile_start_offset, total_tile_size, tileIdx, nullptr);
+}
+
+
+Error unc_decoder_component_interleave::fetch_tile_data_tile_component(const DataExtent& dataExtent,
+                                                                        const UncompressedImageCodec::unci_properties& properties,
+                                                                        uint32_t tile_x, uint32_t tile_y,
+                                                                        std::vector<uint8_t>& tile_data)
+{
+  if (m_tile_height == 0) {
+    return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: unc_decoder_component_interleave tile_height=0"};
+  }
+
+  // Compute per-channel tile sizes (with per-channel tile alignment)
+  std::map<heif_channel, uint64_t> channel_tile_size;
+
+  for (ChannelListEntry& entry : channelList) {
+    uint32_t bits_per_pixel = entry.bits_per_component_sample;
+    if (entry.component_alignment > 0) {
+      uint32_t bytes_per_component = (bits_per_pixel + 7) / 8;
+      skip_to_alignment(bytes_per_component, entry.component_alignment);
+      bits_per_pixel = bytes_per_component * 8;
+    }
+
+    uint32_t bytes_per_row;
+    if (m_uncC->get_pixel_size() != 0) {
+      uint32_t bytes_per_pixel = (bits_per_pixel + 7) / 8;
+      skip_to_alignment(bytes_per_pixel, m_uncC->get_pixel_size());
+      bytes_per_row = bytes_per_pixel * m_tile_width;
+    }
+    else {
+      bytes_per_row = (bits_per_pixel * m_tile_width + 7) / 8;
+    }
+
+    skip_to_alignment(bytes_per_row, m_uncC->get_row_align_size());

-  // --- read required file range
+    uint64_t component_tile_size = bytes_per_row * static_cast<uint64_t>(m_tile_height);

-  std::vector<uint8_t> src_data;
-  Error err = get_compressed_image_data_uncompressed(dataExtent, properties, &src_data, tile_start_offset, total_tile_size, tileIdx, nullptr);
-  if (err) {
-    return err;
+    if (m_uncC->get_tile_align_size() != 0) {
+      skip_to_alignment(component_tile_size, m_uncC->get_tile_align_size());
+    }
+
+    channel_tile_size[entry.channel] = component_tile_size;
   }

-  UncompressedBitReader srcBits(src_data);
+  // Read each channel's tile data and concatenate
+  uint64_t component_start_offset = 0;
+  uint32_t num_tiles = (m_width / m_tile_width) * (m_height / m_tile_height);
+  uint32_t tileIdx = tile_x + tile_y * (m_width / m_tile_width);

+  assert(m_tile_width > 0);
+  assert(m_tile_height > 0);

-  // --- decode tile
+  for (ChannelListEntry& entry : channelList) {
+    uint64_t tile_start_offset = component_start_offset + channel_tile_size[entry.channel] * tileIdx;
+
+    std::vector<uint8_t> channel_data;
+    Error err = get_compressed_image_data_uncompressed(dataExtent, properties, &channel_data, tile_start_offset, channel_tile_size[entry.channel], tileIdx, nullptr);
+    if (err) {
+      return err;
+    }
+
+    tile_data.insert(tile_data.end(), channel_data.begin(), channel_data.end());
+
+    component_start_offset += channel_tile_size[entry.channel] * num_tiles;
+  }
+
+  return Error::Ok;
+}
+
+
+Error unc_decoder_component_interleave::decode_tile(const std::vector<uint8_t>& tile_data,
+                                                     std::shared_ptr<HeifPixelImage>& img,
+                                                     uint32_t out_x0, uint32_t out_y0,
+                                                     uint32_t tile_x, uint32_t tile_y)
+{
+  UncompressedBitReader srcBits(tile_data);
+
+  bool per_channel_tile_align = (m_uncC->get_interleave_type() == interleave_mode_tile_component);

   for (ChannelListEntry& entry : channelList) {
+    srcBits.markTileStart();
     for (uint32_t y = 0; y < entry.tile_height; y++) {
       srcBits.markRowStart();
       if (entry.use_channel) {
@@ -90,6 +159,9 @@ Error unc_decoder_component_interleave::decode_tile(const DataExtent& dataExtent
       }
       srcBits.handleRowAlignment(m_uncC->get_row_align_size());
     }
+    if (per_channel_tile_align) {
+      srcBits.handleTileAlignment(m_uncC->get_tile_align_size());
+    }
   }

   return Error::Ok;
@@ -98,7 +170,8 @@ Error unc_decoder_component_interleave::decode_tile(const DataExtent& dataExtent

 bool unc_decoder_factory_component_interleave::can_decode(const std::shared_ptr<const Box_uncC>& uncC) const
 {
-  return uncC->get_interleave_type() == interleave_mode_component;
+  return uncC->get_interleave_type() == interleave_mode_component ||
+         uncC->get_interleave_type() == interleave_mode_tile_component;
 }

 std::unique_ptr<unc_decoder> unc_decoder_factory_component_interleave::create(
diff --git a/libheif/codecs/uncompressed/unc_decoder_component_interleave.h b/libheif/codecs/uncompressed/unc_decoder_component_interleave.h
index 234064f4..0d54771d 100644
--- a/libheif/codecs/uncompressed/unc_decoder_component_interleave.h
+++ b/libheif/codecs/uncompressed/unc_decoder_component_interleave.h
@@ -34,11 +34,21 @@ public:
                                     std::shared_ptr<const Box_uncC> uncC) :
       unc_decoder_legacybase(width, height, std::move(cmpd), std::move(uncC)) {}

-  Error decode_tile(const DataExtent& dataExtent,
-                    const UncompressedImageCodec::unci_properties& properties,
+  Error fetch_tile_data(const DataExtent& dataExtent,
+                        const UncompressedImageCodec::unci_properties& properties,
+                        uint32_t tile_x, uint32_t tile_y,
+                        std::vector<uint8_t>& tile_data) override;
+
+  Error decode_tile(const std::vector<uint8_t>& tile_data,
                     std::shared_ptr<HeifPixelImage>& img,
                     uint32_t out_x0, uint32_t out_y0,
                     uint32_t tile_x, uint32_t tile_y) override;
+
+private:
+  Error fetch_tile_data_tile_component(const DataExtent& dataExtent,
+                                       const UncompressedImageCodec::unci_properties& properties,
+                                       uint32_t tile_x, uint32_t tile_y,
+                                       std::vector<uint8_t>& tile_data);
 };


diff --git a/libheif/codecs/uncompressed/unc_decoder_legacybase.cc b/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
index a3d177b9..21aef652 100644
--- a/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
@@ -166,172 +166,4 @@ unc_decoder_legacybase::ChannelListEntry unc_decoder_legacybase::buildChannelLis
 }


-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
index 4489b36f..f2a60123 100644
--- a/libheif/codecs/uncompressed/unc_decoder_legacybase.h
+++ b/libheif/codecs/uncompressed/unc_decoder_legacybase.h
@@ -134,6 +134,8 @@ class unc_decoder_legacybase : public unc_decoder
 public:
   ~unc_decoder_legacybase() override = default;

+  void ensure_channel_list(std::shared_ptr<HeifPixelImage>& img) override { ensureChannelList(img); }
+
 protected:
   unc_decoder_legacybase(uint32_t width, uint32_t height,
                          const std::shared_ptr<const Box_cmpd>& cmpd,
@@ -188,17 +190,6 @@ protected:
   // 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);

diff --git a/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.cc
index 89f14c0d..48eca4d0 100644
--- a/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.cc
@@ -27,20 +27,15 @@
 #include <vector>


-Error unc_decoder_mixed_interleave::decode_tile(const DataExtent& dataExtent,
-                                                 const UncompressedImageCodec::unci_properties& properties,
-                                                 std::shared_ptr<HeifPixelImage>& img,
-                                                 uint32_t out_x0, uint32_t out_y0,
-                                                 uint32_t tile_x, uint32_t tile_y)
+Error unc_decoder_mixed_interleave::fetch_tile_data(const DataExtent& dataExtent,
+                                                     const UncompressedImageCodec::unci_properties& properties,
+                                                     uint32_t tile_x, uint32_t tile_y,
+                                                     std::vector<uint8_t>& tile_data)
 {
-  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"};
   }

-  // --- compute which file range we need to read for the tile
-
   uint64_t tile_size = 0;

   for (ChannelListEntry& entry : channelList) {
@@ -65,7 +60,6 @@ Error unc_decoder_mixed_interleave::decode_tile(const DataExtent& dataExtent,
     }
   }

-
   if (m_uncC->get_tile_align_size() != 0) {
     skip_to_alignment(tile_size, m_uncC->get_tile_align_size());
   }
@@ -74,16 +68,16 @@ Error unc_decoder_mixed_interleave::decode_tile(const DataExtent& dataExtent,
   uint32_t tileIdx = tile_x + tile_y * (m_width / m_tile_width);
   uint64_t tile_start_offset = tile_size * tileIdx;

+  return get_compressed_image_data_uncompressed(dataExtent, properties, &tile_data, tile_start_offset, tile_size, tileIdx, nullptr);
+}

-  // --- read required file range
-
-  std::vector<uint8_t> src_data;
-  Error err = get_compressed_image_data_uncompressed(dataExtent, properties, &src_data, tile_start_offset, tile_size, tileIdx, nullptr);
-  if (err) {
-    return err;
-  }

-  UncompressedBitReader srcBits(src_data);
+Error unc_decoder_mixed_interleave::decode_tile(const std::vector<uint8_t>& tile_data,
+                                                 std::shared_ptr<HeifPixelImage>& img,
+                                                 uint32_t out_x0, uint32_t out_y0,
+                                                 uint32_t tile_x, uint32_t tile_y)
+{
+  UncompressedBitReader srcBits(tile_data);

   processTile(srcBits, tile_y, tile_x, out_x0, out_y0);

diff --git a/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.h b/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.h
index c90e283a..7b6b58f5 100644
--- a/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.h
+++ b/libheif/codecs/uncompressed/unc_decoder_mixed_interleave.h
@@ -32,8 +32,12 @@ 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_legacybase(width, height, std::move(cmpd), std::move(uncC)) {}

-  Error decode_tile(const DataExtent& dataExtent,
-                    const UncompressedImageCodec::unci_properties& properties,
+  Error fetch_tile_data(const DataExtent& dataExtent,
+                        const UncompressedImageCodec::unci_properties& properties,
+                        uint32_t tile_x, uint32_t tile_y,
+                        std::vector<uint8_t>& tile_data) override;
+
+  Error decode_tile(const std::vector<uint8_t>& tile_data,
                     std::shared_ptr<HeifPixelImage>& img,
                     uint32_t out_x0, uint32_t out_y0,
                     uint32_t tile_x, uint32_t tile_y) override;
diff --git a/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.cc
index 4ef31a0c..93933ebc 100644
--- a/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.cc
@@ -26,20 +26,15 @@
 #include <vector>


-Error unc_decoder_pixel_interleave::decode_tile(const DataExtent& dataExtent,
-                                                 const UncompressedImageCodec::unci_properties& properties,
-                                                 std::shared_ptr<HeifPixelImage>& img,
-                                                 uint32_t out_x0, uint32_t out_y0,
-                                                 uint32_t tile_x, uint32_t tile_y)
+Error unc_decoder_pixel_interleave::fetch_tile_data(const DataExtent& dataExtent,
+                                                     const UncompressedImageCodec::unci_properties& properties,
+                                                     uint32_t tile_x, uint32_t tile_y,
+                                                     std::vector<uint8_t>& tile_data)
 {
-  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"};
   }

-  // --- compute which file range we need to read for the tile
-
   uint32_t bits_per_row = 0;
   for (uint32_t x = 0; x < m_tile_width; x++) {
     uint32_t bits_per_pixel = 0;
@@ -79,23 +74,18 @@ Error unc_decoder_pixel_interleave::decode_tile(const DataExtent& dataExtent,
   uint32_t tileIdx = tile_x + tile_y * (m_width / m_tile_width);
   uint64_t tile_start_offset = total_tile_size * tileIdx;

+  return get_compressed_image_data_uncompressed(dataExtent, properties, &tile_data, tile_start_offset, total_tile_size, tileIdx, nullptr);
+}

-  // --- read required file range
-
-  std::vector<uint8_t> src_data;
-  Error err = get_compressed_image_data_uncompressed(dataExtent, properties, &src_data, tile_start_offset, total_tile_size, tileIdx, nullptr);
-  if (err) {
-    return err;
-  }
-
-  UncompressedBitReader srcBits(src_data);

-  err = processTile(srcBits, tile_y, tile_x, out_x0, out_y0);
-  if (err) {
-    return err;
-  }
+Error unc_decoder_pixel_interleave::decode_tile(const std::vector<uint8_t>& tile_data,
+                                                 std::shared_ptr<HeifPixelImage>& img,
+                                                 uint32_t out_x0, uint32_t out_y0,
+                                                 uint32_t tile_x, uint32_t tile_y)
+{
+  UncompressedBitReader srcBits(tile_data);

-  return Error::Ok;
+  return processTile(srcBits, tile_y, tile_x, out_x0, out_y0);
 }


diff --git a/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.h b/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.h
index 7c644374..40eb5d44 100644
--- a/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.h
+++ b/libheif/codecs/uncompressed/unc_decoder_pixel_interleave.h
@@ -32,8 +32,12 @@ 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_legacybase(width, height, std::move(cmpd), std::move(uncC)) {}

-  Error decode_tile(const DataExtent& dataExtent,
-                    const UncompressedImageCodec::unci_properties& properties,
+  Error fetch_tile_data(const DataExtent& dataExtent,
+                        const UncompressedImageCodec::unci_properties& properties,
+                        uint32_t tile_x, uint32_t tile_y,
+                        std::vector<uint8_t>& tile_data) override;
+
+  Error decode_tile(const std::vector<uint8_t>& tile_data,
                     std::shared_ptr<HeifPixelImage>& img,
                     uint32_t out_x0, uint32_t out_y0,
                     uint32_t tile_x, uint32_t tile_y) override;
diff --git a/libheif/codecs/uncompressed/unc_decoder_row_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_row_interleave.cc
index 5bae63fc..f437a1fb 100644
--- a/libheif/codecs/uncompressed/unc_decoder_row_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_row_interleave.cc
@@ -26,20 +26,15 @@
 #include <vector>


-Error unc_decoder_row_interleave::decode_tile(const DataExtent& dataExtent,
-                                               const UncompressedImageCodec::unci_properties& properties,
-                                               std::shared_ptr<HeifPixelImage>& img,
-                                               uint32_t out_x0, uint32_t out_y0,
-                                               uint32_t tile_x, uint32_t tile_y)
+Error unc_decoder_row_interleave::fetch_tile_data(const DataExtent& dataExtent,
+                                                   const UncompressedImageCodec::unci_properties& properties,
+                                                   uint32_t tile_x, uint32_t tile_y,
+                                                   std::vector<uint8_t>& tile_data)
 {
-  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"};
   }

-  // --- compute which file range we need to read for the tile
-
   uint32_t bits_per_row = 0;
   for (ChannelListEntry& entry : channelList) {
     uint32_t bits_per_component = entry.bits_per_component_sample;
@@ -80,16 +75,16 @@ Error unc_decoder_row_interleave::decode_tile(const DataExtent& dataExtent,
   uint32_t tileIdx = tile_x + tile_y * (m_width / m_tile_width);
   uint64_t tile_start_offset = total_tile_size * tileIdx;

+  return get_compressed_image_data_uncompressed(dataExtent, properties, &tile_data, tile_start_offset, total_tile_size, tileIdx, nullptr);
+}

-  // --- read required file range
-
-  std::vector<uint8_t> src_data;
-  Error err = get_compressed_image_data_uncompressed(dataExtent, properties, &src_data, tile_start_offset, total_tile_size, tileIdx, nullptr);
-  if (err) {
-    return err;
-  }

-  UncompressedBitReader srcBits(src_data);
+Error unc_decoder_row_interleave::decode_tile(const std::vector<uint8_t>& tile_data,
+                                               std::shared_ptr<HeifPixelImage>& img,
+                                               uint32_t out_x0, uint32_t out_y0,
+                                               uint32_t tile_x, uint32_t tile_y)
+{
+  UncompressedBitReader srcBits(tile_data);

   processTile(srcBits, tile_y, tile_x, out_x0, out_y0);

diff --git a/libheif/codecs/uncompressed/unc_decoder_row_interleave.h b/libheif/codecs/uncompressed/unc_decoder_row_interleave.h
index eee442bf..0d8d9304 100644
--- a/libheif/codecs/uncompressed/unc_decoder_row_interleave.h
+++ b/libheif/codecs/uncompressed/unc_decoder_row_interleave.h
@@ -34,8 +34,12 @@ public:
       unc_decoder_legacybase(width, height, std::move(cmpd), std::move(uncC)) {}


-  Error decode_tile(const DataExtent& dataExtent,
-                    const UncompressedImageCodec::unci_properties& properties,
+  Error fetch_tile_data(const DataExtent& dataExtent,
+                        const UncompressedImageCodec::unci_properties& properties,
+                        uint32_t tile_x, uint32_t tile_y,
+                        std::vector<uint8_t>& tile_data) override;
+
+  Error decode_tile(const std::vector<uint8_t>& tile_data,
                     std::shared_ptr<HeifPixelImage>& img,
                     uint32_t out_x0, uint32_t out_y0,
                     uint32_t tile_x, uint32_t tile_y) override;