Commit c1bc58a6 for libheif

commit c1bc58a6d0ea0e96dd679341eaa50f8d2a8c455c
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Fri Feb 27 12:34:58 2026 +0100

    unci: support images where not all cmpd components are covered by uncC

diff --git a/examples/heif_gen_bayer.cc b/examples/heif_gen_bayer.cc
index a9841908..c59f1b81 100644
--- a/examples/heif_gen_bayer.cc
+++ b/examples/heif_gen_bayer.cc
@@ -50,7 +50,7 @@ struct PatternDefinition
   std::string name;
   uint16_t width;
   uint16_t height;
-  std::vector<heif_bayer_pattern_pixel> cpat;
+  std::vector<heif_uncompressed_component_type> cpat;
 };


@@ -61,10 +61,10 @@ static const PatternDefinition patterns[] = {
   {
     "rggb", 2, 2,
     {
-      {heif_uncompressed_component_type_red,   1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_blue,  1.0f},
+      heif_uncompressed_component_type_red,
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_blue,
     }
   },

@@ -77,25 +77,25 @@ static const PatternDefinition patterns[] = {
   {
     "rgbw", 4, 4,
     {
-      {heif_uncompressed_component_type_Y,     1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_Y,     1.0f},
-      {heif_uncompressed_component_type_red,   1.0f},
-
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_Y,     1.0f},
-      {heif_uncompressed_component_type_blue,  1.0f},
-      {heif_uncompressed_component_type_Y,     1.0f},
-
-      {heif_uncompressed_component_type_Y,     1.0f},
-      {heif_uncompressed_component_type_blue,  1.0f},
-      {heif_uncompressed_component_type_Y,     1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-
-      {heif_uncompressed_component_type_red,   1.0f},
-      {heif_uncompressed_component_type_Y,     1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_Y,     1.0f},
+      heif_uncompressed_component_type_Y,
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_Y,
+      heif_uncompressed_component_type_red,
+
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_Y,
+      heif_uncompressed_component_type_blue,
+      heif_uncompressed_component_type_Y,
+
+      heif_uncompressed_component_type_Y,
+      heif_uncompressed_component_type_blue,
+      heif_uncompressed_component_type_Y,
+      heif_uncompressed_component_type_green,
+
+      heif_uncompressed_component_type_red,
+      heif_uncompressed_component_type_Y,
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_Y,
     }
   },

@@ -107,25 +107,25 @@ static const PatternDefinition patterns[] = {
   {
     "qbc", 4, 4,
     {
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_red,   1.0f},
-      {heif_uncompressed_component_type_red,   1.0f},
-
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_red,   1.0f},
-      {heif_uncompressed_component_type_red,   1.0f},
-
-      {heif_uncompressed_component_type_blue,  1.0f},
-      {heif_uncompressed_component_type_blue,  1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-
-      {heif_uncompressed_component_type_blue,  1.0f},
-      {heif_uncompressed_component_type_blue,  1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
-      {heif_uncompressed_component_type_green, 1.0f},
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_red,
+      heif_uncompressed_component_type_red,
+
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_red,
+      heif_uncompressed_component_type_red,
+
+      heif_uncompressed_component_type_blue,
+      heif_uncompressed_component_type_blue,
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_green,
+
+      heif_uncompressed_component_type_blue,
+      heif_uncompressed_component_type_blue,
+      heif_uncompressed_component_type_green,
+      heif_uncompressed_component_type_green,
     }
   },
 };
@@ -153,14 +153,14 @@ static std::optional<PatternDefinition> parse_pattern_string(const char* str)
   }

   uint16_t dim = (len == 4) ? 2 : 4;
-  std::vector<heif_bayer_pattern_pixel> cpat;
+  std::vector<heif_uncompressed_component_type> cpat;
   cpat.reserve(len);

   for (char c : s) {
     switch (std::tolower(c)) {
-      case 'r': cpat.push_back({heif_uncompressed_component_type_red, 1.0f}); break;
-      case 'g': cpat.push_back({heif_uncompressed_component_type_green, 1.0f}); break;
-      case 'b': cpat.push_back({heif_uncompressed_component_type_blue, 1.0f}); break;
+      case 'r': cpat.push_back(heif_uncompressed_component_type_red); break;
+      case 'g': cpat.push_back(heif_uncompressed_component_type_green); break;
+      case 'b': cpat.push_back(heif_uncompressed_component_type_blue); break;
       default: return {};
     }
   }
@@ -332,7 +332,7 @@ static heif_image* create_bayer_image_from_png(const char* png_filename,

         int px = x % pat->width;
         int py = y % pat->height;
-        uint16_t comp_type = pat->cpat[py * pat->width + px].component_type;
+        auto comp_type = pat->cpat[py * pat->width + px];

         switch (comp_type) {
           case heif_uncompressed_component_type_red:   dst_row[x] = r; break;
@@ -363,7 +363,7 @@ static heif_image* create_bayer_image_from_png(const char* png_filename,

         int px = x % pat->width;
         int py = y % pat->height;
-        uint16_t comp_type = pat->cpat[py * pat->width + px].component_type;
+        auto comp_type = pat->cpat[py * pat->width + px];

         switch (comp_type) {
           case heif_uncompressed_component_type_red:   dst_row[x] = r; break;
@@ -377,10 +377,19 @@ static heif_image* create_bayer_image_from_png(const char* png_filename,
     }
   }

+  // Build heif_bayer_pattern_pixel array from component types.
+  // The component_index values here are the component types themselves — the encoder
+  // will resolve them to proper cmpd indices when writing the cpat box.
+  std::vector<heif_bayer_pattern_pixel> bayer_pixels(pat->cpat.size());
+  for (size_t i = 0; i < pat->cpat.size(); i++) {
+    bayer_pixels[i].component_index = static_cast<uint16_t>(pat->cpat[i]);
+    bayer_pixels[i].component_gain = 1.0f;
+  }
+
   // Set Bayer pattern metadata
   err = heif_image_set_bayer_pattern(bayer_img,
                                      pat->width, pat->height,
-                                     pat->cpat.data());
+                                     bayer_pixels.data());
   if (err.code != heif_error_Ok) {
     std::cerr << "Cannot set Bayer pattern: " << err.message << "\n";
     heif_image_release(bayer_img);
diff --git a/libheif/api/libheif/heif_uncompressed.h b/libheif/api/libheif/heif_uncompressed.h
index df40d051..26b020b0 100644
--- a/libheif/api/libheif/heif_uncompressed.h
+++ b/libheif/api/libheif/heif_uncompressed.h
@@ -62,16 +62,17 @@ typedef enum heif_uncompressed_component_type

 typedef struct heif_bayer_pattern_pixel
 {
-  uint16_t component_type;  // one of heif_uncompressed_component_type values
+  uint16_t component_index;  // index into the component definition (cmpd)
   float component_gain;
 } heif_bayer_pattern_pixel;

 // Set a Bayer / filter array pattern on an image.
-// The pattern is a 2D array of component types with dimensions pattern_width x pattern_height.
+// The pattern is a 2D array of component indices with dimensions pattern_width x pattern_height.
 // The number of entries in patternPixels must be pattern_width * pattern_height.
-// The component_type values correspond to the ISO 23001-17 component types
-// (e.g. heif_uncompressed_component_type_red=4, heif_uncompressed_component_type_green=5, heif_uncompressed_component_type_blue=6).
-// The encoder resolves these component types to cmpd indices when writing the cpat box.
+// The component_index values are indices into the cmpd component definition table.
+// On the encoder path, these indices are generated by heif_image_add_component() and the
+// encoder adds reference components to cmpd for pattern entries that don't have image planes.
+// On the decoder path, they come directly from the cpat box.
 LIBHEIF_API
 heif_error heif_image_set_bayer_pattern(heif_image*,
                                         uint16_t pattern_width,
diff --git a/libheif/codecs/uncompressed/unc_boxes.cc b/libheif/codecs/uncompressed/unc_boxes.cc
index 063c1038..e5c56fdc 100644
--- a/libheif/codecs/uncompressed/unc_boxes.cc
+++ b/libheif/codecs/uncompressed/unc_boxes.cc
@@ -977,7 +977,7 @@ Error Box_cpat::parse(BitstreamRange& range, const heif_security_limits* limits)

   for (size_t i = 0; i < num_pixels; i++) {
     heif_bayer_pattern_pixel pixel{};
-    pixel.component_type = static_cast<uint16_t>(range.read32());
+    pixel.component_index = static_cast<uint16_t>(range.read32());
     pixel.component_gain = range.read_float32();
     m_pattern.pixels[i] = pixel;
   }
@@ -995,7 +995,7 @@ std::string Box_cpat::dump(Indent& indent) const
   sstr << indent << "pattern_height: " << get_pattern_height() << "\n";

   for (const auto& pixel : m_pattern.pixels) {
-    sstr << indent << "component index: " << pixel.component_type << ", gain: " << pixel.component_gain << "\n";
+    sstr << indent << "component index: " << pixel.component_index << ", gain: " << pixel.component_gain << "\n";
   }
   return sstr.str();
 }
@@ -1015,7 +1015,7 @@ Error Box_cpat::write(StreamWriter& writer) const
   writer.write16(m_pattern.pattern_height);

   for (const auto& pixel : m_pattern.pixels) {
-    writer.write32(pixel.component_type);
+    writer.write32(pixel.component_index);
     writer.write_float32(pixel.component_gain);
   }

diff --git a/libheif/codecs/uncompressed/unc_codec.cc b/libheif/codecs/uncompressed/unc_codec.cc
index 45818d86..66326b7f 100644
--- a/libheif/codecs/uncompressed/unc_codec.cc
+++ b/libheif/codecs/uncompressed/unc_codec.cc
@@ -204,6 +204,16 @@ Result<std::shared_ptr<HeifPixelImage>> UncompressedImageCodec::create_image(con
               colourspace,
               chroma);

+  // Populate the cmpd table on the image so add_component_for_index() can look up types.
+  {
+    std::vector<uint16_t> cmpd_types;
+    cmpd_types.reserve(components.size());
+    for (const auto& c : components) {
+      cmpd_types.push_back(c.component_type);
+    }
+    img->set_cmpd_component_types(std::move(cmpd_types));
+  }
+
   for (Box_uncC::Component component : uncC->get_components()) {
     if (component.component_index >= components.size()) {
       return Error{
@@ -217,23 +227,23 @@ Result<std::shared_ptr<HeifPixelImage>> UncompressedImageCodec::create_image(con

     if ((component_type == heif_uncompressed_component_type_Cb) ||
         (component_type == heif_uncompressed_component_type_Cr)) {
-      Result<uint32_t> result = img->add_component((width / chroma_h_subsampling(chroma)),
-                                                   (height / chroma_v_subsampling(chroma)),
-                                                   component_type,
-                                                   unc_component_format_to_datatype(component.component_format),
-                                                   component.component_bit_depth,
-                                                   limits);
+      Result<uint32_t> result = img->add_component_for_index(component.component_index,
+                                                              (width / chroma_h_subsampling(chroma)),
+                                                              (height / chroma_v_subsampling(chroma)),
+                                                              unc_component_format_to_datatype(component.component_format),
+                                                              component.component_bit_depth,
+                                                              limits);
       if (result.is_error()) {
         return result.error();
       }
     }
     else {
-      Result<uint32_t> result = img->add_component(width,
-                                                   height,
-                                                   component_type,
-                                                   unc_component_format_to_datatype(component.component_format),
-                                                   component.component_bit_depth,
-                                                   limits);
+      Result<uint32_t> result = img->add_component_for_index(component.component_index,
+                                                              width,
+                                                              height,
+                                                              unc_component_format_to_datatype(component.component_format),
+                                                              component.component_bit_depth,
+                                                              limits);
       if (result.is_error()) {
         return result.error();
       }
diff --git a/libheif/codecs/uncompressed/unc_decoder.cc b/libheif/codecs/uncompressed/unc_decoder.cc
index 34b77bd9..3aebb37e 100644
--- a/libheif/codecs/uncompressed/unc_decoder.cc
+++ b/libheif/codecs/uncompressed/unc_decoder.cc
@@ -38,6 +38,21 @@
 #include "security_limits.h"


+static Error validate_component_indices(const std::vector<uint32_t>& indices,
+                                        size_t cmpd_size,
+                                        const char* box_name)
+{
+  for (uint32_t idx : indices) {
+    if (idx >= cmpd_size) {
+      return {heif_error_Invalid_input,
+              heif_suberror_Invalid_parameter_value,
+              std::string(box_name) + " component index out of range of cmpd table"};
+    }
+  }
+  return Error::Ok;
+}
+
+
 // --- unc_decoder ---

 unc_decoder::unc_decoder(uint32_t width, uint32_t height,
@@ -454,32 +469,46 @@ Result<std::shared_ptr<HeifPixelImage> > unc_decoder::decode_full_image(

   auto img = *createImgResult;

+  size_t cmpd_size = cmpd ? cmpd->get_components().size() : 0;
+
   if (properties.cpat) {
-    // Resolve cpat component indices to actual component types from cmpd.
-    BayerPattern pattern = properties.cpat->get_pattern();
-    const auto& cmpd_components = cmpd->get_components();
-    for (auto& pixel : pattern.pixels) {
-      uint16_t idx = pixel.component_type;
-      if (idx >= cmpd_components.size()) {
-        return Error(heif_error_Invalid_input,
-                     heif_suberror_Invalid_parameter_value,
-                     "cpat component index out of range");
-      }
-      pixel.component_type = cmpd_components[idx].component_type;
+    const auto& pattern = properties.cpat->get_pattern();
+    std::vector<uint32_t> cpat_indices;
+    for (const auto& pixel : pattern.pixels) {
+      cpat_indices.push_back(pixel.component_index);
+    }
+    Error err = validate_component_indices(cpat_indices, cmpd_size, "cpat");
+    if (err) {
+      return err;
     }
     img->set_bayer_pattern(pattern);
   }

   for (const auto& splz_box : properties.splz) {
-    img->add_polarization_pattern(splz_box->get_pattern());
+    const auto& pattern = splz_box->get_pattern();
+    Error err = validate_component_indices(pattern.component_indices, cmpd_size, "splz");
+    if (err) {
+      return err;
+    }
+    img->add_polarization_pattern(pattern);
   }

   for (const auto& sbpm_box : properties.sbpm) {
-    img->add_sensor_bad_pixels_map(sbpm_box->get_bad_pixels_map());
+    const auto& bad_pixels_map = sbpm_box->get_bad_pixels_map();
+    Error err = validate_component_indices(bad_pixels_map.component_indices, cmpd_size, "sbpm");
+    if (err) {
+      return err;
+    }
+    img->add_sensor_bad_pixels_map(bad_pixels_map);
   }

   for (const auto& snuc_box : properties.snuc) {
-    img->add_sensor_nuc(snuc_box->get_nuc());
+    const auto& nuc = snuc_box->get_nuc();
+    Error err = validate_component_indices(nuc.component_indices, cmpd_size, "snuc");
+    if (err) {
+      return err;
+    }
+    img->add_sensor_nuc(nuc);
   }

   if (properties.cloc) {
diff --git a/libheif/codecs/uncompressed/unc_decoder_bytealign_component_interleave.cc b/libheif/codecs/uncompressed/unc_decoder_bytealign_component_interleave.cc
index daf5696b..1ba6e1c4 100644
--- a/libheif/codecs/uncompressed/unc_decoder_bytealign_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_bytealign_component_interleave.cc
@@ -86,7 +86,7 @@ Error unc_decoder_bytealign_component_interleave::decode_tile(const std::vector<

     comp[i].use = true; // map_uncompressed_component_to_channel(m_cmpd, c, &channel);
     if (comp[i].use) {
-      comp[i].dst_plane = img->get_component(i, &comp[i].dst_plane_stride);
+      comp[i].dst_plane = img->get_component(c.component_index, &comp[i].dst_plane_stride);
     }
     else {
       comp[i].dst_plane = nullptr;
diff --git a/libheif/codecs/uncompressed/unc_decoder_legacybase.cc b/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
index b28d9f9a..af706ae5 100644
--- a/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
+++ b/libheif/codecs/uncompressed/unc_decoder_legacybase.cc
@@ -58,11 +58,9 @@ 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(idx, component, img);
+    ChannelListEntry entry = buildChannelListEntry(component.component_index, component, img);
     channelList.push_back(entry);
-    idx++;
   }
 }

diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
index e8f9727f..a24bc808 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
@@ -112,12 +112,17 @@ unc_encoder_component_interleave::unc_encoder_component_interleave(const std::sh
   if (image->has_bayer_pattern()) {
     const BayerPattern& bayer = image->get_bayer_pattern();

+    // The bayer pattern stores component_index values. When the image has a cmpd
+    // table (add_component path), we look up the component type from it. When it
+    // doesn't (legacy add_plane path), the component_index IS the component type.
+
     // Collect unique component types from the pattern (in order of first appearance)
     std::vector<uint16_t> unique_types;
     std::set<uint16_t> seen;
     for (const auto& pixel : bayer.pixels) {
-      if (seen.insert(pixel.component_type).second) {
-        unique_types.push_back(pixel.component_type);
+      uint16_t comp_type = pixel.component_index;  // legacy: index IS the type
+      if (seen.insert(comp_type).second) {
+        unique_types.push_back(comp_type);
       }
     }

@@ -136,7 +141,8 @@ unc_encoder_component_interleave::unc_encoder_component_interleave(const std::sh
     cpat_pattern.pattern_height = bayer.pattern_height;
     cpat_pattern.pixels.resize(bayer.pixels.size());
     for (size_t i = 0; i < bayer.pixels.size(); i++) {
-      cpat_pattern.pixels[i].component_type = type_to_cmpd_index[bayer.pixels[i].component_type];
+      uint16_t comp_type = bayer.pixels[i].component_index;  // legacy: index IS the type
+      cpat_pattern.pixels[i].component_index = type_to_cmpd_index[comp_type];
       cpat_pattern.pixels[i].component_gain = bayer.pixels[i].component_gain;
     }

diff --git a/libheif/color-conversion/bayer_bilinear.cc b/libheif/color-conversion/bayer_bilinear.cc
index 205bcf85..9e0ef309 100644
--- a/libheif/color-conversion/bayer_bilinear.cc
+++ b/libheif/color-conversion/bayer_bilinear.cc
@@ -122,7 +122,8 @@ Op_bayer_bilinear_to_RGB24_32::convert_colorspace(const std::shared_ptr<const He
   // Build a lookup table: for each pattern position, which RGB channel (0=R,1=G,2=B) does it provide?
   std::vector<int> pattern_channel(pw * ph);
   for (int i = 0; i < pw * ph; i++) {
-    pattern_channel[i] = component_type_to_rgb_index(pattern.pixels[i].component_type);
+    uint16_t comp_type = input->get_component_type(pattern.pixels[i].component_index);
+    pattern_channel[i] = component_type_to_rgb_index(comp_type);
     if (pattern_channel[i] < 0) {
       return Error(heif_error_Unsupported_feature,
                    heif_suberror_Unsupported_data_version,
diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc
index 62e6d25f..e76fc271 100644
--- a/libheif/pixelimage.cc
+++ b/libheif/pixelimage.cc
@@ -125,6 +125,37 @@ heif_channel map_uncompressed_component_to_channel(uint16_t component_type)
 }


+static uint16_t map_channel_to_component_type(heif_channel channel)
+{
+  switch (channel) {
+    case heif_channel_Y:
+      return heif_uncompressed_component_type_Y;
+    case heif_channel_Cb:
+      return heif_uncompressed_component_type_Cb;
+    case heif_channel_Cr:
+      return heif_uncompressed_component_type_Cr;
+    case heif_channel_R:
+      return heif_uncompressed_component_type_red;
+    case heif_channel_G:
+      return heif_uncompressed_component_type_green;
+    case heif_channel_B:
+      return heif_uncompressed_component_type_blue;
+    case heif_channel_Alpha:
+      return heif_uncompressed_component_type_alpha;
+    case heif_channel_filter_array:
+      return heif_uncompressed_component_type_filter_array;
+    case heif_channel_depth:
+      return heif_uncompressed_component_type_depth;
+    case heif_channel_disparity:
+      return heif_uncompressed_component_type_disparity;
+    default:
+      // For interleaved and other channels without a direct match,
+      // use an internal custom value.
+      return static_cast<uint16_t>(1000 + channel);
+  }
+}
+
+

 ImageExtraData::~ImageExtraData()
 {
@@ -421,6 +452,8 @@ Error HeifPixelImage::add_plane(heif_channel channel, uint32_t width, uint32_t h
 {
   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);
   int num_interleaved_pixels = num_interleaved_components_per_plane(m_chroma);

   // for backwards compatibility, allow for 24/32 bits for RGB/RGBA interleaved chromas
@@ -448,6 +481,8 @@ Error HeifPixelImage::add_channel(heif_channel channel, uint32_t width, uint32_t
 {
   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);
   if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
     return err;
   }
@@ -742,54 +777,74 @@ uint32_t HeifPixelImage::get_height(enum heif_channel channel) const

 uint32_t HeifPixelImage::get_width(uint32_t component_idx) const
 {
-  if (component_idx >= m_planes.size()) {
+  auto* comp = find_component_by_index(component_idx);
+  if (!comp) {
     return 0;
   }

-  return m_planes[component_idx].m_width;
+  return comp->m_width;
 }


 uint32_t HeifPixelImage::get_height(uint32_t component_idx) const
 {
-  if (component_idx >= m_planes.size()) {
+  auto* comp = find_component_by_index(component_idx);
+  if (!comp) {
     return 0;
   }

-  return m_planes[component_idx].m_height;
+  return comp->m_height;
 }


 uint32_t HeifPixelImage::get_primary_component() const
 {
-  // first pass: search for color channel
+  // first pass: search for a visual channel

   for (uint32_t idx=0; idx<m_planes.size(); idx++) {
-    if (m_planes[idx].m_channel == heif_channel_interleaved) {
-      return idx;
-    }
-
-    switch (m_planes[idx].m_component_type) {
-      case heif_uncompressed_component_type_Y:
-      case heif_uncompressed_component_type_monochrome:
-      case heif_uncompressed_component_type_red:
-      case heif_uncompressed_component_type_green:
-      case heif_uncompressed_component_type_blue:
-      case heif_uncompressed_component_type_cyan:
-      case heif_uncompressed_component_type_magenta:
-      case heif_uncompressed_component_type_yellow:
-      case heif_uncompressed_component_type_key_black:
-      case heif_uncompressed_component_type_filter_array:
-      case heif_uncompressed_component_type_palette:
-        return idx;
-
+    switch (m_planes[idx].m_channel) {
+      case heif_channel_interleaved:
+      case heif_channel_Y:
+      case heif_channel_R:
+      case heif_channel_G:
+      case heif_channel_B:
+      case heif_channel_filter_array:
+        return m_planes[idx].m_component_index;
       default:
         ; // NOP
     }
   }

-  // second pass: allow anything
+  // second pass: if we have a cmpd table, use component types
+
+  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);
+      switch (comp_type) {
+        case heif_uncompressed_component_type_Y:
+        case heif_uncompressed_component_type_monochrome:
+        case heif_uncompressed_component_type_red:
+        case heif_uncompressed_component_type_green:
+        case heif_uncompressed_component_type_blue:
+        case heif_uncompressed_component_type_cyan:
+        case heif_uncompressed_component_type_magenta:
+        case heif_uncompressed_component_type_yellow:
+        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;
+
+        default:
+          ; // NOP
+      }
+    }
+  }
+
+  // third pass: allow anything

+  if (!m_planes.empty()) {
+    return m_planes[0].m_component_index;
+  }
   return 0;
 }
 #if 0
@@ -2056,38 +2111,60 @@ HeifPixelImage::extract_image_area(uint32_t x0, uint32_t y0, uint32_t w, uint32_

 // --- index-based component access methods

+HeifPixelImage::ImageComponent* HeifPixelImage::find_component_by_index(uint32_t component_index)
+{
+  for (auto& plane : m_planes) {
+    if (plane.m_component_index == component_index) {
+      return &plane;
+    }
+  }
+  return nullptr;
+}
+
+
+const HeifPixelImage::ImageComponent* HeifPixelImage::find_component_by_index(uint32_t component_index) const
+{
+  return const_cast<HeifPixelImage*>(this)->find_component_by_index(component_index);
+}
+
+
 heif_channel HeifPixelImage::get_component_channel(uint32_t component_idx) const
 {
-  assert(component_idx < m_planes.size());
-  return m_planes[component_idx].m_channel;
+  auto* comp = find_component_by_index(component_idx);
+  assert(comp);
+  return comp->m_channel;
 }


 uint32_t HeifPixelImage::get_component_width(uint32_t component_idx) const
 {
-  assert(component_idx < m_planes.size());
-  return m_planes[component_idx].m_width;
+  auto* comp = find_component_by_index(component_idx);
+  assert(comp);
+  return comp->m_width;
 }


 uint32_t HeifPixelImage::get_component_height(uint32_t component_idx) const
 {
-  assert(component_idx < m_planes.size());
-  return m_planes[component_idx].m_height;
+  auto* comp = find_component_by_index(component_idx);
+  assert(comp);
+  return comp->m_height;
 }


 uint8_t HeifPixelImage::get_component_bits_per_pixel(uint32_t component_idx) const
 {
-  assert(component_idx < m_planes.size());
-  return m_planes[component_idx].m_bit_depth;
+  auto* comp = find_component_by_index(component_idx);
+  assert(comp);
+  return comp->m_bit_depth;
 }


 uint8_t HeifPixelImage::get_component_storage_bits_per_pixel(uint32_t component_idx) const
 {
-  assert(component_idx < m_planes.size());
-  uint32_t bpp = m_planes[component_idx].get_bytes_per_pixel() * 8;
+  auto* comp = find_component_by_index(component_idx);
+  assert(comp);
+  uint32_t bpp = comp->get_bytes_per_pixel() * 8;
   assert(bpp <= 255);
   return static_cast<uint8_t>(bpp);
 }
@@ -2095,15 +2172,18 @@ uint8_t HeifPixelImage::get_component_storage_bits_per_pixel(uint32_t component_

 heif_channel_datatype HeifPixelImage::get_component_datatype(uint32_t component_idx) const
 {
-  assert(component_idx < m_planes.size());
-  return m_planes[component_idx].m_datatype;
+  auto* comp = find_component_by_index(component_idx);
+  assert(comp);
+  return comp->m_datatype;
 }


 uint16_t HeifPixelImage::get_component_type(uint32_t component_idx) const
 {
-  assert(component_idx < m_planes.size());
-  return m_planes[component_idx].m_component_type;
+  if (component_idx >= m_cmpd_component_types.size()) {
+    return 0;
+  }
+  return m_cmpd_component_types[component_idx];
 }


@@ -2112,15 +2192,54 @@ Result<uint32_t> HeifPixelImage::add_component(uint32_t width, uint32_t height,
                                                heif_channel_datatype datatype, int bit_depth,
                                                const heif_security_limits* limits)
 {
+  // 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);
+
+  ImageComponent plane;
+  plane.m_channel = map_uncompressed_component_to_channel(component_type);
+  plane.m_component_index = component_index;
+  if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
+    return err;
+  }
+
+  m_planes.push_back(plane);
+  return component_index;
+}
+
+
+Result<uint32_t> HeifPixelImage::add_component_for_index(uint32_t component_index,
+                                                          uint32_t width, uint32_t height,
+                                                          heif_channel_datatype datatype, int bit_depth,
+                                                          const heif_security_limits* limits)
+{
+  if (component_index >= m_cmpd_component_types.size()) {
+    return Error{heif_error_Usage_error, heif_suberror_Invalid_parameter_value,
+                 "component_index out of range of cmpd table"};
+  }
+
+  uint16_t component_type = m_cmpd_component_types[component_index];
+
   ImageComponent plane;
   plane.m_channel = map_uncompressed_component_to_channel(component_type);
-  plane.m_component_type = component_type;
+  plane.m_component_index = component_index;
   if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
     return err;
   }

   m_planes.push_back(plane);
-  return static_cast<uint32_t>(m_planes.size() - 1);
+  return component_index;
+}
+
+
+std::vector<uint32_t> HeifPixelImage::get_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);
+  }
+  return indices;
 }


diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h
index 471802ce..f79e4bf0 100644
--- a/libheif/pixelimage.h
+++ b/libheif/pixelimage.h
@@ -426,29 +426,44 @@ public:
   uint8_t get_component_storage_bits_per_pixel(uint32_t component_idx) const;
   heif_channel_datatype get_component_datatype(uint32_t component_idx) const;

+  // Look up the component type from the cmpd table. Works for any cmpd index,
+  // even those that have no image plane (e.g. bayer reference components).
   uint16_t get_component_type(uint32_t component_idx) const;

+  // 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,
                                  heif_channel_datatype datatype, int bit_depth,
                                  const heif_security_limits* limits);

+  // Decoder path: uses a pre-populated cmpd table to look up the component type.
+  Result<uint32_t> add_component_for_index(uint32_t component_index,
+                                            uint32_t width, uint32_t height,
+                                            heif_channel_datatype datatype, int bit_depth,
+                                            const heif_security_limits* limits);
+
+  // 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); }
+
+  // Returns the sorted list of component_indices of all planes.
+  std::vector<uint32_t> get_component_indices() const;
+
   uint8_t* get_component(uint32_t component_idx, size_t* out_stride);
   const uint8_t* get_component(uint32_t component_idx, size_t* out_stride) const;

   template <typename T>
   T* get_component_data(uint32_t component_idx, size_t* out_stride)
   {
-    if (component_idx >= m_planes.size()) {
+    auto* comp = find_component_by_index(component_idx);
+    if (!comp) {
       if (out_stride) *out_stride = 0;
       return nullptr;
     }

-    auto& comp = m_planes[component_idx];
     if (out_stride) {
-      *out_stride = comp.stride / sizeof(T);
+      *out_stride = comp->stride / sizeof(T);
     }
-    return static_cast<T*>(comp.mem);
+    return static_cast<T*>(comp->mem);
   }

   template <typename T>
@@ -519,7 +534,7 @@ private:
   struct ImageComponent
   {
     heif_channel m_channel = heif_channel_Y;
-    uint16_t m_component_type = 0;  // ISO 23001-17 component type (0 = monochrome)
+    uint32_t m_component_index = 0;  // index into the cmpd component definition table

     // limits=nullptr disables the limits
     Error alloc(uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth,
@@ -561,12 +576,16 @@ private:
   ImageComponent* find_component_for_channel(heif_channel channel);
   const ImageComponent* find_component_for_channel(heif_channel channel) const;

+  ImageComponent* find_component_by_index(uint32_t component_index);
+  const ImageComponent* find_component_by_index(uint32_t component_index) const;
+
   uint32_t m_width = 0;
   uint32_t m_height = 0;
   heif_colorspace m_colorspace = heif_colorspace_undefined;
   heif_chroma m_chroma = heif_chroma_undefined;

   std::vector<ImageComponent> m_planes;
+  std::vector<uint16_t> m_cmpd_component_types;  // indexed by cmpd index
   MemoryHandle m_memory_handle;

   uint32_t m_sample_duration = 0; // duration of a sequence frame