wp/wp-includes/class-wp-image-editor-imagick.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
     8 
     8 
     9 /**
     9 /**
    10  * WordPress Image Editor Class for Image Manipulation through Imagick PHP Module
    10  * WordPress Image Editor Class for Image Manipulation through Imagick PHP Module
    11  *
    11  *
    12  * @since 3.5.0
    12  * @since 3.5.0
    13  * @package WordPress
    13  *
    14  * @subpackage Image_Editor
    14  * @see WP_Image_Editor
    15  * @uses WP_Image_Editor Extends class
       
    16  */
    15  */
    17 class WP_Image_Editor_Imagick extends WP_Image_Editor {
    16 class WP_Image_Editor_Imagick extends WP_Image_Editor {
    18 	/**
    17 	/**
       
    18 	 * Imagick object.
       
    19 	 *
    19 	 * @var Imagick
    20 	 * @var Imagick
    20 	 */
    21 	 */
    21 	protected $image; // Imagick Object
    22 	protected $image;
    22 
    23 
    23 	public function __destruct() {
    24 	public function __destruct() {
    24 		if ( $this->image instanceof Imagick ) {
    25 		if ( $this->image instanceof Imagick ) {
    25 			// we don't need the original in memory anymore
    26 			// we don't need the original in memory anymore
    26 			$this->image->clear();
    27 			$this->image->clear();
    33 	 *
    34 	 *
    34 	 * We require Imagick 2.2.0 or greater, based on whether the queryFormats()
    35 	 * We require Imagick 2.2.0 or greater, based on whether the queryFormats()
    35 	 * method can be called statically.
    36 	 * method can be called statically.
    36 	 *
    37 	 *
    37 	 * @since 3.5.0
    38 	 * @since 3.5.0
    38 	 * @access public
    39 	 *
    39 	 *
    40 	 * @static
    40 	 * @return boolean
    41 	 *
       
    42 	 * @param array $args
       
    43 	 * @return bool
    41 	 */
    44 	 */
    42 	public static function test( $args = array() ) {
    45 	public static function test( $args = array() ) {
    43 
    46 
    44 		// First, test Imagick's extension and classes.
    47 		// First, test Imagick's extension and classes.
    45 		if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) || ! class_exists( 'ImagickPixel' ) )
    48 		if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick', false ) || ! class_exists( 'ImagickPixel', false ) )
    46 			return false;
    49 			return false;
    47 
    50 
    48 		if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) )
    51 		if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) )
    49 			return false;
    52 			return false;
    50 
    53 
    59 			'getimageformat',
    62 			'getimageformat',
    60 			'setimageformat',
    63 			'setimageformat',
    61 			'setimagecompression',
    64 			'setimagecompression',
    62 			'setimagecompressionquality',
    65 			'setimagecompressionquality',
    63 			'setimagepage',
    66 			'setimagepage',
       
    67 			'setoption',
    64 			'scaleimage',
    68 			'scaleimage',
    65 			'cropimage',
    69 			'cropimage',
    66 			'rotateimage',
    70 			'rotateimage',
    67 			'flipimage',
    71 			'flipimage',
    68 			'flopimage',
    72 			'flopimage',
       
    73 			'readimage',
    69 		);
    74 		);
    70 
    75 
    71 		// Now, test for deep requirements within Imagick.
    76 		// Now, test for deep requirements within Imagick.
    72 		if ( ! defined( 'imagick::COMPRESSION_JPEG' ) )
    77 		if ( ! defined( 'imagick::COMPRESSION_JPEG' ) )
    73 			return false;
    78 			return false;
    74 
    79 
    75 		if ( array_diff( $required_methods, get_class_methods( 'Imagick' ) ) )
    80 		$class_methods = array_map( 'strtolower', get_class_methods( 'Imagick' ) );
       
    81 		if ( array_diff( $required_methods, $class_methods ) ) {
    76 			return false;
    82 			return false;
       
    83 		}
       
    84 
       
    85 		// HHVM Imagick does not support loading from URL, so fail to allow fallback to GD.
       
    86 		if ( defined( 'HHVM_VERSION' ) && isset( $args['path'] ) && preg_match( '|^https?://|', $args['path'] ) ) {
       
    87 			return false;
       
    88 		}
    77 
    89 
    78 		return true;
    90 		return true;
    79 	}
    91 	}
    80 
    92 
    81 	/**
    93 	/**
    82 	 * Checks to see if editor supports the mime-type specified.
    94 	 * Checks to see if editor supports the mime-type specified.
    83 	 *
    95 	 *
    84 	 * @since 3.5.0
    96 	 * @since 3.5.0
    85 	 * @access public
    97 	 *
       
    98 	 * @static
    86 	 *
    99 	 *
    87 	 * @param string $mime_type
   100 	 * @param string $mime_type
    88 	 * @return boolean
   101 	 * @return bool
    89 	 */
   102 	 */
    90 	public static function supports_mime_type( $mime_type ) {
   103 	public static function supports_mime_type( $mime_type ) {
    91 		$imagick_extension = strtoupper( self::get_extension( $mime_type ) );
   104 		$imagick_extension = strtoupper( self::get_extension( $mime_type ) );
    92 
   105 
    93 		if ( ! $imagick_extension )
   106 		if ( ! $imagick_extension )
   108 
   121 
   109 	/**
   122 	/**
   110 	 * Loads image from $this->file into new Imagick Object.
   123 	 * Loads image from $this->file into new Imagick Object.
   111 	 *
   124 	 *
   112 	 * @since 3.5.0
   125 	 * @since 3.5.0
   113 	 * @access protected
   126 	 *
   114 	 *
   127 	 * @return true|WP_Error True if loaded; WP_Error on failure.
   115 	 * @return boolean|WP_Error True if loaded; WP_Error on failure.
       
   116 	 */
   128 	 */
   117 	public function load() {
   129 	public function load() {
   118 		if ( $this->image instanceof Imagick )
   130 		if ( $this->image instanceof Imagick )
   119 			return true;
   131 			return true;
   120 
   132 
   121 		if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
   133 		if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
   122 			return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
   134 			return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
   123 
   135 
   124 		/** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
   136 		/*
   125 		// Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits
   137 		 * Even though Imagick uses less PHP memory than GD, set higher limit
   126 		@ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
   138 		 * for users that have low PHP.ini limits.
   127 
   139 		 */
   128 		try {
   140 		wp_raise_memory_limit( 'image' );
   129 			$this->image = new Imagick( $this->file );
   141 
   130 
   142 		try {
   131 			if( ! $this->image->valid() )
   143 			$this->image = new Imagick();
       
   144 			$file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
       
   145 			$filename = $this->file;
       
   146 
       
   147 			if ( 'pdf' == $file_extension ) {
       
   148 				$filename = $this->pdf_setup();
       
   149 			}
       
   150 
       
   151 			// Reading image after Imagick instantiation because `setResolution`
       
   152 			// only applies correctly before the image is read.
       
   153 			$this->image->readImage( $filename );
       
   154 
       
   155 			if ( ! $this->image->valid() )
   132 				return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file);
   156 				return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file);
   133 
   157 
   134 			// Select the first frame to handle animated images properly
   158 			// Select the first frame to handle animated images properly
   135 			if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) )
   159 			if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) )
   136 				$this->image->setIteratorIndex(0);
   160 				$this->image->setIteratorIndex(0);
   151 
   175 
   152 	/**
   176 	/**
   153 	 * Sets Image Compression quality on a 1-100% scale.
   177 	 * Sets Image Compression quality on a 1-100% scale.
   154 	 *
   178 	 *
   155 	 * @since 3.5.0
   179 	 * @since 3.5.0
   156 	 * @access public
       
   157 	 *
   180 	 *
   158 	 * @param int $quality Compression Quality. Range: [1,100]
   181 	 * @param int $quality Compression Quality. Range: [1,100]
   159 	 * @return boolean|WP_Error True if set successfully; WP_Error on failure.
   182 	 * @return true|WP_Error True if set successfully; WP_Error on failure.
   160 	 */
   183 	 */
   161 	public function set_quality( $quality = null ) {
   184 	public function set_quality( $quality = null ) {
   162 		$quality_result = parent::set_quality( $quality );
   185 		$quality_result = parent::set_quality( $quality );
   163 		if ( is_wp_error( $quality_result ) ) {
   186 		if ( is_wp_error( $quality_result ) ) {
   164 			return $quality_result;
   187 			return $quality_result;
   184 
   207 
   185 	/**
   208 	/**
   186 	 * Sets or updates current image size.
   209 	 * Sets or updates current image size.
   187 	 *
   210 	 *
   188 	 * @since 3.5.0
   211 	 * @since 3.5.0
   189 	 * @access protected
       
   190 	 *
   212 	 *
   191 	 * @param int $width
   213 	 * @param int $width
   192 	 * @param int $height
   214 	 * @param int $height
   193 	 *
   215 	 *
   194 	 * @return true|WP_Error
   216 	 * @return true|WP_Error
   198 		if ( !$width || !$height ) {
   220 		if ( !$width || !$height ) {
   199 			try {
   221 			try {
   200 				$size = $this->image->getImageGeometry();
   222 				$size = $this->image->getImageGeometry();
   201 			}
   223 			}
   202 			catch ( Exception $e ) {
   224 			catch ( Exception $e ) {
   203 				return new WP_Error( 'invalid_image', __('Could not read image size'), $this->file );
   225 				return new WP_Error( 'invalid_image', __( 'Could not read image size.' ), $this->file );
   204 			}
   226 			}
   205 		}
   227 		}
   206 
   228 
   207 		if ( ! $width )
   229 		if ( ! $width )
   208 			$width = $size['width'];
   230 			$width = $size['width'];
   219 	 * At minimum, either a height or width must be provided.
   241 	 * At minimum, either a height or width must be provided.
   220 	 * If one of the two is set to null, the resize will
   242 	 * If one of the two is set to null, the resize will
   221 	 * maintain aspect ratio according to the provided dimension.
   243 	 * maintain aspect ratio according to the provided dimension.
   222 	 *
   244 	 *
   223 	 * @since 3.5.0
   245 	 * @since 3.5.0
   224 	 * @access public
       
   225 	 *
   246 	 *
   226 	 * @param  int|null $max_w Image width.
   247 	 * @param  int|null $max_w Image width.
   227 	 * @param  int|null $max_h Image height.
   248 	 * @param  int|null $max_h Image height.
   228 	 * @param  boolean  $crop
   249 	 * @param  bool     $crop
   229 	 * @return boolean|WP_Error
   250 	 * @return bool|WP_Error
   230 	 */
   251 	 */
   231 	public function resize( $max_w, $max_h, $crop = false ) {
   252 	public function resize( $max_w, $max_h, $crop = false ) {
   232 		if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
   253 		if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
   233 			return true;
   254 			return true;
   234 
   255 
   239 
   260 
   240 		if ( $crop ) {
   261 		if ( $crop ) {
   241 			return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
   262 			return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
   242 		}
   263 		}
   243 
   264 
   244 		try {
   265 		// Execute the resize
   245 			/**
   266 		$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
   246 			 * @TODO: Thumbnail is more efficient, given a newer version of Imagemagick.
   267 		if ( is_wp_error( $thumb_result ) ) {
   247 			 * $this->image->thumbnailImage( $dst_w, $dst_h );
   268 			return $thumb_result;
       
   269 		}
       
   270 
       
   271 		return $this->update_size( $dst_w, $dst_h );
       
   272 	}
       
   273 
       
   274 	/**
       
   275 	 * Efficiently resize the current image
       
   276 	 *
       
   277 	 * This is a WordPress specific implementation of Imagick::thumbnailImage(),
       
   278 	 * which resizes an image to given dimensions and removes any associated profiles.
       
   279 	 *
       
   280 	 * @since 4.5.0
       
   281 	 *
       
   282 	 * @param int    $dst_w       The destination width.
       
   283 	 * @param int    $dst_h       The destination height.
       
   284 	 * @param string $filter_name Optional. The Imagick filter to use when resizing. Default 'FILTER_TRIANGLE'.
       
   285 	 * @param bool   $strip_meta  Optional. Strip all profiles, excluding color profiles, from the image. Default true.
       
   286 	 * @return bool|WP_Error
       
   287 	 */
       
   288 	protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIANGLE', $strip_meta = true ) {
       
   289 		$allowed_filters = array(
       
   290 			'FILTER_POINT',
       
   291 			'FILTER_BOX',
       
   292 			'FILTER_TRIANGLE',
       
   293 			'FILTER_HERMITE',
       
   294 			'FILTER_HANNING',
       
   295 			'FILTER_HAMMING',
       
   296 			'FILTER_BLACKMAN',
       
   297 			'FILTER_GAUSSIAN',
       
   298 			'FILTER_QUADRATIC',
       
   299 			'FILTER_CUBIC',
       
   300 			'FILTER_CATROM',
       
   301 			'FILTER_MITCHELL',
       
   302 			'FILTER_LANCZOS',
       
   303 			'FILTER_BESSEL',
       
   304 			'FILTER_SINC',
       
   305 		);
       
   306 
       
   307 		/**
       
   308 		 * Set the filter value if '$filter_name' name is in our whitelist and the related
       
   309 		 * Imagick constant is defined or fall back to our default filter.
       
   310 		 */
       
   311 		if ( in_array( $filter_name, $allowed_filters ) && defined( 'Imagick::' . $filter_name ) ) {
       
   312 			$filter = constant( 'Imagick::' . $filter_name );
       
   313 		} else {
       
   314 			$filter = defined( 'Imagick::FILTER_TRIANGLE' ) ? Imagick::FILTER_TRIANGLE : false;
       
   315 		}
       
   316 
       
   317 		/**
       
   318 		 * Filters whether to strip metadata from images when they're resized.
       
   319 		 *
       
   320 		 * This filter only applies when resizing using the Imagick editor since GD
       
   321 		 * always strips profiles by default.
       
   322 		 *
       
   323 		 * @since 4.5.0
       
   324 		 *
       
   325 		 * @param bool $strip_meta Whether to strip image metadata during resizing. Default true.
       
   326 		 */
       
   327 		if ( apply_filters( 'image_strip_meta', $strip_meta ) ) {
       
   328 			$this->strip_meta(); // Fail silently if not supported.
       
   329 		}
       
   330 
       
   331 		try {
       
   332 			/*
       
   333 			 * To be more efficient, resample large images to 5x the destination size before resizing
       
   334 			 * whenever the output size is less that 1/3 of the original image size (1/3^2 ~= .111),
       
   335 			 * unless we would be resampling to a scale smaller than 128x128.
   248 			 */
   336 			 */
   249 			$this->image->scaleImage( $dst_w, $dst_h );
   337 			if ( is_callable( array( $this->image, 'sampleImage' ) ) ) {
       
   338 				$resize_ratio = ( $dst_w / $this->size['width'] ) * ( $dst_h / $this->size['height'] );
       
   339 				$sample_factor = 5;
       
   340 
       
   341 				if ( $resize_ratio < .111 && ( $dst_w * $sample_factor > 128 && $dst_h * $sample_factor > 128 ) ) {
       
   342 					$this->image->sampleImage( $dst_w * $sample_factor, $dst_h * $sample_factor );
       
   343 				}
       
   344 			}
       
   345 
       
   346 			/*
       
   347 			 * Use resizeImage() when it's available and a valid filter value is set.
       
   348 			 * Otherwise, fall back to the scaleImage() method for resizing, which
       
   349 			 * results in better image quality over resizeImage() with default filter
       
   350 			 * settings and retains backward compatibility with pre 4.5 functionality.
       
   351 			 */
       
   352 			if ( is_callable( array( $this->image, 'resizeImage' ) ) && $filter ) {
       
   353 				$this->image->setOption( 'filter:support', '2.0' );
       
   354 				$this->image->resizeImage( $dst_w, $dst_h, $filter, 1 );
       
   355 			} else {
       
   356 				$this->image->scaleImage( $dst_w, $dst_h );
       
   357 			}
       
   358 
       
   359 			// Set appropriate quality settings after resizing.
       
   360 			if ( 'image/jpeg' == $this->mime_type ) {
       
   361 				if ( is_callable( array( $this->image, 'unsharpMaskImage' ) ) ) {
       
   362 					$this->image->unsharpMaskImage( 0.25, 0.25, 8, 0.065 );
       
   363 				}
       
   364 
       
   365 				$this->image->setOption( 'jpeg:fancy-upsampling', 'off' );
       
   366 			}
       
   367 
       
   368 			if ( 'image/png' === $this->mime_type ) {
       
   369 				$this->image->setOption( 'png:compression-filter', '5' );
       
   370 				$this->image->setOption( 'png:compression-level', '9' );
       
   371 				$this->image->setOption( 'png:compression-strategy', '1' );
       
   372 				$this->image->setOption( 'png:exclude-chunk', 'all' );
       
   373 			}
       
   374 
       
   375 			/*
       
   376 			 * If alpha channel is not defined, set it opaque.
       
   377 			 *
       
   378 			 * Note that Imagick::getImageAlphaChannel() is only available if Imagick
       
   379 			 * has been compiled against ImageMagick version 6.4.0 or newer.
       
   380 			 */
       
   381 			if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) )
       
   382 				&& is_callable( array( $this->image, 'setImageAlphaChannel' ) )
       
   383 				&& defined( 'Imagick::ALPHACHANNEL_UNDEFINED' )
       
   384 				&& defined( 'Imagick::ALPHACHANNEL_OPAQUE' )
       
   385 			) {
       
   386 				if ( $this->image->getImageAlphaChannel() === Imagick::ALPHACHANNEL_UNDEFINED ) {
       
   387 					$this->image->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE );
       
   388 				}
       
   389 			}
       
   390 
       
   391 			// Limit the bit depth of resized images to 8 bits per channel.
       
   392 			if ( is_callable( array( $this->image, 'getImageDepth' ) ) && is_callable( array( $this->image, 'setImageDepth' ) ) ) {
       
   393 				if ( 8 < $this->image->getImageDepth() ) {
       
   394 					$this->image->setImageDepth( 8 );
       
   395 				}
       
   396 			}
       
   397 
       
   398 			if ( is_callable( array( $this->image, 'setInterlaceScheme' ) ) && defined( 'Imagick::INTERLACE_NO' ) ) {
       
   399 				$this->image->setInterlaceScheme( Imagick::INTERLACE_NO );
       
   400 			}
       
   401 
   250 		}
   402 		}
   251 		catch ( Exception $e ) {
   403 		catch ( Exception $e ) {
   252 			return new WP_Error( 'image_resize_error', $e->getMessage() );
   404 			return new WP_Error( 'image_resize_error', $e->getMessage() );
   253 		}
   405 		}
   254 
       
   255 		return $this->update_size( $dst_w, $dst_h );
       
   256 	}
   406 	}
   257 
   407 
   258 	/**
   408 	/**
   259 	 * Resize multiple images from a single source.
   409 	 * Resize multiple images from a single source.
   260 	 *
   410 	 *
   261 	 * @since 3.5.0
   411 	 * @since 3.5.0
   262 	 * @access public
       
   263 	 *
   412 	 *
   264 	 * @param array $sizes {
   413 	 * @param array $sizes {
   265 	 *     An array of image size arrays. Default sizes are 'small', 'medium', 'large'.
   414 	 *     An array of image size arrays. Default sizes are 'small', 'medium', 'medium_large', 'large'.
   266 	 *
   415 	 *
   267 	 *     Either a height or width must be provided.
   416 	 *     Either a height or width must be provided.
   268 	 *     If one of the two is set to null, the resize will
   417 	 *     If one of the two is set to null, the resize will
   269 	 *     maintain aspect ratio according to the provided dimension.
   418 	 *     maintain aspect ratio according to the provided dimension.
   270 	 *
   419 	 *
   271 	 *     @type array $size {
   420 	 *     @type array $size {
   272 	 *         @type int  ['width']  Optional. Image width.
   421 	 *         Array of height, width values, and whether to crop.
   273 	 *         @type int  ['height'] Optional. Image height.
   422 	 *
       
   423 	 *         @type int  $width  Image width. Optional if `$height` is specified.
       
   424 	 *         @type int  $height Image height. Optional if `$width` is specified.
   274 	 *         @type bool $crop   Optional. Whether to crop the image. Default false.
   425 	 *         @type bool $crop   Optional. Whether to crop the image. Default false.
   275 	 *     }
   426 	 *     }
   276 	 * }
   427 	 * }
   277 	 * @return array An array of resized images' metadata by size.
   428 	 * @return array An array of resized images' metadata by size.
   278 	 */
   429 	 */
   326 
   477 
   327 	/**
   478 	/**
   328 	 * Crops Image.
   479 	 * Crops Image.
   329 	 *
   480 	 *
   330 	 * @since 3.5.0
   481 	 * @since 3.5.0
   331 	 * @access public
   482 	 *
   332 	 *
   483 	 * @param int  $src_x The start x position to crop from.
   333 	 * @param int $src_x The start x position to crop from.
   484 	 * @param int  $src_y The start y position to crop from.
   334 	 * @param int $src_y The start y position to crop from.
   485 	 * @param int  $src_w The width to crop.
   335 	 * @param int $src_w The width to crop.
   486 	 * @param int  $src_h The height to crop.
   336 	 * @param int $src_h The height to crop.
   487 	 * @param int  $dst_w Optional. The destination width.
   337 	 * @param int $dst_w Optional. The destination width.
   488 	 * @param int  $dst_h Optional. The destination height.
   338 	 * @param int $dst_h Optional. The destination height.
   489 	 * @param bool $src_abs Optional. If the source crop points are absolute.
   339 	 * @param boolean $src_abs Optional. If the source crop points are absolute.
   490 	 * @return bool|WP_Error
   340 	 * @return boolean|WP_Error
       
   341 	 */
   491 	 */
   342 	public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
   492 	public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
   343 		if ( $src_abs ) {
   493 		if ( $src_abs ) {
   344 			$src_w -= $src_x;
   494 			$src_w -= $src_x;
   345 			$src_h -= $src_y;
   495 			$src_h -= $src_y;
   355 				if ( ! $dst_w )
   505 				if ( ! $dst_w )
   356 					$dst_w = $src_w;
   506 					$dst_w = $src_w;
   357 				if ( ! $dst_h )
   507 				if ( ! $dst_h )
   358 					$dst_h = $src_h;
   508 					$dst_h = $src_h;
   359 
   509 
   360 				$this->image->scaleImage( $dst_w, $dst_h );
   510 				$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
       
   511 				if ( is_wp_error( $thumb_result ) ) {
       
   512 					return $thumb_result;
       
   513 				}
       
   514 
   361 				return $this->update_size();
   515 				return $this->update_size();
   362 			}
   516 			}
   363 		}
   517 		}
   364 		catch ( Exception $e ) {
   518 		catch ( Exception $e ) {
   365 			return new WP_Error( 'image_crop_error', $e->getMessage() );
   519 			return new WP_Error( 'image_crop_error', $e->getMessage() );
   369 
   523 
   370 	/**
   524 	/**
   371 	 * Rotates current image counter-clockwise by $angle.
   525 	 * Rotates current image counter-clockwise by $angle.
   372 	 *
   526 	 *
   373 	 * @since 3.5.0
   527 	 * @since 3.5.0
   374 	 * @access public
       
   375 	 *
   528 	 *
   376 	 * @param float $angle
   529 	 * @param float $angle
   377 	 * @return boolean|WP_Error
   530 	 * @return true|WP_Error
   378 	 */
   531 	 */
   379 	public function rotate( $angle ) {
   532 	public function rotate( $angle ) {
   380 		/**
   533 		/**
   381 		 * $angle is 360-$angle because Imagick rotates clockwise
   534 		 * $angle is 360-$angle because Imagick rotates clockwise
   382 		 * (GD rotates counter-clockwise)
   535 		 * (GD rotates counter-clockwise)
   383 		 */
   536 		 */
   384 		try {
   537 		try {
   385 			$this->image->rotateImage( new ImagickPixel('none'), 360-$angle );
   538 			$this->image->rotateImage( new ImagickPixel('none'), 360-$angle );
   386 
   539 
       
   540 			// Normalise Exif orientation data so that display is consistent across devices.
       
   541 			if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
       
   542 				$this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
       
   543 			}
       
   544 
   387 			// Since this changes the dimensions of the image, update the size.
   545 			// Since this changes the dimensions of the image, update the size.
   388 			$result = $this->update_size();
   546 			$result = $this->update_size();
   389 			if ( is_wp_error( $result ) )
   547 			if ( is_wp_error( $result ) )
   390 				return $result;
   548 				return $result;
   391 
   549 
   399 
   557 
   400 	/**
   558 	/**
   401 	 * Flips current image.
   559 	 * Flips current image.
   402 	 *
   560 	 *
   403 	 * @since 3.5.0
   561 	 * @since 3.5.0
   404 	 * @access public
   562 	 *
   405 	 *
   563 	 * @param bool $horz Flip along Horizontal Axis
   406 	 * @param boolean $horz Flip along Horizontal Axis
   564 	 * @param bool $vert Flip along Vertical Axis
   407 	 * @param boolean $vert Flip along Vertical Axis
   565 	 * @return true|WP_Error
   408 	 * @returns boolean|WP_Error
       
   409 	 */
   566 	 */
   410 	public function flip( $horz, $vert ) {
   567 	public function flip( $horz, $vert ) {
   411 		try {
   568 		try {
   412 			if ( $horz )
   569 			if ( $horz )
   413 				$this->image->flipImage();
   570 				$this->image->flipImage();
   423 
   580 
   424 	/**
   581 	/**
   425 	 * Saves current image to file.
   582 	 * Saves current image to file.
   426 	 *
   583 	 *
   427 	 * @since 3.5.0
   584 	 * @since 3.5.0
   428 	 * @access public
       
   429 	 *
   585 	 *
   430 	 * @param string $destfilename
   586 	 * @param string $destfilename
   431 	 * @param string $mime_type
   587 	 * @param string $mime_type
   432 	 * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
   588 	 * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
   433 	 */
   589 	 */
   447 		}
   603 		}
   448 
   604 
   449 		return $saved;
   605 		return $saved;
   450 	}
   606 	}
   451 
   607 
       
   608 	/**
       
   609 	 *
       
   610 	 * @param Imagick $image
       
   611 	 * @param string $filename
       
   612 	 * @param string $mime_type
       
   613 	 * @return array|WP_Error
       
   614 	 */
   452 	protected function _save( $image, $filename = null, $mime_type = null ) {
   615 	protected function _save( $image, $filename = null, $mime_type = null ) {
   453 		list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
   616 		list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
   454 
   617 
   455 		if ( ! $filename )
   618 		if ( ! $filename )
   456 			$filename = $this->generate_filename( null, null, $extension );
   619 			$filename = $this->generate_filename( null, null, $extension );
   486 
   649 
   487 	/**
   650 	/**
   488 	 * Streams current image to browser.
   651 	 * Streams current image to browser.
   489 	 *
   652 	 *
   490 	 * @since 3.5.0
   653 	 * @since 3.5.0
   491 	 * @access public
   654 	 *
   492 	 *
   655 	 * @param string $mime_type The mime type of the image.
   493 	 * @param string $mime_type
   656 	 * @return bool|WP_Error True on success, WP_Error object on failure.
   494 	 * @return boolean|WP_Error
       
   495 	 */
   657 	 */
   496 	public function stream( $mime_type = null ) {
   658 	public function stream( $mime_type = null ) {
   497 		list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
   659 		list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
   498 
   660 
   499 		try {
   661 		try {
   511 			return new WP_Error( 'image_stream_error', $e->getMessage() );
   673 			return new WP_Error( 'image_stream_error', $e->getMessage() );
   512 		}
   674 		}
   513 
   675 
   514 		return true;
   676 		return true;
   515 	}
   677 	}
       
   678 
       
   679 	/**
       
   680 	 * Strips all image meta except color profiles from an image.
       
   681 	 *
       
   682 	 * @since 4.5.0
       
   683 	 *
       
   684 	 * @return true|WP_Error True if stripping metadata was successful. WP_Error object on error.
       
   685 	 */
       
   686 	protected function strip_meta() {
       
   687 
       
   688 		if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) {
       
   689 			/* translators: %s: ImageMagick method name */
       
   690 			return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::getImageProfiles()</code>' ) );
       
   691 		}
       
   692 
       
   693 		if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) {
       
   694 			/* translators: %s: ImageMagick method name */
       
   695 			return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::removeImageProfile()</code>' ) );
       
   696 		}
       
   697 
       
   698 		/*
       
   699 		 * Protect a few profiles from being stripped for the following reasons:
       
   700 		 *
       
   701 		 * - icc:  Color profile information
       
   702 		 * - icm:  Color profile information
       
   703 		 * - iptc: Copyright data
       
   704 		 * - exif: Orientation data
       
   705 		 * - xmp:  Rights usage data
       
   706 		 */
       
   707 		$protected_profiles = array(
       
   708 			'icc',
       
   709 			'icm',
       
   710 			'iptc',
       
   711 			'exif',
       
   712 			'xmp',
       
   713 		);
       
   714 
       
   715 		try {
       
   716 			// Strip profiles.
       
   717 			foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) {
       
   718 				if ( ! in_array( $key, $protected_profiles ) ) {
       
   719 					$this->image->removeImageProfile( $key );
       
   720 				}
       
   721 			}
       
   722 
       
   723 		} catch ( Exception $e ) {
       
   724 			return new WP_Error( 'image_strip_meta_error', $e->getMessage() );
       
   725 		}
       
   726 
       
   727 		return true;
       
   728 	}
       
   729 
       
   730 	/**
       
   731 	 * Sets up Imagick for PDF processing.
       
   732 	 * Increases rendering DPI and only loads first page.
       
   733 	 *
       
   734 	 * @since 4.7.0
       
   735 	 *
       
   736 	 * @return string|WP_Error File to load or WP_Error on failure.
       
   737 	 */
       
   738 	protected function pdf_setup() {
       
   739 		try {
       
   740 			// By default, PDFs are rendered in a very low resolution.
       
   741 			// We want the thumbnail to be readable, so increase the rendering DPI.
       
   742 			$this->image->setResolution( 128, 128 );
       
   743 
       
   744 			// Only load the first page.
       
   745 			return $this->file . '[0]';
       
   746 		}
       
   747 		catch ( Exception $e ) {
       
   748 			return new WP_Error( 'pdf_setup_failed', $e->getMessage(), $this->file );
       
   749 		}
       
   750 	}
       
   751 
   516 }
   752 }