Commit 8b62c508 for libheif

commit 8b62c5088a7ad02b81682e97dfbfbcc8fbca2a0f
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Fri Feb 20 01:16:52 2026 +0100

    unci: support multi-component images in encoder_component_interleave

diff --git a/libheif/codecs/uncompressed/unc_decoder_legacybase.cc b/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
index 37fec032..b28d9f9a 100644
--- a/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
@@ -58,9 +58,11 @@ void unc_decoder_legacybase::ensureChannelList(std::shared_ptr<HeifPixelImage>&

 void unc_decoder_legacybase::buildChannelList(std::shared_ptr<HeifPixelImage>& img)
 {
+  uint32_t idx = 0;
   for (Box_uncC::Component component : m_uncC->get_components()) {
-    ChannelListEntry entry = buildChannelListEntry(component, img);
+    ChannelListEntry entry = buildChannelListEntry(idx, component, img);
     channelList.push_back(entry);
+    idx++;
   }
 }

@@ -134,12 +136,13 @@ void unc_decoder_legacybase::processComponentTileRow(ChannelListEntry& entry, Un
 }


-unc_decoder_legacybase::ChannelListEntry unc_decoder_legacybase::buildChannelListEntry(Box_uncC::Component component,
+unc_decoder_legacybase::ChannelListEntry unc_decoder_legacybase::buildChannelListEntry(uint32_t component_idx,
+                                                                                        Box_uncC::Component component,
                                                                                         std::shared_ptr<HeifPixelImage>& img)
 {
   ChannelListEntry entry;
   entry.use_channel = map_uncompressed_component_to_channel(m_cmpd, component, &(entry.channel));
-  entry.dst_plane = img->get_plane(entry.channel, &(entry.dst_plane_stride));
+  entry.dst_plane = img->get_component(component_idx, &(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
diff --git a/libheif/codecs/uncompressed/unc_decoder_legacybase.h b/libheif/codecs/uncompressed/unc_decoder_legacybase.h
index db88f064..79b93fcc 100644
--- a/libheif/codecs/uncompressed/unc_decoder_legacybase.h
+++ b/libheif/codecs/uncompressed/unc_decoder_legacybase.h
@@ -198,7 +198,7 @@ protected:

 private:
   void buildChannelList(std::shared_ptr<HeifPixelImage>& img);
-  ChannelListEntry buildChannelListEntry(Box_uncC::Component component, std::shared_ptr<HeifPixelImage>& img);
+  ChannelListEntry buildChannelListEntry(uint32_t component_idx, Box_uncC::Component component, std::shared_ptr<HeifPixelImage>& img);
 };

 #endif
diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
index 36aebf3f..ae5837bd 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
@@ -27,22 +27,17 @@


 bool unc_encoder_factory_component_interleave::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;
   }

   // Check if any component has non-byte-aligned bpp
-  for (auto channel : {heif_channel_Y, heif_channel_Cb, heif_channel_Cr,
-                        heif_channel_R, heif_channel_G, heif_channel_B,
-                        heif_channel_Alpha, heif_channel_filter_array,
-                        heif_channel_depth, heif_channel_disparity}) {
-    if (image->has_channel(channel)) {
-      int bpp = image->get_bits_per_pixel(channel);
-      if (bpp % 8 != 0) {
-        return true;
-      }
+  uint32_t n = image->get_number_of_components();
+  for (uint32_t i = 0; i < n; i++) {
+    if (image->get_component_bits_per_pixel(i) % 8 != 0) {
+      return true;
     }
   }

@@ -51,48 +46,38 @@ bool unc_encoder_factory_component_interleave::can_encode(const std::shared_ptr<


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


-void unc_encoder_component_interleave::add_channel_if_exists(const std::shared_ptr<const HeifPixelImage>& image, heif_channel channel)
+unc_encoder_component_interleave::unc_encoder_component_interleave(const std::shared_ptr<const HeifPixelImage>& image,
+                                                                   const heif_encoding_options& options)
 {
-  if (image->has_channel(channel)) {
-    uint8_t bpp = image->get_bits_per_pixel(channel);
-    m_components.push_back({channel, heif_channel_to_component_type(channel), bpp});
-  }
-}
+  bool is_nonvisual = (image->get_colorspace() == heif_colorspace_nonvisual);
+  uint32_t num_components = image->get_number_of_components();

+  for (uint32_t idx = 0; idx < num_components; idx++) {
+    heif_uncompressed_component_type comp_type;
+    heif_channel ch = heif_channel_Y; // default for nonvisual

-unc_encoder_component_interleave::unc_encoder_component_interleave(const std::shared_ptr<const HeifPixelImage>& image,
-                                                     const heif_encoding_options& options)
-{
-  // Special case for heif_channel_Y:
-  // - if this is a YCbCr image, use component_type_Y,
-  // - otherwise, use component_type_monochrome
-
-  if (image->has_channel(heif_channel_Y)) {
-    uint8_t bpp = image->get_bits_per_pixel(heif_channel_Y);
-    if (image->has_channel(heif_channel_Cb) && image->has_channel(heif_channel_Cr)) {
-      m_components.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_Y, bpp});
+    if (is_nonvisual) {
+      comp_type = static_cast<heif_uncompressed_component_type>(image->get_component_type(idx));
     }
     else {
-      m_components.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_monochrome, bpp});
+      ch = image->get_component_channel(idx);
+      if (ch == heif_channel_Y && !image->has_channel(heif_channel_Cb)) {
+        comp_type = component_type_monochrome;
+      }
+      else {
+        comp_type = heif_channel_to_component_type(ch);
+      }
     }
-  }
-
-  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);

+    uint8_t bpp = image->get_component_bits_per_pixel(idx);
+    m_components.push_back({idx, ch, comp_type, bpp});
+  }

   uint16_t index = 0;
   for (const auto& comp : m_components) {
@@ -149,12 +134,12 @@ std::vector<uint8_t> unc_encoder_component_interleave::encode_tile(const std::sh
   data.reserve(total_size);

   for (const auto& comp : m_components) {
-    uint32_t plane_width = src_image->get_width(comp.channel);
-    uint32_t plane_height = src_image->get_height(comp.channel);
+    uint32_t plane_width = src_image->get_component_width(comp.component_idx);
+    uint32_t plane_height = src_image->get_component_height(comp.component_idx);
     uint8_t bpp = comp.bpp;

     size_t src_stride;
-    const uint8_t* src_data = src_image->get_plane(comp.channel, &src_stride);
+    const uint8_t* src_data = src_image->get_component(comp.component_idx, &src_stride);

     for (uint32_t y = 0; y < plane_height; y++) {
       const uint8_t* row = src_data + src_stride * y;
diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.h b/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
index 8ee45e1b..6e837090 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
@@ -40,14 +40,13 @@ public:
 private:
   struct channel_component
   {
+    uint32_t component_idx;
     heif_channel channel;
     heif_uncompressed_component_type component_type;
     uint8_t bpp;
   };

   std::vector<channel_component> m_components;
-
-  void add_channel_if_exists(const std::shared_ptr<const HeifPixelImage>& image, heif_channel channel);
 };


diff --git a/tests/uncompressed_encode_multicomponent.cc b/tests/uncompressed_encode_multicomponent.cc
index 02c3c95c..96bb8105 100644
--- a/tests/uncompressed_encode_multicomponent.cc
+++ b/tests/uncompressed_encode_multicomponent.cc
@@ -388,6 +388,11 @@ TEST_CASE("Multi-mono uint16")
   test_multi_mono<uint16_t>(heif_channel_datatype_unsigned_integer, 16, "multi_mono_uint16.heif");
 }

+TEST_CASE("Multi-mono uint12")
+{
+  test_multi_mono<uint16_t>(heif_channel_datatype_unsigned_integer, 12, "multi_mono_uint12.heif");
+}
+
 TEST_CASE("Multi-mono uint32")
 {
   test_multi_mono<uint32_t>(heif_channel_datatype_unsigned_integer, 32, "multi_mono_uint32.heif");