Commit cb6e5249 for libheif

commit cb6e5249703fa2ca633e4e3129888a7082979f17
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Tue Feb 24 21:36:42 2026 +0100

    unci: assign Bayer pattern to heif_image

diff --git a/libheif/api/libheif/heif_image.h b/libheif/api/libheif/heif_image.h
index 7ba290ee..ae24712c 100644
--- a/libheif/api/libheif/heif_image.h
+++ b/libheif/api/libheif/heif_image.h
@@ -75,7 +75,9 @@ typedef enum heif_colorspace
   heif_colorspace_monochrome = 2,

   // Indicates that this image has no visual channels.
-  heif_colorspace_nonvisual = 3
+  heif_colorspace_nonvisual = 3,
+
+  heif_colorspace_filter_array = 4
 } heif_colorspace;

 typedef enum heif_channel
diff --git a/libheif/codecs/uncompressed/unc_boxes.cc b/libheif/codecs/uncompressed/unc_boxes.cc
index c76a2af2..67797b78 100644
--- a/libheif/codecs/uncompressed/unc_boxes.cc
+++ b/libheif/codecs/uncompressed/unc_boxes.cc
@@ -956,30 +956,30 @@ Error Box_cpat::parse(BitstreamRange& range, const heif_security_limits* limits)
     return unsupported_version_error("cpat");
   }

-  m_pattern_width = range.read16();
-  m_pattern_height = range.read16();
+  m_pattern.m_pattern_width = range.read16();
+  m_pattern.m_pattern_height = range.read16();

-  if (m_pattern_width == 0 || m_pattern_height == 0) {
+  if (m_pattern.m_pattern_width == 0 || m_pattern.m_pattern_height == 0) {
     return {heif_error_Invalid_input,
             heif_suberror_Invalid_parameter_value,
             "Zero Bayer pattern size."};
   }

   auto max_bayer_pattern_size = limits->max_bayer_pattern_pixels;
-  if (max_bayer_pattern_size && m_pattern_height > max_bayer_pattern_size / m_pattern_width) {
+  if (max_bayer_pattern_size && m_pattern.m_pattern_height > max_bayer_pattern_size / m_pattern.m_pattern_width) {
     return {heif_error_Invalid_input,
             heif_suberror_Security_limit_exceeded,
             "Maximum Bayer pattern size exceeded."};
   }

-  m_components.resize(size_t{m_pattern_width} * m_pattern_height);
+  m_pattern.m_components.resize(size_t{m_pattern.m_pattern_width} * m_pattern.m_pattern_height);

-  for (uint16_t i = 0; i < m_pattern_height; i++) {
-    for (uint16_t j = 0; j < m_pattern_width; j++) {
-      PatternComponent component{};
+  for (uint16_t i = 0; i < m_pattern.m_pattern_height; i++) {
+    for (uint16_t j = 0; j < m_pattern.m_pattern_width; j++) {
+      BayerPattern::PatternComponent component{};
       component.component_index = range.read32();
       component.component_gain = range.read_float32();
-      m_components[i] = component;
+      m_pattern.m_components[i] = component;
     }
   }

@@ -995,7 +995,7 @@ std::string Box_cpat::dump(Indent& indent) const
   sstr << indent << "pattern_width: " << get_pattern_width() << "\n";
   sstr << indent << "pattern_height: " << get_pattern_height() << "\n";

-  for (const auto& component : m_components) {
+  for (const auto& component : m_pattern.m_components) {
     sstr << indent << "component index: " << component.component_index << ", gain: " << component.component_gain << "\n";
   }
   return sstr.str();
@@ -1006,17 +1006,17 @@ Error Box_cpat::write(StreamWriter& writer) const
 {
   size_t box_start = reserve_box_header_space(writer);

-  if (m_pattern_width * size_t{m_pattern_height} != m_components.size()) {
+  if (m_pattern.m_pattern_width * size_t{m_pattern.m_pattern_height} != m_pattern.m_components.size()) {
     // needs to be rectangular
     return {heif_error_Usage_error,
             heif_suberror_Invalid_parameter_value,
             "incorrect number of pattern components"};
   }

-  writer.write16(m_pattern_width);
-  writer.write16(m_pattern_height);
+  writer.write16(m_pattern.m_pattern_width);
+  writer.write16(m_pattern.m_pattern_height);

-  for (const auto& component : m_components) {
+  for (const auto& component : m_pattern.m_components) {
     writer.write32(component.component_index);
     writer.write_float32(component.component_gain);
   }
@@ -1025,3 +1025,9 @@ Error Box_cpat::write(StreamWriter& writer) const

   return Error::Ok;
 }
+
+
+void Box_cpat::set_bayer_pattern(const BayerPattern& pattern)
+{
+  m_pattern = pattern;
+}
diff --git a/libheif/codecs/uncompressed/unc_boxes.h b/libheif/codecs/uncompressed/unc_boxes.h
index a0dba450..85d7e803 100644
--- a/libheif/codecs/uncompressed/unc_boxes.h
+++ b/libheif/codecs/uncompressed/unc_boxes.h
@@ -354,22 +354,20 @@ public:
     set_short_type(fourcc("cpat"));
   }

-  struct PatternComponent
-  {
-    uint32_t component_index;
-    float component_gain;
-  };
-
   uint16_t get_pattern_width() const
   {
-    return m_pattern_width;
+    return m_pattern.m_pattern_width;
   }

   uint16_t get_pattern_height() const
   {
-    return m_pattern_height;
+    return m_pattern.m_pattern_height;
   }

+  const BayerPattern& get_bayer_pattern() const { return m_pattern; }
+
+  void set_bayer_pattern(const BayerPattern& pattern);
+
   std::string dump(Indent&) const override;

   Error write(StreamWriter& writer) const override;
@@ -377,9 +375,7 @@ public:
 protected:
   Error parse(BitstreamRange& range, const heif_security_limits* limits) override;

-  uint16_t m_pattern_width = 0;
-  uint16_t m_pattern_height = 0;
-  std::vector<PatternComponent> m_components;
+  BayerPattern m_pattern;
 };


diff --git a/libheif/codecs/uncompressed/unc_codec.cc b/libheif/codecs/uncompressed/unc_codec.cc
index 354456b0..b3253a86 100644
--- a/libheif/codecs/uncompressed/unc_codec.cc
+++ b/libheif/codecs/uncompressed/unc_codec.cc
@@ -129,9 +129,8 @@ Error UncompressedImageCodec::get_heif_chroma_uncompressed(const std::shared_ptr
   }

   if (componentSet == (1 << component_type_filter_array)) {
-    // TODO - we should look up the components
-    *out_chroma = heif_chroma_monochrome;
-    *out_colourspace = heif_colorspace_monochrome;
+    *out_chroma = heif_chroma_monochrome; // TODO: ?
+    *out_colourspace = heif_colorspace_filter_array;
   }

   // TODO: more combinations
@@ -446,6 +445,7 @@ void UncompressedImageCodec::unci_properties::fill_from_image_item(const std::sh
   uncC = uncC_mut;
   cmpC = image->get_property<Box_cmpC>();
   icef = image->get_property<Box_icef>();
+  cpat = image->get_property<Box_cpat>();
 }


diff --git a/libheif/codecs/uncompressed/unc_codec.h b/libheif/codecs/uncompressed/unc_codec.h
index 8d722068..9dadbcdf 100644
--- a/libheif/codecs/uncompressed/unc_codec.h
+++ b/libheif/codecs/uncompressed/unc_codec.h
@@ -63,6 +63,7 @@ public:
     std::shared_ptr<const Box_uncC> uncC;
     std::shared_ptr<const Box_cmpC> cmpC;
     std::shared_ptr<const Box_icef> icef;
+    std::shared_ptr<const Box_cpat> cpat;
     // ...

     void fill_from_image_item(const std::shared_ptr<const ImageItem>&);
diff --git a/libheif/codecs/uncompressed/unc_decoder.cc b/libheif/codecs/uncompressed/unc_decoder.cc
index f58e0a54..5b287da2 100644
--- a/libheif/codecs/uncompressed/unc_decoder.cc
+++ b/libheif/codecs/uncompressed/unc_decoder.cc
@@ -454,6 +454,10 @@ Result<std::shared_ptr<HeifPixelImage> > unc_decoder::decode_full_image(

   auto img = *createImgResult;

+  if (properties.cpat) {
+    img->set_bayer_pattern(properties.cpat->get_bayer_pattern());
+  }
+
   auto decoderResult = unc_decoder_factory::get_unc_decoder(width, height, cmpd, uncC);
   if (!decoderResult) {
     return decoderResult.error();
diff --git a/libheif/codecs/uncompressed/unc_types.h b/libheif/codecs/uncompressed/unc_types.h
index 8213051b..de63f0da 100644
--- a/libheif/codecs/uncompressed/unc_types.h
+++ b/libheif/codecs/uncompressed/unc_types.h
@@ -23,6 +23,7 @@
 #define LIBHEIF_UNC_TYPES_H

 #include <cstdint>
+#include <vector>

 /**
  * Component type.
@@ -287,6 +288,18 @@ enum heif_uncompressed_interleave_mode
 };


+struct BayerPattern
+{
+  struct PatternComponent
+  {
+    uint32_t component_index;
+    float component_gain;
+  };
+
+  uint16_t m_pattern_width = 0;
+  uint16_t m_pattern_height = 0;
+  std::vector<PatternComponent> m_components;
+};


 #endif //LIBHEIF_UNC_TYPES_H
diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc
index 2ec48234..600c87b8 100644
--- a/libheif/pixelimage.cc
+++ b/libheif/pixelimage.cc
@@ -883,6 +883,9 @@ uint8_t HeifPixelImage::get_visual_image_bits_per_pixel() const
     case heif_colorspace_nonvisual:
       return 0;
       break;
+    case heif_colorspace_filter_array:
+      assert(has_channel(heif_channel_filter_array));
+      return get_bits_per_pixel(heif_channel_filter_array);
     default:
       assert(false);
       return 0;
diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h
index 47181119..2d5fba0a 100644
--- a/libheif/pixelimage.h
+++ b/libheif/pixelimage.h
@@ -39,6 +39,8 @@
 #include <cassert>
 #include <string>

+#include "codecs/uncompressed/unc_types.h"
+
 heif_chroma chroma_from_subsampling(int h, int v);

 uint32_t chroma_width(uint32_t w, heif_chroma chroma);
@@ -194,6 +196,12 @@ public:
   }
 #endif

+  void set_bayer_pattern(const BayerPattern& pattern) { m_bayer_pattern = pattern; }
+
+  bool has_bayer_pattern() const { return m_bayer_pattern.has_value(); }
+
+  const BayerPattern& get_bayer_pattern() const { assert(m_bayer_pattern); return *m_bayer_pattern; }
+
 private:
   bool m_premultiplied_alpha = false;
   nclx_profile m_color_profile_nclx = nclx_profile::undefined();
@@ -212,6 +220,8 @@ private:
   heif_omaf_image_projection m_omaf_image_projection = heif_omaf_image_projection::heif_omaf_image_projection_flat;
 #endif

+  std::optional<BayerPattern> m_bayer_pattern;
+
 protected:
   std::shared_ptr<Box_clli> get_clli_box() const;