diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f32e236778..ab86835314 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7316,3 +7316,19 @@ function is_wp_version_compatible( $required ) { function is_php_version_compatible( $required ) { return empty( $required ) || version_compare( phpversion(), $required, '>=' ); } + +/** + * Check if two numbers are nearly the same. + * + * This is similar to using `round()` but the precision is more fine-grained. + * + * @since 5.3.0 + * + * @param int|float $expected The expected value. + * @param int|float $actual The actual number. + * @param int|float $precision The allowed variation. + * @return bool Whether the numbers match whithin the specified precision. + */ +function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) { + return abs( (float) $expected - (float) $actual ) <= $precision; +} diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 6f104a20c7..76fa325919 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -543,8 +543,28 @@ function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = fa return $output; } + // Stop if the destination size is larger than the original image dimensions. + if ( empty( $dest_h ) ) { + if ( $orig_w < $dest_w ) { + return false; + } + } elseif ( empty( $dest_w ) ) { + if ( $orig_h < $dest_h ) { + return false; + } + } else { + if ( $orig_w < $dest_w && $orig_h < $dest_h ) { + return false; + } + } + if ( $crop ) { - // crop the largest possible portion of the original image that we can size to $dest_w x $dest_h + // Crop the largest possible portion of the original image that we can size to $dest_w x $dest_h. + // Note that the requested crop dimensions are used as a maximum bounding box for the original image. + // If the original image's width or height is less than the requested width or height + // only the greater one will be cropped. + // For example when the original image is 600x300, and the requested crop dimensions are 400x400, + // the resulting image will be 400x300. $aspect_ratio = $orig_w / $orig_h; $new_w = min( $dest_w, $orig_w ); $new_h = min( $dest_h, $orig_h ); @@ -584,7 +604,7 @@ function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = fa $s_y = floor( ( $orig_h - $crop_h ) / 2 ); } } else { - // don't crop, just resize using $dest_w x $dest_h as a maximum bounding box + // Resize using $dest_w x $dest_h as a maximum bounding box. $crop_w = $orig_w; $crop_h = $orig_h; @@ -594,15 +614,29 @@ function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = fa list( $new_w, $new_h ) = wp_constrain_dimensions( $orig_w, $orig_h, $dest_w, $dest_h ); } - // if the resulting image would be the same size or larger we don't want to resize it - if ( $new_w >= $orig_w && $new_h >= $orig_h && intval( $dest_w ) !== intval( $orig_w ) && intval( $dest_h ) !== intval( $orig_h ) ) { - return false; + if ( wp_fuzzy_number_match( $new_w, $orig_w ) && wp_fuzzy_number_match( $new_h, $orig_h ) ) { + // The new size has virtually the same dimensions as the original image. + + /** + * Filters whether to proceed with making an image sub-size with identical dimensions + * with the original/source image. Differences of 1px may be due to rounding and are ignored. + * + * @since 5.3.0 + * + * @param bool The filtered value. + * @param int Original image width. + * @param int Original image height. + */ + $proceed = (bool) apply_filters( 'wp_image_resize_identical_dimensions', false, $orig_w, $orig_h ); + + if ( ! $proceed ) { + return false; + } } - // the return array matches the parameters to imagecopyresampled() + // The return array matches the parameters to imagecopyresampled(). // int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h return array( 0, 0, (int) $s_x, (int) $s_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h ); - } /** @@ -664,7 +698,7 @@ function wp_image_matches_ratio( $source_width, $source_height, $target_width, $ } // If the image dimensions are within 1px of the expected size, we consider it a match. - $matched = ( abs( $constrained_size[0] - $expected_size[0] ) <= 1 && abs( $constrained_size[1] - $expected_size[1] ) <= 1 ); + $matched = ( wp_fuzzy_number_match( $constrained_size[0], $expected_size[0] ) && wp_fuzzy_number_match( $constrained_size[1], $expected_size[1] ) ); return $matched; } diff --git a/tests/phpunit/tests/image/dimensions.php b/tests/phpunit/tests/image/dimensions.php index 392524453f..87b494ba87 100644 --- a/tests/phpunit/tests/image/dimensions.php +++ b/tests/phpunit/tests/image/dimensions.php @@ -129,6 +129,17 @@ class Tests_Image_Dimensions extends WP_UnitTestCase { } function test_640x480() { + // crop 640x480 to fit 640x480 (no change) + $out = image_resize_dimensions( 640, 480, 640, 480, true ); + $this->assertFalse( $out ); + + // resize 640x480 to fit 640x480 (no change) + $out = image_resize_dimensions( 640, 480, 640, 480, false ); + $this->assertFalse( $out ); + + // Test with the filter override. + add_filter( 'wp_image_resize_identical_dimensions', '__return_true' ); + // crop 640x480 to fit 640x480 (no change) $out = image_resize_dimensions( 640, 480, 640, 480, true ); // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h @@ -138,6 +149,8 @@ class Tests_Image_Dimensions extends WP_UnitTestCase { $out = image_resize_dimensions( 640, 480, 640, 480, false ); // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h $this->assertEquals( array( 0, 0, 0, 0, 640, 480, 640, 480 ), $out ); + + remove_filter( 'wp_image_resize_identical_dimensions', '__return_true' ); } /**