Commit ec7d3f8b for libheif

commit ec7d3f8b41d1797e0d1df5a1cab079ada3b89257
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Thu Mar 12 15:15:00 2026 +0100

    unci: build cmpd in HeifPixelImage

diff --git a/libheif/codecs/uncompressed/unc_boxes.cc b/libheif/codecs/uncompressed/unc_boxes.cc
index d5381863..2bd50224 100644
--- a/libheif/codecs/uncompressed/unc_boxes.cc
+++ b/libheif/codecs/uncompressed/unc_boxes.cc
@@ -130,6 +130,16 @@ template <typename T> const char* get_name(T val, const std::map<T, const char*>
   }
 }

+void Box_cmpd::set_components(const std::vector<uint16_t>& components)
+{
+  m_components.clear();
+
+  for (const auto& component : components) {
+    m_components.push_back({component, {}});
+  }
+}
+
+
 Error Box_cmpd::parse(BitstreamRange& range, const heif_security_limits* limits)
 {
   uint32_t component_count = range.read32();
diff --git a/libheif/codecs/uncompressed/unc_boxes.h b/libheif/codecs/uncompressed/unc_boxes.h
index b2c54183..4ea310f2 100644
--- a/libheif/codecs/uncompressed/unc_boxes.h
+++ b/libheif/codecs/uncompressed/unc_boxes.h
@@ -73,6 +73,8 @@ public:
     return index;
   }

+  void set_components(const std::vector<uint16_t>&);
+
 protected:
   Error parse(BitstreamRange& range, const heif_security_limits* limits) override;

diff --git a/libheif/codecs/uncompressed/unc_encoder.cc b/libheif/codecs/uncompressed/unc_encoder.cc
index feb1bb7c..c07fcc41 100644
--- a/libheif/codecs/uncompressed/unc_encoder.cc
+++ b/libheif/codecs/uncompressed/unc_encoder.cc
@@ -83,6 +83,8 @@ unc_encoder::unc_encoder(const std::shared_ptr<const HeifPixelImage>& image)
   m_cmpd = std::make_shared<Box_cmpd>();
   m_uncC = std::make_shared<Box_uncC>();

+  m_cmpd->set_components(image->get_cmpd_component_types());
+
   // --- Bayer pattern: add reference components to cmpd and generate cpat box

   if (image->has_bayer_pattern()) {
diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
index 620134b6..d5e31cc2 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
@@ -72,14 +72,16 @@ unc_encoder_component_interleave::unc_encoder_component_interleave(const std::sh
     auto comp_format = to_unc_component_format(image->get_component_datatype(idx));
     bool aligned = (bpp % 8 == 0);

-    m_components.push_back({idx, ch, comp_type, comp_format, bpp, aligned});
+    uint16_t cmpd_idx = image->get_component_cmpd_index();
+
+    m_components.push_back({cmpd_idx, ch, comp_type, comp_format, bpp, aligned});
   }

   // Build cmpd/uncC boxes
   bool little_endian = false;

   for (const auto& comp : m_components) {
-    uint16_t cmpd_index = m_cmpd->add_component({static_cast<uint16_t>(comp.component_type)});
+    //uint16_t cmpd_index = m_cmpd->add_component({static_cast<uint16_t>(comp.component_type)});

     uint8_t component_align_size = 0;

@@ -87,7 +89,7 @@ unc_encoder_component_interleave::unc_encoder_component_interleave(const std::sh
       little_endian = true;
     }

-    m_uncC->add_component({cmpd_index, comp.bpp, comp.component_format, component_align_size});
+    m_uncC->add_component({comp.component_idx, comp.bpp, comp.component_format, component_align_size});
   }

   m_uncC->set_interleave_type(interleave_mode_component);
diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.h b/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
index c5322292..98ae382d 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
@@ -40,7 +40,7 @@ public:
 private:
   struct channel_component
   {
-    uint32_t component_idx;
+    uint16_t component_idx;
     heif_channel channel;
     heif_uncompressed_component_type component_type;
     heif_uncompressed_component_format component_format;
diff --git a/libheif/codecs/uncompressed/unc_encoder_rgb_block_pixel_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_rgb_block_pixel_interleave.cc
index 8b21f55d..80b18c89 100644
--- a/libheif/codecs/uncompressed/unc_encoder_rgb_block_pixel_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_rgb_block_pixel_interleave.cc
@@ -59,9 +59,8 @@ unc_encoder_rgb_block_pixel_interleave::unc_encoder_rgb_block_pixel_interleave(c
                                                                              const heif_encoding_options& options)
     : unc_encoder(image)
 {
-  uint16_t idx_r = m_cmpd->add_component({heif_uncompressed_component_type_red});
-  uint16_t idx_g = m_cmpd->add_component({heif_uncompressed_component_type_green});
-  uint16_t idx_b = m_cmpd->add_component({heif_uncompressed_component_type_blue});
+  auto cmpd_idx = image->get_component_cmpd_indices_interleaved();
+  assert(cmpd_idx.size() == 3);

   uint8_t bpp = image->get_bits_per_pixel(heif_channel_interleaved);

@@ -74,9 +73,9 @@ unc_encoder_rgb_block_pixel_interleave::unc_encoder_rgb_block_pixel_interleave(c
   m_uncC->set_sampling_type(sampling_mode_no_subsampling);
   m_uncC->set_block_little_endian(true);

-  m_uncC->add_component({idx_r, bpp, component_format_unsigned, 0});
-  m_uncC->add_component({idx_g, bpp, component_format_unsigned, 0});
-  m_uncC->add_component({idx_b, bpp, component_format_unsigned, 0});
+  m_uncC->add_component({cmpd_idx[0], bpp, component_format_unsigned, 0});
+  m_uncC->add_component({cmpd_idx[1], bpp, component_format_unsigned, 0});
+  m_uncC->add_component({cmpd_idx[2], bpp, component_format_unsigned, 0});
 }


diff --git a/libheif/codecs/uncompressed/unc_encoder_rgb_bytealign_pixel_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_rgb_bytealign_pixel_interleave.cc
index dc183cf4..1797dc15 100644
--- a/libheif/codecs/uncompressed/unc_encoder_rgb_bytealign_pixel_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_rgb_bytealign_pixel_interleave.cc
@@ -59,18 +59,12 @@ unc_encoder_rgb_bytealign_pixel_interleave::unc_encoder_rgb_bytealign_pixel_inte
                                        const heif_encoding_options& options)
     : unc_encoder(image)
 {
-  uint16_t idx_r = m_cmpd->add_component({heif_uncompressed_component_type_red});
-  uint16_t idx_g = m_cmpd->add_component({heif_uncompressed_component_type_green});
-  uint16_t idx_b = m_cmpd->add_component({heif_uncompressed_component_type_blue});
+  auto cmpd_idx = image->get_component_cmpd_indices_interleaved();

   bool save_alpha = image->has_alpha();
-  uint16_t idx_a = 0;
-
-  if (save_alpha) {
-    idx_a = m_cmpd->add_component({heif_uncompressed_component_type_alpha});
-  }

   m_bytes_per_pixel = save_alpha ? 8 : 6;
+  assert(cmpd_idx.size() == m_bytes_per_pixel);

   bool little_endian = (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE ||
                         image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE);
@@ -90,11 +84,11 @@ unc_encoder_rgb_bytealign_pixel_interleave::unc_encoder_rgb_bytealign_pixel_inte
   m_uncC->set_components_little_endian(false); // little_endian);
   m_uncC->set_pixel_size(m_bytes_per_pixel);

-  m_uncC->add_component({idx_r, bpp, component_format_unsigned, component_align_size});
-  m_uncC->add_component({idx_g, bpp, component_format_unsigned, component_align_size});
-  m_uncC->add_component({idx_b, bpp, component_format_unsigned, component_align_size});
+  m_uncC->add_component({cmpd_idx[0], bpp, component_format_unsigned, component_align_size});
+  m_uncC->add_component({cmpd_idx[1], bpp, component_format_unsigned, component_align_size});
+  m_uncC->add_component({cmpd_idx[2], bpp, component_format_unsigned, component_align_size});
   if (save_alpha) {
-    m_uncC->add_component({idx_a, bpp, component_format_unsigned, component_align_size});
+    m_uncC->add_component({cmpd_idx[3], bpp, component_format_unsigned, component_align_size});
   }
 }

diff --git a/libheif/codecs/uncompressed/unc_encoder_rgb_pixel_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_rgb_pixel_interleave.cc
index 871d314d..bfac3a46 100644
--- a/libheif/codecs/uncompressed/unc_encoder_rgb_pixel_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_rgb_pixel_interleave.cc
@@ -55,18 +55,11 @@ unc_encoder_rgb_pixel_interleave::unc_encoder_rgb_pixel_interleave(const std::sh
                                              const heif_encoding_options& options)
     : unc_encoder(image)
 {
-  uint16_t idx_r = m_cmpd->add_component({heif_uncompressed_component_type_red});
-  uint16_t idx_g = m_cmpd->add_component({heif_uncompressed_component_type_green});
-  uint16_t idx_b = m_cmpd->add_component({heif_uncompressed_component_type_blue});
-
+  auto cmpd_idx = image->get_component_cmpd_indices_interleaved();
   bool save_alpha = image->has_alpha();
-  uint16_t idx_a = 0;
-
-  if (save_alpha) {
-    idx_a = m_cmpd->add_component({heif_uncompressed_component_type_alpha});
-  }

   m_bytes_per_pixel = save_alpha ? 4 : 3;
+  assert(cmpd_idx.size() == m_bytes_per_pixel);

   uint8_t bpp = image->get_bits_per_pixel(heif_channel_interleaved);

@@ -86,11 +79,11 @@ unc_encoder_rgb_pixel_interleave::unc_encoder_rgb_pixel_interleave(const std::sh

   m_uncC->set_interleave_type(interleave_mode_pixel);
   m_uncC->set_sampling_type(sampling_mode_no_subsampling);
-  m_uncC->add_component({idx_r, bpp, component_format_unsigned, component_align_size});
-  m_uncC->add_component({idx_g, bpp, component_format_unsigned, component_align_size});
-  m_uncC->add_component({idx_b, bpp, component_format_unsigned, component_align_size});
+  m_uncC->add_component({cmpd_idx[0], bpp, component_format_unsigned, component_align_size});
+  m_uncC->add_component({cmpd_idx[1], bpp, component_format_unsigned, component_align_size});
+  m_uncC->add_component({cmpd_idx[2], bpp, component_format_unsigned, component_align_size});
   if (save_alpha) {
-    m_uncC->add_component({idx_a, bpp, component_format_unsigned, component_align_size});
+    m_uncC->add_component({cmpd_idx[3], bpp, component_format_unsigned, component_align_size});
   }
 }

diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc
index b8cdc479..fdecd125 100644
--- a/libheif/pixelimage.cc
+++ b/libheif/pixelimage.cc
@@ -129,33 +129,57 @@ heif_channel map_uncompressed_component_to_channel(uint16_t component_type)
 }


-static uint16_t map_channel_to_component_type(heif_channel channel)
+static std::vector<uint16_t> map_channel_to_component_type(heif_channel channel, heif_chroma chroma)
 {
   switch (channel) {
     case heif_channel_Y:
-      return heif_uncompressed_component_type_Y;
+      return {heif_uncompressed_component_type_Y};
     case heif_channel_Cb:
-      return heif_uncompressed_component_type_Cb;
+      return {heif_uncompressed_component_type_Cb};
     case heif_channel_Cr:
-      return heif_uncompressed_component_type_Cr;
+      return {heif_uncompressed_component_type_Cr};
     case heif_channel_R:
-      return heif_uncompressed_component_type_red;
+      return {heif_uncompressed_component_type_red};
     case heif_channel_G:
-      return heif_uncompressed_component_type_green;
+      return {heif_uncompressed_component_type_green};
     case heif_channel_B:
-      return heif_uncompressed_component_type_blue;
+      return {heif_uncompressed_component_type_blue};
     case heif_channel_Alpha:
-      return heif_uncompressed_component_type_alpha;
+      return {heif_uncompressed_component_type_alpha};
     case heif_channel_filter_array:
-      return heif_uncompressed_component_type_filter_array;
+      return {heif_uncompressed_component_type_filter_array};
     case heif_channel_depth:
-      return heif_uncompressed_component_type_depth;
+      return {heif_uncompressed_component_type_depth};
     case heif_channel_disparity:
-      return heif_uncompressed_component_type_disparity;
+      return {heif_uncompressed_component_type_disparity};
+    case heif_channel_interleaved:
+      switch (chroma) {
+        case heif_chroma_interleaved_RGB:
+        case heif_chroma_interleaved_RRGGBB_BE:
+        case heif_chroma_interleaved_RRGGBB_LE:
+          return {
+            heif_uncompressed_component_type_red,
+            heif_uncompressed_component_type_green,
+            heif_uncompressed_component_type_blue
+          };
+        case heif_chroma_interleaved_RGBA:
+        case heif_chroma_interleaved_RRGGBBAA_BE:
+        case heif_chroma_interleaved_RRGGBBAA_LE:
+          return {
+            heif_uncompressed_component_type_red,
+            heif_uncompressed_component_type_green,
+            heif_uncompressed_component_type_blue,
+            heif_uncompressed_component_type_alpha
+          };
+        default:
+          assert(false);
+          return {static_cast<uint16_t>(1000 + channel)};
+          break;
+      }
     default:
-      // For interleaved and other channels without a direct match,
+      // For other channels without a direct match,
       // use an internal custom value.
-      return static_cast<uint16_t>(1000 + channel);
+      return {static_cast<uint16_t>(1000 + channel)};
   }
 }

@@ -469,13 +493,32 @@ static uint32_t rounded_size(uint32_t s)
   return s;
 }

-Error HeifPixelImage::add_plane(heif_channel channel, uint32_t width, uint32_t height, int bit_depth,
-                                const heif_security_limits* limits)
+HeifPixelImage::ImageComponent HeifPixelImage::new_image_plane_for_channel(heif_channel channel)
 {
   ImageComponent plane;
+
+  // libheif channel type
+
   plane.m_channel = channel;
-  m_cmpd_component_types.push_back(map_channel_to_component_type(channel));
-  plane.m_component_index = static_cast<uint32_t>(m_cmpd_component_types.size() - 1);
+
+  // ISO 23001-17 component types
+  // For interleaved planes, several component types are added to cmpd
+
+  auto cmpd = map_channel_to_component_type(channel, m_chroma);
+  for (size_t i = 0; i < cmpd.size(); i++) {
+    plane.m_component_index.push_back(static_cast<uint32_t>(m_cmpd_component_types.size()));
+    m_cmpd_component_types.push_back(cmpd[i]);
+  }
+
+  return plane;
+}
+
+
+Error HeifPixelImage::add_plane(heif_channel channel, uint32_t width, uint32_t height, int bit_depth,
+                                const heif_security_limits* limits)
+{
+  ImageComponent plane = new_image_plane_for_channel(channel);
+
   int num_interleaved_pixels = num_interleaved_components_per_plane(m_chroma);

   // for backwards compatibility, allow for 24/32 bits for RGB/RGBA interleaved chromas
@@ -501,10 +544,8 @@ Error HeifPixelImage::add_plane(heif_channel channel, uint32_t width, uint32_t h
 Error HeifPixelImage::add_channel(heif_channel channel, uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth,
                                   const heif_security_limits* limits)
 {
-  ImageComponent plane;
-  plane.m_channel = channel;
-  m_cmpd_component_types.push_back(map_channel_to_component_type(channel));
-  plane.m_component_index = static_cast<uint32_t>(m_cmpd_component_types.size() - 1);
+  ImageComponent plane = new_image_plane_for_channel(channel);
+
   if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
     return err;
   }
@@ -831,7 +872,7 @@ uint32_t HeifPixelImage::get_primary_component() const
       case heif_channel_G:
       case heif_channel_B:
       case heif_channel_filter_array:
-        return m_planes[idx].m_component_index;
+        return m_planes[idx].m_component_index[0];
       default:
         ; // NOP
     }
@@ -841,7 +882,7 @@ uint32_t HeifPixelImage::get_primary_component() const

   if (!m_cmpd_component_types.empty()) {
     for (uint32_t idx=0; idx<m_planes.size(); idx++) {
-      uint16_t comp_type = get_component_type(m_planes[idx].m_component_index);
+      uint16_t comp_type = get_component_type(m_planes[idx].m_component_index[0]);
       switch (comp_type) {
         case heif_uncompressed_component_type_Y:
         case heif_uncompressed_component_type_monochrome:
@@ -854,7 +895,7 @@ uint32_t HeifPixelImage::get_primary_component() const
         case heif_uncompressed_component_type_key_black:
         case heif_uncompressed_component_type_filter_array:
         case heif_uncompressed_component_type_palette:
-          return m_planes[idx].m_component_index;
+          return m_planes[idx].m_component_index[0];

         default:
           ; // NOP
@@ -865,7 +906,7 @@ uint32_t HeifPixelImage::get_primary_component() const
   // third pass: allow anything

   if (!m_planes.empty()) {
-    return m_planes[0].m_component_index;
+    return m_planes[0].m_component_index[0];
   }
   return 0;
 }
@@ -2110,7 +2151,10 @@ HeifPixelImage::extract_image_area(uint32_t x0, uint32_t y0, uint32_t w, uint32_
 HeifPixelImage::ImageComponent* HeifPixelImage::find_component_by_index(uint32_t component_index)
 {
   for (auto& plane : m_planes) {
-    if (plane.m_component_index == component_index) {
+    // we search through all indices in case we have an interleaved plane
+    if (std::find(plane.m_component_index.begin(),
+                  plane.m_component_index.end(),
+                  component_index) != plane.m_component_index.end()) {
       return &plane;
     }
   }
@@ -2183,6 +2227,14 @@ uint16_t HeifPixelImage::get_component_type(uint32_t component_idx) const
 }


+std::vector<uint16_t> HeifPixelImage::get_component_cmpd_indices_interleaved() const
+{
+  const ImageComponent* comp = find_component_for_channel(heif_channel_interleaved);
+  assert(comp);
+  return comp->m_component_index;
+}
+
+
 Result<uint32_t> HeifPixelImage::add_component(uint32_t width, uint32_t height,
                                                uint16_t component_type,
                                                heif_channel_datatype datatype, int bit_depth,
@@ -2190,11 +2242,11 @@ Result<uint32_t> HeifPixelImage::add_component(uint32_t width, uint32_t height,
 {
   // Auto-generate component_index by appending to cmpd table
   m_cmpd_component_types.push_back(component_type);
-  uint32_t component_index = static_cast<uint32_t>(m_cmpd_component_types.size() - 1);
+  uint16_t component_index = static_cast<uint16_t>(m_cmpd_component_types.size() - 1);

   ImageComponent plane;
   plane.m_channel = map_uncompressed_component_to_channel(component_type);
-  plane.m_component_index = component_index;
+  plane.m_component_index = std::vector{component_index};
   if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
     return err;
   }
@@ -2218,7 +2270,7 @@ Result<uint32_t> HeifPixelImage::add_component_for_index(uint32_t component_inde

   ImageComponent plane;
   plane.m_channel = map_uncompressed_component_to_channel(component_type);
-  plane.m_component_index = component_index;
+  plane.m_component_index = std::vector{static_cast<uint16_t>(component_index)};
   if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
     return err;
   }
@@ -2233,7 +2285,7 @@ std::vector<uint32_t> HeifPixelImage::get_used_component_indices() const
   std::vector<uint32_t> indices;
   indices.reserve(m_planes.size());
   for (const auto& plane : m_planes) {
-    indices.push_back(plane.m_component_index);
+    indices.insert(indices.end(), plane.m_component_index.begin(), plane.m_component_index.end());
   }
   return indices;
 }
diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h
index c01f18c5..002b7b31 100644
--- a/libheif/pixelimage.h
+++ b/libheif/pixelimage.h
@@ -441,6 +441,12 @@ public:
   // even those that have no image plane (e.g. bayer reference components).
   uint16_t get_component_type(uint32_t component_idx) const;

+  std::vector<uint16_t> get_cmpd_component_types() const { return m_cmpd_component_types; }
+
+  std::vector<uint16_t> get_component_cmpd_indices_interleaved() const;
+
+  uint16_t get_component_cmpd_index() const { assert(m_cmpd_component_types.size()==1); return m_cmpd_component_types[0]; }
+
   // Encoder path: auto-generates component_index by appending to cmpd table.
   Result<uint32_t> add_component(uint32_t width, uint32_t height,
                                  uint16_t component_type,
@@ -456,6 +462,8 @@ public:
   // Populate the cmpd component types table (decoder path).
   void set_cmpd_component_types(std::vector<uint16_t> types) { m_cmpd_component_types = std::move(types); }

+  const std::vector<uint16_t>& get_cmpd_component_types() { return m_cmpd_component_types; }
+
   // Returns the sorted list of component_indices of all planes that have pixel data.
   std::vector<uint32_t> get_used_component_indices() const;

@@ -545,7 +553,10 @@ private:
   struct ImageComponent
   {
     heif_channel m_channel = heif_channel_Y;
-    uint32_t m_component_index = 0;  // index into the cmpd component definition table
+
+    // index into the cmpd component definition table
+    // Interleaved channels will have a list of indices in the order R,G,B,A
+    std::vector<uint16_t> m_component_index;

     // limits=nullptr disables the limits
     Error alloc(uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth,
@@ -590,6 +601,8 @@ private:
   ImageComponent* find_component_by_index(uint32_t component_index);
   const ImageComponent* find_component_by_index(uint32_t component_index) const;

+  ImageComponent new_image_plane_for_channel(heif_channel channel);
+
   uint32_t m_width = 0;
   uint32_t m_height = 0;
   heif_colorspace m_colorspace = heif_colorspace_undefined;