Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(modifiers): remove the profile without mutating the shared image - #124

Merged
olivervogel merged 1 commit into
Intervention:developfrom
nlemoine:fix/remove-profile-modifier
Aug 28, 2026
Merged

fix(modifiers): remove the profile without mutating the shared image#124
olivervogel merged 1 commit into
Intervention:developfrom
nlemoine:fix/remove-profile-modifier

Conversation

@nlemoine

Copy link
Copy Markdown
Contributor

RemoveProfileModifier removes icc-profile-data straight off the vips image and never calls 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 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:

$image = $manager->read('icc.jpg');
$image->removeProfile();   // gone
$image->resize(20, 20);    // back, and it ends up in the encoded result

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 too:

$clone = clone $image;
$clone->removeProfile();   // the original loses its profile as well

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 what resize() and cover() use for shrink on load. So a resize following a removeProfile() now decodes the source at full size. On a 4000px JPEG down to 200px:

develop      removeProfile -> resize    6.9 ms   profile in output: yes
this branch  removeProfile -> resize   88.2 ms   profile in output: no
this branch  resize -> removeProfile    6.2 ms   profile in output: no

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 ResizeModifier and CoverModifier having 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 keep flag is involved here.

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.
@olivervogel

Copy link
Copy Markdown
Member

Thanks.

@olivervogel
olivervogel merged commit a097cc1 into Intervention:develop Aug 28, 2026
6 checks passed
@nlemoine

Copy link
Copy Markdown
Contributor Author

Thank you!

@nlemoine

Copy link
Copy Markdown
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.

@olivervogel

Copy link
Copy Markdown
Member

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.

You're right. I added 4.1.4.

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.

3 participants