Commit be56fd40 for libheif
commit be56fd40a8f51e6d78ae0b0bfbb1807df47673c6
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Thu Feb 26 20:13:52 2026 +0100
unci: add support for polarization pattern (splz)
diff --git a/libheif/api/libheif/heif_security.h b/libheif/api/libheif/heif_security.h
index d95378a0..3760db22 100644
--- a/libheif/api/libheif/heif_security.h
+++ b/libheif/api/libheif/heif_security.h
@@ -45,6 +45,7 @@ typedef struct heif_security_limits
// in 1.5 GB memory need for YUV-4:2:0 or 4 GB for RGB32.
uint64_t max_image_size_pixels;
uint64_t max_number_of_tiles;
+ // Also used for polarization pattern (splz) size limit.
uint32_t max_bayer_pattern_pixels;
uint32_t max_items;
diff --git a/libheif/api/libheif/heif_uncompressed.cc b/libheif/api/libheif/heif_uncompressed.cc
index c9b21f98..d40efcd0 100644
--- a/libheif/api/libheif/heif_uncompressed.cc
+++ b/libheif/api/libheif/heif_uncompressed.cc
@@ -25,6 +25,7 @@
#include "image-items/unc_image.h"
#include <array>
+#include <cstring>
#include <memory>
#include <algorithm>
@@ -105,6 +106,154 @@ heif_error heif_image_get_bayer_pattern(const heif_image* image,
}
+float heif_polarization_angle_no_filter()
+{
+ uint32_t bits = 0xFFFFFFFF;
+ float f;
+ memcpy(&f, &bits, sizeof(f));
+ return f;
+}
+
+
+int heif_polarization_angle_is_no_filter(float angle)
+{
+ uint32_t bits;
+ memcpy(&bits, &angle, sizeof(bits));
+ return bits == 0xFFFFFFFF;
+}
+
+
+heif_error heif_image_add_polarization_pattern(heif_image* image,
+ uint32_t num_component_indices,
+ const uint32_t* component_indices,
+ uint16_t pattern_width,
+ uint16_t pattern_height,
+ const float* polarization_angles)
+{
+ if (image == nullptr || polarization_angles == nullptr) {
+ return heif_error_null_pointer_argument;
+ }
+
+ if (num_component_indices > 0 && component_indices == nullptr) {
+ return heif_error_null_pointer_argument;
+ }
+
+ if (pattern_width == 0 || pattern_height == 0) {
+ return {heif_error_Usage_error,
+ heif_suberror_Invalid_parameter_value,
+ "Polarization pattern dimensions must be non-zero."};
+ }
+
+ PolarizationPattern pattern;
+ pattern.component_indices.assign(component_indices, component_indices + num_component_indices);
+ pattern.pattern_width = pattern_width;
+ pattern.pattern_height = pattern_height;
+
+ size_t num_pixels = size_t{pattern_width} * pattern_height;
+ pattern.polarization_angles.assign(polarization_angles, polarization_angles + num_pixels);
+
+ image->image->add_polarization_pattern(pattern);
+
+ return heif_error_success;
+}
+
+
+int heif_image_get_number_of_polarization_patterns(const heif_image* image)
+{
+ if (image == nullptr) {
+ return 0;
+ }
+
+ return static_cast<int>(image->image->get_polarization_patterns().size());
+}
+
+
+heif_error heif_image_get_polarization_pattern_info(const heif_image* image,
+ int pattern_index,
+ uint32_t* out_num_component_indices,
+ uint16_t* out_pattern_width,
+ uint16_t* out_pattern_height)
+{
+ if (image == nullptr) {
+ return heif_error_null_pointer_argument;
+ }
+
+ const auto& patterns = image->image->get_polarization_patterns();
+ if (pattern_index < 0 || static_cast<size_t>(pattern_index) >= patterns.size()) {
+ return {heif_error_Usage_error,
+ heif_suberror_Invalid_parameter_value,
+ "Polarization pattern index out of range."};
+ }
+
+ const auto& p = patterns[pattern_index];
+ if (out_num_component_indices) {
+ *out_num_component_indices = static_cast<uint32_t>(p.component_indices.size());
+ }
+ if (out_pattern_width) {
+ *out_pattern_width = p.pattern_width;
+ }
+ if (out_pattern_height) {
+ *out_pattern_height = p.pattern_height;
+ }
+
+ return heif_error_success;
+}
+
+
+heif_error heif_image_get_polarization_pattern_data(const heif_image* image,
+ int pattern_index,
+ uint32_t* out_component_indices,
+ float* out_polarization_angles)
+{
+ if (image == nullptr || out_polarization_angles == nullptr) {
+ return heif_error_null_pointer_argument;
+ }
+
+ const auto& patterns = image->image->get_polarization_patterns();
+ if (pattern_index < 0 || static_cast<size_t>(pattern_index) >= patterns.size()) {
+ return {heif_error_Usage_error,
+ heif_suberror_Invalid_parameter_value,
+ "Polarization pattern index out of range."};
+ }
+
+ const auto& p = patterns[pattern_index];
+
+ if (out_component_indices && !p.component_indices.empty()) {
+ std::copy(p.component_indices.begin(), p.component_indices.end(), out_component_indices);
+ }
+
+ size_t num_pixels = size_t{p.pattern_width} * p.pattern_height;
+ std::copy(p.polarization_angles.begin(), p.polarization_angles.begin() + num_pixels, out_polarization_angles);
+
+ return heif_error_success;
+}
+
+
+int heif_image_get_polarization_pattern_index_for_component(const heif_image* image,
+ uint32_t component_index)
+{
+ if (image == nullptr) {
+ return -1;
+ }
+
+ const auto& patterns = image->image->get_polarization_patterns();
+ for (size_t i = 0; i < patterns.size(); i++) {
+ const auto& p = patterns[i];
+ if (p.component_indices.empty()) {
+ // Empty component list means pattern applies to all components.
+ return static_cast<int>(i);
+ }
+ for (uint32_t idx : p.component_indices) {
+ if (idx == component_index) {
+ return static_cast<int>(i);
+ }
+ }
+ }
+
+ return -1;
+}
+
+
heif_unci_image_parameters* heif_unci_image_parameters_alloc()
{
auto* params = new heif_unci_image_parameters();
diff --git a/libheif/api/libheif/heif_uncompressed.h b/libheif/api/libheif/heif_uncompressed.h
index 3f5cf0cb..c6e680d2 100644
--- a/libheif/api/libheif/heif_uncompressed.h
+++ b/libheif/api/libheif/heif_uncompressed.h
@@ -94,6 +94,64 @@ LIBHEIF_API
heif_error heif_image_get_bayer_pattern(const heif_image*,
heif_bayer_pattern_pixel* out_patternPixels);
+// --- Polarization pattern (ISO 23001-17, Section 6.1.5)
+
+// Special float value indicating "no polarization filter" at a pattern position.
+// On the wire this is the IEEE 754 bit pattern 0xFFFFFFFF (a signaling NaN).
+// Test with heif_polarization_angle_is_no_filter() below, or with isnan()/std::isnan().
+
+// Returns a float with the 0xFFFFFFFF bit pattern (NaN) representing "no polarization filter".
+LIBHEIF_API
+float heif_polarization_angle_no_filter(void);
+
+// Returns non-zero if the given angle has the "no filter" bit pattern (0xFFFFFFFF).
+LIBHEIF_API
+int heif_polarization_angle_is_no_filter(float angle);
+
+// Add a polarization pattern to an image.
+// component_indices: array of component indices this pattern applies to (may be NULL if num_component_indices == 0,
+// meaning the pattern applies to all components).
+// polarization_angles: array of pattern_width * pattern_height float values.
+// Each is an angle in degrees [0.0, 360.0), or heif_polarization_angle_no_filter() for "no filter".
+// Multiple patterns can be added (one per distinct component group).
+LIBHEIF_API
+heif_error heif_image_add_polarization_pattern(heif_image*,
+ uint32_t num_component_indices,
+ const uint32_t* component_indices,
+ uint16_t pattern_width,
+ uint16_t pattern_height,
+ const float* polarization_angles);
+
+// Returns the number of polarization patterns on this image (0 if none).
+LIBHEIF_API
+int heif_image_get_number_of_polarization_patterns(const heif_image*);
+
+// Get the sizes/dimensions of a polarization pattern (to allocate arrays for the data query).
+LIBHEIF_API
+heif_error heif_image_get_polarization_pattern_info(const heif_image*,
+ int pattern_index,
+ uint32_t* out_num_component_indices,
+ uint16_t* out_pattern_width,
+ uint16_t* out_pattern_height);
+
+// Get the actual data of a polarization pattern.
+// Caller must provide pre-allocated arrays:
+// out_component_indices: num_component_indices entries (may be NULL if num_component_indices == 0)
+// out_polarization_angles: pattern_width * pattern_height entries
+LIBHEIF_API
+heif_error heif_image_get_polarization_pattern_data(const heif_image*,
+ int pattern_index,
+ uint32_t* out_component_indices,
+ float* out_polarization_angles);
+
+// Find the polarization pattern index that applies to a given component index.
+// Returns the pattern index (>= 0), or -1 if no pattern matches.
+// A pattern with an empty component list (component_count == 0) matches all components.
+LIBHEIF_API
+int heif_image_get_polarization_pattern_index_for_component(const heif_image*,
+ uint32_t component_index);
+
+
// --- 'unci' images
// This is similar to heif_metadata_compression. We should try to keep the integers compatible, but each enum will just
diff --git a/libheif/box.cc b/libheif/box.cc
index bae957fb..945d1de9 100644
--- a/libheif/box.cc
+++ b/libheif/box.cc
@@ -672,6 +672,10 @@ Error Box::read(BitstreamRange& range, std::shared_ptr<Box>* result, const heif_
box = std::make_shared<Box_cpat>();
break;
+ case fourcc("splz"):
+ box = std::make_shared<Box_splz>();
+ break;
+
case fourcc("uncv"):
box = std::make_shared<Box_uncv>();
break;
diff --git a/libheif/codecs/uncompressed/unc_boxes.cc b/libheif/codecs/uncompressed/unc_boxes.cc
index 26396f6d..0ddcd106 100644
--- a/libheif/codecs/uncompressed/unc_boxes.cc
+++ b/libheif/codecs/uncompressed/unc_boxes.cc
@@ -1023,3 +1023,102 @@ Error Box_cpat::write(StreamWriter& writer) const
return Error::Ok;
}
+
+
+Error Box_splz::parse(BitstreamRange& range, const heif_security_limits* limits)
+{
+ parse_full_box_header(range);
+
+ if (get_version() != 0) {
+ return unsupported_version_error("splz");
+ }
+
+ uint32_t component_count = range.read32();
+ m_pattern.component_indices.resize(component_count);
+ for (uint32_t i = 0; i < component_count; i++) {
+ m_pattern.component_indices[i] = range.read32();
+ }
+
+ m_pattern.pattern_width = range.read16();
+ m_pattern.pattern_height = range.read16();
+
+ if (m_pattern.pattern_width == 0 || m_pattern.pattern_height == 0) {
+ return {heif_error_Invalid_input,
+ heif_suberror_Invalid_parameter_value,
+ "Zero polarization pattern size."};
+ }
+
+ auto max_pattern_size = limits->max_bayer_pattern_pixels;
+ if (max_pattern_size && m_pattern.pattern_height > max_pattern_size / m_pattern.pattern_width) {
+ return {heif_error_Invalid_input,
+ heif_suberror_Security_limit_exceeded,
+ "Maximum polarization pattern size exceeded."};
+ }
+
+ size_t num_pixels = size_t{m_pattern.pattern_width} * m_pattern.pattern_height;
+ m_pattern.polarization_angles.resize(num_pixels);
+
+ for (size_t i = 0; i < num_pixels; i++) {
+ m_pattern.polarization_angles[i] = range.read_float32();
+ }
+
+ return range.get_error();
+}
+
+
+std::string Box_splz::dump(Indent& indent) const
+{
+ std::ostringstream sstr;
+
+ sstr << FullBox::dump(indent);
+
+ sstr << indent << "component_count: " << m_pattern.component_indices.size() << "\n";
+ for (size_t i = 0; i < m_pattern.component_indices.size(); i++) {
+ sstr << indent << " component_index[" << i << "]: " << m_pattern.component_indices[i] << "\n";
+ }
+
+ sstr << indent << "pattern_width: " << m_pattern.pattern_width << "\n";
+ sstr << indent << "pattern_height: " << m_pattern.pattern_height << "\n";
+
+ for (uint16_t y = 0; y < m_pattern.pattern_height; y++) {
+ for (uint16_t x = 0; x < m_pattern.pattern_width; x++) {
+ float angle = m_pattern.polarization_angles[y * m_pattern.pattern_width + x];
+ if (heif_polarization_angle_is_no_filter(angle)) {
+ sstr << indent << " [" << x << "," << y << "]: no filter\n";
+ }
+ else {
+ sstr << indent << " [" << x << "," << y << "]: " << angle << " degrees\n";
+ }
+ }
+ }
+
+ return sstr.str();
+}
+
+
+Error Box_splz::write(StreamWriter& writer) const
+{
+ size_t box_start = reserve_box_header_space(writer);
+
+ if (m_pattern.pattern_width * size_t{m_pattern.pattern_height} != m_pattern.polarization_angles.size()) {
+ return {heif_error_Usage_error,
+ heif_suberror_Invalid_parameter_value,
+ "incorrect number of polarization pattern angles"};
+ }
+
+ writer.write32(static_cast<uint32_t>(m_pattern.component_indices.size()));
+ for (uint32_t idx : m_pattern.component_indices) {
+ writer.write32(idx);
+ }
+
+ writer.write16(m_pattern.pattern_width);
+ writer.write16(m_pattern.pattern_height);
+
+ for (float angle : m_pattern.polarization_angles) {
+ writer.write_float32(angle);
+ }
+
+ prepend_header(writer, box_start);
+
+ return Error::Ok;
+}
diff --git a/libheif/codecs/uncompressed/unc_boxes.h b/libheif/codecs/uncompressed/unc_boxes.h
index 013f357d..e2f22962 100644
--- a/libheif/codecs/uncompressed/unc_boxes.h
+++ b/libheif/codecs/uncompressed/unc_boxes.h
@@ -374,6 +374,35 @@ protected:
};
+/**
+ * Polarization pattern definition box (splz).
+ *
+ * Describes the polarization filter array pattern on an image sensor.
+ * Multiple splz boxes can exist (one per set of components with
+ * different polarization filters).
+ *
+ * This is from ISO/IEC 23001-17 Section 6.1.5.
+ */
+class Box_splz : public FullBox
+{
+public:
+ Box_splz() { set_short_type(fourcc("splz")); }
+
+ const PolarizationPattern& get_pattern() const { return m_pattern; }
+
+ void set_pattern(const PolarizationPattern& pattern) { m_pattern = pattern; }
+
+ std::string dump(Indent&) const override;
+
+ Error write(StreamWriter& writer) const override;
+
+protected:
+ Error parse(BitstreamRange& range, const heif_security_limits* limits) override;
+
+ PolarizationPattern m_pattern;
+};
+
+
void fill_uncC_and_cmpd_from_profile(const std::shared_ptr<Box_uncC>& uncC,
std::shared_ptr<Box_cmpd>& cmpd);
diff --git a/libheif/codecs/uncompressed/unc_codec.cc b/libheif/codecs/uncompressed/unc_codec.cc
index f9d54f8c..787a501c 100644
--- a/libheif/codecs/uncompressed/unc_codec.cc
+++ b/libheif/codecs/uncompressed/unc_codec.cc
@@ -447,6 +447,7 @@ void UncompressedImageCodec::unci_properties::fill_from_image_item(const std::sh
cmpC = image->get_property<Box_cmpC>();
icef = image->get_property<Box_icef>();
cpat = image->get_property<Box_cpat>();
+ splz = image->get_all_properties<Box_splz>();
}
diff --git a/libheif/codecs/uncompressed/unc_codec.h b/libheif/codecs/uncompressed/unc_codec.h
index 9dadbcdf..8ad09922 100644
--- a/libheif/codecs/uncompressed/unc_codec.h
+++ b/libheif/codecs/uncompressed/unc_codec.h
@@ -64,7 +64,7 @@ public:
std::shared_ptr<const Box_cmpC> cmpC;
std::shared_ptr<const Box_icef> icef;
std::shared_ptr<const Box_cpat> cpat;
- // ...
+ std::vector<std::shared_ptr<const Box_splz>> splz;
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 eade4360..b47779e0 100644
--- a/libheif/codecs/uncompressed/unc_decoder.cc
+++ b/libheif/codecs/uncompressed/unc_decoder.cc
@@ -470,6 +470,10 @@ Result<std::shared_ptr<HeifPixelImage> > unc_decoder::decode_full_image(
img->set_bayer_pattern(pattern);
}
+ for (const auto& splz_box : properties.splz) {
+ img->add_polarization_pattern(splz_box->get_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_enc.cc b/libheif/codecs/uncompressed/unc_enc.cc
index 9f9be044..4aa66f19 100644
--- a/libheif/codecs/uncompressed/unc_enc.cc
+++ b/libheif/codecs/uncompressed/unc_enc.cc
@@ -67,6 +67,7 @@ std::shared_ptr<class Box_VisualSampleEntry> Encoder_uncompressed::get_sample_de
case fourcc("cmpC"):
case fourcc("icef"):
case fourcc("cpat"):
+ case fourcc("splz"):
uncv->append_child_box(prop);
break;
}
diff --git a/libheif/codecs/uncompressed/unc_encoder.cc b/libheif/codecs/uncompressed/unc_encoder.cc
index f5ccc0b5..77dc871f 100644
--- a/libheif/codecs/uncompressed/unc_encoder.cc
+++ b/libheif/codecs/uncompressed/unc_encoder.cc
@@ -164,6 +164,10 @@ Result<Encoder::CodedImageData> unc_encoder::encode_static(const std::shared_ptr
codedImageData.properties.push_back(m_cpat);
}
+ for (const auto& splz : m_splz) {
+ codedImageData.properties.push_back(splz);
+ }
+
// --- encode image
diff --git a/libheif/codecs/uncompressed/unc_encoder.h b/libheif/codecs/uncompressed/unc_encoder.h
index d9565ebf..1187ed72 100644
--- a/libheif/codecs/uncompressed/unc_encoder.h
+++ b/libheif/codecs/uncompressed/unc_encoder.h
@@ -32,6 +32,7 @@
class Box_uncC;
class Box_cmpd;
class Box_cpat;
+class Box_splz;
class HeifPixelImage;
heif_uncompressed_component_type heif_channel_to_component_type(heif_channel channel);
@@ -49,6 +50,7 @@ public:
std::shared_ptr<Box_cmpd> get_cmpd() const { return m_cmpd; }
std::shared_ptr<Box_uncC> get_uncC() const { return m_uncC; }
std::shared_ptr<Box_cpat> get_cpat() const { return m_cpat; }
+ std::vector<std::shared_ptr<Box_splz>> get_splz() const { return m_splz; }
virtual uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const = 0;
@@ -65,6 +67,7 @@ protected:
std::shared_ptr<Box_cmpd> m_cmpd;
std::shared_ptr<Box_uncC> m_uncC;
std::shared_ptr<Box_cpat> m_cpat;
+ std::vector<std::shared_ptr<Box_splz>> m_splz;
};
diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
index 0b5b2aab..06f48117 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
@@ -143,6 +143,14 @@ unc_encoder_component_interleave::unc_encoder_component_interleave(const std::sh
m_cpat = std::make_shared<Box_cpat>();
m_cpat->set_pattern(cpat_pattern);
}
+
+ if (image->has_polarization_patterns()) {
+ for (const auto& pol : image->get_polarization_patterns()) {
+ auto splz = std::make_shared<Box_splz>();
+ splz->set_pattern(pol);
+ m_splz.push_back(splz);
+ }
+ }
}
diff --git a/libheif/image-items/image_item.h b/libheif/image-items/image_item.h
index 81e28c51..8c0abe8e 100644
--- a/libheif/image-items/image_item.h
+++ b/libheif/image-items/image_item.h
@@ -111,6 +111,18 @@ public:
return nullptr;
}
+ template<class BoxType>
+ std::vector<std::shared_ptr<const BoxType>> get_all_properties() const
+ {
+ std::vector<std::shared_ptr<const BoxType>> result;
+ for (auto& property : m_properties) {
+ if (auto box = std::dynamic_pointer_cast<const BoxType>(property)) {
+ result.push_back(box);
+ }
+ }
+ return result;
+ }
+
heif_property_id add_property(std::shared_ptr<Box> property, bool essential);
heif_property_id add_property_without_deduplication(std::shared_ptr<Box> property, bool essential);
diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h
index 77f0c128..788248b1 100644
--- a/libheif/pixelimage.h
+++ b/libheif/pixelimage.h
@@ -48,6 +48,15 @@ struct BayerPattern
std::vector<heif_bayer_pattern_pixel> pixels;
};
+struct PolarizationPattern
+{
+ std::vector<uint32_t> component_indices; // empty = applies to all components
+ uint16_t pattern_width = 0;
+ uint16_t pattern_height = 0;
+ std::vector<float> polarization_angles; // pattern_width * pattern_height entries
+ // 0xFFFFFFFF bit-pattern (NaN) = no polarization filter
+};
+
heif_chroma chroma_from_subsampling(int h, int v);
uint32_t chroma_width(uint32_t w, heif_chroma chroma);
@@ -198,6 +207,18 @@ public:
virtual void set_bayer_pattern(const BayerPattern& pattern) { m_bayer_pattern = pattern; }
+
+ // --- polarization pattern
+
+ bool has_polarization_patterns() const { return !m_polarization_patterns.empty(); }
+
+ const std::vector<PolarizationPattern>& get_polarization_patterns() const { return m_polarization_patterns; }
+
+ virtual void set_polarization_patterns(const std::vector<PolarizationPattern>& p) { m_polarization_patterns = p; }
+
+ virtual void add_polarization_pattern(const PolarizationPattern& p) { m_polarization_patterns.push_back(p); }
+
+
#if HEIF_WITH_OMAF
bool has_omaf_image_projection() const {
return (m_omaf_image_projection != heif_omaf_image_projection_flat);
@@ -228,6 +249,8 @@ private:
std::optional<BayerPattern> m_bayer_pattern;
+ std::vector<PolarizationPattern> m_polarization_patterns;
+
#if HEIF_WITH_OMAF
heif_omaf_image_projection m_omaf_image_projection = heif_omaf_image_projection::heif_omaf_image_projection_flat;
#endif