From 1dba638698cb283f52fe6d59848951e7951136fd Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Tue, 14 Jul 2026 15:36:44 +0530 Subject: [PATCH] fix(media): prevent Imagick pre-sampling upscale --- .../class-wp-image-editor-imagick.php | 19 +++- tests/phpunit/tests/image/editorImagick.php | 93 +++++++++++++++++++ 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 2cb3a694c567b..73cadbba3bc0b 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -492,15 +492,24 @@ protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIAN /* * To be more efficient, resample large images to 5x the destination size before resizing - * whenever the output size is less that 1/3 of the original image size (1/3^2 ~= .111), - * unless we would be resampling to a scale smaller than 128x128. + * whenever the output size is less than 1/3 of the original image size (1/3^2 ~= 0.111), + * but only when resampling reduces both source dimensions and keeps both dimensions above 128px. */ if ( is_callable( array( $this->image, 'sampleImage' ) ) ) { - $resize_ratio = ( $dst_w / $this->size['width'] ) * ( $dst_h / $this->size['height'] ); + $source_size = $this->image->getImageGeometry(); + $resize_ratio = ( $dst_w / $source_size['width'] ) * ( $dst_h / $source_size['height'] ); $sample_factor = 5; + $sample_width = $dst_w * $sample_factor; + $sample_height = $dst_h * $sample_factor; - if ( $resize_ratio < .111 && ( $dst_w * $sample_factor > 128 && $dst_h * $sample_factor > 128 ) ) { - $this->image->sampleImage( $dst_w * $sample_factor, $dst_h * $sample_factor ); + if ( + $resize_ratio < 0.111 + && $sample_width > 128 + && $sample_height > 128 + && $sample_width < $source_size['width'] + && $sample_height < $source_size['height'] + ) { + $this->image->sampleImage( $sample_width, $sample_height ); } } diff --git a/tests/phpunit/tests/image/editorImagick.php b/tests/phpunit/tests/image/editorImagick.php index e120c32502ad5..1009ef1d11fb4 100644 --- a/tests/phpunit/tests/image/editorImagick.php +++ b/tests/phpunit/tests/image/editorImagick.php @@ -65,6 +65,99 @@ public function test_resize() { ); } + /** + * Tests that pre-sampling does not upscale the source image. + * + * @ticket 48842 + */ + public function test_thumbnail_image_presampling_does_not_upscale() { + $image = new class() { + public $source_size; + public $sample_size; + + // phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + public function getImageGeometry() { + return $this->source_size; + } + + public function sampleImage( $width, $height ) { + $this->sample_size = array( $width, $height ); + } + + public function scaleImage() { + } + // phpcs:enable + }; + + $editor = new class( null ) extends WP_Image_Editor_Imagick { + public function call_thumbnail_image( $image, $source_size, $editor_size, $destination_size ) { + $image->source_size = array( + 'width' => $source_size[0], + 'height' => $source_size[1], + ); + $image->sample_size = null; + $this->image = $image; + $this->size = array( + 'width' => $editor_size[0], + 'height' => $editor_size[1], + ); + + $result = $this->thumbnail_image( $destination_size[0], $destination_size[1], 'FILTER_TRIANGLE', false ); + + return is_wp_error( $result ) ? $result : $image->sample_size; + } + }; + + // The ticket's source and destination dimensions must not trigger an upscale. + $this->assertNull( + $editor->call_thumbnail_image( + $image, + array( 10315, 7049 ), + array( 10315, 7049 ), + array( 2560, 1750 ) + ) + ); + + // Use current geometry because cropping can make the editor's stored size stale. + $this->assertNull( + $editor->call_thumbnail_image( + $image, + array( 200, 1000 ), + array( 1000, 1000 ), + array( 50, 100 ) + ) + ); + + // Skip pre-sampling unless both intermediate dimensions are smaller than the source. + $this->assertNull( + $editor->call_thumbnail_image( + $image, + array( 1500, 1000 ), + array( 1500, 1000 ), + array( 300, 150 ) + ) + ); + $this->assertNull( + $editor->call_thumbnail_image( + $image, + array( 2000, 750 ), + array( 2000, 750 ), + array( 300, 150 ) + ) + ); + + // Keep pre-sampling when both intermediate dimensions are smaller than the source. + $this->assertSame( + array( 1500, 750 ), + $editor->call_thumbnail_image( + $image, + array( 4800, 2400 ), + array( 4800, 2400 ), + array( 300, 150 ) + ) + ); + } + /** * Tests multi_resize() with single image resize and no crop. */