Skip to content

fix(preview): preserve ICC colour profiles in generated previews - #66028

Open
susnux wants to merge 7 commits into
masterfrom
fix/preview-icc-profiles
Open

susnux wants to merge 7 commits into
masterfrom
fix/preview-icc-profiles

Conversation

@susnux

@susnux susnux commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Checklist

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

s0len and others added 7 commits October 29, 2026 23:41
GD strips the ICC colour profile when loading an image but keeps the
pixel values, so previews of wide-gamut photos (Display P3, Adobe RGB)
are served as untagged JPEGs that browsers render as sRGB, looking
washed out compared to the original.

Remember the source profile in OC\Image, carry it across the resize and
crop copies the preview generator uses, and re-embed it into the encoded
JPEG (APP2) or PNG (iCCP) output. No colour conversion is done: the
pixels already match the profile, they were only mistagged. Only RGB
profiles are kept, since GD converts CMYK and grayscale sources to RGB.

Also re-attach the profile in the HEIC provider after stripImage()
removes it from small thumbnails.

Closes #22951

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Mattias Svensson <mattias.m.svensson@gmail.com>
The colour profile fixtures embedded profiles copyrighted by Apple with
all rights reserved and were annotated as 2013 ownCloud images.

Regenerate them as synthetic gradients tagged with the CC0 Display P3
and CGATS001 CMYK profiles from Compact-ICC-Profiles, annotate them
separately and add a PNG fixture to cover reading the iCCP chunk.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
The iCCP chunk of a PNG was inflated without a limit, so a few hundred
KiB of compressed zeros made a single preview request allocate hundreds
of MiB and run into the memory limit.

Cap profiles at 64 KiB, which is far above real display profiles: bound
the decompression, stop collecting JPEG APP2 chunks beyond it and only
accept profiles whose declared size matches their actual size.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Many cameras and photo editors tag their output with an sRGB profile.
Untagged images are displayed as sRGB anyway, so carrying that profile
only grows every preview: the common 3 KiB sRGB IEC61966-2.1 profile
quadruples a 64px thumbnail.

Detect sRGB by its colorants and tone curve instead of the profile
name, which varies between vendors, so linear or otherwise differently
encoded profiles with sRGB primaries are still preserved.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
loadFromFile() read up to 8 MiB of every JPEG and PNG a second time
just to find the colour profile, and missed profiles behind larger
metadata.

Parse the segment and chunk headers through a reader closure instead,
backed by fseek()/fread() for files, so only the headers and the profile
itself are read.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Imaginary is asked to strip metadata so that EXIF data like the location
does not leak into previews, but libvips drops the colour profile along
with it and does not convert the pixels. Wide gamut JPEG and PNG
previews generated by Imaginary therefore still looked washed out.

Read the profile from the head of the source that is already read for
the orientation check and splice it into the encoded response. For that
the profile handling of OC\Image is exposed as static helpers.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
The helpers searched the whole file for the ICC markers, which can also
occur inside compressed image data, and did not notice malformed output.
Walk the segment and chunk structure instead and verify the iCCP CRC.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
@susnux susnux added this to the Nextcloud 36 milestone Oct 29, 2026
@susnux
susnux marked this pull request as ready for review October 30, 2026 11:53
@susnux
susnux requested a review from a team as a code owner October 30, 2026 11:53
@susnux
susnux requested review from Altahrim, CarlSchwan, provokateurin, salmart-dev and skjnldsv and removed request for a team and salmart-dev October 30, 2026 11:54

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Color management support

3 participants