fix(modifiers): keep the colorspace of the source in grayscale - #122
Merged
olivervogel merged 1 commit intoAug 25, 2026
Merged
Conversation
Going back to SRGB moved a cmyk source to another colorspace, which is not what a color operation should do. Remember the interpretation of the source and return to it instead. A cmyk source now stays cmyk and the grey lands in the black channel, cmyk(0 0 0 40) where the source pixel was cmyk(5 58 7 0). An srgb source is unaffected, the second call was already going back to srgb, and the bandcount is unchanged in every direction. colourspace() takes its target verbatim while it guesses a sane source, so interpretations that are tags rather than color spaces have no route back. multiband, matrix and rgb sources threw with the plain restore, they fall back to srgb the way they did before. The two conversions are also committed in one go now, a failing restore used to leave the caller holding the b-w intermediate with its bands gone.
deluxetom
approved these changes
Aug 25, 2026
Member
|
Thank you. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
GrayscaleModifierconverts toB_Wand then hardcodesSRGBon the way back, so a cmyk source comes out srgb. Same thing the Imagick driver did in Intervention/image#1516, where olivervogel saidgrayscale()is just a visual color operation and that the vips driver behaving this way "is actually more of a bug than a feature". This is that change for vips.On
tests/resources/cmyk.jpg, pixel (0,0):The grey lands in the black channel and the ink channels come out at zero. The srgb path is untouched, the second call was already going back to srgb there. Bandcount is unchanged in every direction, 3 stays 3, 4 stays 4, cmyk stays 4, and alpha survives the
B_Wround trip. I hadCanNormalizeBandsin here at first and took it out, it never fires on this path and it would have turned a 3 band srgb source into 4.One thing that is not obvious.
colourspace()guesses a sensible source interpretation on the way in, but it takes its target verbatim. So restoring the raw interpretation throws for the ones that are tags rather than color spaces, and those are reachable:multiband,matrixandrgball threw a rawJcupitt\Vips\Exceptionwith the plain restore. They fall back to srgb now, the way they behaved before.yxy,labqandscrgbround-trip fine and are preserved.The two conversions are also built as one pipeline and committed with a single
setNative(). With two calls theB_Wintermediate was already committed when the restore threw, so the caller was left holding a 1 band image with its color gone.Worth knowing, on the encoder side. A cmyk source now stays cmyk through to the encoder, and libvips attaches its built-in cmyk profile:
So
grayscale()used to double as "drop the cmyk profile", because it laundered the image to srgb. It no longer does. The output is a valid cmyk JPEG, though most browsers will not render one.strip: truedoes not remove the profile either,JpegEncodermaps strip toForeignKeep::ICC, which keeps ICC. Tell me if you would rather handle that at the encoder level.Also worth knowing:
grayscale()is no longer idempotent on cmyk. Repeated calls drift the black channel, raw K goes 101, 113, 126, 138, 149, and by the fifth pass a little color comes back. Each pass is another round trip through the cmyk profile. It converged before because it landed on srgb and stayed there.Three tests, one on the cmyk colorspace and the resulting ink values, one on the tag fallback, one pinning the srgb path and its bandcount. The first fails on
develop. The key assertion is a range rather than a fixed value, the exact number depends on the lcms2 the build linked against and I only have one locally. Full suite, phpstan and phpcs pass, the only failure isFontProcessorTest::testBoxSizeTtfwhich fails ondeveloptoo.