Commit 9049fc17 for libheif
commit 9049fc17b1a1276440d540f2cd6cebc6fd144071
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Fri Apr 3 14:12:21 2026 +0200
unci: fix writing multiple components with byte-aligned and non-byte-aligned bpp
diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
index cf9bf64c..67a2e0e8 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.cc
@@ -76,22 +76,27 @@ unc_encoder_component_interleave::unc_encoder_component_interleave(const std::sh
}
// Build cmpd/uncC boxes
- bool little_endian = false;
+ m_use_memcpy = true;
for (const auto& comp : m_components) {
- //uint16_t cmpd_index = m_cmpd->add_component({static_cast<uint16_t>(comp.component_type)});
-
- uint8_t component_align_size = 0;
-
- if (comp.byte_aligned && comp.bpp > 8) {
- little_endian = true;
+ if (!comp.byte_aligned) {
+ m_use_memcpy = false;
+ break;
}
+ }
- m_uncC->add_component({comp.component_idx, comp.bpp, comp.component_format, component_align_size});
+ for (const auto& comp : m_components) {
+ m_uncC->add_component({comp.component_idx, comp.bpp, comp.component_format, 0});
}
m_uncC->set_interleave_type(interleave_mode_component);
- m_uncC->set_components_little_endian(little_endian);
+ if (m_use_memcpy) {
+ m_uncC->set_components_little_endian(std::endian::native == std::endian::little);
+ }
+ else {
+ // we use dense packing
+ m_uncC->set_components_little_endian(false);
+ }
m_uncC->set_block_size(0);
if (image->get_chroma_format() == heif_chroma_420) {
@@ -153,7 +158,9 @@ std::vector<uint8_t> unc_encoder_component_interleave::encode_tile(const std::sh
size_t src_stride;
const uint8_t* src_data = src_image->get_component(comp.component_idx, &src_stride);
- if (comp.byte_aligned) {
+ if (m_use_memcpy) {
+ assert(comp.byte_aligned);
+
// Byte-aligned path: memcpy per row
int bytes_per_pixel = (bpp + 7) / 8;
diff --git a/libheif/codecs/uncompressed/unc_encoder_component_interleave.h b/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
index c5322292..b2c954c3 100644
--- a/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
+++ b/libheif/codecs/uncompressed/unc_encoder_component_interleave.h
@@ -49,6 +49,7 @@ private:
};
std::vector<channel_component> m_components;
+ bool m_use_memcpy = false;
};
diff --git a/tests/uncompressed_encode_multicomponent.cc b/tests/uncompressed_encode_multicomponent.cc
index 13df6c1b..3f8ce3ba 100644
--- a/tests/uncompressed_encode_multicomponent.cc
+++ b/tests/uncompressed_encode_multicomponent.cc
@@ -432,3 +432,124 @@ TEST_CASE("Multi-mono complex64")
{
test_multi_mono<heif_complex64>(heif_channel_datatype_complex_number, 128, "multi_mono_complex64.heif");
}
+
+
+// Test that mixing byte-aligned (16-bit) and non-byte-aligned (14-bit) components
+// in the same image roundtrips correctly. This exercises the encoder's endianness
+// handling: the 16-bit component triggers components_little_endian=true in the uncC
+// box, but the 14-bit component uses MSB-first bit packing.
+TEST_CASE("Mixed bpp: 16-bit and 14-bit components")
+{
+ constexpr int kW = 8;
+ constexpr int kH = 8;
+ constexpr int kBpp16 = 16;
+ constexpr int kBpp14 = 14;
+ constexpr uint16_t kMaxVal14 = (1 << kBpp14) - 1;
+
+ // Create image
+ heif_image* image = nullptr;
+ heif_error err = heif_image_create(kW, kH, heif_colorspace_nonvisual,
+ heif_chroma_undefined, &image);
+ REQUIRE(err.code == heif_error_Ok);
+
+ // Add 16-bit component (byte-aligned)
+ uint32_t idx0 = 0;
+ err = heif_image_add_component(image, kW, kH, kMonoComponentType,
+ heif_channel_datatype_unsigned_integer, kBpp16, &idx0);
+ REQUIRE(err.code == heif_error_Ok);
+
+ // Add 14-bit component (non-byte-aligned)
+ uint32_t idx1 = 0;
+ err = heif_image_add_component(image, kW, kH, kMonoComponentType,
+ heif_channel_datatype_unsigned_integer, kBpp14, &idx1);
+ REQUIRE(err.code == heif_error_Ok);
+
+ // Fill 16-bit component
+ {
+ size_t stride = 0;
+ uint16_t* data = heif_image_get_component_uint16(image, idx0, &stride);
+ REQUIRE(data != nullptr);
+ for (uint32_t y = 0; y < kH; y++) {
+ for (uint32_t x = 0; x < kW; x++) {
+ data[y * stride + x] = static_cast<uint16_t>(y * kW + x + 100);
+ }
+ }
+ }
+
+ // Fill 14-bit component (values must fit in 14 bits)
+ {
+ size_t stride = 0;
+ uint16_t* data = heif_image_get_component_uint16(image, idx1, &stride);
+ REQUIRE(data != nullptr);
+ for (uint32_t y = 0; y < kH; y++) {
+ for (uint32_t x = 0; x < kW; x++) {
+ data[y * stride + x] = static_cast<uint16_t>((y * kW + x + 200) & kMaxVal14);
+ }
+ }
+ }
+
+ // Encode
+ heif_context* ctx = heif_context_alloc();
+ heif_encoder* encoder = nullptr;
+ err = heif_context_get_encoder_for_format(ctx, heif_compression_uncompressed, &encoder);
+ REQUIRE(err.code == heif_error_Ok);
+
+ err = heif_context_encode_image(ctx, image, encoder, nullptr, nullptr);
+ REQUIRE(err.code == heif_error_Ok);
+
+ std::string output_path = get_tests_output_file_path("mixed_bpp_16_14.heif");
+ err = heif_context_write_to_file(ctx, output_path.c_str());
+ REQUIRE(err.code == heif_error_Ok);
+
+ heif_encoder_release(encoder);
+ heif_image_release(image);
+ heif_context_free(ctx);
+
+ // Decode and verify
+ heif_context* ctx2 = heif_context_alloc();
+ err = heif_context_read_from_file(ctx2, output_path.c_str(), nullptr);
+ REQUIRE(err.code == heif_error_Ok);
+
+ heif_image_handle* handle = nullptr;
+ err = heif_context_get_primary_image_handle(ctx2, &handle);
+ REQUIRE(err.code == heif_error_Ok);
+
+ heif_image* decoded = nullptr;
+ err = heif_decode_image(handle, &decoded, heif_colorspace_undefined,
+ heif_chroma_undefined, nullptr);
+ REQUIRE(err.code == heif_error_Ok);
+
+ REQUIRE(heif_image_get_number_of_used_components(decoded) == 2);
+
+ // Verify 16-bit component
+ {
+ size_t stride = 0;
+ const uint16_t* data = heif_image_get_component_uint16_readonly(decoded, idx0, &stride);
+ REQUIRE(data != nullptr);
+ for (uint32_t y = 0; y < kH; y++) {
+ for (uint32_t x = 0; x < kW; x++) {
+ uint16_t expected = static_cast<uint16_t>(y * kW + x + 100);
+ INFO("16-bit component at (" << x << "," << y << ")");
+ REQUIRE(data[y * stride + x] == expected);
+ }
+ }
+ }
+
+ // Verify 14-bit component
+ {
+ size_t stride = 0;
+ const uint16_t* data = heif_image_get_component_uint16_readonly(decoded, idx1, &stride);
+ REQUIRE(data != nullptr);
+ for (uint32_t y = 0; y < kH; y++) {
+ for (uint32_t x = 0; x < kW; x++) {
+ uint16_t expected = static_cast<uint16_t>((y * kW + x + 200) & kMaxVal14);
+ INFO("14-bit component at (" << x << "," << y << ")");
+ REQUIRE(data[y * stride + x] == expected);
+ }
+ }
+ }
+
+ heif_image_release(decoded);
+ heif_image_handle_release(handle);
+ heif_context_free(ctx2);
+}