diff -r 48c4eec2b7e6 -r 8c2e4d02f4ef wp/wp-includes/class-wp-image-editor-imagick.php --- 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() );