Commit 0b99ba41 for libheif
commit 0b99ba418cdccc905905a64ebb6a1223d1ccd29c
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Fri Feb 27 15:18:27 2026 +0100
gimi: implement unci component IDs
diff --git a/examples/heif_info.cc b/examples/heif_info.cc
index 46f1eea2..c26a73d9 100644
--- a/examples/heif_info.cc
+++ b/examples/heif_info.cc
@@ -44,6 +44,8 @@
#include <libheif/heif_experimental.h>
#include "libheif/heif_sequences.h"
#include <libheif/heif_text.h>
+#include <libheif/heif_image_handle.h>
+#include <libheif/heif_uncompressed.h>
#include <fstream>
#include <iostream>
@@ -778,6 +780,36 @@ int main(int argc, char** argv)
properties_shown = true;
}
+ // --- cmpd components
+
+ uint32_t num_cmpd_components = heif_image_handle_get_number_of_cmpd_components(handle);
+ if (num_cmpd_components > 0) {
+ int num_content_ids = heif_image_handle_has_gimi_component_content_ids(handle);
+
+ std::cout << " components:\n";
+ for (uint32_t i = 0; i < num_cmpd_components; i++) {
+ uint16_t type = heif_image_handle_get_cmpd_component_type(handle, i);
+ std::cout << " [" << i << "] type: " << type;
+
+ const char* uri = heif_image_handle_get_cmpd_component_type_uri(handle, i);
+ if (uri) {
+ std::cout << " (uri: " << uri << ")";
+ heif_string_release(uri);
+ }
+
+ if (num_content_ids > 0) {
+ const char* content_id = heif_image_handle_get_gimi_component_content_id(handle, i);
+ if (content_id) {
+ std::cout << " content ID: " << content_id;
+ heif_string_release(content_id);
+ }
+ }
+
+ std::cout << "\n";
+ }
+ properties_shown = true;
+ }
+
#if HEIF_WITH_OMAF
// --- OMAF
diff --git a/libheif/api/libheif/heif_image_handle.cc b/libheif/api/libheif/heif_image_handle.cc
index 5423b142..d996a7d0 100644
--- a/libheif/api/libheif/heif_image_handle.cc
+++ b/libheif/api/libheif/heif_image_handle.cc
@@ -21,7 +21,11 @@
#include "heif_image_handle.h"
#include "api_structs.h"
#include "box.h"
+#if WITH_UNCOMPRESSED_CODEC
+#include "codecs/uncompressed/unc_boxes.h"
+#endif
#include <climits>
+#include <cstring>
#include <string>
#include <memory>
@@ -186,3 +190,108 @@ void heif_image_handle_set_gimi_content_id(heif_image_handle* handle, const char
handle->context->add_property(handle->image->get_id(), gimi_box, false);
handle->image->set_gimi_sample_content_id(content_id);
}
+
+
+uint32_t heif_image_handle_get_number_of_cmpd_components(const heif_image_handle* handle)
+{
+#if WITH_UNCOMPRESSED_CODEC
+ if (!handle) {
+ return 0;
+ }
+ auto cmpd = handle->image->get_property<Box_cmpd>();
+ if (!cmpd) {
+ return 0;
+ }
+ return static_cast<uint32_t>(cmpd->get_components().size());
+#else
+ return 0;
+#endif
+}
+
+
+uint16_t heif_image_handle_get_cmpd_component_type(const heif_image_handle* handle, uint32_t component_idx)
+{
+#if WITH_UNCOMPRESSED_CODEC
+ if (!handle) {
+ return 0;
+ }
+ auto cmpd = handle->image->get_property<Box_cmpd>();
+ if (!cmpd) {
+ return 0;
+ }
+ const auto& components = cmpd->get_components();
+ if (component_idx >= components.size()) {
+ return 0;
+ }
+ return components[component_idx].component_type;
+#else
+ return 0;
+#endif
+}
+
+
+const char* heif_image_handle_get_cmpd_component_type_uri(const heif_image_handle* handle, uint32_t component_idx)
+{
+#if WITH_UNCOMPRESSED_CODEC
+ if (!handle) {
+ return nullptr;
+ }
+ auto cmpd = handle->image->get_property<Box_cmpd>();
+ if (!cmpd) {
+ return nullptr;
+ }
+ const auto& components = cmpd->get_components();
+ if (component_idx >= components.size()) {
+ return nullptr;
+ }
+ const auto& uri = components[component_idx].component_type_uri;
+ if (uri.empty()) {
+ return nullptr;
+ }
+ char* uristring = new char[uri.size() + 1];
+ strcpy(uristring, uri.c_str());
+ return uristring;
+#else
+ return nullptr;
+#endif
+}
+
+
+int heif_image_handle_has_gimi_component_content_ids(const heif_image_handle* handle)
+{
+#if WITH_UNCOMPRESSED_CODEC
+ if (!handle) {
+ return 0;
+ }
+ auto box = handle->image->get_property<Box_gimi_component_content_ids>();
+ if (!box) {
+ return 0;
+ }
+ return static_cast<int>(box->get_content_ids().size());
+#else
+ return 0;
+#endif
+}
+
+
+const char* heif_image_handle_get_gimi_component_content_id(const heif_image_handle* handle, uint32_t component_idx)
+{
+#if WITH_UNCOMPRESSED_CODEC
+ if (!handle) {
+ return nullptr;
+ }
+ auto box = handle->image->get_property<Box_gimi_component_content_ids>();
+ if (!box) {
+ return nullptr;
+ }
+ const auto& ids = box->get_content_ids();
+ if (component_idx >= ids.size()) {
+ return nullptr;
+ }
+ char* idstring = new char[ids[component_idx].size() + 1];
+ strcpy(idstring, ids[component_idx].c_str());
+ return idstring;
+#else
+ return nullptr;
+#endif
+}
diff --git a/libheif/api/libheif/heif_image_handle.h b/libheif/api/libheif/heif_image_handle.h
index d14dae02..3b4f811b 100644
--- a/libheif/api/libheif/heif_image_handle.h
+++ b/libheif/api/libheif/heif_image_handle.h
@@ -125,6 +125,37 @@ const char* heif_image_handle_get_gimi_content_id(const heif_image_handle* handl
LIBHEIF_API
void heif_image_handle_set_gimi_content_id(heif_image_handle* handle, const char* content_id);
+
+// --- cmpd component queries
+
+// Returns the number of components in the cmpd box, or 0 if no cmpd property exists.
+LIBHEIF_API
+uint32_t heif_image_handle_get_number_of_cmpd_components(const heif_image_handle*);
+
+// Returns the component_type for the given cmpd component index.
+// Returns 0 if out of range or no cmpd property.
+LIBHEIF_API
+uint16_t heif_image_handle_get_cmpd_component_type(const heif_image_handle*, uint32_t component_idx);
+
+// Returns the component_type_uri for the given cmpd component index (component_type >= 0x8000).
+// Returns NULL if the component does not have a URI.
+// The returned string must be freed with heif_string_release().
+LIBHEIF_API
+const char* heif_image_handle_get_cmpd_component_type_uri(const heif_image_handle*, uint32_t component_idx);
+
+
+// --- GIMI component content IDs (handle-level)
+
+// Returns non-zero (count of content IDs) if an ItemComponentContentIDProperty is set, 0 otherwise.
+LIBHEIF_API
+int heif_image_handle_has_gimi_component_content_ids(const heif_image_handle*);
+
+// Returns the GIMI component content ID for the given component index.
+// Returns NULL if no ItemComponentContentIDProperty is set or index is out of range.
+// The returned string must be freed with heif_string_release().
+LIBHEIF_API
+const char* heif_image_handle_get_gimi_component_content_id(const heif_image_handle*, uint32_t component_idx);
+
#ifdef __cplusplus
}
#endif
diff --git a/libheif/api/libheif/heif_uncompressed.cc b/libheif/api/libheif/heif_uncompressed.cc
index b1169a72..5ed77ef6 100644
--- a/libheif/api/libheif/heif_uncompressed.cc
+++ b/libheif/api/libheif/heif_uncompressed.cc
@@ -763,6 +763,53 @@ heif_image_get_component_X(complex32, heif_complex32)
heif_image_get_component_X(complex64, heif_complex64)
+// --- GIMI component content IDs
+
+int heif_image_has_component_content_ids(const heif_image* image)
+{
+ if (!image || !image->image) {
+ return 0;
+ }
+ return static_cast<int>(image->image->get_component_content_ids().size());
+}
+
+
+const char* heif_image_get_component_content_id(const heif_image* image, uint32_t component_idx)
+{
+ if (!image || !image->image || !image->image->has_component_content_ids()) {
+ return nullptr;
+ }
+
+ const auto& ids = image->image->get_component_content_ids();
+ if (component_idx >= ids.size()) {
+ return nullptr;
+ }
+
+ char* idstring = new char[ids[component_idx].size() + 1];
+ strcpy(idstring, ids[component_idx].c_str());
+ return idstring;
+}
+
+
+heif_error heif_image_set_component_content_id(heif_image* image,
+ uint32_t component_idx,
+ const char* content_id)
+{
+ if (!image || !image->image || !content_id) {
+ return heif_error_null_pointer_argument;
+ }
+
+ auto ids = image->image->get_component_content_ids();
+ if (component_idx >= ids.size()) {
+ ids.resize(component_idx + 1);
+ }
+ ids[component_idx] = content_id;
+ image->image->set_component_content_ids(ids);
+
+ return heif_error_success;
+}
+
+
heif_error heif_context_add_empty_unci_image(heif_context* ctx,
const heif_unci_image_parameters* parameters,
const heif_encoding_options* encoding_options,
diff --git a/libheif/api/libheif/heif_uncompressed.h b/libheif/api/libheif/heif_uncompressed.h
index e8ac61b3..ea36071f 100644
--- a/libheif/api/libheif/heif_uncompressed.h
+++ b/libheif/api/libheif/heif_uncompressed.h
@@ -387,6 +387,26 @@ const heif_complex64* heif_image_get_component_complex64_readonly(const heif_ima
LIBHEIF_API
heif_complex64* heif_image_get_component_complex64(heif_image*, uint32_t component_idx, size_t* out_stride);
+
+// --- GIMI component content IDs
+
+// Returns non-zero (the number of content IDs) if component content IDs are set, 0 otherwise.
+LIBHEIF_API
+int heif_image_has_component_content_ids(const heif_image*);
+
+// Get a component content ID by cmpd component index.
+// Returns NULL if no content IDs are set or if the index is out of range.
+// The returned string must be freed with heif_string_release().
+LIBHEIF_API
+const char* heif_image_get_component_content_id(const heif_image*, uint32_t component_idx);
+
+// Set a component content ID for a single cmpd component.
+// If the internal array is too small, it will be resized (new entries default to empty strings).
+LIBHEIF_API
+heif_error heif_image_set_component_content_id(heif_image*,
+ uint32_t component_idx,
+ const char* content_id);
+
#ifdef __cplusplus
}
#endif
diff --git a/libheif/box.cc b/libheif/box.cc
index 60da3edb..fd3c001d 100644
--- a/libheif/box.cc
+++ b/libheif/box.cc
@@ -773,6 +773,11 @@ Error Box::read(BitstreamRange& range, std::shared_ptr<Box>* result, const heif_
else if (hdr.get_uuid_type() == std::vector<uint8_t>{0x26, 0x1e, 0xf3, 0x74, 0x1d, 0x97, 0x5b, 0xba, 0xac, 0xbd, 0x9d, 0x2c, 0x8e, 0xa7, 0x35, 0x22}) {
box = std::make_shared<Box_gimi_content_id>();
}
+#if WITH_UNCOMPRESSED_CODEC
+ else if (hdr.get_uuid_type() == std::vector<uint8_t>{0x9d, 0xb9, 0xdd, 0x6e, 0x37, 0x3c, 0x5a, 0x4e, 0x81, 0x10, 0x21, 0xfc, 0x83, 0xa9, 0x11, 0xfd}) {
+ box = std::make_shared<Box_gimi_component_content_ids>();
+ }
+#endif
else {
box = std::make_shared<Box_other>(hdr.get_short_type());
}
@@ -5177,4 +5182,3 @@ std::string Box_gimi_content_id::dump(Indent& indent) const
return sstr.str();
}
-
diff --git a/libheif/codecs/uncompressed/unc_boxes.cc b/libheif/codecs/uncompressed/unc_boxes.cc
index e5c56fdc..06296ef1 100644
--- a/libheif/codecs/uncompressed/unc_boxes.cc
+++ b/libheif/codecs/uncompressed/unc_boxes.cc
@@ -1405,3 +1405,58 @@ Error Box_cloc::write(StreamWriter& writer) const
return Error::Ok;
}
+
+
+Error Box_gimi_component_content_ids::parse(BitstreamRange& range, const heif_security_limits* limits)
+{
+ uint32_t number_of_components = range.read32();
+
+ if (limits->max_components && number_of_components > limits->max_components) {
+ std::stringstream sstr;
+ sstr << "GIMI component content IDs box contains " << number_of_components
+ << " components, but security limit is set to " << limits->max_components << " components";
+ return {heif_error_Invalid_input,
+ heif_suberror_Security_limit_exceeded,
+ sstr.str()};
+ }
+
+ for (uint32_t i = 0; i < number_of_components; i++) {
+ if (range.eof()) {
+ return {heif_error_Invalid_input,
+ heif_suberror_End_of_data,
+ "Not enough data for all component content IDs"};
+ }
+ m_content_ids.push_back(range.read_string());
+ }
+
+ return range.get_error();
+}
+
+
+Error Box_gimi_component_content_ids::write(StreamWriter& writer) const
+{
+ size_t box_start = reserve_box_header_space(writer);
+
+ writer.write32(static_cast<uint32_t>(m_content_ids.size()));
+
+ for (const auto& id : m_content_ids) {
+ writer.write(id);
+ }
+
+ prepend_header(writer, box_start);
+
+ return Error::Ok;
+}
+
+
+std::string Box_gimi_component_content_ids::dump(Indent& indent) const
+{
+ std::ostringstream sstr;
+ sstr << Box::dump(indent);
+
+ for (size_t i = 0; i < m_content_ids.size(); i++) {
+ sstr << indent << "[" << i << "] content ID: " << m_content_ids[i] << "\n";
+ }
+
+ return sstr.str();
+}
diff --git a/libheif/codecs/uncompressed/unc_boxes.h b/libheif/codecs/uncompressed/unc_boxes.h
index e6878041..d6a06243 100644
--- a/libheif/codecs/uncompressed/unc_boxes.h
+++ b/libheif/codecs/uncompressed/unc_boxes.h
@@ -495,4 +495,45 @@ public:
};
+/**
+ * GIMI ItemComponentContentIDProperty.
+ *
+ * A UUID-type item property that assigns a unique Content ID string
+ * to each cmpd component of an image item.
+ *
+ * UUID: 9db9dd6e-373c-5a4e-8110-21fc83a911fd
+ */
+class Box_gimi_component_content_ids : public Box
+{
+public:
+ Box_gimi_component_content_ids()
+ {
+ set_uuid_type(std::vector<uint8_t>{0x9d, 0xb9, 0xdd, 0x6e, 0x37, 0x3c, 0x5a, 0x4e,
+ 0x81, 0x10, 0x21, 0xfc, 0x83, 0xa9, 0x11, 0xfd});
+ }
+
+ bool is_essential() const override { return false; }
+
+ bool is_transformative_property() const override { return false; }
+
+ std::string dump(Indent&) const override;
+
+ const char* debug_box_name() const override { return "GIMI Component Content IDs"; }
+
+ const std::vector<std::string>& get_content_ids() const { return m_content_ids; }
+
+ void set_content_ids(const std::vector<std::string>& ids) { m_content_ids = ids; }
+
+ [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::ignorable; }
+
+protected:
+ Error parse(BitstreamRange& range, const heif_security_limits*) override;
+
+ Error write(StreamWriter& writer) const override;
+
+private:
+ std::vector<std::string> m_content_ids;
+};
+
+
#endif //LIBHEIF_UNC_BOXES_H
diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc
index ab3bf239..18ae7f7f 100644
--- a/libheif/image-items/image_item.cc
+++ b/libheif/image-items/image_item.cc
@@ -37,6 +37,10 @@
#include "plugin_registry.h"
#include "security_limits.h"
+#if WITH_UNCOMPRESSED_CODEC
+#include "codecs/uncompressed/unc_boxes.h"
+#endif
+
#include <limits>
#include <cassert>
#include <cstring>
@@ -931,6 +935,15 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_image(const heif_decod
img->set_gimi_sample_content_id(gimi_content_id->get_content_id());
}
+#if WITH_UNCOMPRESSED_CODEC
+ // GIMI component content IDs
+
+ auto gimi_comp_ids = get_property<Box_gimi_component_content_ids>();
+ if (gimi_comp_ids) {
+ img->set_component_content_ids(gimi_comp_ids->get_content_ids());
+ }
+#endif
+
#if HEIF_WITH_OMAF
// Image projection (OMAF)
auto prfr = get_property<Box_prfr>();
diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc
index 0ff24ef8..a0e0126c 100644
--- a/libheif/pixelimage.cc
+++ b/libheif/pixelimage.cc
@@ -32,6 +32,10 @@
#include "codecs/uncompressed/unc_types.h"
+#if WITH_UNCOMPRESSED_CODEC
+#include "codecs/uncompressed/unc_boxes.h"
+#endif
+
heif_chroma chroma_from_subsampling(int h, int v)
{
@@ -319,6 +323,14 @@ std::vector<std::shared_ptr<Box>> ImageExtraData::generate_property_boxes(bool g
}
#endif
+#if WITH_UNCOMPRESSED_CODEC
+ if (has_component_content_ids()) {
+ auto ccid = std::make_shared<Box_gimi_component_content_ids>();
+ ccid->set_content_ids(get_component_content_ids());
+ properties.push_back(ccid);
+ }
+#endif
+
return properties;
}
@@ -1961,6 +1973,10 @@ void HeifPixelImage::forward_all_metadata_from(const std::shared_ptr<const HeifP
set_gimi_sample_content_id(src_image->get_gimi_sample_content_id());
}
+ if (src_image->has_component_content_ids()) {
+ set_component_content_ids(src_image->get_component_content_ids());
+ }
+
if (auto* tai = src_image->get_tai_timestamp()) {
set_tai_timestamp(tai);
}
diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h
index 5e53888d..c01f18c5 100644
--- a/libheif/pixelimage.h
+++ b/libheif/pixelimage.h
@@ -204,6 +204,13 @@ public:
std::string get_gimi_sample_content_id() const { assert(has_gimi_sample_content_id()); return *m_gimi_sample_content_id; }
+ void set_component_content_ids(const std::vector<std::string>& ids) { m_component_content_ids = ids; }
+
+ bool has_component_content_ids() const { return !m_component_content_ids.empty(); }
+
+ const std::vector<std::string>& get_component_content_ids() const { return m_component_content_ids; }
+
+
// --- bayer pattern
bool has_bayer_pattern() const { return m_bayer_pattern.has_value(); }
@@ -283,6 +290,8 @@ private:
std::optional<std::string> m_gimi_sample_content_id;
+ std::vector<std::string> m_component_content_ids;
+
std::optional<BayerPattern> m_bayer_pattern;
std::vector<PolarizationPattern> m_polarization_patterns;