Commit 594bc19f for libheif

commit 594bc19fca8168b0233e72dfe022566b0d00e0e6
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Sun Feb 8 17:05:31 2026 +0100

    unci: move computation of tile sizes into unc_encoders

diff --git a/libheif/codecs/uncompressed/unc_boxes.cc b/libheif/codecs/uncompressed/unc_boxes.cc
index b3f49fc0..87fd6c12 100644
--- a/libheif/codecs/uncompressed/unc_boxes.cc
+++ b/libheif/codecs/uncompressed/unc_boxes.cc
@@ -414,41 +414,6 @@ Error Box_uncC::write(StreamWriter& writer) const
 }


-uint64_t Box_uncC::compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const
-{
-  if (m_profile != 0) {
-    switch (m_profile) {
-      case fourcc("rgba"):
-        return 4 * uint64_t{tile_width} * tile_height;
-
-      case fourcc("rgb3"):
-        return 3 * uint64_t{tile_width} * tile_height;
-
-      default:
-        assert(false);
-        return 0;
-    }
-  }
-
-  switch (m_interleave_type) {
-    case interleave_mode_component:
-    case interleave_mode_pixel: {
-      uint32_t bytes_per_pixel = 0;
-
-      for (const auto& comp : m_components) {
-        assert(comp.component_bit_depth % 8 == 0); // TODO: component sizes that are no multiples of bytes
-        bytes_per_pixel += comp.component_bit_depth / 8;
-      }
-
-      return bytes_per_pixel * uint64_t{tile_width} * tile_height;
-    }
-    default:
-      assert(false);
-      return 0;
-  }
-}
-
-
 Error Box_cmpC::parse(BitstreamRange& range, const heif_security_limits* limits)
 {
   parse_full_box_header(range);
diff --git a/libheif/codecs/uncompressed/unc_boxes.h b/libheif/codecs/uncompressed/unc_boxes.h
index 8be60abd..4a6ab69a 100644
--- a/libheif/codecs/uncompressed/unc_boxes.h
+++ b/libheif/codecs/uncompressed/unc_boxes.h
@@ -216,8 +216,6 @@ public:

   uint32_t get_number_of_tiles() const { return m_num_tile_rows * m_num_tile_rows; }

-  uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const;
-
 protected:
   Error parse(BitstreamRange& range, const heif_security_limits* limits) override;

diff --git a/libheif/codecs/uncompressed/unc_encoder.h b/libheif/codecs/uncompressed/unc_encoder.h
index 6d500afa..0e5620bc 100644
--- a/libheif/codecs/uncompressed/unc_encoder.h
+++ b/libheif/codecs/uncompressed/unc_encoder.h
@@ -42,6 +42,8 @@ public:
   std::shared_ptr<Box_uncC> get_uncC() const { return m_uncC; }


+  virtual uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const = 0;
+
   [[nodiscard]] virtual std::vector<uint8_t> encode_tile(const std::shared_ptr<const HeifPixelImage>& image) const = 0;

   Result<Encoder::CodedImageData> encode_static(const std::shared_ptr<const HeifPixelImage>& src_image,
diff --git a/libheif/codecs/uncompressed/unc_encoder_planar.cc b/libheif/codecs/uncompressed/unc_encoder_planar.cc
index 5c60dff8..ebd389c8 100644
--- a/libheif/codecs/uncompressed/unc_encoder_planar.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_planar.cc
@@ -22,7 +22,7 @@


 bool unc_encoder_factory_planar::can_encode(const std::shared_ptr<const HeifPixelImage>& image,
-                                    const heif_encoding_options& options) const
+                                            const heif_encoding_options& options) const
 {
   if (image->has_channel(heif_channel_interleaved)) {
     return false;
@@ -32,14 +32,13 @@ bool unc_encoder_factory_planar::can_encode(const std::shared_ptr<const HeifPixe
 }


-std::unique_ptr<const unc_encoder>  unc_encoder_factory_planar::create(const std::shared_ptr<const HeifPixelImage>& image,
-                                                        const heif_encoding_options& options) const
+std::unique_ptr<const unc_encoder> unc_encoder_factory_planar::create(const std::shared_ptr<const HeifPixelImage>& image,
+                                                                      const heif_encoding_options& options) const
 {
   return std::make_unique<unc_encoder_planar>(image, options);
 }


-
 heif_uncompressed_component_type heif_channel_to_component_type(heif_channel channel)
 {
   switch (channel) {
@@ -61,60 +60,46 @@ heif_uncompressed_component_type heif_channel_to_component_type(heif_channel cha
 }


-struct channel_component
-{
-  heif_channel channel;
-  heif_uncompressed_component_type component_type;
-};
-
-void add_channel_if_exists(const std::shared_ptr<const HeifPixelImage>& image, std::vector<channel_component>& list, heif_channel channel)
+void unc_encoder_planar::add_channel_if_exists(const std::shared_ptr<const HeifPixelImage>& image, heif_channel channel)
 {
   if (image->has_channel(channel)) {
-    list.push_back({channel, heif_channel_to_component_type(channel)});
+    m_components.push_back({channel, heif_channel_to_component_type(channel)});
   }
 }

-std::vector<channel_component> get_channels(const std::shared_ptr<const HeifPixelImage>& image)
-{
-  std::vector<channel_component> channels;

+unc_encoder_planar::unc_encoder_planar(const std::shared_ptr<const HeifPixelImage>& image,
+                                       const heif_encoding_options& options)
+{
   // Special case for heif_channel_Y:
   // - if this an YCbCr image, use component_type_Y,
   // - otherwise, use component_type_monochrome

   if (image->has_channel(heif_channel_Y)) {
     if (image->has_channel(heif_channel_Cb) && image->has_channel(heif_channel_Cr)) {
-      channels.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_Y});
+      m_components.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_Y});
     }
     else {
-      channels.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_monochrome});
+      m_components.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_monochrome});
     }
   }

-  add_channel_if_exists(image, channels, heif_channel_Cb);
-  add_channel_if_exists(image, channels, heif_channel_Cr);
-  add_channel_if_exists(image, channels, heif_channel_R);
-  add_channel_if_exists(image, channels, heif_channel_G);
-  add_channel_if_exists(image, channels, heif_channel_B);
-  add_channel_if_exists(image, channels, heif_channel_Alpha);
-  add_channel_if_exists(image, channels, heif_channel_filter_array);
-  add_channel_if_exists(image, channels, heif_channel_depth);
-  add_channel_if_exists(image, channels, heif_channel_disparity);
-
-  return channels;
-}
-
+  add_channel_if_exists(image, heif_channel_Cb);
+  add_channel_if_exists(image, heif_channel_Cr);
+  add_channel_if_exists(image, heif_channel_R);
+  add_channel_if_exists(image, heif_channel_G);
+  add_channel_if_exists(image, heif_channel_B);
+  add_channel_if_exists(image, heif_channel_Alpha);
+  add_channel_if_exists(image, heif_channel_filter_array);
+  add_channel_if_exists(image, heif_channel_depth);
+  add_channel_if_exists(image, heif_channel_disparity);

-unc_encoder_planar::unc_encoder_planar(const std::shared_ptr<const HeifPixelImage>& image,
-                                                        const heif_encoding_options& options)
-{
-  auto channels = get_channels(image);

   // if we have any component > 8 bits, we enable this
   bool little_endian = false;

   uint16_t index = 0;
-  for (channel_component channelcomponent : channels) {
+  for (channel_component channelcomponent : m_components) {
     m_cmpd->add_component({channelcomponent.component_type});

     uint8_t bpp = image->get_bits_per_pixel(channelcomponent.channel);
@@ -144,6 +129,30 @@ unc_encoder_planar::unc_encoder_planar(const std::shared_ptr<const HeifPixelImag
   else {
     m_uncC->set_sampling_type(0);
   }
+
+
+  // --- compute bytes per pixel
+
+  m_bytes_per_pixel_x4 = 0;
+
+  for (channel_component channelcomponent : m_components) {
+    int bpp = image->get_bits_per_pixel(channelcomponent.channel);
+    int bytes_per_pixel = 4 * (bpp + 7) / 8;
+
+    if (channelcomponent.channel == heif_channel_Cb ||
+        channelcomponent.channel == heif_channel_Cr) {
+      int downsampling = chroma_h_subsampling(image->get_chroma_format()) * chroma_v_subsampling(image->get_chroma_format());
+      bytes_per_pixel /= downsampling;
+    }
+
+    m_bytes_per_pixel_x4 += bytes_per_pixel;
+  }
+}
+
+
+uint64_t unc_encoder_planar::compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const
+{
+  return tile_width * tile_height * m_bytes_per_pixel_x4 / 4;
 }


@@ -151,13 +160,11 @@ std::vector<uint8_t> unc_encoder_planar::encode_tile(const std::shared_ptr<const
 {
   std::vector<uint8_t> data;

-  auto channels = get_channels(src_image);
-
   // compute total size of all components

   uint64_t total_size = 0;

-  for (channel_component channelcomponent : channels) {
+  for (channel_component channelcomponent : m_components) {
     int bpp = src_image->get_bits_per_pixel(channelcomponent.channel);
     int bytes_per_pixel = (bpp + 7) / 8;

@@ -170,7 +177,7 @@ std::vector<uint8_t> unc_encoder_planar::encode_tile(const std::shared_ptr<const

   uint64_t out_data_start_pos = 0;

-  for (channel_component channelcomponent : channels) {
+  for (channel_component channelcomponent : m_components) {
     int bpp = src_image->get_bits_per_pixel(channelcomponent.channel);
     int bytes_per_pixel = (bpp + 7) / 8;

diff --git a/libheif/codecs/uncompressed/unc_encoder_planar.h b/libheif/codecs/uncompressed/unc_encoder_planar.h
index 3609b319..8fdbde51 100644
--- a/libheif/codecs/uncompressed/unc_encoder_planar.h
+++ b/libheif/codecs/uncompressed/unc_encoder_planar.h
@@ -17,6 +17,7 @@
 #define LIBHEIF_UNC_ENCODER_PLANAR_H

 #include "unc_encoder.h"
+#include "unc_types.h"

 class unc_encoder_planar : public unc_encoder
 {
@@ -24,7 +25,21 @@ public:
   unc_encoder_planar(const std::shared_ptr<const HeifPixelImage>& image,
                      const heif_encoding_options& options);

+  uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const override;
+
   [[nodiscard]] std::vector<uint8_t> encode_tile(const std::shared_ptr<const HeifPixelImage>& image) const override;
+
+private:
+  struct channel_component
+  {
+    heif_channel channel;
+    heif_uncompressed_component_type component_type;
+  };
+
+  std::vector<channel_component> m_components;
+  uint32_t m_bytes_per_pixel_x4;
+
+  void add_channel_if_exists(const std::shared_ptr<const HeifPixelImage>& image, heif_channel channel);
 };


diff --git a/libheif/codecs/uncompressed/unc_encoder_rgb3_rgba.cc b/libheif/codecs/uncompressed/unc_encoder_rgb3_rgba.cc
index 0e681286..7227e6a7 100644
--- a/libheif/codecs/uncompressed/unc_encoder_rgb3_rgba.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_rgb3_rgba.cc
@@ -64,6 +64,8 @@ unc_encoder_rgb3_rgba::unc_encoder_rgb3_rgba(const std::shared_ptr<const HeifPix
     m_cmpd->add_component({component_type_alpha});
   }

+  m_bytes_per_pixel = save_alpha ? 4 : 3;
+
   uint8_t bpp = image->get_bits_per_pixel(heif_channel_interleaved);

   if (bpp == 8) {
@@ -91,22 +93,25 @@ unc_encoder_rgb3_rgba::unc_encoder_rgb3_rgba(const std::shared_ptr<const HeifPix
 }


-std::vector<uint8_t> unc_encoder_rgb3_rgba::encode_tile(const std::shared_ptr<const HeifPixelImage>& src_image) const
+
+uint64_t unc_encoder_rgb3_rgba::compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const
 {
-  std::vector<uint8_t> data;
+  return tile_width * tile_height * m_bytes_per_pixel;
+}

-  bool save_alpha = src_image->has_alpha();

-  int bytes_per_pixel = save_alpha ? 4 : 3;
+std::vector<uint8_t> unc_encoder_rgb3_rgba::encode_tile(const std::shared_ptr<const HeifPixelImage>& src_image) const
+{
+  std::vector<uint8_t> data;

   size_t src_stride;
   const uint8_t* src_data = src_image->get_plane(heif_channel_interleaved, &src_stride);

-  uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_image->get_width() * bytes_per_pixel;
+  uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_image->get_width() * m_bytes_per_pixel;
   data.resize(out_size);

   for (uint32_t y = 0; y < src_image->get_height(); y++) {
-    memcpy(data.data() + y * src_image->get_width() * bytes_per_pixel, src_data + src_stride * y, src_image->get_width() * bytes_per_pixel);
+    memcpy(data.data() + y * src_image->get_width() * m_bytes_per_pixel, src_data + src_stride * y, src_image->get_width() * m_bytes_per_pixel);
   }

   return data;
diff --git a/libheif/codecs/uncompressed/unc_encoder_rgb3_rgba.h b/libheif/codecs/uncompressed/unc_encoder_rgb3_rgba.h
index 2c196287..e64fdb7b 100644
--- a/libheif/codecs/uncompressed/unc_encoder_rgb3_rgba.h
+++ b/libheif/codecs/uncompressed/unc_encoder_rgb3_rgba.h
@@ -29,7 +29,12 @@ public:
   unc_encoder_rgb3_rgba(const std::shared_ptr<const HeifPixelImage>& image,
                         const heif_encoding_options& options);

+  uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const override;
+
   [[nodiscard]] std::vector<uint8_t> encode_tile(const std::shared_ptr<const HeifPixelImage>& image) const override;
+
+private:
+  uint8_t m_bytes_per_pixel = 0;
 };


diff --git a/libheif/codecs/uncompressed/unc_encoder_rgb_hdr_packed_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_rgb_hdr_packed_interleave.cc
index 993bd7eb..abd16c7e 100644
--- a/libheif/codecs/uncompressed/unc_encoder_rgb_hdr_packed_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_rgb_hdr_packed_interleave.cc
@@ -60,10 +60,10 @@ unc_encoder_rgb_hdr_packed_interleave::unc_encoder_rgb_hdr_packed_interleave(con
   uint8_t bpp = image->get_bits_per_pixel(heif_channel_interleaved);

   uint8_t nBits = static_cast<uint8_t>(3 * bpp);
-  uint8_t bytes_per_pixel = static_cast<uint8_t>((nBits + 7) / 8);
+  m_bytes_per_pixel = static_cast<uint8_t>((nBits + 7) / 8);

   m_uncC->set_interleave_type(interleave_mode_pixel);
-  m_uncC->set_pixel_size(bytes_per_pixel);
+  m_uncC->set_pixel_size(m_bytes_per_pixel);
   m_uncC->set_sampling_type(0);
   m_uncC->set_components_little_endian(true);

@@ -73,20 +73,23 @@ unc_encoder_rgb_hdr_packed_interleave::unc_encoder_rgb_hdr_packed_interleave(con
 }


+uint64_t unc_encoder_rgb_hdr_packed_interleave::compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const
+{
+  return tile_width * tile_height * m_bytes_per_pixel;
+}
+
+
 std::vector<uint8_t> unc_encoder_rgb_hdr_packed_interleave::encode_tile(const std::shared_ptr<const HeifPixelImage>& src_image) const
 {
   std::vector<uint8_t> data;

   uint8_t bpp = src_image->get_bits_per_pixel(heif_channel_interleaved);

-  uint8_t nBits = static_cast<uint8_t>(3 * bpp);
-  uint8_t bytes_per_pixel = static_cast<uint8_t>((nBits + 7) / 8);
-
   size_t src_stride;
   const auto* src_data = reinterpret_cast<const uint16_t*>(src_image->get_plane(heif_channel_interleaved, &src_stride));
   src_stride /= 2;

-  uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_image->get_width() * bytes_per_pixel;
+  uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_image->get_width() * m_bytes_per_pixel;
   data.resize(out_size);

   uint8_t* p = data.data();
@@ -104,7 +107,7 @@ std::vector<uint8_t> unc_encoder_rgb_hdr_packed_interleave::encode_tile(const st
       *p++ = static_cast<uint8_t>((combined_pixel >> 16) & 0xFF);
       *p++ = static_cast<uint8_t>((combined_pixel >> 24) & 0xFF);

-      if (bytes_per_pixel > 4) {
+      if (m_bytes_per_pixel > 4) {
         *p++ = static_cast<uint8_t>((combined_pixel >> 32) & 0xFF);
       }
     }
diff --git a/libheif/codecs/uncompressed/unc_encoder_rgb_hdr_packed_interleave.h b/libheif/codecs/uncompressed/unc_encoder_rgb_hdr_packed_interleave.h
index 3dd69646..e3948761 100644
--- a/libheif/codecs/uncompressed/unc_encoder_rgb_hdr_packed_interleave.h
+++ b/libheif/codecs/uncompressed/unc_encoder_rgb_hdr_packed_interleave.h
@@ -24,7 +24,12 @@ public:
   unc_encoder_rgb_hdr_packed_interleave(const std::shared_ptr<const HeifPixelImage>& image,
                                         const heif_encoding_options& options);

+  uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const override;
+
   [[nodiscard]] std::vector<uint8_t> encode_tile(const std::shared_ptr<const HeifPixelImage>& image) const override;
+
+private:
+  uint8_t m_bytes_per_pixel = 0;
 };


diff --git a/libheif/codecs/uncompressed/unc_encoder_rrggbb.cc b/libheif/codecs/uncompressed/unc_encoder_rrggbb.cc
index 26d3e1aa..e5809d8d 100644
--- a/libheif/codecs/uncompressed/unc_encoder_rrggbb.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_rrggbb.cc
@@ -68,6 +68,8 @@ unc_encoder_rrggbb::unc_encoder_rrggbb(const std::shared_ptr<const HeifPixelImag
     m_cmpd->add_component({component_type_alpha});
   }

+  m_bytes_per_pixel = save_alpha ? 8 : 6;
+
   bool little_endian = (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE ||
                         image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE);

@@ -91,22 +93,24 @@ unc_encoder_rrggbb::unc_encoder_rrggbb(const std::shared_ptr<const HeifPixelImag
 }


-std::vector<uint8_t> unc_encoder_rrggbb::encode_tile(const std::shared_ptr<const HeifPixelImage>& src_image) const
+uint64_t unc_encoder_rrggbb::compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const
 {
-  std::vector<uint8_t> data;
+  return tile_width * tile_height * m_bytes_per_pixel;
+}

-  bool save_alpha = src_image->has_alpha();

-  int bytes_per_pixel = save_alpha ? 8 : 6;
+std::vector<uint8_t> unc_encoder_rrggbb::encode_tile(const std::shared_ptr<const HeifPixelImage>& src_image) const
+{
+  std::vector<uint8_t> data;

   size_t src_stride;
   const uint8_t* src_data = src_image->get_plane(heif_channel_interleaved, &src_stride);

-  uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_image->get_width() * bytes_per_pixel;
+  uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_image->get_width() * m_bytes_per_pixel;
   data.resize(out_size);

   for (uint32_t y = 0; y < src_image->get_height(); y++) {
-    memcpy(data.data() + y * src_image->get_width() * bytes_per_pixel, src_data + src_stride * y, src_image->get_width() * bytes_per_pixel);
+    memcpy(data.data() + y * src_image->get_width() * m_bytes_per_pixel, src_data + src_stride * y, src_image->get_width() * m_bytes_per_pixel);
   }

   return data;
diff --git a/libheif/codecs/uncompressed/unc_encoder_rrggbb.h b/libheif/codecs/uncompressed/unc_encoder_rrggbb.h
index 431cb19e..3a518fdf 100644
--- a/libheif/codecs/uncompressed/unc_encoder_rrggbb.h
+++ b/libheif/codecs/uncompressed/unc_encoder_rrggbb.h
@@ -29,7 +29,12 @@ public:
   unc_encoder_rrggbb(const std::shared_ptr<const HeifPixelImage>& image,
                      const heif_encoding_options& options);

+  uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const override;
+
   [[nodiscard]] std::vector<uint8_t> encode_tile(const std::shared_ptr<const HeifPixelImage>& image) const override;
+
+private:
+  uint8_t m_bytes_per_pixel = 0;
 };


diff --git a/libheif/image-items/unc_image.cc b/libheif/image-items/unc_image.cc
index f4b26b15..5151e00b 100644
--- a/libheif/image-items/unc_image.cc
+++ b/libheif/image-items/unc_image.cc
@@ -191,9 +191,8 @@ Result<std::shared_ptr<ImageItem_uncompressed>> ImageItem_uncompressed::add_unci
   // Create empty image. If we use compression, we append the data piece by piece.

   if (parameters->compression == heif_unci_compression_off) {
-    assert(false); // TODO compute_tile_data_size_bytes() is too simplistic
-    uint64_t tile_size = uncC->compute_tile_data_size_bytes(parameters->image_width / uncC->get_number_of_tile_columns(),
-                                                            parameters->image_height / uncC->get_number_of_tile_rows());
+    uint64_t tile_size = unci_image->m_unc_encoder->compute_tile_data_size_bytes(parameters->image_width / uncC->get_number_of_tile_columns(),
+                                                                                 parameters->image_height / uncC->get_number_of_tile_rows());

     std::vector<uint8_t> dummydata;
     dummydata.resize(tile_size);
@@ -249,7 +248,7 @@ Error ImageItem_uncompressed::add_image_tile(uint32_t tile_x, uint32_t tile_y, c

     // uncompressed

-    uint64_t tile_data_size = uncC->compute_tile_data_size_bytes(tile_width, tile_height);
+    uint64_t tile_data_size = m_unc_encoder->compute_tile_data_size_bytes(tile_width, tile_height);

     get_file()->replace_iloc_data(get_id(), tile_idx * tile_data_size, *codedBitstreamResult, 0);
   }