fix(modifiers): remove the profile without mutating the shared image - #124
Merged
olivervogel merged 1 commit intoAug 28, 2026
Merged
Conversation
RemoveProfileModifier removed `icc-profile-data` straight off the vips
image and never called `setNative()`. It is the only modifier in this
driver that mutates the shared native in place instead of handing a new
one over, and it has two consequences.
The core keeps the source ref that the decoder stashed on it, so a later
resize is served by the combined load and resize op, which reloads the
original file. The profile comes back, on the image and in the encoded
result:
$image->removeProfile();
$image->resize(20, 20); // icc-profile-data is back
And `Image::__clone` clones the core but not the vips image behind it, so
both point at the same one. Removing the field in place strips the
original as well as the clone.
So copy the image, remove the field on the copy, and hand it over with
`setNative()`, which clears the stash. An image carrying no profile
returns early, which also keeps the remove call safe, libvips throws on a
field that is not there.
Clearing the stash costs the shrink on load path for any resize that
follows, since that ref is what `resize()` and `cover()` use for it. On a
4000px JPEG down to 200px, removing the profile first goes from 7 ms to
88 ms. Those 7 ms return an image that still carries its profile though,
so at equal output there is no regression, and doing the removal after
the resize stays at 6 ms.
deluxetom
approved these changes
Aug 27, 2026
Member
|
Thanks. |
Contributor
Author
|
Thank you! |
Contributor
Author
|
Hi @olivervogel, thanks for releasing this! I think the 4.1.3 has been dropped on the wrong commit? 4.1.2 and 4.1.3 point at the same SHA. |
Member
You're right. I added 4.1.4. |
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.
RemoveProfileModifierremovesicc-profile-datastraight off the vips image and never callssetNative(). It is the only modifier in this driver that mutates the shared native in place instead of handing a new one over, and it has two consequences.The core keeps the source ref the decoder stashed on it, so a later resize goes through the combined load and resize op and reloads the original file. The profile comes back, on the image and in the encoded output:
And
Image::__cloneclones the core but not the vips image behind it, so both point at the same one. Removing the field in place strips the original too:So copy the image, remove the field on the copy, and hand it over with
setNative(), which clears the stash. An image carrying no profile returns early, which also keeps the remove call safe, libvips throws on a field that is not there.One thing to know about.
setNative()clears the source ref the decoder stashed on the core, and that ref is whatresize()andcover()use for shrink on load. So a resize following aremoveProfile()now decodes the source at full size. On a 4000px JPEG down to 200px:develop is not faster, it is fast because it does not do the job, those 6.9 ms return an image that still carries its profile. So at equal output there is no regression, the choice is between 88 ms and 6 ms depending on call order, and the natural order is already the cheap one. The overhead scales with the source, 1.5x at 500px and 14x at 4000px.
The alternative is to keep the stash and teach the resize path to reapply the removal after the reload, with a flag on the core like the one in #123. That would keep both, at the cost of
ResizeModifierandCoverModifierhaving to know about profile removal. Tell me if you would rather have that.Three tests added, two of them pin one symptom each and fail on the current implementation.
No overlap with #123, different file, and it needs nothing from it. libvips does not synthesise an ICC profile at save time the way it does for EXIF, so no
keepflag is involved here.