Commit 26e10ceb for libheif
commit 26e10ceb601f80082c8d22016ee0c583c702b3c8
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sun Sep 20 15:06:08 2026 +0200
Map a bit depth of 0 to a sample width of 0
bytes_per_sample_for_bit_depth() returned 1 for any depth of 8 or less,
including the 0 that HeifPixelImage::get_bits_per_pixel() reports for a
plane that does not exist. The ColorState wrappers guarded that case, but
the operators that call the helper on plane depths directly did not, so a
missing plane looked like a valid one-byte plane until a later check
caught it (Op_YCbCr_to_RGB) or add_channel() refused the zero depth
(Op_bayer_bilinear_to_RGB24_32). Nothing reachable through the planner
depended on it; this closes the gap for direct callers and future code.
The helper now returns 0 for a depth of 0. Every raw caller compares the
result against 1, 2 or sizeof(Pixel) and already has an error branch for
anything else. The two ColorState wrappers no longer need their own guard.
diff --git a/libheif/color-conversion/colorconversion.cc b/libheif/color-conversion/colorconversion.cc
index e2f71052..a13dd2f6 100644
--- a/libheif/color-conversion/colorconversion.cc
+++ b/libheif/color-conversion/colorconversion.cc
@@ -342,16 +342,14 @@ static bool all_existing_planes_satisfy(const ColorState& s, bool include_alpha,
int ColorState::get_bytes_per_sample(heif_channel channel) const
{
- int bpp = get_bits_per_pixel(channel);
- return bpp != 0 ? bytes_per_sample_for_bit_depth(bpp) : 0;
+ return bytes_per_sample_for_bit_depth(get_bits_per_pixel(channel)); // 0 if the plane does not exist
}
int ColorState::get_max_bytes_per_sample() const
{
// The bit depth to sample width mapping is monotonic, so the widest plane is the deepest.
- int max_bpp = get_max_bits_per_pixel();
- return max_bpp != 0 ? bytes_per_sample_for_bit_depth(max_bpp) : 0;
+ return bytes_per_sample_for_bit_depth(get_max_bits_per_pixel());
}
diff --git a/libheif/image/pixelimage.h b/libheif/image/pixelimage.h
index cf7c8e69..ca151c15 100644
--- a/libheif/image/pixelimage.h
+++ b/libheif/image/pixelimage.h
@@ -61,8 +61,15 @@ std::vector<heif_chroma> get_valid_chroma_values_for_colorspace(heif_colorspace
// (1, 2, 4, 8 or 16). This is the single source of truth for the sample width: plane
// allocation uses it, and so does every code path that reinterprets plane memory as
// uint8_t/uint16_t samples (color conversion in particular), so that they cannot drift.
+//
+// A bit depth of 0 is what get_bits_per_pixel() reports for a plane that does not exist.
+// It maps to 0 bytes, so that a missing plane can never pass for a valid one-byte plane
+// at the call sites that compare the width against sizeof(uint8_t).
inline int bytes_per_sample_for_bit_depth(int bit_depth)
{
+ if (bit_depth <= 0) {
+ return 0;
+ }
if (bit_depth <= 8) {
return 1;
}
diff --git a/tests/conversion.cc b/tests/conversion.cc
index c9628742..f2f16e3f 100644
--- a/tests/conversion.cc
+++ b/tests/conversion.cc
@@ -1481,3 +1481,25 @@ TEST_CASE("Op_to_sdr_planes equalizes mixed colour depths", "[heif_image]")
CHECK(op.state_after_conversion(uniform8, target, options, *options_ext).empty());
}
}
+
+
+// bytes_per_sample_for_bit_depth() is the single source of truth for the storage width of a
+// plane. A depth of 0 is what get_bits_per_pixel() reports for a plane that does not exist;
+// it must not map to the width of a valid one-byte plane.
+TEST_CASE("bytes_per_sample_for_bit_depth", "[heif_image]")
+{
+ CHECK(bytes_per_sample_for_bit_depth(0) == 0);
+ CHECK(bytes_per_sample_for_bit_depth(-1) == 0);
+
+ for (int bits = 1; bits <= 128; bits++) {
+ int expected = (bits <= 8) ? 1 : (bits <= 16) ? 2 : (bits <= 32) ? 4 : (bits <= 64) ? 8 : 16;
+ INFO("bits = " << bits);
+ CHECK(bytes_per_sample_for_bit_depth(bits) == expected);
+ }
+
+ // The ColorState wrappers agree, including for a missing plane.
+ ColorState s(heif_colorspace_RGB, heif_chroma_444, false, 12);
+ CHECK(s.get_bytes_per_sample(heif_channel_R) == 2);
+ CHECK(s.get_bytes_per_sample(heif_channel_Alpha) == 0);
+ CHECK(s.get_max_bytes_per_sample() == 2);
+}