Commit f2546bef291 for woocommerce
commit f2546bef291e74a6e7cbbcb28059395ac89f4580
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Fri Jul 31 15:31:49 2026 +0200
Improve Product Image srcset to prevent wide images from appearing blurry (#67105)
* Improve Product Image srcset to avoid very wide images to look blurry
* Add changelog
* PHPStan
* Remove filter after generating the image HTML
* Typo
* Add guards
* Add test
* Add try/finally
* Update test
* Add extra guards
* PHPStan
* Update changelog
* Harden guards
* Simplify code
* Harden guards (II)
* Harden guards (III)
diff --git a/plugins/woocommerce/changelog/fix-67103-product-image-updated-srcset b/plugins/woocommerce/changelog/fix-67103-product-image-updated-srcset
new file mode 100644
index 00000000000..2abb9834a41
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-67103-product-image-updated-srcset
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Improve Product Image srcset to prevent wide images from appearing blurry
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php
index 3e69abad07d..d70685be959 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php
@@ -260,7 +260,67 @@ class ProductImage extends AbstractBlock {
$attr['loading'] = $loading_attr;
}
- return $provided_image_id_is_valid ? wp_get_attachment_image( $image_id, $image_size, false, $attr ) : $product->get_image( $image_size, $attr );
+ $adjust_srcset = function ( $sources, $size_array, $image_src, $image_meta ) use ( $aspect_ratio ) {
+ if (
+ ! is_string( $aspect_ratio ) ||
+ ! is_array( $sources ) ||
+ ! is_array( $image_meta ) ||
+ ! isset( $image_meta['width'], $image_meta['height'] ) ||
+ ! is_numeric( $image_meta['width'] ) ||
+ ! is_numeric( $image_meta['height'] ) ||
+ $image_meta['width'] <= 0 ||
+ $image_meta['height'] <= 0
+ ) {
+ return $sources;
+ }
+
+ $aspect_ratio_parts = explode( '/', $aspect_ratio );
+
+ if (
+ count( $aspect_ratio_parts ) !== 2 ||
+ ! is_numeric( $aspect_ratio_parts[0] ) ||
+ ! is_numeric( $aspect_ratio_parts[1] ) ||
+ $aspect_ratio_parts[0] <= 0 ||
+ $aspect_ratio_parts[1] <= 0
+ ) {
+ return $sources;
+ }
+
+ $block_aspect_ratio = $aspect_ratio_parts[0] / $aspect_ratio_parts[1];
+ $image_aspect_ratio = $image_meta['width'] / $image_meta['height'];
+
+ if ( $image_aspect_ratio > $block_aspect_ratio ) {
+ $stretch_factor = $image_aspect_ratio / $block_aspect_ratio;
+
+ foreach ( $sources as $key => $source ) {
+ if ( ! is_array( $source ) || ! isset( $source['value'] ) || ! is_numeric( $source['value'] ) ) {
+ continue;
+ }
+ $sources[ $key ]['value'] = (int) round( $source['value'] / $stretch_factor );
+ }
+ }
+
+ return $sources;
+ };
+
+ $maybe_adjust_srcset =
+ 'cover' === $attributes['scale'] &&
+ is_string( $aspect_ratio ) &&
+ false !== strpos( $aspect_ratio, '/' );
+
+ if ( $maybe_adjust_srcset ) {
+ add_filter( 'wp_calculate_image_srcset', $adjust_srcset, 10, 4 );
+ }
+
+ try {
+ $image_html = $provided_image_id_is_valid ? wp_get_attachment_image( $image_id, $image_size, false, $attr ) : $product->get_image( $image_size, $attr );
+ } finally {
+ if ( $maybe_adjust_srcset ) {
+ remove_filter( 'wp_calculate_image_srcset', $adjust_srcset, 10 );
+ }
+ }
+
+ return $image_html;
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductImage.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductImage.php
index 568449b6f0d..1b4fefddd9c 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductImage.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductImage.php
@@ -349,6 +349,40 @@ class ProductImage extends \WP_UnitTestCase {
$product->delete( true );
}
+ /**
+ * @testdox Should reduce srcset width descriptors for wide images in portrait aspect ratios.
+ */
+ public function test_product_image_render_adjusts_srcset_for_portrait_aspect_ratio() {
+ $data = $this->create_product_with_image();
+
+ wp_update_attachment_metadata(
+ $data['image_id'],
+ array(
+ 'width' => 2000,
+ 'height' => 1000,
+ 'file' => 'wide-image.jpg',
+ 'sizes' => array(
+ 'large' => array(
+ 'file' => 'wide-image-1024x512.jpg',
+ 'width' => 1024,
+ 'height' => 512,
+ 'mime-type' => 'image/jpeg',
+ ),
+ ),
+ )
+ );
+ update_post_meta( $data['image_id'], '_wp_attached_file', 'wide-image.jpg' );
+
+ $default_markup = $this->render_product_image_block( $data['product'], '{"imageSizing":"single"}' );
+ $portrait_markup = $this->render_product_image_block( $data['product'], '{"aspectRatio":"3/4"}' );
+
+ $this->assertStringContainsString( 'wide-image-1024x512.jpg 1024w', $default_markup );
+ $this->assertStringContainsString( 'wide-image-1024x512.jpg 384w', $portrait_markup );
+
+ $data['product']->delete( true );
+ wp_delete_attachment( $data['image_id'], true );
+ }
+
/**
* Test that the ProductImage block handles invalid product IDs correctly.
*/