Skip to content

fix(bin): lcms2 panic on 16-bit PNGs with an ICC profile#32

Merged
FreezyLemon merged 2 commits into
rust-av:mainfrom
Gheop:fix-lcms2-16bit-icc
Jul 5, 2026
Merged

fix(bin): lcms2 panic on 16-bit PNGs with an ICC profile#32
FreezyLemon merged 2 commits into
rust-av:mainfrom
Gheop:fix-lcms2-16bit-icc

Conversation

@Gheop

@Gheop Gheop commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Scoring any 16-bit PNG that embeds an ICC profile panics since #31:

thread 'main' panicked at lcms2-6.1.1/src/transform.rs:84:9:
assertion `left == right` failed: PixelFormat(262170) has 6 bytes per pixel, but the input format has 2

This includes the bundled test pair: ssimulacra2_rs image tank_source.png tank_distorted.png crashes at current HEAD (tank_distorted.png is 16-bit RGB with an iCCP chunk).

Cause: apply_icc_transform_u16 builds a Transform<u16, u16> with PixelFormat::RGB_16. lcms2-rs asserts that the element type size matches the pixel size (6 bytes for RGB_16); only u8 slices are special-cased, which is why the 8-bit path works.

Fix: transform [u16; 3] pixels instead of bare u16.

Includes a regression test that runs the image command on the tank pair and checks the score against --no-icc (the embedded profile is sRGB, so the conversion should be nearly neutral; observed delta is ~0.008).

Transform<u16, u16> with PixelFormat::RGB_16 trips the lcms2-rs size
assertion: RGB_16 is 6 bytes per pixel while size_of::<u16>() is 2, and
only u8 slices are special-cased. Scoring any 16-bit PNG that embeds an
ICC profile (including the bundled tank_distorted.png) panics.

Transform [u16; 3] pixels instead, and add a regression test that runs
the image command on the tank pair.
@FreezyLemon

Copy link
Copy Markdown
Contributor

Good catch! This should not have slipped through, especially considering it fails on our own test data...

When reviewing the code, I notice that extract_rgb_alpha_u16 creates a flat Vec<u16>. I don't think that's the right choice in the first place because we do not operate on single samples, we always consider the entire pixel. Could you include a change that returns a Vec<[u16 ;3]> and just uses that from then on?

Review feedback: the code always operates on whole pixels, so
extract_rgb_alpha_u16 now returns Vec<[u16; 3]> and the ICC transform
uses it directly, with no flat buffer or re-chunking in between.
@Gheop

Gheop commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Done in 7835d2f — extract_rgb_alpha_u16 now returns Vec<[u16; 3]> and the ICC transform consumes it directly, no flat buffer or re-chunking left in the 16-bit path.

@FreezyLemon FreezyLemon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@FreezyLemon FreezyLemon merged commit 05f2b4e into rust-av:main Jul 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants