diff --git a/src/wp-admin/includes/image-edit.php b/src/wp-admin/includes/image-edit.php index 2f6bc25740e2d..f6548cf6e5efb 100644 --- a/src/wp-admin/includes/image-edit.php +++ b/src/wp-admin/includes/image-edit.php @@ -577,25 +577,20 @@ function _rotate_image_resource( $img, $angle ) { function _flip_image_resource( $img, $horz, $vert ) { _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::flip()' ); - $w = imagesx( $img ); - $h = imagesy( $img ); - $dst = wp_imagecreatetruecolor( $w, $h ); - - if ( is_gd_image( $dst ) ) { - $sx = $vert ? ( $w - 1 ) : 0; - $sy = $horz ? ( $h - 1 ) : 0; - $sw = $vert ? -$w : $w; - $sh = $horz ? -$h : $h; - - if ( imagecopyresampled( $dst, $img, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) { - if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0. - imagedestroy( $img ); - } + if ( ! is_gd_image( $img ) || ( ! $horz && ! $vert ) ) { + return $img; + } - $img = $dst; - } + if ( $horz && $vert ) { + $mode = IMG_FLIP_BOTH; + } elseif ( $horz ) { + $mode = IMG_FLIP_VERTICAL; + } else { + $mode = IMG_FLIP_HORIZONTAL; } + imageflip( $img, $mode ); + return $img; } diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index 34c0453ff693a..88ed9473e685a 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -438,25 +438,20 @@ public function rotate( $angle ) { * @return true|WP_Error */ public function flip( $horz, $vert ) { - $w = $this->size['width']; - $h = $this->size['height']; - $dst = wp_imagecreatetruecolor( $w, $h ); - - if ( is_gd_image( $dst ) ) { - $sx = $vert ? ( $w - 1 ) : 0; - $sy = $horz ? ( $h - 1 ) : 0; - $sw = $vert ? -$w : $w; - $sh = $horz ? -$h : $h; - - if ( imagecopyresampled( $dst, $this->image, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) { - if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0. - imagedestroy( $this->image ); - } + if ( ! $horz && ! $vert ) { + return true; + } - $this->image = $dst; + if ( $horz && $vert ) { + $mode = IMG_FLIP_BOTH; + } elseif ( $horz ) { + $mode = IMG_FLIP_VERTICAL; + } else { + $mode = IMG_FLIP_HORIZONTAL; + } - return true; - } + if ( is_gd_image( $this->image ) && imageflip( $this->image, $mode ) ) { + return true; } return new WP_Error( 'image_flip_error', __( 'Image flip failed.' ), $this->file ); diff --git a/tests/phpunit/tests/image/editorGd.php b/tests/phpunit/tests/image/editorGd.php index e1763a7d5dd3e..97af19aa08aeb 100644 --- a/tests/phpunit/tests/image/editorGd.php +++ b/tests/phpunit/tests/image/editorGd.php @@ -586,6 +586,159 @@ public function test_flip() { $this->assertSame( $color_top_left, imagecolorat( $property->getValue( $gd_image_editor ), 0, 99 ) ); } + /** + * Tests that flipping moves every pixel to the expected position. + * + * @ticket 66113 + * + * @dataProvider data_flip + * + * @covers WP_Image_Editor_GD::flip + * + * @param bool $horz Whether to flip along the horizontal axis. + * @param bool $vert Whether to flip along the vertical axis. + */ + public function test_flip_moves_pixels( $horz, $vert ) { + $gd_image_editor = new WP_Image_Editor_GD( DIR_TESTDATA . '/images/gradient-square.jpg' ); + $gd_image_editor->load(); + + $property = new ReflectionProperty( $gd_image_editor, 'image' ); + if ( PHP_VERSION_ID < 80100 ) { + $property->setAccessible( true ); + } + + // A 3x2 image in which every pixel has a distinct color. + $width = 3; + $height = 2; + $image = imagecreatetruecolor( $width, $height ); + for ( $x = 0; $x < $width; $x++ ) { + for ( $y = 0; $y < $height; $y++ ) { + imagesetpixel( $image, $x, $y, ( $x + 1 ) * 0x100000 + ( $y + 1 ) * 0x10 ); + } + } + $original = imagecreatetruecolor( $width, $height ); + imagecopy( $original, $image, 0, 0, 0, 0, $width, $height ); + + $property->setValue( $gd_image_editor, $image ); + + $size_property = new ReflectionProperty( $gd_image_editor, 'size' ); + if ( PHP_VERSION_ID < 80100 ) { + $size_property->setAccessible( true ); + } + $size_property->setValue( + $gd_image_editor, + array( + 'width' => $width, + 'height' => $height, + ) + ); + + $this->assertTrue( $gd_image_editor->flip( $horz, $vert ), 'flip() should return true on success.' ); + + $flipped = $property->getValue( $gd_image_editor ); + for ( $x = 0; $x < $width; $x++ ) { + for ( $y = 0; $y < $height; $y++ ) { + $this->assertSame( + imagecolorat( $original, $x, $y ), + imagecolorat( $flipped, $vert ? $width - 1 - $x : $x, $horz ? $height - 1 - $y : $y ), + "Pixel ($x, $y) was not moved to the expected position." + ); + } + } + } + + /** + * Tests that the deprecated _flip_image_resource() moves every pixel to the expected position. + * + * @ticket 66113 + * + * @dataProvider data_flip + * + * @covers ::_flip_image_resource + * + * @expectedDeprecated _flip_image_resource + * + * @param bool $horz Whether to flip along the horizontal axis. + * @param bool $vert Whether to flip along the vertical axis. + */ + public function test_flip_image_resource_moves_pixels( $horz, $vert ) { + require_once ABSPATH . 'wp-admin/includes/image-edit.php'; + + // A 3x2 image in which every pixel has a distinct color. + $width = 3; + $height = 2; + $image = imagecreatetruecolor( $width, $height ); + for ( $x = 0; $x < $width; $x++ ) { + for ( $y = 0; $y < $height; $y++ ) { + imagesetpixel( $image, $x, $y, ( $x + 1 ) * 0x100000 + ( $y + 1 ) * 0x10 ); + } + } + $original = imagecreatetruecolor( $width, $height ); + imagecopy( $original, $image, 0, 0, 0, 0, $width, $height ); + + $flipped = _flip_image_resource( $image, $horz, $vert ); + + $this->assertTrue( is_gd_image( $flipped ), '_flip_image_resource() should return a GD image.' ); + for ( $x = 0; $x < $width; $x++ ) { + for ( $y = 0; $y < $height; $y++ ) { + $this->assertSame( + imagecolorat( $original, $x, $y ), + imagecolorat( $flipped, $vert ? $width - 1 - $x : $x, $horz ? $height - 1 - $y : $y ), + "Pixel ($x, $y) was not moved to the expected position." + ); + } + } + } + + /** + * Tests that flipping returns a WP_Error, not false, when the image cannot be flipped. + * + * @ticket 66113 + * + * @covers WP_Image_Editor_GD::flip + */ + public function test_flip_returns_wp_error_on_failure() { + $gd_image_editor = new WP_Image_Editor_GD( DIR_TESTDATA . '/images/gradient-square.jpg' ); + $gd_image_editor->load(); + + $property = new ReflectionProperty( $gd_image_editor, 'image' ); + if ( PHP_VERSION_ID < 80100 ) { + $property->setAccessible( true ); + } + $property->setValue( $gd_image_editor, false ); + + $result = $gd_image_editor->flip( true, false ); + + $this->assertWPError( $result, 'flip() should return a WP_Error on failure.' ); + $this->assertSame( 'image_flip_error', $result->get_error_code(), 'The error code should be image_flip_error.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_flip() { + return array( + 'horizontal axis' => array( + 'horz' => true, + 'vert' => false, + ), + 'vertical axis' => array( + 'horz' => false, + 'vert' => true, + ), + 'both axes' => array( + 'horz' => true, + 'vert' => true, + ), + 'neither axis' => array( + 'horz' => false, + 'vert' => false, + ), + ); + } + /** * Tests that an image created with WP_Image_Editor_GD preserves alpha with no resizing. *