Commit 20efde9d for libheif

commit 20efde9d78a4f5b483b45eccacd09d6150650935
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Tue Feb 10 21:09:13 2026 +0100

    unci: write multi-component images with bytealign_component_interleave

diff --git a/libheif/codecs/uncompressed/unc_boxes.cc b/libheif/codecs/uncompressed/unc_boxes.cc
index b34ba80c..c76a2af2 100644
--- a/libheif/codecs/uncompressed/unc_boxes.cc
+++ b/libheif/codecs/uncompressed/unc_boxes.cc
@@ -46,6 +46,7 @@ bool is_valid_component_format(uint8_t format)

 static std::map<heif_uncompressed_component_format, const char*> sNames_uncompressed_component_format{
     {component_format_unsigned, "unsigned"},
+    {component_format_signed,   "signed"},
     {component_format_float,    "float"},
     {component_format_complex,  "complex"}
 };
diff --git a/libheif/codecs/uncompressed/unc_encoder.h b/libheif/codecs/uncompressed/unc_encoder.h
index ecbd8d6c..099953ef 100644
--- a/libheif/codecs/uncompressed/unc_encoder.h
+++ b/libheif/codecs/uncompressed/unc_encoder.h
@@ -35,6 +35,8 @@ class HeifPixelImage;

 heif_uncompressed_component_type heif_channel_to_component_type(heif_channel channel);

+heif_uncompressed_component_format to_unc_component_format(heif_channel_datatype channel_datatype);
+

 class unc_encoder
 {
diff --git a/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.cc
index 475819ae..568386d3 100644
--- a/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.cc
@@ -44,61 +44,53 @@ std::unique_ptr<const unc_encoder> unc_encoder_factory_bytealign_component_inter
 }


-void unc_encoder_bytealign_component_interleave::add_channel_if_exists(const std::shared_ptr<const HeifPixelImage>& image, heif_channel channel)
-{
-  if (image->has_channel(channel)) {
-    m_components.push_back({channel, heif_channel_to_component_type(channel)});
-  }
-}
-
-
 unc_encoder_bytealign_component_interleave::unc_encoder_bytealign_component_interleave(const std::shared_ptr<const HeifPixelImage>& image,
                                        const heif_encoding_options& options)
 {
-  // Special case for heif_channel_Y:
-  // - if this an YCbCr image, use component_type_Y,
-  // - otherwise, use component_type_monochrome
+  bool is_nonvisual = (image->get_colorspace() == heif_colorspace_nonvisual);
+  uint32_t num_components = image->get_number_of_components();
+
+  for (uint32_t idx = 0; idx < num_components; idx++) {
+    heif_uncompressed_component_type comp_type;

-  if (image->has_channel(heif_channel_Y)) {
-    if (image->has_channel(heif_channel_Cb) && image->has_channel(heif_channel_Cr)) {
-      m_components.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_Y});
+    if (is_nonvisual) {
+      comp_type = static_cast<heif_uncompressed_component_type>(image->get_component_type(idx));
     }
     else {
-      m_components.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_monochrome});
+      heif_channel ch = image->get_component_channel(idx);
+      if (ch == heif_channel_Y && !image->has_channel(heif_channel_Cb)) {
+        comp_type = component_type_monochrome;
+      }
+      else {
+        comp_type = heif_channel_to_component_type(ch);
+      }
     }
-  }

-  add_channel_if_exists(image, heif_channel_Cb);
-  add_channel_if_exists(image, heif_channel_Cr);
-  add_channel_if_exists(image, heif_channel_R);
-  add_channel_if_exists(image, heif_channel_G);
-  add_channel_if_exists(image, heif_channel_B);
-  add_channel_if_exists(image, heif_channel_Alpha);
-  add_channel_if_exists(image, heif_channel_filter_array);
-  add_channel_if_exists(image, heif_channel_depth);
-  add_channel_if_exists(image, heif_channel_disparity);
+    uint8_t bpp = image->get_component_bits_per_pixel(idx);
+    auto datatype = image->get_component_datatype(idx);
+    auto comp_format = to_unc_component_format(datatype);

+    m_components.push_back({idx, comp_type, comp_format, bpp});
+  }

-  // if we have any component > 8 bits, we enable this
+  // Build cmpd/uncC boxes
   bool little_endian = false;

-  uint16_t index = 0;
-  for (channel_component channelcomponent : m_components) {
-    m_cmpd->add_component({channelcomponent.component_type});
-
-    uint8_t bpp = image->get_bits_per_pixel(channelcomponent.channel);
-    uint8_t component_align_size = static_cast<uint8_t>((bpp + 7) / 8);
+  uint16_t box_index = 0;
+  for (const auto& comp : m_components) {
+    m_cmpd->add_component({comp.component_type});

-    if (bpp % 8 == 0) {
+    uint8_t component_align_size = static_cast<uint8_t>((comp.bpp + 7) / 8);
+    if (comp.bpp % 8 == 0) {
       component_align_size = 0;
     }

-    if (bpp > 8) {
-      little_endian = true; // TODO: depending on the host endianness
+    if (comp.bpp > 8) {
+      little_endian = true;
     }

-    m_uncC->add_component({index, bpp, component_format_unsigned, component_align_size});
-    index++;
+    m_uncC->add_component({box_index, comp.bpp, comp.component_format, component_align_size});
+    box_index++;
   }

   m_uncC->set_interleave_type(interleave_mode_component);
@@ -114,19 +106,20 @@ unc_encoder_bytealign_component_interleave::unc_encoder_bytealign_component_inte
     m_uncC->set_sampling_type(sampling_mode_no_subsampling);
   }

-
   // --- compute bytes per pixel

   m_bytes_per_pixel_x4 = 0;

-  for (channel_component channelcomponent : m_components) {
-    int bpp = image->get_bits_per_pixel(channelcomponent.channel);
-    int bytes_per_pixel = 4 * (bpp + 7) / 8;
+  for (const auto& comp : m_components) {
+    int bytes_per_pixel = 4 * (comp.bpp + 7) / 8;

-    if (channelcomponent.channel == heif_channel_Cb ||
-        channelcomponent.channel == heif_channel_Cr) {
-      int downsampling = chroma_h_subsampling(image->get_chroma_format()) * chroma_v_subsampling(image->get_chroma_format());
-      bytes_per_pixel /= downsampling;
+    if (!is_nonvisual) {
+      heif_channel ch = image->get_component_channel(comp.component_idx);
+      if (ch == heif_channel_Cb || ch == heif_channel_Cr) {
+        int downsampling = chroma_h_subsampling(image->get_chroma_format())
+                         * chroma_v_subsampling(image->get_chroma_format());
+        bytes_per_pixel /= downsampling;
+      }
     }

     m_bytes_per_pixel_x4 += bytes_per_pixel;
@@ -142,36 +135,37 @@ uint64_t unc_encoder_bytealign_component_interleave::compute_tile_data_size_byte

 std::vector<uint8_t> unc_encoder_bytealign_component_interleave::encode_tile(const std::shared_ptr<const HeifPixelImage>& src_image) const
 {
-  std::vector<uint8_t> data;
-
   // compute total size of all components

   uint64_t total_size = 0;

-  for (channel_component channelcomponent : m_components) {
-    int bpp = src_image->get_bits_per_pixel(channelcomponent.channel);
-    int bytes_per_pixel = (bpp + 7) / 8;
-
-    total_size += static_cast<uint64_t>(src_image->get_height(channelcomponent.channel)) * src_image->get_width(channelcomponent.channel) * bytes_per_pixel;
+  for (const auto& comp : m_components) {
+    int bytes_per_pixel = (comp.bpp + 7) / 8;
+    uint32_t w = src_image->get_component_width(comp.component_idx);
+    uint32_t h = src_image->get_component_height(comp.component_idx);
+    total_size += static_cast<uint64_t>(h) * w * bytes_per_pixel;
   }

+  std::vector<uint8_t> data;
   data.resize(total_size);

   // output all component planes

   uint64_t out_data_start_pos = 0;

-  for (channel_component channelcomponent : m_components) {
-    int bpp = src_image->get_bits_per_pixel(channelcomponent.channel);
-    int bytes_per_pixel = (bpp + 7) / 8;
+  for (const auto& comp : m_components) {
+    int bytes_per_pixel = (comp.bpp + 7) / 8;
+    uint32_t w = src_image->get_component_width(comp.component_idx);
+    uint32_t h = src_image->get_component_height(comp.component_idx);

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

-    for (uint32_t y = 0; y < src_image->get_height(channelcomponent.channel); y++) {
-      uint32_t width = src_image->get_width(channelcomponent.channel);
-      memcpy(data.data() + out_data_start_pos, src_data + src_stride * y, width * bytes_per_pixel);
-      out_data_start_pos += width * bytes_per_pixel;
+    for (uint32_t y = 0; y < h; y++) {
+      memcpy(data.data() + out_data_start_pos,
+             src_data + src_stride * y,
+             w * bytes_per_pixel);
+      out_data_start_pos += w * bytes_per_pixel;
     }
   }

diff --git a/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.h b/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.h
index b5660dc9..b6a7eb63 100644
--- a/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.h
+++ b/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.h
@@ -38,16 +38,16 @@ public:
   [[nodiscard]] std::vector<uint8_t> encode_tile(const std::shared_ptr<const HeifPixelImage>& image) const override;

 private:
-  struct channel_component
+  struct encoded_component
   {
-    heif_channel channel;
+    uint32_t component_idx;
     heif_uncompressed_component_type component_type;
+    heif_uncompressed_component_format component_format;
+    uint8_t bpp;
   };

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