Skip to content

Commit 39b25ce

Browse files
committed
Media: Use imageflip() to flip images with GD.
`WP_Image_Editor_GD::flip()` and the deprecated `_flip_image_resource()` mirrored images by copying them with `imagecopyresampled()` and negative source dimensions. With the GD 2.4.0 bundled in PHP 8.6, this produces an all-black image. It affects flips in the image editor, and also JPEG uploads with an EXIF orientation of 2, 3 or 4, which `WP_Image_Editor::maybe_exif_rotate()` flips. Both functions now flip the image in place with `imageflip()`, and the output stays the same as before. Palette images are still converted to truecolor, alpha blending is turned off and alpha saving on, and the resolution and interlacing are reset to GD's defaults of 96 DPI and off. This matches what `rotate()`, `crop()` and `resize()` produce. Whether every operation should keep the source image's resolution and interlacing will be decided in a follow-up ticket. When no valid image is loaded, `flip()` now returns a `WP_Error` instead of throwing a `TypeError`. Props sanjith2023, rumancodes, arshidkv12, dhruvang21, abhishekfdd, desrosj. Fixes #66113. git-svn-id: https://develop.svn.wordpress.org/trunk@65367 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 055b013 commit 39b25ce

4 files changed

Lines changed: 766 additions & 29 deletions

File tree

‎src/wp-admin/includes/image-edit.php‎

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,7 @@ function _rotate_image_resource( $img, $angle ) {
565565
* Flips an image resource. Internal use only.
566566
*
567567
* @since 2.9.0
568+
* @since 7.2.0 Switched to using imageflip() instead of imagecopyresampled() with negative dimensions.
568569
* @deprecated 3.5.0 Use WP_Image_Editor::flip()
569570
* @see WP_Image_Editor::flip()
570571
*
@@ -577,25 +578,43 @@ function _rotate_image_resource( $img, $angle ) {
577578
function _flip_image_resource( $img, $horz, $vert ) {
578579
_deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::flip()' );
579580

580-
$w = imagesx( $img );
581-
$h = imagesy( $img );
582-
$dst = wp_imagecreatetruecolor( $w, $h );
581+
if ( ! is_gd_image( $img ) ) {
582+
return $img;
583+
}
583584

584-
if ( is_gd_image( $dst ) ) {
585-
$sx = $vert ? ( $w - 1 ) : 0;
586-
$sy = $horz ? ( $h - 1 ) : 0;
587-
$sw = $vert ? -$w : $w;
588-
$sh = $horz ? -$h : $h;
585+
// Keep returning a truecolor image, like the previous implementation did. imagewebp() rejects palette images.
586+
if ( ! imageistruecolor( $img ) && ! imagepalettetotruecolor( $img ) ) {
587+
return $img;
588+
}
589589

590-
if ( imagecopyresampled( $dst, $img, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) {
591-
if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0.
592-
imagedestroy( $img );
593-
}
590+
// Match the alpha settings of the previous implementation's copy, including when no axis is flipped.
591+
imagealphablending( $img, false );
592+
imagesavealpha( $img, true );
594593

595-
$img = $dst;
596-
}
594+
/*
595+
* imageflip() changes the image in place, so the image keeps its current resolution and interlace setting.
596+
* _rotate_image_resource() and _crop_image_resource() copy into a new image, which resets them to GD's defaults:
597+
* 96 DPI and no interlacing. Reset them here too, so a flip gives the same result as the other edits.
598+
*
599+
* @link https://core.trac.wordpress.org/ticket/66113
600+
*/
601+
imageresolution( $img, 96, 96 );
602+
imageinterlace( $img, 0 );
603+
604+
if ( ! $horz && ! $vert ) {
605+
return $img;
597606
}
598607

608+
if ( $horz && $vert ) {
609+
$mode = IMG_FLIP_BOTH;
610+
} elseif ( $horz ) {
611+
$mode = IMG_FLIP_VERTICAL;
612+
} else {
613+
$mode = IMG_FLIP_HORIZONTAL;
614+
}
615+
616+
imageflip( $img, $mode );
617+
599618
return $img;
600619
}
601620

‎src/wp-includes/class-wp-image-editor-gd.php‎

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -432,31 +432,59 @@ public function rotate( $angle ) {
432432
* Flips current image.
433433
*
434434
* @since 3.5.0
435+
* @since 7.2.0 Switched to using imageflip() instead of imagecopyresampled() with negative dimensions.
435436
*
436437
* @param bool $horz Flip along Horizontal Axis.
437438
* @param bool $vert Flip along Vertical Axis.
438439
* @return true|WP_Error
439440
*/
440441
public function flip( $horz, $vert ) {
441-
$w = $this->size['width'];
442-
$h = $this->size['height'];
443-
$dst = wp_imagecreatetruecolor( $w, $h );
442+
if ( ! is_gd_image( $this->image ) ) {
443+
return new WP_Error( 'image_flip_error', __( 'Image flip failed.' ), $this->file );
444+
}
444445

445-
if ( is_gd_image( $dst ) ) {
446-
$sx = $vert ? ( $w - 1 ) : 0;
447-
$sy = $horz ? ( $h - 1 ) : 0;
448-
$sw = $vert ? -$w : $w;
449-
$sh = $horz ? -$h : $h;
446+
/*
447+
* imageflip() works in place and keeps palette images as palette images.
448+
* The previous implementation always copied into a new truecolor image,
449+
* and saving or streaming as WebP relies on that, since imagewebp()
450+
* rejects palette images. Convert first to keep that behavior.
451+
*/
452+
if ( ! imageistruecolor( $this->image ) && ! imagepalettetotruecolor( $this->image ) ) {
453+
return new WP_Error( 'image_flip_error', __( 'Image flip failed.' ), $this->file );
454+
}
450455

451-
if ( imagecopyresampled( $dst, $this->image, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) {
452-
if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0.
453-
imagedestroy( $this->image );
454-
}
456+
/*
457+
* imageflip() also keeps the current alpha settings, which rotate() leaves
458+
* with blending enabled. Reset them to what the previous implementation
459+
* always produced, including when no axis is flipped.
460+
*/
461+
imagealphablending( $this->image, false );
462+
imagesavealpha( $this->image, true );
455463

456-
$this->image = $dst;
464+
/*
465+
* imageflip() changes the image in place, so the image keeps its current resolution and interlace setting.
466+
* rotate(), crop() and resize() copy into a new image, which resets them to GD's defaults: 96 DPI and no
467+
* interlacing. Reset them here too, so a flip gives the same result as the other edits.
468+
*
469+
* @link https://core.trac.wordpress.org/ticket/66113
470+
*/
471+
imageresolution( $this->image, 96, 96 );
472+
imageinterlace( $this->image, 0 );
457473

458-
return true;
459-
}
474+
if ( ! $horz && ! $vert ) {
475+
return true;
476+
}
477+
478+
if ( $horz && $vert ) {
479+
$mode = IMG_FLIP_BOTH;
480+
} elseif ( $horz ) {
481+
$mode = IMG_FLIP_VERTICAL;
482+
} else {
483+
$mode = IMG_FLIP_HORIZONTAL;
484+
}
485+
486+
if ( imageflip( $this->image, $mode ) ) {
487+
return true;
460488
}
461489

462490
return new WP_Error( 'image_flip_error', __( 'Image flip failed.' ), $this->file );

0 commit comments

Comments
 (0)