Skip to content

Media: Use imageflip() in WP_Image_Editor_GD::flip() and keep its true|WP_Error contract. - #13925

Closed
RumanCodes wants to merge 5 commits into
WordPress:trunkfrom
RumanCodes:fix/66113-gd-imageflip
Closed

RumanCodes wants to merge 5 commits into
WordPress:trunkfrom
RumanCodes:fix/66113-gd-imageflip

Conversation

@RumanCodes

@RumanCodes RumanCodes commented Oct 2, 2026 •

Copy link
Copy Markdown

Trac ticket: https://core.trac.wordpress.org/ticket/66113

Builds on #13549 by @arshidkv12 (that change is kept as its own commit) and addresses its open review comment from @dhruvang21. Thanks to #13920 by @sanjithsarkar for pointing out the deprecated _flip_image_resource(), which has the same code; it is covered here too.

Problem

WP_Image_Editor_GD::flip() flips images with imagecopyresampled() and negative source dimensions. With PHP 8.6's bundled GD 2.4.0 this produces a blank (all-black) image, and Tests_Image_Editor_GD::test_flip fails:

Failed asserting that 0 is identical to 8434407.

Solution

  • From GD: Use imageflip() for flipping images #13549: use imageflip(), the dedicated GD API (available since PHP 5.5). It produces the same result as before on older GD and the correct result on GD 2.4.0.
  • This follow-up: keep the documented true|WP_Error return contract. GD: Use imageflip() for flipping images #13549 returned imageflip()'s bool directly, so flip() could return false, unlike WP_Image_Editor::flip() and the Imagick editor. A failure now returns the existing image_flip_error WP_Error.
  • Also: the deprecated _flip_image_resource() uses imageflip() the same way.
  • Tests (in tests/phpunit/tests/image/editorGd.php):
    • test_flip_moves_pixels and test_flip_image_resource_moves_pixels check every pixel of a small image with distinct colors, for horizontal, vertical, both, and no flip (4 data cases each).
    • test_flip_returns_wp_error_on_failure covers the error path.

Test results

On the current branch (rebased on trunk), --filter Tests_Image_Editor_GD (33 test runs):

PHP 8.3.33 / GD 2.1 PHP 8.6.0beta2 / GD 2.4.0
trunk 32 pass, 1 error (TypeError in the new failure test) 7 failures + 1 error
this PR 33/33 pass 33/33 pass

On PHP 8.6, trunk fails the existing test_flip (Failed asserting that 0 is identical to 8434407.) and all six flip cases of the new pixel tests. On GD 2.1 the old code already flips correctly, so the pixel tests pass on trunk there.

--group image passes with this PR on PHP 8.6 (280 tests, 49 skipped). PHPCS and PHPCompatibility are clean.

How to test

  1. LOCAL_PHP=8.6-fpm npm run env:start (PHP 8.6 with GD 2.4.0).
  2. LOCAL_PHP=8.6-fpm npm run test:php -- --filter Tests_Image_Editor_GD. On trunk test_flip fails; with this PR all tests pass.
  3. Repeat on the default PHP version (npm run env:start) to confirm nothing changes on older GD.
  4. Optional, in the UI: Media > Library, edit an image, use "Flip vertical" / "Flip horizontal", and save. The result should be mirrored correctly, not black.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Reproducing the failure on PHP 8.6, root-cause investigation, writing the follow-up change and tests, and drafting this description. I reviewed and verified everything. The imageflip() change itself is the original PR author's work.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props arshidkv12, algorithmsunlocks, desrosj, dhruvang21.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@RumanCodes

Copy link
Copy Markdown
Author

The two failing "Upgrade Develop Version Tests" jobs (Upgrade from 5.3 / PHP 8.4 with MySQL 5.7 multisite, and Upgrade from 7.1 / PHP 7.4 with MySQL 5.7) are unrelated to this change: setup-php failed to install WP-CLI ("Could not setup wp-cli"), so wp core download exits with 127. All PHPUnit jobs (PHP 7.4–8.6), coding standards, and E2E pass. Could someone re-run the failed jobs?

arshidkv12 and others added 3 commits October 12, 2026 00:46
Replaces the imagecopyresampled() implementation, which relies on negative source dimensions, with imageflip(). The old approach produces incorrect pixel data with PHP 8.6's bundled GD 2.4.0.

See #66113.
…()`.

Converts a failed `imageflip()` call, or a missing GD image, into the existing `image_flip_error` WP_Error instead of returning `false`, matching `WP_Image_Editor::flip()` and the Imagick implementation.

Adds tests confirming that every pixel moves to the expected position for each flip direction, and that a failure returns a WP_Error.

Props ruman.
See #66113.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`_flip_image_resource()` used the same `imagecopyresampled()` approach with negative source dimensions as `WP_Image_Editor_GD::flip()`, which produces a blank image with PHP 8.6's bundled GD 2.4.0. It now uses `imageflip()` as well, and returns the image unchanged when it is not a GD image or no axis is given.

Props ruman.
See #66113.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@desrosj

desrosj commented Nov 3, 2026

Copy link
Copy Markdown
Member

@propsbot add dhruvang21

@desrosj

desrosj commented Nov 3, 2026

Copy link
Copy Markdown
Member

@propsbot add arshidkv12

@desrosj

desrosj commented Nov 3, 2026

Copy link
Copy Markdown
Member

@propsbot add @sanjithsarkar.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Both flip paths lose the previous truecolor conversion, causing indexed PNG/GIF exports to WebP to produce no image data; transparency-aware conversion must be restored and regression coverage added.

2 open issues
What changed in this PR

This PR replaces GD’s negative-dimension resampling with imageflip() to address incorrect image flipping on GD 2.4.0, while preserving the editor’s documented true|WP_Error return contract.

Changes:

  • Uses in-place flipping in the GD editor and deprecated image-resource helper.
  • Returns image_flip_error when the editor cannot perform a requested flip.
  • Adds pixel-position tests for all axis combinations and an invalid-image failure test.
File Description
tests/​phpunit/​tests/​image/​editorGd.php Adds pixel-level flip checks and error-contract coverage.
src/​wp-includes/​class-wp-image-editor-gd.php Replaces resampling with imageflip() and retains error handling.
src/​wp-admin/​includes/​image-edit.php Updates the deprecated flip helper to use imageflip().

Review effort: Balanced

Comment on lines +580 to +582
if ( ! is_gd_image( $img ) || ( ! $horz && ! $vert ) ) {
return $img;
}
Comment on lines +441 to +443
if ( ! $horz && ! $vert ) {
return true;
}

@desrosj desrosj left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this, @RumanCodes!

When you have a chance, could you please link your WordPress.org user account with your GitHub one? You'll find instructions on the Make Core blog. This ensures you'll rightly receive attribution for you contributions in the release.

Since Trac is still the canonical source of truth for this repository (it's a mirror of the SVN repository), conversation often happens there. I added a comment with feedback on Core-66113, but I could not mention you by username because I'm not clear what that is.

In addition to the point raised contextually, could you please add @since tags noting the differences in behavior where appropriate? Also, it would be great if you could include the list of other changes that will result from this switch started on #13920 in the PR description. Those cases will also need unit tests, and a few may require decisions or additional considerations in the code. Copilot was kind enough to flag a few of those for you as well.

Comment on lines +453 to +454
if ( is_gd_image( $this->image ) && imageflip( $this->image, $mode ) ) {
return true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this image check happens after the early return for the dimensions, an invalid image that's passed will never be flagged when dimensions are empty.

In my opinion, it's preferable to flag the more severe issue first. The dimensions do not matter if the image is invalid or empty.

@RumanCodes

Copy link
Copy Markdown
Author

@desrosj thanks! I have connected my github now!

@desrosj

desrosj commented Nov 6, 2026

Copy link
Copy Markdown
Member

@desrosj thanks! I have connected my github now!

Thank you!

It seems that #13920 is a bit further along and more polished after my most recent review, so I'm going to close this one out to avoid splitting efforts and attention. I hope you'll continue helping to test this issue over there, but you'll receive credit regardless for your help so far!

@desrosj desrosj closed this Nov 6, 2026
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.

4 participants