wp/wp-includes/class-wp-image-editor-imagick.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
--- a/wp/wp-includes/class-wp-image-editor-imagick.php	Fri Sep 05 18:40:08 2025 +0200
+++ b/wp/wp-includes/class-wp-image-editor-imagick.php	Fri Sep 05 18:52:52 2025 +0200
@@ -190,12 +190,14 @@
 	 * Sets Image Compression quality on a 1-100% scale.
 	 *
 	 * @since 3.5.0
+	 * @since 6.8.0 The `$dims` parameter was added.
 	 *
-	 * @param int $quality Compression Quality. Range: [1,100]
+	 * @param int   $quality Compression Quality. Range: [1,100]
+	 * @param array $dims    Optional. Image dimensions array with 'width' and 'height' keys.
 	 * @return true|WP_Error True if set successfully; WP_Error on failure.
 	 */
-	public function set_quality( $quality = null ) {
-		$quality_result = parent::set_quality( $quality );
+	public function set_quality( $quality = null, $dims = array() ) {
+		$quality_result = parent::set_quality( $quality, $dims );
 		if ( is_wp_error( $quality_result ) ) {
 			return $quality_result;
 		} else {
@@ -206,6 +208,7 @@
 			switch ( $this->mime_type ) {
 				case 'image/jpeg':
 					$this->image->setImageCompressionQuality( $quality );
+					$this->image->setCompressionQuality( $quality );
 					$this->image->setImageCompression( imagick::COMPRESSION_JPEG );
 					break;
 				case 'image/webp':
@@ -214,14 +217,23 @@
 					if ( 'lossless' === $webp_info['type'] ) {
 						// Use WebP lossless settings.
 						$this->image->setImageCompressionQuality( 100 );
+						$this->image->setCompressionQuality( 100 );
 						$this->image->setOption( 'webp:lossless', 'true' );
+						parent::set_quality( 100 );
 					} else {
 						$this->image->setImageCompressionQuality( $quality );
+						$this->image->setCompressionQuality( $quality );
 					}
 					break;
 				case 'image/avif':
+					// Set the AVIF encoder to work faster, with minimal impact on image size.
+					$this->image->setOption( 'heic:speed', 7 );
+					$this->image->setImageCompressionQuality( $quality );
+					$this->image->setCompressionQuality( $quality );
+					break;
 				default:
 					$this->image->setImageCompressionQuality( $quality );
+					$this->image->setCompressionQuality( $quality );
 			}
 		} catch ( Exception $e ) {
 			return new WP_Error( 'image_quality_error', $e->getMessage() );
@@ -258,10 +270,10 @@
 		}
 
 		/*
-		 * If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images
+		 * If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF and HEIC images
 		 * are properly sized without affecting previous `getImageGeometry` behavior.
 		 */
-		if ( ( ! $width || ! $height ) && 'image/avif' === $this->mime_type ) {
+		if ( ( ! $width || ! $height ) && ( 'image/avif' === $this->mime_type || wp_is_heic_image_mime_type( $this->mime_type ) ) ) {
 			$size   = wp_getimagesize( $this->file );
 			$width  = $size[0];
 			$height = $size[1];
@@ -336,7 +348,7 @@
 	 *     If true, image will be cropped to the specified dimensions using center positions.
 	 *     If an array, the image will be cropped using the array to specify the crop location:
 	 *
-	 *     @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
+	 *     @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
 	 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
 	 * }
 	 * @return true|WP_Error
@@ -357,6 +369,14 @@
 			return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
 		}
 
+		$this->set_quality(
+			null,
+			array(
+				'width'  => $dst_w,
+				'height' => $dst_h,
+			)
+		);
+
 		// Execute the resize.
 		$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
 		if ( is_wp_error( $thumb_result ) ) {
@@ -464,7 +484,38 @@
 				$this->image->setOption( 'png:compression-filter', '5' );
 				$this->image->setOption( 'png:compression-level', '9' );
 				$this->image->setOption( 'png:compression-strategy', '1' );
-				$this->image->setOption( 'png:exclude-chunk', 'all' );
+				// Check to see if a PNG is indexed, and find the pixel depth.
+				if ( is_callable( array( $this->image, 'getImageDepth' ) ) ) {
+					$indexed_pixel_depth = $this->image->getImageDepth();
+
+					// Indexed PNG files get some additional handling.
+					if ( 0 < $indexed_pixel_depth && 8 >= $indexed_pixel_depth ) {
+						// Check for an alpha channel.
+						if (
+							is_callable( array( $this->image, 'getImageAlphaChannel' ) )
+							&& $this->image->getImageAlphaChannel()
+						) {
+							$this->image->setOption( 'png:include-chunk', 'tRNS' );
+						} else {
+							$this->image->setOption( 'png:exclude-chunk', 'all' );
+						}
+
+						// Reduce colors in the images to maximum needed, using the global colorspace.
+						$max_colors = pow( 2, $indexed_pixel_depth );
+						if ( is_callable( array( $this->image, 'getImageColors' ) ) ) {
+							$current_colors = $this->image->getImageColors();
+							$max_colors     = min( $max_colors, $current_colors );
+						}
+						$this->image->quantizeImage( $max_colors, $this->image->getColorspace(), 0, false, false );
+
+						/**
+						 * If the colorspace is 'gray', use the png8 format to ensure it stays indexed.
+						 */
+						if ( Imagick::COLORSPACE_GRAY === $this->image->getImageColorspace() ) {
+							$this->image->setOption( 'png:format', 'png8' );
+						}
+					}
+				}
 			}
 
 			/*
@@ -483,11 +534,23 @@
 				}
 			}
 
-			// Limit the bit depth of resized images to 8 bits per channel.
+			// Limit the bit depth of resized images.
 			if ( is_callable( array( $this->image, 'getImageDepth' ) ) && is_callable( array( $this->image, 'setImageDepth' ) ) ) {
-				if ( 8 < $this->image->getImageDepth() ) {
-					$this->image->setImageDepth( 8 );
-				}
+				/**
+				 * Filters the maximum bit depth of resized images.
+				 *
+				 * This filter only applies when resizing using the Imagick editor since GD
+				 * does not support getting or setting bit depth.
+				 *
+				 * Use this to adjust the maximum bit depth of resized images.
+				 *
+				 * @since 6.8.0
+				 *
+				 * @param int $max_depth   The maximum bit depth. Default is the input depth.
+				 * @param int $image_depth The bit depth of the original image.
+				 */
+				$max_depth = apply_filters( 'image_max_bit_depth', $this->image->getImageDepth(), $this->image->getImageDepth() );
+				$this->image->setImageDepth( $max_depth );
 			}
 		} catch ( Exception $e ) {
 			return new WP_Error( 'image_resize_error', $e->getMessage() );