Commit 8797a50e for libheif
commit 8797a50e85ec072685054a349bd6a0bced040f27
Author: Dirk Lemstra <dirk@lemstra.org>
Date: Sun Oct 11 21:47:42 2026 +0200
Honor NCLX range autocorrection when decoding sequences
diff --git a/libheif/sequences/track_visual.cc b/libheif/sequences/track_visual.cc
index e97838fa..6c0b10be 100644
--- a/libheif/sequences/track_visual.cc
+++ b/libheif/sequences/track_visual.cc
@@ -368,10 +368,28 @@ Result<std::shared_ptr<HeifPixelImage> > Track_Visual::decode_next_image_sample(
const SampleTiming& outputTiming = m_presentation_timeline[m_next_sample_to_be_output % m_presentation_timeline.size()];
const auto* sampleToChunk = m_stsc->get_chunk(outputTiming.chunkIdx + 1);
auto sampleDescription = m_stsd->get_sample_entry(sampleToChunk->sample_description_index - 1);
+ const auto bitstream_nclx = image->get_color_profile_nclx();
// Sample-entry colour information takes precedence over bitstream metadata.
for (const auto& colr : sampleDescription->get_child_boxes<Box_colr>()) {
- image->set_color_profile(colr->get_color_profile());
+ auto profile = colr->get_color_profile();
+ if (auto nclx = std::dynamic_pointer_cast<const color_profile_nclx>(profile)) {
+ auto consolidated_nclx = nclx->get_nclx_color_profile();
+ if (options.version >= 9 && options.autocorrect_broken_input &&
+ bitstream_nclx.is_defined() && bitstream_nclx.get_full_range_flag() &&
+ !consolidated_nclx.get_full_range_flag()) {
+ image->add_warning({
+ heif_error_Invalid_input,
+ heif_suberror_NCLX_colr_VUI_mismatch,
+ "Autocorrecting full-range flag to ON (colr=limited, bitstream=full)"
+ });
+ consolidated_nclx.set_full_range_flag(true);
+ }
+ image->set_color_profile_nclx(consolidated_nclx);
+ }
+ else {
+ image->set_color_profile(profile);
+ }
}
if (auto clli = sampleDescription->get_child_box<Box_clli>()) {
image->set_clli(clli->clli);
diff --git a/tests/image_description_metadata.cc b/tests/image_description_metadata.cc
index 7b32e0ed..27f6f54c 100644
--- a/tests/image_description_metadata.cc
+++ b/tests/image_description_metadata.cc
@@ -316,8 +316,15 @@ static std::vector<uint8_t> encode_metadata_sequence()
}
}
heif_image_set_duration(image, 1);
+ heif_color_profile_nclx* nclx = heif_nclx_color_profile_alloc();
+ REQUIRE(nclx != nullptr);
+ nclx->full_range_flag = 1;
+ REQUIRE(heif_image_set_nclx_color_profile(image, nclx).code == heif_error_Ok);
+ heif_sequence_encoding_options* encoding_options = heif_sequence_encoding_options_alloc();
+ REQUIRE(encoding_options != nullptr);
+ encoding_options->output_nclx_profile = nclx;
for (int frame = 0; frame < 3; ++frame) {
- REQUIRE(heif_track_encode_sequence_image(track, image, encoder, nullptr).code == heif_error_Ok);
+ REQUIRE(heif_track_encode_sequence_image(track, image, encoder, encoding_options).code == heif_error_Ok);
}
REQUIRE(heif_track_encode_end_of_sequence(track, encoder).code == heif_error_Ok);
std::vector<uint8_t> file;
@@ -325,6 +332,8 @@ static std::vector<uint8_t> encode_metadata_sequence()
writer.writer_api_version = 1;
writer.write = sequence_metadata_writer;
REQUIRE(heif_context_write(context, &writer, &file).code == heif_error_Ok);
+ heif_sequence_encoding_options_release(encoding_options);
+ heif_nclx_color_profile_free(nclx);
heif_image_release(image);
heif_track_release(track);
heif_encoder_release(encoder);
@@ -398,7 +407,9 @@ TEST_CASE("sequence sample-entry colour metadata is applied to every decoded fra
}
const bool with_metadata = GENERATE(false, true);
const bool convert_to_rgb = GENERATE(false, true);
+ const bool autocorrect = GENERATE(false, true);
INFO("sample-entry metadata: " << with_metadata << ", RGB conversion: " << convert_to_rgb);
+ INFO("autocorrect: " << autocorrect);
auto file = encode_metadata_sequence();
const std::vector<uint8_t> icc = {1, 2, 3, 4, 5};
if (with_metadata) {
@@ -411,6 +422,7 @@ TEST_CASE("sequence sample-entry colour metadata is applied to every decoded fra
heif_decoding_options* options = heif_decoding_options_alloc();
options->ignore_sequence_editlist = 1;
options->output_image_nclx_profile_passthrough = 1;
+ options->autocorrect_broken_input = autocorrect;
for (int frame = 0; frame < 3; ++frame) {
INFO("frame: " << frame);
heif_image* image = nullptr;
@@ -419,12 +431,17 @@ TEST_CASE("sequence sample-entry colour metadata is applied to every decoded fra
convert_to_rgb ? heif_chroma_444 : heif_chroma_undefined,
options).code == heif_error_Ok);
REQUIRE(image != nullptr);
+ if (with_metadata && autocorrect) {
+ heif_error warning{};
+ REQUIRE(heif_image_get_decoding_warnings(image, 0, &warning, 1) == 1);
+ CHECK(warning.subcode == heif_suberror_NCLX_colr_VUI_mismatch);
+ }
if (with_metadata && convert_to_rgb) {
size_t stride = 0;
const uint8_t* pixels = heif_image_get_plane_readonly2(image, heif_channel_R, &stride);
REQUIRE(pixels != nullptr);
- CHECK(pixels[0] >= 55);
- CHECK(pixels[0] <= 57);
+ CHECK(pixels[0] >= (autocorrect ? 63 : 55));
+ CHECK(pixels[0] <= (autocorrect ? 65 : 57));
}
if (with_metadata && !convert_to_rgb) {
heif_color_profile_nclx* nclx = nullptr;
@@ -432,7 +449,7 @@ TEST_CASE("sequence sample-entry colour metadata is applied to every decoded fra
CHECK(nclx->color_primaries == 9);
CHECK(nclx->transfer_characteristics == 16);
CHECK(nclx->matrix_coefficients == 9);
- CHECK(nclx->full_range_flag == 0);
+ CHECK(nclx->full_range_flag == (autocorrect ? 1 : 0));
heif_nclx_color_profile_free(nclx);
}
CHECK(heif_image_get_raw_color_profile_size(image) == (with_metadata ? icc.size() : 0));