Skip to content

YCbCr images >14 bpp can no longer be converted to RGB (regression in 1.21.0, e9b3f68d) #1881

Closed

Description

@happyaron

Summary

Since 1.21.0, any YCbCr image with more than 14 bits per pixel fails to decode to
RGB with Unsupported feature: Unsupported color conversion. Decoding to YUV
output still works — only the conversion to RGB is affected, which is the path
every viewer, thumbnailer and heif-dec ... .png uses.

Two files in libheif's own test corpus reproduce it:

  • tests/data/uncompressed_comp_Y16U16V16_422.heif
  • tests/data/uncompressed_mix_Y16U16V16_422.heif

Neither carries an nclx box, so matrix_coefficients is the default
(unspecified / BT.601) — these are ordinary 16-bit YCbCr 4:2:2 images, nothing
exotic.

Reproduction

$ heif-dec tests/data/uncompressed_comp_Y16U16V16_422.heif out.png
File contains 1 image
Could not decode image: Unsupported feature: Unsupported color conversion

$ heif-dec tests/data/uncompressed_comp_Y16U16V16_422.heif out.y4m
File contains 1 image
Written to out.y4m                     # YUV output is fine

heif-info reports the image as colorspace: YCbCr, 4:2:2, bit depth: 16,
color profile: no.

Affected versions

Bad: 1.21.0, 1.21.1, 1.21.2, 1.22.0, 1.22.1, 1.22.2, 1.23.0, 1.23.1, and current
master (1a3583b).
Good: 1.20.2 and earlier.

Cause

git bisect between v1.20.2 and v1.21.2 lands on:

e9b3f68d4eeae3ddbbca65e56c7355a96d4bf850
"limit color conversion function 14 bpp (#1572)"

The entire change is four lines in libheif/color-conversion/yuv2rgb.cc, in
Op_YCbCr_to_RGB<Pixel>::state_after_conversion:

  if (input_state.bits_per_pixel > 14) {
    return {};
  }

Returning an empty state list removes this operation from the conversion graph,
so the pipeline can find no route from YCbCr to RGB and fails.

The commit landed alongside 86f09c7d ("add (incomplete) implementation of
YCgCo-R color transformation"), both from issue #1572. YCgCo-R needs bit-depth
headroom — Cg and Co each require an extra bit — so a cap is understandable
for that transform. But the check is unconditional: it does not look at
matrix_coefficients, so it also rejects plain BT.601/BT.709 YCbCr at 15 and 16
bpp, which needs no headroom and worked fine before.

Why it went unnoticed for three releases

The test suite never converts a >14 bpp YCbCr image to RGB. tests/uncompressed_decode.cc
checks either 8-bit RGB output or native YCbCr output, and the two 16-bit 4:2:2
fixtures above are not referenced by any test that exercises the RGB path.

Suggested fix

Restrict the cap to the transforms that actually need the headroom, e.g.

  // YCgCo-R needs 2 bits of headroom; other matrices do not
  int matrix = input_state.nclx.get_matrix_coefficients();
  if ((matrix == 8 || matrix == 16 || matrix == 17) &&
      input_state.bits_per_pixel > 14) {
    return {};
  }

and add a regression test that decodes one of the 16-bit 4:2:2 fixtures to RGB.

One caveat: I have confirmed that these files decoded to RGB successfully in
1.20.2 and fail from 1.21.0 on, but I have not audited the conversion arithmetic
for numerical correctness at 15/16 bpp. If the limit turns out to be needed more
broadly than YCgCo-R, it would still be better to surface it as an explicit,
documented limitation than as a generic "unsupported color conversion" failure.

Activity

  1. farindk commented on Aug 31, 2026

    @farindk
    Member

    Thanks. Fixed with the above commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions