Commit f2ff813d for libheif

commit f2ff813d33409256594df6caa2687017e4582bef
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Tue Feb 10 16:28:01 2026 +0100

    pixelimage: use a list of components instead of a map of heif_channels (see #1660)

diff --git a/libheif/api/libheif/heif_experimental.cc b/libheif/api/libheif/heif_experimental.cc
index 838eefe9..608cdf63 100644
--- a/libheif/api/libheif/heif_experimental.cc
+++ b/libheif/api/libheif/heif_experimental.cc
@@ -440,6 +440,108 @@ heif_image_get_channel_X(complex32, heif_complex32, heif_channel_datatype_comple
 heif_image_get_channel_X(complex64, heif_complex64, heif_channel_datatype_complex_number, 64)


+// --- index-based component access
+
+int heif_image_get_number_of_components(const heif_image* image)
+{
+  if (!image || !image->image) {
+    return 0;
+  }
+  return image->image->get_number_of_components();
+}
+
+
+heif_channel heif_image_get_component_channel(const heif_image* image, int component_idx)
+{
+  if (!image || !image->image) {
+    return heif_channel_Y;
+  }
+  return image->image->get_component_channel(component_idx);
+}
+
+
+int heif_image_get_component_width(const heif_image* image, int component_idx)
+{
+  if (!image || !image->image) {
+    return 0;
+  }
+  return static_cast<int>(image->image->get_component_width(component_idx));
+}
+
+
+int heif_image_get_component_height(const heif_image* image, int component_idx)
+{
+  if (!image || !image->image) {
+    return 0;
+  }
+  return static_cast<int>(image->image->get_component_height(component_idx));
+}
+
+
+int heif_image_get_component_bits_per_pixel(const heif_image* image, int component_idx)
+{
+  if (!image || !image->image) {
+    return 0;
+  }
+  return image->image->get_component_bits_per_pixel(component_idx);
+}
+
+
+const uint8_t* heif_image_get_component_readonly(const heif_image* image, int component_idx, size_t* out_stride)
+{
+  if (!image || !image->image) {
+    if (out_stride) *out_stride = 0;
+    return nullptr;
+  }
+  return image->image->get_component(component_idx, out_stride);
+}
+
+
+uint8_t* heif_image_get_component(heif_image* image, int component_idx, size_t* out_stride)
+{
+  if (!image || !image->image) {
+    if (out_stride) *out_stride = 0;
+    return nullptr;
+  }
+  return image->image->get_component(component_idx, out_stride);
+}
+
+
+#define heif_image_get_component_X(name, type) \
+const type* heif_image_get_component_ ## name ## _readonly(const struct heif_image* image, \
+                                                            int component_idx, \
+                                                            size_t* out_stride) \
+{                                                            \
+  if (!image || !image->image) {                             \
+    if (out_stride) *out_stride = 0;                         \
+    return nullptr;                                          \
+  }                                                          \
+  return image->image->get_component_data<type>(component_idx, out_stride); \
+}                                                            \
+                                                             \
+type* heif_image_get_component_ ## name (struct heif_image* image, \
+                                         int component_idx,       \
+                                         size_t* out_stride)      \
+{                                                            \
+  if (!image || !image->image) {                             \
+    if (out_stride) *out_stride = 0;                         \
+    return nullptr;                                          \
+  }                                                          \
+  return image->image->get_component_data<type>(component_idx, out_stride); \
+}
+
+heif_image_get_component_X(uint16, uint16_t)
+heif_image_get_component_X(uint32, uint32_t)
+heif_image_get_component_X(uint64, uint64_t)
+heif_image_get_component_X(int16, int16_t)
+heif_image_get_component_X(int32, int32_t)
+heif_image_get_component_X(int64, int64_t)
+heif_image_get_component_X(float32, float)
+heif_image_get_component_X(float64, double)
+heif_image_get_component_X(complex32, heif_complex32)
+heif_image_get_component_X(complex64, heif_complex64)
+
+
 #endif


diff --git a/libheif/api/libheif/heif_experimental.h b/libheif/api/libheif/heif_experimental.h
index 8776eee9..1175dd07 100644
--- a/libheif/api/libheif/heif_experimental.h
+++ b/libheif/api/libheif/heif_experimental.h
@@ -321,6 +321,91 @@ LIBHEIF_API
 heif_complex64* heif_image_get_channel_complex64(heif_image*,
                                                  enum heif_channel channel,
                                                  size_t* out_stride);
+
+
+// --- index-based component access (for ISO 23001-17 multi-component images)
+
+LIBHEIF_API
+int heif_image_get_number_of_components(const heif_image*);
+
+LIBHEIF_API
+enum heif_channel heif_image_get_component_channel(const heif_image*, int component_idx);
+
+LIBHEIF_API
+int heif_image_get_component_width(const heif_image*, int component_idx);
+
+LIBHEIF_API
+int heif_image_get_component_height(const heif_image*, int component_idx);
+
+LIBHEIF_API
+int heif_image_get_component_bits_per_pixel(const heif_image*, int component_idx);
+
+LIBHEIF_API
+const uint8_t* heif_image_get_component_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+uint8_t* heif_image_get_component(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const uint16_t* heif_image_get_component_uint16_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+uint16_t* heif_image_get_component_uint16(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const uint32_t* heif_image_get_component_uint32_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+uint32_t* heif_image_get_component_uint32(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const uint64_t* heif_image_get_component_uint64_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+uint64_t* heif_image_get_component_uint64(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const int16_t* heif_image_get_component_int16_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+int16_t* heif_image_get_component_int16(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const int32_t* heif_image_get_component_int32_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+int32_t* heif_image_get_component_int32(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const int64_t* heif_image_get_component_int64_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+int64_t* heif_image_get_component_int64(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const float* heif_image_get_component_float32_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+float* heif_image_get_component_float32(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const double* heif_image_get_component_float64_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+double* heif_image_get_component_float64(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const heif_complex32* heif_image_get_component_complex32_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+heif_complex32* heif_image_get_component_complex32(heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+const heif_complex64* heif_image_get_component_complex64_readonly(const heif_image*, int component_idx, size_t* out_stride);
+
+LIBHEIF_API
+heif_complex64* heif_image_get_component_complex64(heif_image*, int component_idx, size_t* out_stride);
+
 #endif

 #ifdef __cplusplus
diff --git a/libheif/color-conversion/colorconversion.cc b/libheif/color-conversion/colorconversion.cc
index 5a4d467a..fca791a2 100644
--- a/libheif/color-conversion/colorconversion.cc
+++ b/libheif/color-conversion/colorconversion.cc
@@ -574,7 +574,7 @@ Result<std::shared_ptr<HeifPixelImage>> convert_colorspace(const std::shared_ptr
   // interleaved output format.
   // For planar formats, we include an alpha plane when included in the input.

-  if (num_interleaved_pixels_per_plane(target_chroma) > 1) {
+  if (num_interleaved_components_per_plane(target_chroma) > 1) {
     output_state.has_alpha = is_interleaved_with_alpha(target_chroma);
   }
   else {
diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc
index 744f37c6..618dae5e 100644
--- a/libheif/pixelimage.cc
+++ b/libheif/pixelimage.cc
@@ -239,13 +239,34 @@ std::vector<std::shared_ptr<Box>> ImageExtraData::generate_property_boxes(bool g

 HeifPixelImage::~HeifPixelImage()
 {
-  for (auto& iter : m_planes) {
-    delete[] iter.second.allocated_mem;
+  for (auto& component : m_planes) {
+    delete[] component.allocated_mem;
   }
 }


-int num_interleaved_pixels_per_plane(heif_chroma chroma)
+HeifPixelImage::ImageComponent* HeifPixelImage::find_component_for_channel(heif_channel channel)
+{
+  for (auto& component : m_planes) {
+    if (component.m_channel == channel) {
+      return &component;
+    }
+  }
+  return nullptr;
+}
+
+const HeifPixelImage::ImageComponent* HeifPixelImage::find_component_for_channel(heif_channel channel) const
+{
+  for (const auto& component : m_planes) {
+    if (component.m_channel == channel) {
+      return &component;
+    }
+  }
+  return nullptr;
+}
+
+
+int num_interleaved_components_per_plane(heif_chroma chroma)
 {
   switch (chroma) {
     case heif_chroma_undefined:
@@ -339,10 +360,9 @@ static uint32_t rounded_size(uint32_t s)
 Error HeifPixelImage::add_plane(heif_channel channel, uint32_t width, uint32_t height, int bit_depth,
                                 const heif_security_limits* limits)
 {
-  assert(!has_channel(channel));
-
-  ImagePlane plane;
-  int num_interleaved_pixels = num_interleaved_pixels_per_plane(m_chroma);
+  ImageComponent plane;
+  plane.m_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

@@ -358,7 +378,7 @@ Error HeifPixelImage::add_plane(heif_channel channel, uint32_t width, uint32_t h
     return err;
   }
   else {
-    m_planes.insert(std::make_pair(channel, plane));
+    m_planes.push_back(plane);
     return Error::Ok;
   }
 }
@@ -367,18 +387,19 @@ 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)
 {
-  ImagePlane plane;
+  ImageComponent plane;
+  plane.m_channel = channel;
   if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
     return err;
   }
   else {
-    m_planes.insert(std::make_pair(channel, plane));
+    m_planes.push_back(plane);
     return Error::Ok;
   }
 }


-Error HeifPixelImage::ImagePlane::alloc(uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth,
+Error HeifPixelImage::ImageComponent::alloc(uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth,
                                         int num_interleaved_components,
                                         const heif_security_limits* limits,
                                         MemoryHandle& memory_handle)
@@ -408,8 +429,7 @@ Error HeifPixelImage::ImagePlane::alloc(uint32_t width, uint32_t height, heif_ch
   m_datatype = datatype;


-  int bytes_per_component = get_bytes_per_pixel();
-  int bytes_per_pixel = num_interleaved_components * bytes_per_component;
+  int bytes_per_pixel = get_bytes_per_pixel();

   stride = m_mem_width * bytes_per_pixel;
   stride = (stride + alignment - 1U) & ~(alignment - 1U);
@@ -475,24 +495,23 @@ Error HeifPixelImage::ImagePlane::alloc(uint32_t width, uint32_t height, heif_ch
 Error HeifPixelImage::extend_padding_to_size(uint32_t width, uint32_t height, bool adjust_size,
                                              const heif_security_limits* limits)
 {
-  for (auto& planeIter : m_planes) {
-    auto* plane = &planeIter.second;
-
+  for (auto& component : m_planes) {
     uint32_t subsampled_width, subsampled_height;
-    get_subsampled_size(width, height, planeIter.first, m_chroma,
+    get_subsampled_size(width, height, component.m_channel, m_chroma,
                         &subsampled_width, &subsampled_height);

-    uint32_t old_width = plane->m_width;
-    uint32_t old_height = plane->m_height;
+    uint32_t old_width = component.m_width;
+    uint32_t old_height = component.m_height;

-    int bytes_per_pixel = get_storage_bits_per_pixel(planeIter.first) / 8;
+    int bytes_per_pixel = component.get_bytes_per_pixel();

-    if (plane->m_mem_width < subsampled_width ||
-        plane->m_mem_height < subsampled_height) {
+    if (component.m_mem_width < subsampled_width ||
+        component.m_mem_height < subsampled_height) {

-      ImagePlane newPlane;
-      if (auto err = newPlane.alloc(subsampled_width, subsampled_height, plane->m_datatype, plane->m_bit_depth,
-                                    num_interleaved_pixels_per_plane(m_chroma),
+      ImageComponent newPlane;
+      newPlane.m_channel = component.m_channel;
+      if (auto err = newPlane.alloc(subsampled_width, subsampled_height, component.m_datatype, component.m_bit_depth,
+                                    num_interleaved_components_per_plane(m_chroma),
                                     limits, m_memory_handle))
       {
         return err;
@@ -505,14 +524,13 @@ Error HeifPixelImage::extend_padding_to_size(uint32_t width, uint32_t height, bo

       // copy the visible part of the old plane into the new plane

-      for (uint32_t y = 0; y < plane->m_height; y++) {
+      for (uint32_t y = 0; y < component.m_height; y++) {
         memcpy(static_cast<uint8_t*>(newPlane.mem) + y * newPlane.stride,
-               static_cast<uint8_t*>(plane->mem) + y * plane->stride,
-               plane->m_width * bytes_per_pixel);
+               static_cast<uint8_t*>(component.mem) + y * component.stride,
+               component.m_width * bytes_per_pixel);
       }

-      planeIter.second = newPlane;
-      plane = &planeIter.second;
+      component = newPlane;
     }

     // extend plane size
@@ -520,23 +538,23 @@ Error HeifPixelImage::extend_padding_to_size(uint32_t width, uint32_t height, bo
     if (old_width != subsampled_width) {
       for (uint32_t y = 0; y < old_height; y++) {
         for (uint32_t x = old_width; x < subsampled_width; x++) {
-          memcpy(static_cast<uint8_t*>(plane->mem) + y * plane->stride + x * bytes_per_pixel,
-                 static_cast<uint8_t*>(plane->mem) + y * plane->stride + (old_width - 1) * bytes_per_pixel,
+          memcpy(static_cast<uint8_t*>(component.mem) + y * component.stride + x * bytes_per_pixel,
+                 static_cast<uint8_t*>(component.mem) + y * component.stride + (old_width - 1) * bytes_per_pixel,
                  bytes_per_pixel);
         }
       }
     }

     for (uint32_t y = old_height; y < subsampled_height; y++) {
-      memcpy(static_cast<uint8_t*>(plane->mem) + y * plane->stride,
-             static_cast<uint8_t*>(plane->mem) + (old_height - 1) * plane->stride,
+      memcpy(static_cast<uint8_t*>(component.mem) + y * component.stride,
+             static_cast<uint8_t*>(component.mem) + (old_height - 1) * component.stride,
              subsampled_width * bytes_per_pixel);
     }


     if (adjust_size) {
-      plane->m_width = subsampled_width;
-      plane->m_height = subsampled_height;
+      component.m_width = subsampled_width;
+      component.m_height = subsampled_height;
     }
   }

@@ -553,23 +571,22 @@ Error HeifPixelImage::extend_padding_to_size(uint32_t width, uint32_t height, bo

 Error HeifPixelImage::extend_to_size_with_zero(uint32_t width, uint32_t height, const heif_security_limits* limits)
 {
-  for (auto& planeIter : m_planes) {
-    auto* plane = &planeIter.second;
-
+  for (auto& component : m_planes) {
     uint32_t subsampled_width, subsampled_height;
-    get_subsampled_size(width, height, planeIter.first, m_chroma,
+    get_subsampled_size(width, height, component.m_channel, m_chroma,
                         &subsampled_width, &subsampled_height);

-    uint32_t old_width = plane->m_width;
-    uint32_t old_height = plane->m_height;
+    uint32_t old_width = component.m_width;
+    uint32_t old_height = component.m_height;

-    int bytes_per_pixel = get_storage_bits_per_pixel(planeIter.first) / 8;
+    int bytes_per_pixel = component.get_bytes_per_pixel();

-    if (plane->m_mem_width < subsampled_width ||
-        plane->m_mem_height < subsampled_height) {
+    if (component.m_mem_width < subsampled_width ||
+        component.m_mem_height < subsampled_height) {

-      ImagePlane newPlane;
-      if (auto err = newPlane.alloc(subsampled_width, subsampled_height, plane->m_datatype, plane->m_bit_depth, num_interleaved_pixels_per_plane(m_chroma), limits, m_memory_handle)) {
+      ImageComponent newPlane;
+      newPlane.m_channel = component.m_channel;
+      if (auto err = newPlane.alloc(subsampled_width, subsampled_height, component.m_datatype, component.m_bit_depth, num_interleaved_components_per_plane(m_chroma), limits, m_memory_handle)) {
         return err;
       }

@@ -580,44 +597,43 @@ Error HeifPixelImage::extend_to_size_with_zero(uint32_t width, uint32_t height,

       // copy the visible part of the old plane into the new plane

-      for (uint32_t y = 0; y < plane->m_height; y++) {
+      for (uint32_t y = 0; y < component.m_height; y++) {
         memcpy(static_cast<uint8_t*>(newPlane.mem) + y * newPlane.stride,
-               static_cast<uint8_t*>(plane->mem) + y * plane->stride,
-               plane->m_width * bytes_per_pixel);
+               static_cast<uint8_t*>(component.mem) + y * component.stride,
+               component.m_width * bytes_per_pixel);
       }

       // --- replace existing image plane with reallocated plane

-      delete[] planeIter.second.allocated_mem;
+      delete[] component.allocated_mem;

-      planeIter.second = newPlane;
-      plane = &planeIter.second;
+      component = newPlane;
     }

     // extend plane size

     uint8_t fill = 0;
-    if (bytes_per_pixel == 1 && (planeIter.first == heif_channel_Cb || planeIter.first == heif_channel_Cr)) {
+    if (bytes_per_pixel == 1 && (component.m_channel == heif_channel_Cb || component.m_channel == heif_channel_Cr)) {
       fill = 128;
     }

     if (old_width != subsampled_width) {
       for (uint32_t y = 0; y < old_height; y++) {
-        memset(static_cast<uint8_t*>(plane->mem) + y * plane->stride + old_width * bytes_per_pixel,
+        memset(static_cast<uint8_t*>(component.mem) + y * component.stride + old_width * bytes_per_pixel,
                fill,
                bytes_per_pixel * (subsampled_width - old_width));
       }
     }

     for (uint32_t y = old_height; y < subsampled_height; y++) {
-      memset(static_cast<uint8_t*>(plane->mem) + y * plane->stride,
+      memset(static_cast<uint8_t*>(component.mem) + y * component.stride,
              fill,
              subsampled_width * bytes_per_pixel);
     }


-    plane->m_width = subsampled_width;
-    plane->m_height = subsampled_height;
+    component.m_width = subsampled_width;
+    component.m_height = subsampled_height;
   }

   // modify the logical image size
@@ -630,7 +646,7 @@ Error HeifPixelImage::extend_to_size_with_zero(uint32_t width, uint32_t height,

 bool HeifPixelImage::has_channel(heif_channel channel) const
 {
-  return (m_planes.find(channel) != m_planes.end());
+  return find_component_for_channel(channel) != nullptr;
 }


@@ -645,23 +661,23 @@ bool HeifPixelImage::has_alpha() const

 uint32_t HeifPixelImage::get_width(enum heif_channel channel) const
 {
-  auto iter = m_planes.find(channel);
-  if (iter == m_planes.end()) {
+  auto* comp = find_component_for_channel(channel);
+  if (!comp) {
     return 0;
   }

-  return iter->second.m_width;
+  return comp->m_width;
 }


 uint32_t HeifPixelImage::get_height(enum heif_channel channel) const
 {
-  auto iter = m_planes.find(channel);
-  if (iter == m_planes.end()) {
+  auto* comp = find_component_for_channel(channel);
+  if (!comp) {
     return 0;
   }

-  return iter->second.m_height;
+  return comp->m_height;
 }


@@ -669,8 +685,8 @@ std::set<heif_channel> HeifPixelImage::get_channel_set() const
 {
   std::set<heif_channel> channels;

-  for (const auto& plane : m_planes) {
-    channels.insert(plane.first);
+  for (const auto& component : m_planes) {
+    channels.insert(component.m_channel);
   }

   return channels;
@@ -679,39 +695,25 @@ std::set<heif_channel> HeifPixelImage::get_channel_set() const

 uint8_t HeifPixelImage::get_storage_bits_per_pixel(enum heif_channel channel) const
 {
-  if (channel == heif_channel_interleaved) {
-    auto chroma = get_chroma_format();
-    switch (chroma) {
-      case heif_chroma_interleaved_RGB:
-        return 24;
-      case heif_chroma_interleaved_RGBA:
-        return 32;
-      case heif_chroma_interleaved_RRGGBB_BE:
-      case heif_chroma_interleaved_RRGGBB_LE:
-        return 48;
-      case heif_chroma_interleaved_RRGGBBAA_BE:
-      case heif_chroma_interleaved_RRGGBBAA_LE:
-        return 64;
-      default:
-        return -1; // invalid channel/chroma specification
-    }
-  }
-  else {
-    uint32_t bpp = (get_bits_per_pixel(channel) + 7U) & ~7U;
-    assert(bpp <= 255);
-    return static_cast<uint8_t>(bpp);
+  auto* comp = find_component_for_channel(channel);
+  if (!comp) {
+    return -1;
   }
+
+  uint32_t bpp = comp->get_bytes_per_pixel() * 8;
+  assert(bpp <= 255);
+  return static_cast<uint8_t>(bpp);
 }


 uint8_t HeifPixelImage::get_bits_per_pixel(enum heif_channel channel) const
 {
-  auto iter = m_planes.find(channel);
-  if (iter == m_planes.end()) {
+  auto* comp = find_component_for_channel(channel);
+  if (!comp) {
     return -1;
   }

-  return iter->second.m_bit_depth;
+  return comp->m_bit_depth;
 }


@@ -750,23 +752,23 @@ uint8_t HeifPixelImage::get_visual_image_bits_per_pixel() const

 heif_channel_datatype HeifPixelImage::get_datatype(enum heif_channel channel) const
 {
-  auto iter = m_planes.find(channel);
-  if (iter == m_planes.end()) {
+  auto* comp = find_component_for_channel(channel);
+  if (!comp) {
     return heif_channel_datatype_undefined;
   }

-  return iter->second.m_datatype;
+  return comp->m_datatype;
 }


 int HeifPixelImage::get_number_of_interleaved_components(heif_channel channel) const
 {
-  auto iter = m_planes.find(channel);
-  if (iter == m_planes.end()) {
+  auto* comp = find_component_for_channel(channel);
+  if (!comp) {
     return 0;
   }

-  return iter->second.m_num_interleaved_components;
+  return comp->m_num_interleaved_components;
 }


@@ -781,9 +783,9 @@ Error HeifPixelImage::copy_new_plane_from(const std::shared_ptr<const HeifPixelI
   uint32_t width = src_image->get_width(src_channel);
   uint32_t height = src_image->get_height(src_channel);

-  auto src_plane_iter = src_image->m_planes.find(src_channel);
-  assert(src_plane_iter != src_image->m_planes.end());
-  const auto& src_plane = src_plane_iter->second;
+  const auto* src_plane_ptr = src_image->find_component_for_channel(src_channel);
+  assert(src_plane_ptr != nullptr);
+  const auto& src_plane = *src_plane_ptr;

   auto err = add_channel(dst_channel, width, height,
                          src_plane.m_datatype,
@@ -857,7 +859,7 @@ Error HeifPixelImage::fill_new_plane(heif_channel dst_channel, uint16_t value, i

 void HeifPixelImage::fill_plane(heif_channel dst_channel, uint16_t value)
 {
-  int num_interleaved = num_interleaved_pixels_per_plane(m_chroma);
+  int num_interleaved = num_interleaved_components_per_plane(m_chroma);

   int bpp = get_bits_per_pixel(dst_channel);
   uint32_t width = get_width(dst_channel);
@@ -893,11 +895,19 @@ void HeifPixelImage::transfer_plane_from_image_as(const std::shared_ptr<HeifPixe
 {
   // TODO: check that dst_channel does not exist yet

-  ImagePlane plane = source->m_planes[src_channel];
-  source->m_planes.erase(src_channel);
+  // Find and remove the component from source
+  ImageComponent plane;
+  for (auto it = source->m_planes.begin(); it != source->m_planes.end(); ++it) {
+    if (it->m_channel == src_channel) {
+      plane = *it;
+      source->m_planes.erase(it);
+      break;
+    }
+  }
   source->m_memory_handle.free(plane.allocation_size);

-  m_planes.insert(std::make_pair(dst_channel, plane));
+  plane.m_channel = dst_channel;
+  m_planes.push_back(plane);

   // Note: we assume that image planes are never transferred between heif_contexts
   m_memory_handle.alloc(plane.allocation_size,
@@ -1040,48 +1050,38 @@ Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::rotate_ccw(int angle_deg

   // --- rotate all channels

-  for (const auto &plane_pair: m_planes) {
-    heif_channel channel = plane_pair.first;
-    const ImagePlane &plane = plane_pair.second;
-
-    /*
-    if (plane.bit_depth != 8) {
-      return Error(heif_error_Unsupported_feature,
-                   heif_suberror_Unspecified,
-                   "Can currently only rotate images with 8 bits per pixel");
-    }
-    */
+  for (const auto &component: m_planes) {
+    heif_channel channel = component.m_channel;

-    uint32_t out_plane_width = plane.m_width;
-    uint32_t out_plane_height = plane.m_height;
+    uint32_t out_plane_width = component.m_width;
+    uint32_t out_plane_height = component.m_height;

     if (angle_degrees == 90 || angle_degrees == 270) {
       std::swap(out_plane_width, out_plane_height);
     }

-    Error err = out_img->add_channel(channel, out_plane_width, out_plane_height, plane.m_datatype, plane.m_bit_depth, limits);
+    Error err = out_img->add_channel(channel, out_plane_width, out_plane_height, component.m_datatype, component.m_bit_depth, limits);
     if (err) {
       return err;
     }

-    auto out_plane_iter = out_img->m_planes.find(channel);
-    assert(out_plane_iter != out_img->m_planes.end());
-    ImagePlane& out_plane = out_plane_iter->second;
+    // The output plane is the last one added
+    ImageComponent& out_plane = out_img->m_planes.back();

-    if (plane.m_bit_depth <= 8) {
-      plane.rotate_ccw<uint8_t>(angle_degrees, out_plane);
+    if (component.m_bit_depth <= 8) {
+      component.rotate_ccw<uint8_t>(angle_degrees, out_plane);
     }
-    else if (plane.m_bit_depth <= 16) {
-      plane.rotate_ccw<uint16_t>(angle_degrees, out_plane);
+    else if (component.m_bit_depth <= 16) {
+      component.rotate_ccw<uint16_t>(angle_degrees, out_plane);
     }
-    else if (plane.m_bit_depth <= 32) {
-      plane.rotate_ccw<uint32_t>(angle_degrees, out_plane);
+    else if (component.m_bit_depth <= 32) {
+      component.rotate_ccw<uint32_t>(angle_degrees, out_plane);
     }
-    else if (plane.m_bit_depth <= 64) {
-      plane.rotate_ccw<uint64_t>(angle_degrees, out_plane);
+    else if (component.m_bit_depth <= 64) {
+      component.rotate_ccw<uint64_t>(angle_degrees, out_plane);
     }
-    else if (plane.m_bit_depth <= 128) {
-      plane.rotate_ccw<heif_complex64>(angle_degrees, out_plane);
+    else if (component.m_bit_depth <= 128) {
+      component.rotate_ccw<heif_complex64>(angle_degrees, out_plane);
     }
   }
   // --- pass the color profiles to the new image
@@ -1095,8 +1095,8 @@ Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::rotate_ccw(int angle_deg
 }

 template<typename T>
-void HeifPixelImage::ImagePlane::rotate_ccw(int angle_degrees,
-                                            ImagePlane& out_plane) const
+void HeifPixelImage::ImageComponent::rotate_ccw(int angle_degrees,
+                                            ImageComponent& out_plane) const
 {
   uint32_t w = m_width;
   uint32_t h = m_height;
@@ -1127,7 +1127,7 @@ void HeifPixelImage::ImagePlane::rotate_ccw(int angle_degrees,


 template<typename T>
-void HeifPixelImage::ImagePlane::mirror_inplace(heif_transform_mirror_direction direction)
+void HeifPixelImage::ImageComponent::mirror_inplace(heif_transform_mirror_direction direction)
 {
   uint32_t w = m_width;
   uint32_t h = m_height;
@@ -1181,27 +1181,25 @@ Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::mirror_inplace(heif_tran
   }


-  for (auto& plane_pair : m_planes) {
-    ImagePlane& plane = plane_pair.second;
-
-    if (plane.m_bit_depth <= 8) {
-      plane.mirror_inplace<uint8_t>(direction);
+  for (auto& component : m_planes) {
+    if (component.m_bit_depth <= 8) {
+      component.mirror_inplace<uint8_t>(direction);
     }
-    else if (plane.m_bit_depth <= 16) {
-      plane.mirror_inplace<uint16_t>(direction);
+    else if (component.m_bit_depth <= 16) {
+      component.mirror_inplace<uint16_t>(direction);
     }
-    else if (plane.m_bit_depth <= 32) {
-      plane.mirror_inplace<uint32_t>(direction);
+    else if (component.m_bit_depth <= 32) {
+      component.mirror_inplace<uint32_t>(direction);
     }
-    else if (plane.m_bit_depth <= 64) {
-      plane.mirror_inplace<uint64_t>(direction);
+    else if (component.m_bit_depth <= 64) {
+      component.mirror_inplace<uint64_t>(direction);
     }
-    else if (plane.m_bit_depth <= 128) {
-      plane.mirror_inplace<heif_complex64>(direction);
+    else if (component.m_bit_depth <= 128) {
+      component.mirror_inplace<heif_complex64>(direction);
     }
     else {
       std::stringstream sstr;
-      sstr << "Cannot mirror images with " << plane.m_bit_depth << " bits per pixel";
+      sstr << "Cannot mirror images with " << component.m_bit_depth << " bits per pixel";
       return Error{heif_error_Unsupported_feature,
                    heif_suberror_Unspecified,
                    sstr.str()};
@@ -1212,24 +1210,28 @@ Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::mirror_inplace(heif_tran
 }


-int HeifPixelImage::ImagePlane::get_bytes_per_pixel() const
+int HeifPixelImage::ImageComponent::get_bytes_per_pixel() const
 {
+  int bytes_per_component;
+
   if (m_bit_depth <= 8) {
-    return 1;
+    bytes_per_component = 1;
   }
   else if (m_bit_depth <= 16) {
-    return 2;
+    bytes_per_component = 2;
   }
   else if (m_bit_depth <= 32) {
-    return 4;
+    bytes_per_component = 4;
   }
   else if (m_bit_depth <= 64) {
-    return 8;
+    bytes_per_component = 8;
   }
   else {
     assert(m_bit_depth <= 128);
-    return 16;
+    bytes_per_component = 16;
   }
+
+  return bytes_per_component * m_num_interleaved_components;
 }


@@ -1271,9 +1273,8 @@ Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::crop(uint32_t left, uint

   // --- crop all channels

-  for (const auto& plane_pair : m_planes) {
-    heif_channel channel = plane_pair.first;
-    const ImagePlane& plane = plane_pair.second;
+  for (const auto& component : m_planes) {
+    heif_channel channel = component.m_channel;

     uint32_t plane_left = get_subsampled_size_h(left, channel, m_chroma, scaling_mode::is_divisible); // is always divisible
     uint32_t plane_right = get_subsampled_size_h(right, channel, m_chroma, scaling_mode::round_down); // this keeps enough chroma since 'right' is a coordinate and not the width
@@ -1283,19 +1284,18 @@ Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::crop(uint32_t left, uint
     auto err = out_img->add_channel(channel,
                                     plane_right - plane_left + 1,
                                     plane_bottom - plane_top + 1,
-                                    plane.m_datatype,
-                                    plane.m_bit_depth,
+                                    component.m_datatype,
+                                    component.m_bit_depth,
                                     limits);
     if (err) {
       return err;
     }

-    auto out_plane_iter = out_img->m_planes.find(channel);
-    assert(out_plane_iter != out_img->m_planes.end());
-    ImagePlane& out_plane = out_plane_iter->second;
+    // The output plane is the last one added
+    ImageComponent& out_plane = out_img->m_planes.back();

-    int bytes_per_pixel = plane.get_bytes_per_pixel();
-    plane.crop(plane_left, plane_right, plane_top, plane_bottom, bytes_per_pixel, out_plane);
+    int bytes_per_pixel = component.get_bytes_per_pixel();
+    component.crop(plane_left, plane_right, plane_top, plane_bottom, bytes_per_pixel, out_plane);
   }

   // --- pass the color profiles to the new image
@@ -1309,8 +1309,8 @@ Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::crop(uint32_t left, uint
 }


-void HeifPixelImage::ImagePlane::crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
-                                      int bytes_per_pixel, ImagePlane& out_plane) const
+void HeifPixelImage::ImageComponent::crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
+                                      int bytes_per_pixel, ImageComponent& out_plane) const
 {
   size_t in_stride = stride;
   auto* in_data = static_cast<const uint8_t*>(mem);
@@ -1330,8 +1330,8 @@ Error HeifPixelImage::fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_
 {
   for (const auto& channel : {heif_channel_R, heif_channel_G, heif_channel_B, heif_channel_Alpha}) {

-    const auto plane_iter = m_planes.find(channel);
-    if (plane_iter == m_planes.end()) {
+    auto* comp = find_component_for_channel(channel);
+    if (!comp) {

       // alpha channel is optional, R,G,B is required
       if (channel == heif_channel_Alpha) {
@@ -1343,7 +1343,7 @@ Error HeifPixelImage::fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_

     }

-    ImagePlane& plane = plane_iter->second;
+    ImageComponent& plane = *comp;

     if (plane.m_bit_depth != 8) {
       return {heif_error_Unsupported_feature,
@@ -1373,7 +1373,7 @@ Error HeifPixelImage::fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_
       default:
         // initialization only to avoid warning of uninitialized variable.
         val16 = 0;
-        // Should already be detected by the check above ("m_planes.find").
+        // Should already be detected by the check above ("find_component_for_channel").
         assert(false);
     }

@@ -1616,11 +1616,11 @@ Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr<HeifPixelImage>& ou

   // --- scale all channels

-  int nInterleaved = num_interleaved_pixels_per_plane(m_chroma);
+  int nInterleaved = num_interleaved_components_per_plane(m_chroma);
   if (nInterleaved > 1) {
-    auto plane_iter = m_planes.find(heif_channel_interleaved);
-    assert(plane_iter != m_planes.end()); // the plane must exist since we have an interleaved chroma format
-    const ImagePlane& plane = plane_iter->second;
+    const auto* comp = find_component_for_channel(heif_channel_interleaved);
+    assert(comp != nullptr); // the plane must exist since we have an interleaved chroma format
+    const ImageComponent& plane = *comp;

     uint32_t out_w = out_img->get_width(heif_channel_interleaved);
     uint32_t out_h = out_img->get_height(heif_channel_interleaved);
@@ -1672,9 +1672,9 @@ Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr<HeifPixelImage>& ou
     }
   }
   else {
-    for (const auto& plane_pair : m_planes) {
-      heif_channel channel = plane_pair.first;
-      const ImagePlane& plane = plane_pair.second;
+    for (const auto& component : m_planes) {
+      heif_channel channel = component.m_channel;
+      const ImageComponent& plane = component;

       if (!out_img->has_channel(channel)) {
         return {heif_error_Invalid_input, heif_suberror_Unspecified, "scaling input has extra color plane"};
@@ -1886,3 +1886,61 @@ HeifPixelImage::extract_image_area(uint32_t x0, uint32_t y0, uint32_t w, uint32_

   return areaImg;
 }
+
+
+// --- index-based component access methods
+
+heif_channel HeifPixelImage::get_component_channel(int component_idx) const
+{
+  assert(component_idx >= 0 && component_idx < static_cast<int>(m_planes.size()));
+  return m_planes[component_idx].m_channel;
+}
+
+
+uint32_t HeifPixelImage::get_component_width(int component_idx) const
+{
+  assert(component_idx >= 0 && component_idx < static_cast<int>(m_planes.size()));
+  return m_planes[component_idx].m_width;
+}
+
+
+uint32_t HeifPixelImage::get_component_height(int component_idx) const
+{
+  assert(component_idx >= 0 && component_idx < static_cast<int>(m_planes.size()));
+  return m_planes[component_idx].m_height;
+}
+
+
+uint8_t HeifPixelImage::get_component_bits_per_pixel(int component_idx) const
+{
+  assert(component_idx >= 0 && component_idx < static_cast<int>(m_planes.size()));
+  return m_planes[component_idx].m_bit_depth;
+}
+
+
+uint8_t HeifPixelImage::get_component_storage_bits_per_pixel(int component_idx) const
+{
+  assert(component_idx >= 0 && component_idx < static_cast<int>(m_planes.size()));
+  uint32_t bpp = m_planes[component_idx].get_bytes_per_pixel() * 8;
+  assert(bpp <= 255);
+  return static_cast<uint8_t>(bpp);
+}
+
+
+heif_channel_datatype HeifPixelImage::get_component_datatype(int component_idx) const
+{
+  assert(component_idx >= 0 && component_idx < static_cast<int>(m_planes.size()));
+  return m_planes[component_idx].m_datatype;
+}
+
+
+uint8_t* HeifPixelImage::get_component(int component_idx, size_t* out_stride)
+{
+  return get_component_data<uint8_t>(component_idx, out_stride);
+}
+
+
+const uint8_t* HeifPixelImage::get_component(int component_idx, size_t* out_stride) const
+{
+  return get_component_data<uint8_t>(component_idx, out_stride);
+}
diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h
index 2c0ec507..21af3604 100644
--- a/libheif/pixelimage.h
+++ b/libheif/pixelimage.h
@@ -48,7 +48,7 @@ uint32_t channel_height(uint32_t h, heif_chroma chroma, heif_channel channel);

 bool is_interleaved_with_alpha(heif_chroma chroma);

-int num_interleaved_pixels_per_plane(heif_chroma chroma);
+int num_interleaved_components_per_plane(heif_chroma chroma);

 bool is_integer_multiple_of_chroma_size(uint32_t width,
                                         uint32_t height,
@@ -267,8 +267,8 @@ public:
   template <typename T>
   T* get_channel(heif_channel channel, size_t* out_stride)
   {
-    auto iter = m_planes.find(channel);
-    if (iter == m_planes.end()) {
+    auto* comp = find_component_for_channel(channel);
+    if (!comp) {
       if (out_stride)
         *out_stride = 0;

@@ -276,12 +276,10 @@ public:
     }

     if (out_stride) {
-      *out_stride = static_cast<int>(iter->second.stride / sizeof(T));
+      *out_stride = static_cast<int>(comp->stride / sizeof(T));
     }

-    //assert(sizeof(T) == iter->second.get_bytes_per_pixel());
-
-    return static_cast<T*>(iter->second.mem);
+    return static_cast<T*>(comp->mem);
   }

   template <typename T>
@@ -290,6 +288,43 @@ public:
     return const_cast<HeifPixelImage*>(this)->get_channel<T>(channel, out_stride);
   }

+
+  // --- index-based component access (for ISO 23001-17 multi-component images)
+
+  int get_number_of_components() const { return static_cast<int>(m_planes.size()); }
+
+  heif_channel get_component_channel(int component_idx) const;
+
+  uint32_t get_component_width(int component_idx) const;
+  uint32_t get_component_height(int component_idx) const;
+  uint8_t get_component_bits_per_pixel(int component_idx) const;
+  uint8_t get_component_storage_bits_per_pixel(int component_idx) const;
+  heif_channel_datatype get_component_datatype(int component_idx) const;
+
+  uint8_t* get_component(int component_idx, size_t* out_stride);
+  const uint8_t* get_component(int component_idx, size_t* out_stride) const;
+
+  template <typename T>
+  T* get_component_data(int component_idx, size_t* out_stride)
+  {
+    if (component_idx < 0 || component_idx >= static_cast<int>(m_planes.size())) {
+      if (out_stride) *out_stride = 0;
+      return nullptr;
+    }
+
+    auto& comp = m_planes[component_idx];
+    if (out_stride) {
+      *out_stride = comp.stride / sizeof(T);
+    }
+    return static_cast<T*>(comp.mem);
+  }
+
+  template <typename T>
+  const T* get_component_data(int component_idx, size_t* out_stride) const
+  {
+    return const_cast<HeifPixelImage*>(this)->get_component_data<T>(component_idx, out_stride);
+  }
+
   Error copy_new_plane_from(const std::shared_ptr<const HeifPixelImage>& src_image,
                             heif_channel src_channel,
                             heif_channel dst_channel,
@@ -349,8 +384,10 @@ public:
   const std::vector<Error>& get_warnings() const { return m_warnings; }

 private:
-  struct ImagePlane
+  struct ImageComponent
   {
+    heif_channel m_channel = heif_channel_Y;
+
     // limits=nullptr disables the limits
     Error alloc(uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth,
                 int num_interleaved_components,
@@ -358,6 +395,10 @@ private:
                 MemoryHandle& memory_handle);

     heif_channel_datatype m_datatype = heif_channel_datatype_unsigned_integer;
+
+    // logical bit depth per component
+    // For interleaved formats, it is the number of bits for one component.
+    // It is not the storage width.
     uint8_t m_bit_depth = 0;
     uint8_t m_num_interleaved_components = 1;

@@ -379,17 +420,20 @@ private:
     template <typename T> void mirror_inplace(heif_transform_mirror_direction);

     template<typename T>
-    void rotate_ccw(int angle_degrees, ImagePlane& out_plane) const;
+    void rotate_ccw(int angle_degrees, ImageComponent& out_plane) const;

-    void crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom, int bytes_per_pixel, ImagePlane& out_plane) const;
+    void crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom, int bytes_per_pixel, ImageComponent& out_plane) const;
   };

+  ImageComponent* find_component_for_channel(heif_channel channel);
+  const ImageComponent* find_component_for_channel(heif_channel channel) 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::map<heif_channel, ImagePlane> m_planes;
+  std::vector<ImageComponent> m_planes;
   MemoryHandle m_memory_handle;

   uint32_t m_sample_duration = 0; // duration of a sequence frame
diff --git a/tests/conversion.cc b/tests/conversion.cc
index 73cb00b1..6d016fb3 100644
--- a/tests/conversion.cc
+++ b/tests/conversion.cc
@@ -69,7 +69,7 @@ uint16_t SwapBytesIfNeeded(uint16_t v, heif_chroma chroma) {
 template <typename T>
 std::string PrintChannel(const HeifPixelImage& image, heif_channel channel) {
   heif_chroma chroma = image.get_chroma_format();
-  int num_interleaved = num_interleaved_pixels_per_plane(chroma);
+  int num_interleaved = num_interleaved_components_per_plane(chroma);
   bool is_interleaved = num_interleaved > 1;
   uint32_t max_cols = is_interleaved ? 3 : 10;
   uint32_t max_rows = 10;
@@ -135,7 +135,7 @@ double GetPsnr(const HeifPixelImage& original, const HeifPixelImage& compressed,
   compressed_stride /= (int)sizeof(T);
   double mse = 0.0;

-  int num_interleaved = num_interleaved_pixels_per_plane(chroma);
+  int num_interleaved = num_interleaved_components_per_plane(chroma);
   int alpha_max = (1 << original.get_bits_per_pixel(channel)) - 1;
   CAPTURE(expect_alpha_max);
   for (uint32_t y = 0; y < h; y++) {