wp/wp-includes/class-wp-image-editor-imagick.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
equal deleted inserted replaced
21:48c4eec2b7e6 22:8c2e4d02f4ef
   188 
   188 
   189 	/**
   189 	/**
   190 	 * Sets Image Compression quality on a 1-100% scale.
   190 	 * Sets Image Compression quality on a 1-100% scale.
   191 	 *
   191 	 *
   192 	 * @since 3.5.0
   192 	 * @since 3.5.0
   193 	 *
   193 	 * @since 6.8.0 The `$dims` parameter was added.
   194 	 * @param int $quality Compression Quality. Range: [1,100]
   194 	 *
       
   195 	 * @param int   $quality Compression Quality. Range: [1,100]
       
   196 	 * @param array $dims    Optional. Image dimensions array with 'width' and 'height' keys.
   195 	 * @return true|WP_Error True if set successfully; WP_Error on failure.
   197 	 * @return true|WP_Error True if set successfully; WP_Error on failure.
   196 	 */
   198 	 */
   197 	public function set_quality( $quality = null ) {
   199 	public function set_quality( $quality = null, $dims = array() ) {
   198 		$quality_result = parent::set_quality( $quality );
   200 		$quality_result = parent::set_quality( $quality, $dims );
   199 		if ( is_wp_error( $quality_result ) ) {
   201 		if ( is_wp_error( $quality_result ) ) {
   200 			return $quality_result;
   202 			return $quality_result;
   201 		} else {
   203 		} else {
   202 			$quality = $this->get_quality();
   204 			$quality = $this->get_quality();
   203 		}
   205 		}
   204 
   206 
   205 		try {
   207 		try {
   206 			switch ( $this->mime_type ) {
   208 			switch ( $this->mime_type ) {
   207 				case 'image/jpeg':
   209 				case 'image/jpeg':
   208 					$this->image->setImageCompressionQuality( $quality );
   210 					$this->image->setImageCompressionQuality( $quality );
       
   211 					$this->image->setCompressionQuality( $quality );
   209 					$this->image->setImageCompression( imagick::COMPRESSION_JPEG );
   212 					$this->image->setImageCompression( imagick::COMPRESSION_JPEG );
   210 					break;
   213 					break;
   211 				case 'image/webp':
   214 				case 'image/webp':
   212 					$webp_info = wp_get_webp_info( $this->file );
   215 					$webp_info = wp_get_webp_info( $this->file );
   213 
   216 
   214 					if ( 'lossless' === $webp_info['type'] ) {
   217 					if ( 'lossless' === $webp_info['type'] ) {
   215 						// Use WebP lossless settings.
   218 						// Use WebP lossless settings.
   216 						$this->image->setImageCompressionQuality( 100 );
   219 						$this->image->setImageCompressionQuality( 100 );
       
   220 						$this->image->setCompressionQuality( 100 );
   217 						$this->image->setOption( 'webp:lossless', 'true' );
   221 						$this->image->setOption( 'webp:lossless', 'true' );
       
   222 						parent::set_quality( 100 );
   218 					} else {
   223 					} else {
   219 						$this->image->setImageCompressionQuality( $quality );
   224 						$this->image->setImageCompressionQuality( $quality );
       
   225 						$this->image->setCompressionQuality( $quality );
   220 					}
   226 					}
   221 					break;
   227 					break;
   222 				case 'image/avif':
   228 				case 'image/avif':
       
   229 					// Set the AVIF encoder to work faster, with minimal impact on image size.
       
   230 					$this->image->setOption( 'heic:speed', 7 );
       
   231 					$this->image->setImageCompressionQuality( $quality );
       
   232 					$this->image->setCompressionQuality( $quality );
       
   233 					break;
   223 				default:
   234 				default:
   224 					$this->image->setImageCompressionQuality( $quality );
   235 					$this->image->setImageCompressionQuality( $quality );
       
   236 					$this->image->setCompressionQuality( $quality );
   225 			}
   237 			}
   226 		} catch ( Exception $e ) {
   238 		} catch ( Exception $e ) {
   227 			return new WP_Error( 'image_quality_error', $e->getMessage() );
   239 			return new WP_Error( 'image_quality_error', $e->getMessage() );
   228 		}
   240 		}
   229 		return true;
   241 		return true;
   256 		if ( ! $height ) {
   268 		if ( ! $height ) {
   257 			$height = $size['height'];
   269 			$height = $size['height'];
   258 		}
   270 		}
   259 
   271 
   260 		/*
   272 		/*
   261 		 * If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images
   273 		 * If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF and HEIC images
   262 		 * are properly sized without affecting previous `getImageGeometry` behavior.
   274 		 * are properly sized without affecting previous `getImageGeometry` behavior.
   263 		 */
   275 		 */
   264 		if ( ( ! $width || ! $height ) && 'image/avif' === $this->mime_type ) {
   276 		if ( ( ! $width || ! $height ) && ( 'image/avif' === $this->mime_type || wp_is_heic_image_mime_type( $this->mime_type ) ) ) {
   265 			$size   = wp_getimagesize( $this->file );
   277 			$size   = wp_getimagesize( $this->file );
   266 			$width  = $size[0];
   278 			$width  = $size[0];
   267 			$height = $size[1];
   279 			$height = $size[1];
   268 		}
   280 		}
   269 
   281 
   334 	 * @param bool|array $crop  {
   346 	 * @param bool|array $crop  {
   335 	 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
   347 	 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
   336 	 *     If true, image will be cropped to the specified dimensions using center positions.
   348 	 *     If true, image will be cropped to the specified dimensions using center positions.
   337 	 *     If an array, the image will be cropped using the array to specify the crop location:
   349 	 *     If an array, the image will be cropped using the array to specify the crop location:
   338 	 *
   350 	 *
   339 	 *     @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
   351 	 *     @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
   340 	 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
   352 	 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
   341 	 * }
   353 	 * }
   342 	 * @return true|WP_Error
   354 	 * @return true|WP_Error
   343 	 */
   355 	 */
   344 	public function resize( $max_w, $max_h, $crop = false ) {
   356 	public function resize( $max_w, $max_h, $crop = false ) {
   354 		list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
   366 		list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
   355 
   367 
   356 		if ( $crop ) {
   368 		if ( $crop ) {
   357 			return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
   369 			return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
   358 		}
   370 		}
       
   371 
       
   372 		$this->set_quality(
       
   373 			null,
       
   374 			array(
       
   375 				'width'  => $dst_w,
       
   376 				'height' => $dst_h,
       
   377 			)
       
   378 		);
   359 
   379 
   360 		// Execute the resize.
   380 		// Execute the resize.
   361 		$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
   381 		$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
   362 		if ( is_wp_error( $thumb_result ) ) {
   382 		if ( is_wp_error( $thumb_result ) ) {
   363 			return $thumb_result;
   383 			return $thumb_result;
   462 
   482 
   463 			if ( 'image/png' === $this->mime_type ) {
   483 			if ( 'image/png' === $this->mime_type ) {
   464 				$this->image->setOption( 'png:compression-filter', '5' );
   484 				$this->image->setOption( 'png:compression-filter', '5' );
   465 				$this->image->setOption( 'png:compression-level', '9' );
   485 				$this->image->setOption( 'png:compression-level', '9' );
   466 				$this->image->setOption( 'png:compression-strategy', '1' );
   486 				$this->image->setOption( 'png:compression-strategy', '1' );
   467 				$this->image->setOption( 'png:exclude-chunk', 'all' );
   487 				// Check to see if a PNG is indexed, and find the pixel depth.
       
   488 				if ( is_callable( array( $this->image, 'getImageDepth' ) ) ) {
       
   489 					$indexed_pixel_depth = $this->image->getImageDepth();
       
   490 
       
   491 					// Indexed PNG files get some additional handling.
       
   492 					if ( 0 < $indexed_pixel_depth && 8 >= $indexed_pixel_depth ) {
       
   493 						// Check for an alpha channel.
       
   494 						if (
       
   495 							is_callable( array( $this->image, 'getImageAlphaChannel' ) )
       
   496 							&& $this->image->getImageAlphaChannel()
       
   497 						) {
       
   498 							$this->image->setOption( 'png:include-chunk', 'tRNS' );
       
   499 						} else {
       
   500 							$this->image->setOption( 'png:exclude-chunk', 'all' );
       
   501 						}
       
   502 
       
   503 						// Reduce colors in the images to maximum needed, using the global colorspace.
       
   504 						$max_colors = pow( 2, $indexed_pixel_depth );
       
   505 						if ( is_callable( array( $this->image, 'getImageColors' ) ) ) {
       
   506 							$current_colors = $this->image->getImageColors();
       
   507 							$max_colors     = min( $max_colors, $current_colors );
       
   508 						}
       
   509 						$this->image->quantizeImage( $max_colors, $this->image->getColorspace(), 0, false, false );
       
   510 
       
   511 						/**
       
   512 						 * If the colorspace is 'gray', use the png8 format to ensure it stays indexed.
       
   513 						 */
       
   514 						if ( Imagick::COLORSPACE_GRAY === $this->image->getImageColorspace() ) {
       
   515 							$this->image->setOption( 'png:format', 'png8' );
       
   516 						}
       
   517 					}
       
   518 				}
   468 			}
   519 			}
   469 
   520 
   470 			/*
   521 			/*
   471 			 * If alpha channel is not defined, set it opaque.
   522 			 * If alpha channel is not defined, set it opaque.
   472 			 *
   523 			 *
   481 				if ( $this->image->getImageAlphaChannel() === Imagick::ALPHACHANNEL_UNDEFINED ) {
   532 				if ( $this->image->getImageAlphaChannel() === Imagick::ALPHACHANNEL_UNDEFINED ) {
   482 					$this->image->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE );
   533 					$this->image->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE );
   483 				}
   534 				}
   484 			}
   535 			}
   485 
   536 
   486 			// Limit the bit depth of resized images to 8 bits per channel.
   537 			// Limit the bit depth of resized images.
   487 			if ( is_callable( array( $this->image, 'getImageDepth' ) ) && is_callable( array( $this->image, 'setImageDepth' ) ) ) {
   538 			if ( is_callable( array( $this->image, 'getImageDepth' ) ) && is_callable( array( $this->image, 'setImageDepth' ) ) ) {
   488 				if ( 8 < $this->image->getImageDepth() ) {
   539 				/**
   489 					$this->image->setImageDepth( 8 );
   540 				 * Filters the maximum bit depth of resized images.
   490 				}
   541 				 *
       
   542 				 * This filter only applies when resizing using the Imagick editor since GD
       
   543 				 * does not support getting or setting bit depth.
       
   544 				 *
       
   545 				 * Use this to adjust the maximum bit depth of resized images.
       
   546 				 *
       
   547 				 * @since 6.8.0
       
   548 				 *
       
   549 				 * @param int $max_depth   The maximum bit depth. Default is the input depth.
       
   550 				 * @param int $image_depth The bit depth of the original image.
       
   551 				 */
       
   552 				$max_depth = apply_filters( 'image_max_bit_depth', $this->image->getImageDepth(), $this->image->getImageDepth() );
       
   553 				$this->image->setImageDepth( $max_depth );
   491 			}
   554 			}
   492 		} catch ( Exception $e ) {
   555 		} catch ( Exception $e ) {
   493 			return new WP_Error( 'image_resize_error', $e->getMessage() );
   556 			return new WP_Error( 'image_resize_error', $e->getMessage() );
   494 		}
   557 		}
   495 	}
   558 	}