Commit 1dade618 for libheif
commit 1dade6187e50a05e870eeb369d7817a2f7c54bf1
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sun Oct 25 17:29:49 2026 +0100
Separate color profile storage from encoding properties [GHSA-hhg3-fxg7-3gxw]
diff --git a/libheif/context.cc b/libheif/context.cc
index 623634ed..bc5bbcd6 100644
--- a/libheif/context.cc
+++ b/libheif/context.cc
@@ -786,16 +786,7 @@ Error HeifContext::interpret_heif_file_images()
for (const auto& prop : properties) {
auto colr = std::dynamic_pointer_cast<Box_colr>(prop);
if (colr) {
- // The ImageItem color-profile setters also add 'colr' properties for
- // encoding. On read, only populate the description: this property is
- // already associated with the item in the file.
- auto profile = colr->get_color_profile();
- if (auto icc = std::dynamic_pointer_cast<const color_profile_raw>(profile)) {
- image->ImageDescription::set_color_profile_icc(icc);
- }
- else if (auto nclx = std::dynamic_pointer_cast<const color_profile_nclx>(profile)) {
- image->ImageDescription::set_color_profile_nclx(nclx->get_nclx_color_profile());
- }
+ image->set_color_profile(colr->get_color_profile());
continue;
}
@@ -1117,11 +1108,11 @@ Error HeifContext::interpret_heif_file_images()
auto tile_img = iter->second;
if (image->get_color_profile_icc() == nullptr && tile_img->get_color_profile_icc()) {
- image->ImageDescription::set_color_profile_icc(tile_img->get_color_profile_icc());
+ image->set_color_profile_icc(tile_img->get_color_profile_icc());
}
if (!image->has_nclx_color_profile() && tile_img->has_nclx_color_profile()) {
- image->ImageDescription::set_color_profile_nclx(tile_img->get_color_profile_nclx());
+ image->set_color_profile_nclx(tile_img->get_color_profile_nclx());
}
}
}
diff --git a/libheif/image-items/grid.cc b/libheif/image-items/grid.cc
index b1ce0f5c..b8448089 100644
--- a/libheif/image-items/grid.cc
+++ b/libheif/image-items/grid.cc
@@ -985,7 +985,7 @@ Error ImageItem_Grid::add_image_tile(uint32_t tile_x, uint32_t tile_y,
heif_image_input_class_normal,
m_tile_encoding_options->output_nclx_profile);
for (auto& property : colr_boxes) {
- add_property(property, is_property_essential(property));
+ add_color_profile_property(property);
}
// Add transformative properties
diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc
index c4d80331..949ea782 100644
--- a/libheif/image-items/image_item.cc
+++ b/libheif/image-items/image_item.cc
@@ -108,6 +108,16 @@ heif_property_id ImageItem::add_property_without_deduplication(const std::shared
}
+heif_property_id ImageItem::add_color_profile_property(const std::shared_ptr<Box_colr>& property)
+{
+ if (!property) {
+ return 0;
+ }
+
+ return add_property(property, is_property_essential(property));
+}
+
+
heif_compression_format ImageItem::compression_format_from_fourcc_infe_type(uint32_t type)
{
switch (type) {
@@ -417,6 +427,11 @@ Error ImageItem::encode_to_item(HeifContext* ctx,
// set item properties
for (auto& propertyBox : codingResult->properties) {
+ if (auto colr = std::dynamic_pointer_cast<Box_colr>(propertyBox)) {
+ add_color_profile_property(colr);
+ continue;
+ }
+
bool essential = is_property_essential(propertyBox);
// TODO: can we simply use add_property() ?
@@ -899,19 +914,6 @@ void ImageItem::set_pixel_ratio(uint32_t h, uint32_t v)
}
-void ImageItem::set_color_profile_nclx(const nclx_profile& profile)
-{
- ImageDescription::set_color_profile_nclx(profile);
- add_property(create_colr_box_nclx(), false);
-}
-
-
-void ImageItem::set_color_profile_icc(const std::shared_ptr<const color_profile_raw>& profile)
-{
- ImageDescription::set_color_profile_icc(profile);
- add_property(create_colr_box_icc(), false);
-}
-
void ImageItem::set_omaf_image_projection(heif_omaf_image_projection projection)
{
ImageDescription::set_omaf_image_projection(projection);
diff --git a/libheif/image-items/image_item.h b/libheif/image-items/image_item.h
index 789025db..f4cd9fe1 100644
--- a/libheif/image-items/image_item.h
+++ b/libheif/image-items/image_item.h
@@ -171,6 +171,10 @@ public:
heif_property_id add_property_without_deduplication(const std::shared_ptr<Box>& property, bool essential);
+ // Encoding only: attach an already constructed 'colr' property to the file.
+ // Reading a profile and setting ImageDescription metadata must not do this.
+ virtual heif_property_id add_color_profile_property(const std::shared_ptr<Box_colr>& property);
+
void set_resolution(uint32_t w, uint32_t h)
{
m_width = w;
@@ -345,10 +349,6 @@ public:
void set_pixel_ratio(uint32_t h, uint32_t v) override;
- void set_color_profile_nclx(const nclx_profile& profile) override;
-
- void set_color_profile_icc(const std::shared_ptr<const color_profile_raw>& profile) override;
-
void set_omaf_image_projection(heif_omaf_image_projection image_projection) override;
// --- miaf
diff --git a/libheif/image/image_description.h b/libheif/image/image_description.h
index d3e5a6d2..7b2873d8 100644
--- a/libheif/image/image_description.h
+++ b/libheif/image/image_description.h
@@ -171,22 +171,21 @@ public:
bool has_nclx_color_profile() const;
- virtual void set_color_profile_nclx(const nclx_profile& profile) { m_color_profile_nclx = profile; }
+ void set_color_profile_nclx(const nclx_profile& profile) { m_color_profile_nclx = profile; }
nclx_profile get_color_profile_nclx() const { return m_color_profile_nclx; }
// get the stored nclx fallback or return the default nclx if none is stored
nclx_profile get_color_profile_nclx_with_fallback() const;
- virtual void set_color_profile_icc(const std::shared_ptr<const color_profile_raw>& profile) { m_color_profile_icc = profile; }
+ void set_color_profile_icc(const std::shared_ptr<const color_profile_raw>& profile) { m_color_profile_icc = profile; }
bool has_icc_color_profile() const { return m_color_profile_icc != nullptr; }
const std::shared_ptr<const color_profile_raw>& get_color_profile_icc() const { return m_color_profile_icc; }
- // This wrapper calls the virtual setters. On an ImageItem, those setters
- // also add 'colr' properties for encoding. When reading an existing 'colr'
- // property, use the qualified ImageDescription setters instead.
+ // These setters only store profile metadata. Encoding an ImageItem must add
+ // a 'colr' property explicitly through ImageItem::add_color_profile_property().
void set_color_profile(const std::shared_ptr<const color_profile>& profile)
{
auto icc = std::dynamic_pointer_cast<const color_profile_raw>(profile);
diff --git a/tests/read_color_profile_properties.cc b/tests/read_color_profile_properties.cc
index 6ed16a70..17210acc 100644
--- a/tests/read_color_profile_properties.cc
+++ b/tests/read_color_profile_properties.cc
@@ -26,8 +26,16 @@
#include "catch_amalgamated.hpp"
#include "context.h"
+#include "image-items/iden.h"
#include "image-items/image_item.h"
#include "test-config.h"
+#include "libheif/heif.h"
+#include "libheif/heif_properties.h"
+
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <vector>
TEST_CASE("reading a file does not add color profile properties")
@@ -46,3 +54,69 @@ TEST_CASE("reading a file does not add color profile properties")
REQUIRE(image->has_nclx_color_profile());
REQUIRE(image->get_color_profile_icc() == nullptr);
}
+
+
+TEST_CASE("setting an item profile does not add a property")
+{
+ HeifContext ctx;
+ auto id = ctx.get_heif_file()->add_new_image(fourcc("iden"));
+ REQUIRE(id);
+
+ ImageItem_iden image(&ctx, *id);
+ nclx_profile profile;
+ profile.set_sRGB_defaults();
+ image.set_color_profile(std::make_shared<color_profile_nclx>(profile));
+ auto icc = std::make_shared<color_profile_raw>(fourcc("prof"), std::vector<uint8_t>{1, 2, 3, 4});
+ image.set_color_profile_icc(icc);
+
+ REQUIRE(image.has_nclx_color_profile());
+ REQUIRE(image.get_color_profile_icc() == icc);
+ REQUIRE(image.get_all_properties<Box_colr>().empty());
+
+ auto colr = std::make_shared<Box_colr>();
+ colr->set_color_profile(std::make_shared<color_profile_nclx>(profile));
+ REQUIRE(image.add_color_profile_property(colr) != 0);
+ REQUIRE(image.get_all_properties<Box_colr>().size() == 1);
+
+ std::vector<std::shared_ptr<Box>> stored_properties;
+ REQUIRE(!ctx.get_heif_file()->get_properties(*id, stored_properties));
+ REQUIRE(stored_properties.size() == 1);
+ REQUIRE(std::dynamic_pointer_cast<Box_colr>(stored_properties.front()) != nullptr);
+}
+
+
+TEST_CASE("encoding writes an explicit color profile property")
+{
+ if (!heif_have_encoder_for_format(heif_compression_uncompressed)) {
+ SKIP("uncompressed encoder unavailable");
+ }
+
+ heif_image* image = nullptr;
+ REQUIRE(heif_image_create(8, 8, heif_colorspace_RGB, heif_chroma_interleaved_RGB, &image).code == heif_error_Ok);
+ REQUIRE(heif_image_add_plane(image, heif_channel_interleaved, 8, 8, 8).code == heif_error_Ok);
+ size_t stride = 0;
+ uint8_t* pixels = heif_image_get_plane2(image, heif_channel_interleaved, &stride);
+ REQUIRE(pixels != nullptr);
+ for (int y = 0; y < 8; ++y) {
+ std::memset(pixels + y * stride, 128, 8 * 3);
+ }
+
+ const uint8_t icc_data[] = {0x01, 0x02, 0x03, 0x04};
+ REQUIRE(heif_image_set_raw_color_profile(image, "prof", icc_data, sizeof(icc_data)).code == heif_error_Ok);
+
+ heif_context* context = heif_context_alloc();
+ heif_encoder* encoder = nullptr;
+ REQUIRE(heif_context_get_encoder_for_format(context, heif_compression_uncompressed, &encoder).code == heif_error_Ok);
+ heif_image_handle* handle = nullptr;
+ auto err = heif_context_encode_image(context, image, encoder, nullptr, &handle);
+ INFO("encode error: " << err.message);
+ REQUIRE(err.code == heif_error_Ok);
+
+ auto colr_type = static_cast<heif_item_property_type>(heif_fourcc('c', 'o', 'l', 'r'));
+ REQUIRE(heif_item_get_properties_of_type(context, heif_image_handle_get_item_id(handle), colr_type, nullptr, 0) == 1);
+
+ heif_image_handle_release(handle);
+ heif_encoder_release(encoder);
+ heif_context_free(context);
+ heif_image_release(image);
+}