Commit 8936e7c0 for libheif

commit 8936e7c0875437ebac6ea0d84055f340b36d2eb8
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Fri Feb 20 11:35:39 2026 +0100

    unci: combine the bytealign_component_interleave encoder into the component_interleave encoder

diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt
index 0bff75bd..97eca435 100644
--- a/libheif/CMakeLists.txt
+++ b/libheif/CMakeLists.txt
@@ -319,8 +319,6 @@ if (WITH_UNCOMPRESSED_CODEC)
             codecs/uncompressed/unc_encoder_rgb_bytealign_pixel_interleave.h
             codecs/uncompressed/unc_encoder_component_interleave.cc
             codecs/uncompressed/unc_encoder_component_interleave.h
-            codecs/uncompressed/unc_encoder_bytealign_component_interleave.cc
-            codecs/uncompressed/unc_encoder_bytealign_component_interleave.h
             codecs/uncompressed/unc_encoder_rgb_block_pixel_interleave.cc
             codecs/uncompressed/unc_encoder_rgb_block_pixel_interleave.h)
 endif ()
diff --git a/libheif/codecs/uncompressed/unc_encoder.cc b/libheif/codecs/uncompressed/unc_encoder.cc
index ba927a91..61d2acf5 100644
--- a/libheif/codecs/uncompressed/unc_encoder.cc
+++ b/libheif/codecs/uncompressed/unc_encoder.cc
@@ -26,7 +26,6 @@
 #include "pixelimage.h"
 #include "unc_boxes.h"
 #include "unc_encoder_component_interleave.h"
-#include "unc_encoder_bytealign_component_interleave.h"
 #include "unc_encoder_rgb_block_pixel_interleave.h"
 #include "unc_encoder_rgb_pixel_interleave.h"
 #include "unc_encoder_rgb_bytealign_pixel_interleave.h"
@@ -89,14 +88,12 @@ Result<std::unique_ptr<const unc_encoder> > unc_encoder_factory::get_unc_encoder
   static unc_encoder_factory_rgb_block_pixel_interleave enc_rgb_block_pixel_interleave;
   static unc_encoder_factory_rgb_bytealign_pixel_interleave enc_rgb_bytealign_pixel_interleave;
   static unc_encoder_factory_component_interleave enc_component_interleave;
-  static unc_encoder_factory_bytealign_component_interleave enc_bytealign_component_interleave;

   static const unc_encoder_factory* encoders[]{
     &enc_rgb_pixel_interleave,
     &enc_rgb_block_pixel_interleave,
     &enc_rgb_bytealign_pixel_interleave,
-    &enc_component_interleave,
-    &enc_bytealign_component_interleave
+    &enc_component_interleave
   };

   for (const unc_encoder_factory* enc : encoders) {
diff --git a/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.cc
deleted file mode 100644
index 568386d3..00000000
--- a/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * HEIF codec.
- * Copyright (c) 2026 Dirk Farin <dirk.farin@gmail.com>
- *
- * This file is part of libheif.
- *
- * libheif is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * libheif is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "unc_encoder_bytealign_component_interleave.h"
-
-#include <cstring>
-
-#include "pixelimage.h"
-#include "unc_boxes.h"
-
-
-bool unc_encoder_factory_bytealign_component_interleave::can_encode(const std::shared_ptr<const HeifPixelImage>& image,
-                                            const heif_encoding_options& options) const
-{
-  if (image->has_channel(heif_channel_interleaved)) {
-    return false;
-  }
-
-  return true;
-}
-
-
-std::unique_ptr<const unc_encoder> unc_encoder_factory_bytealign_component_interleave::create(const std::shared_ptr<const HeifPixelImage>& image,
-                                                                      const heif_encoding_options& options) const
-{
-  return std::make_unique<unc_encoder_bytealign_component_interleave>(image, options);
-}
-
-
-unc_encoder_bytealign_component_interleave::unc_encoder_bytealign_component_interleave(const std::shared_ptr<const HeifPixelImage>& image,
-                                       const heif_encoding_options& options)
-{
-  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 (is_nonvisual) {
-      comp_type = static_cast<heif_uncompressed_component_type>(image->get_component_type(idx));
-    }
-    else {
-      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);
-      }
-    }
-
-    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});
-  }
-
-  // Build cmpd/uncC boxes
-  bool little_endian = false;
-
-  uint16_t box_index = 0;
-  for (const auto& comp : m_components) {
-    m_cmpd->add_component({comp.component_type});
-
-    uint8_t component_align_size = static_cast<uint8_t>((comp.bpp + 7) / 8);
-    if (comp.bpp % 8 == 0) {
-      component_align_size = 0;
-    }
-
-    if (comp.bpp > 8) {
-      little_endian = true;
-    }
-
-    m_uncC->add_component({box_index, comp.bpp, comp.component_format, component_align_size});
-    box_index++;
-  }
-
-  m_uncC->set_interleave_type(interleave_mode_component);
-  m_uncC->set_components_little_endian(little_endian);
-
-  if (image->get_chroma_format() == heif_chroma_420) {
-    m_uncC->set_sampling_type(sampling_mode_420);
-  }
-  else if (image->get_chroma_format() == heif_chroma_422) {
-    m_uncC->set_sampling_type(sampling_mode_422);
-  }
-  else {
-    m_uncC->set_sampling_type(sampling_mode_no_subsampling);
-  }
-
-  // --- compute bytes per pixel
-
-  m_bytes_per_pixel_x4 = 0;
-
-  for (const auto& comp : m_components) {
-    int bytes_per_pixel = 4 * (comp.bpp + 7) / 8;
-
-    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;
-  }
-}
-
-
-uint64_t unc_encoder_bytealign_component_interleave::compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const
-{
-  return tile_width * tile_height * m_bytes_per_pixel_x4 / 4;
-}
-
-
-std::vector<uint8_t> unc_encoder_bytealign_component_interleave::encode_tile(const std::shared_ptr<const HeifPixelImage>& src_image) const
-{
-  // compute total size of all components
-
-  uint64_t total_size = 0;
-
-  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 (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_component(comp.component_idx, &src_stride);
-
-    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;
-    }
-  }
-
-  return data;
-}
diff --git a/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.h b/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.h
deleted file mode 100644
index b6a7eb63..00000000
--- a/libheif/codecs/uncompressed/unc_encoder_bytealign_component_interleave.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * HEIF codec.
- * Copyright (c) 2026 Dirk Farin <dirk.farin@gmail.com>
- *
- * This file is part of libheif.
- *
- * libheif is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * libheif is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef LIBHEIF_UNC_ENCODER_BYTEALIGN_COMPONENT_INTERLEAVE_H
-#define LIBHEIF_UNC_ENCODER_BYTEALIGN_COMPONENT_INTERLEAVE_H
-
-#include "unc_encoder.h"
-#include "unc_types.h"
-
-#include <memory>
-#include <vector>
-
-class unc_encoder_bytealign_component_interleave : public unc_encoder
-{
-public:
-  unc_encoder_bytealign_component_interleave(const std::shared_ptr<const HeifPixelImage>& image,
-                     const heif_encoding_options& options);
-
-  uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const override;
-
-  [[nodiscard]] std::vector<uint8_t> encode_tile(const std::shared_ptr<const HeifPixelImage>& image) const override;
-
-private:
-  struct encoded_component
-  {
-    uint32_t component_idx;
-    heif_uncompressed_component_type component_type;
-    heif_uncompressed_component_format component_format;
-    uint8_t bpp;
-  };
-
-  std::vector<encoded_component> m_components;
-  uint32_t m_bytes_per_pixel_x4;
-};
-
-
-class unc_encoder_factory_bytealign_component_interleave : public unc_encoder_factory
-{
-public:
-
-private:
-  [[nodiscard]] bool can_encode(const std::shared_ptr<const HeifPixelImage>& image,
-                                const heif_encoding_options& options) const override;
-
-  std::unique_ptr<const unc_encoder> create(const std::shared_ptr<const HeifPixelImage>& image,
-                                            const heif_encoding_options& options) const override;
-};
-
-#endif //LIBHEIF_UNC_ENCODER_BYTEALIGN_COMPONENT_INTERLEAVE_H
diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
index ae5837bd..0bcd1a18 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
@@ -33,15 +33,7 @@ bool unc_encoder_factory_component_interleave::can_encode(const std::shared_ptr<
     return false;
   }

-  // Check if any component has non-byte-aligned bpp
-  uint32_t n = image->get_number_of_components();
-  for (uint32_t i = 0; i < n; i++) {
-    if (image->get_component_bits_per_pixel(i) % 8 != 0) {
-      return true;
-    }
-  }
-
-  return false;
+  return true;
 }


@@ -76,18 +68,31 @@ unc_encoder_component_interleave::unc_encoder_component_interleave(const std::sh
     }

     uint8_t bpp = image->get_component_bits_per_pixel(idx);
-    m_components.push_back({idx, ch, comp_type, bpp});
+    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 index = 0;
+  // Build cmpd/uncC boxes
+  bool little_endian = false;
+
+  uint16_t box_index = 0;
   for (const auto& comp : m_components) {
     m_cmpd->add_component({comp.component_type});
-    m_uncC->add_component({index, comp.bpp, component_format_unsigned, 0});
-    index++;
+
+    uint8_t component_align_size = 0;
+
+    if (comp.byte_aligned && comp.bpp > 8) {
+      little_endian = true;
+    }
+
+    m_uncC->add_component({box_index, comp.bpp, comp.component_format, component_align_size});
+    box_index++;
   }

   m_uncC->set_interleave_type(interleave_mode_component);
-  m_uncC->set_components_little_endian(false);
+  m_uncC->set_components_little_endian(little_endian);
   m_uncC->set_block_size(0);

   if (image->get_chroma_format() == heif_chroma_420) {
@@ -120,7 +125,13 @@ uint64_t unc_encoder_component_interleave::compute_tile_data_size_bytes(uint32_t
       }
     }

-    uint64_t row_bytes = (static_cast<uint64_t>(plane_width) * comp.bpp + 7) / 8;
+    uint64_t row_bytes;
+    if (comp.byte_aligned) {
+      row_bytes = static_cast<uint64_t>(plane_width) * ((comp.bpp + 7) / 8);
+    }
+    else {
+      row_bytes = (static_cast<uint64_t>(plane_width) * comp.bpp + 7) / 8;
+    }
     total += row_bytes * plane_height;
   }
   return total;
@@ -131,7 +142,9 @@ std::vector<uint8_t> unc_encoder_component_interleave::encode_tile(const std::sh
 {
   uint64_t total_size = compute_tile_data_size_bytes(src_image->get_width(), src_image->get_height());
   std::vector<uint8_t> data;
-  data.reserve(total_size);
+  data.resize(total_size);
+
+  uint64_t out_pos = 0;

   for (const auto& comp : m_components) {
     uint32_t plane_width = src_image->get_component_width(comp.component_idx);
@@ -141,39 +154,53 @@ std::vector<uint8_t> unc_encoder_component_interleave::encode_tile(const std::sh
     size_t src_stride;
     const uint8_t* src_data = src_image->get_component(comp.component_idx, &src_stride);

-    for (uint32_t y = 0; y < plane_height; y++) {
-      const uint8_t* row = src_data + src_stride * y;
-
-      uint64_t accumulator = 0;
-      int accumulated_bits = 0;
-
-      for (uint32_t x = 0; x < plane_width; x++) {
-        uint32_t sample;
+    if (comp.byte_aligned) {
+      // Byte-aligned path: memcpy per row
+      int bytes_per_pixel = (bpp + 7) / 8;

-        if (bpp <= 8) {
-          sample = row[x];
-        }
-        else if (bpp <= 16) {
-          sample = reinterpret_cast<const uint16_t*>(row)[x];
-        }
-        else {
-          sample = reinterpret_cast<const uint32_t*>(row)[x];
+      for (uint32_t y = 0; y < plane_height; y++) {
+        memcpy(data.data() + out_pos,
+               src_data + src_stride * y,
+               plane_width * bytes_per_pixel);
+        out_pos += plane_width * bytes_per_pixel;
+      }
+    }
+    else {
+      // Bit-packed path: bit accumulator with row-end flush
+      for (uint32_t y = 0; y < plane_height; y++) {
+        const uint8_t* row = src_data + src_stride * y;
+
+        uint64_t accumulator = 0;
+        int accumulated_bits = 0;
+
+        for (uint32_t x = 0; x < plane_width; x++) {
+          uint32_t sample;
+
+          if (bpp <= 8) {
+            sample = row[x];
+          }
+          else if (bpp <= 16) {
+            sample = reinterpret_cast<const uint16_t*>(row)[x];
+          }
+          else {
+            sample = reinterpret_cast<const uint32_t*>(row)[x];
+          }
+
+          accumulator = (accumulator << bpp) | sample;
+          accumulated_bits += bpp;
+
+          while (accumulated_bits >= 8) {
+            accumulated_bits -= 8;
+            data[out_pos++] = static_cast<uint8_t>(accumulator >> accumulated_bits);
+            accumulator &= (uint64_t{1} << accumulated_bits) - 1;
+          }
         }

-        accumulator = (accumulator << bpp) | sample;
-        accumulated_bits += bpp;
-
-        while (accumulated_bits >= 8) {
-          accumulated_bits -= 8;
-          data.push_back(static_cast<uint8_t>(accumulator >> accumulated_bits));
-          accumulator &= (uint64_t{1} << accumulated_bits) - 1;
+        // Flush partial byte at row end (pad with zeros in LSBs)
+        if (accumulated_bits > 0) {
+          data[out_pos++] = static_cast<uint8_t>(accumulator << (8 - accumulated_bits));
         }
       }
-
-      // Flush partial byte at row end (pad with zeros in LSBs)
-      if (accumulated_bits > 0) {
-        data.push_back(static_cast<uint8_t>(accumulator << (8 - accumulated_bits)));
-      }
     }
   }

diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.h b/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
index 6e837090..c5322292 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
@@ -43,7 +43,9 @@ private:
     uint32_t component_idx;
     heif_channel channel;
     heif_uncompressed_component_type component_type;
+    heif_uncompressed_component_format component_format;
     uint8_t bpp;
+    bool byte_aligned;
   };

   std::vector<channel_component> m_components;