wp/wp-includes/class-wp-image-editor-imagick.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
    21 	 */
    21 	 */
    22 	protected $image;
    22 	protected $image;
    23 
    23 
    24 	public function __destruct() {
    24 	public function __destruct() {
    25 		if ( $this->image instanceof Imagick ) {
    25 		if ( $this->image instanceof Imagick ) {
    26 			// we don't need the original in memory anymore
    26 			// We don't need the original in memory anymore.
    27 			$this->image->clear();
    27 			$this->image->clear();
    28 			$this->image->destroy();
    28 			$this->image->destroy();
    29 		}
    29 		}
    30 	}
    30 	}
    31 
    31 
    81 		$class_methods = array_map( 'strtolower', get_class_methods( 'Imagick' ) );
    81 		$class_methods = array_map( 'strtolower', get_class_methods( 'Imagick' ) );
    82 		if ( array_diff( $required_methods, $class_methods ) ) {
    82 		if ( array_diff( $required_methods, $class_methods ) ) {
    83 			return false;
    83 			return false;
    84 		}
    84 		}
    85 
    85 
    86 		// HHVM Imagick does not support loading from URL, so fail to allow fallback to GD.
       
    87 		if ( defined( 'HHVM_VERSION' ) && isset( $args['path'] ) && preg_match( '|^https?://|', $args['path'] ) ) {
       
    88 			return false;
       
    89 		}
       
    90 
       
    91 		return true;
    86 		return true;
    92 	}
    87 	}
    93 
    88 
    94 	/**
    89 	/**
    95 	 * Checks to see if editor supports the mime-type specified.
    90 	 * Checks to see if editor supports the mime-type specified.
   106 			return false;
   101 			return false;
   107 		}
   102 		}
   108 
   103 
   109 		// setIteratorIndex is optional unless mime is an animated format.
   104 		// setIteratorIndex is optional unless mime is an animated format.
   110 		// Here, we just say no if you are missing it and aren't loading a jpeg.
   105 		// Here, we just say no if you are missing it and aren't loading a jpeg.
   111 		if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && $mime_type != 'image/jpeg' ) {
   106 		if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && 'image/jpeg' !== $mime_type ) {
   112 				return false;
   107 				return false;
   113 		}
   108 		}
   114 
   109 
   115 		try {
   110 		try {
       
   111 			// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
   116 			return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
   112 			return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
   117 		} catch ( Exception $e ) {
   113 		} catch ( Exception $e ) {
   118 			return false;
   114 			return false;
   119 		}
   115 		}
   120 	}
   116 	}
   144 		try {
   140 		try {
   145 			$this->image    = new Imagick();
   141 			$this->image    = new Imagick();
   146 			$file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
   142 			$file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
   147 			$filename       = $this->file;
   143 			$filename       = $this->file;
   148 
   144 
   149 			if ( 'pdf' == $file_extension ) {
   145 			if ( 'pdf' === $file_extension ) {
   150 				$filename = $this->pdf_setup();
   146 				$filename = $this->pdf_setup();
   151 			}
   147 			}
   152 
   148 
   153 			// Reading image after Imagick instantiation because `setResolution`
   149 			// Reading image after Imagick instantiation because `setResolution`
   154 			// only applies correctly before the image is read.
   150 			// only applies correctly before the image is read.
   156 
   152 
   157 			if ( ! $this->image->valid() ) {
   153 			if ( ! $this->image->valid() ) {
   158 				return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
   154 				return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
   159 			}
   155 			}
   160 
   156 
   161 			// Select the first frame to handle animated images properly
   157 			// Select the first frame to handle animated images properly.
   162 			if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) {
   158 			if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) {
   163 				$this->image->setIteratorIndex( 0 );
   159 				$this->image->setIteratorIndex( 0 );
   164 			}
   160 			}
   165 
   161 
   166 			$this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
   162 			$this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
   191 		} else {
   187 		} else {
   192 			$quality = $this->get_quality();
   188 			$quality = $this->get_quality();
   193 		}
   189 		}
   194 
   190 
   195 		try {
   191 		try {
   196 			if ( 'image/jpeg' == $this->mime_type ) {
   192 			if ( 'image/jpeg' === $this->mime_type ) {
   197 				$this->image->setImageCompressionQuality( $quality );
   193 				$this->image->setImageCompressionQuality( $quality );
   198 				$this->image->setImageCompression( imagick::COMPRESSION_JPEG );
   194 				$this->image->setImageCompression( imagick::COMPRESSION_JPEG );
   199 			} else {
   195 			} else {
   200 				$this->image->setImageCompressionQuality( $quality );
   196 				$this->image->setImageCompressionQuality( $quality );
   201 			}
   197 			}
   211 	 *
   207 	 *
   212 	 * @since 3.5.0
   208 	 * @since 3.5.0
   213 	 *
   209 	 *
   214 	 * @param int $width
   210 	 * @param int $width
   215 	 * @param int $height
   211 	 * @param int $height
   216 	 *
       
   217 	 * @return true|WP_Error
   212 	 * @return true|WP_Error
   218 	 */
   213 	 */
   219 	protected function update_size( $width = null, $height = null ) {
   214 	protected function update_size( $width = null, $height = null ) {
   220 		$size = null;
   215 		$size = null;
   221 		if ( ! $width || ! $height ) {
   216 		if ( ! $width || ! $height ) {
   244 	 * If one of the two is set to null, the resize will
   239 	 * If one of the two is set to null, the resize will
   245 	 * maintain aspect ratio according to the provided dimension.
   240 	 * maintain aspect ratio according to the provided dimension.
   246 	 *
   241 	 *
   247 	 * @since 3.5.0
   242 	 * @since 3.5.0
   248 	 *
   243 	 *
   249 	 * @param  int|null $max_w Image width.
   244 	 * @param int|null $max_w Image width.
   250 	 * @param  int|null $max_h Image height.
   245 	 * @param int|null $max_h Image height.
   251 	 * @param  bool     $crop
   246 	 * @param bool     $crop
   252 	 * @return bool|WP_Error
   247 	 * @return bool|WP_Error
   253 	 */
   248 	 */
   254 	public function resize( $max_w, $max_h, $crop = false ) {
   249 	public function resize( $max_w, $max_h, $crop = false ) {
   255 		if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) {
   250 		if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) {
   256 			return true;
   251 			return true;
   258 
   253 
   259 		$dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
   254 		$dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
   260 		if ( ! $dims ) {
   255 		if ( ! $dims ) {
   261 			return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions' ) );
   256 			return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions' ) );
   262 		}
   257 		}
       
   258 
   263 		list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
   259 		list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
   264 
   260 
   265 		if ( $crop ) {
   261 		if ( $crop ) {
   266 			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 );
   267 		}
   263 		}
   268 
   264 
   269 		// Execute the resize
   265 		// Execute the resize.
   270 		$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
   266 		$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
   271 		if ( is_wp_error( $thumb_result ) ) {
   267 		if ( is_wp_error( $thumb_result ) ) {
   272 			return $thumb_result;
   268 			return $thumb_result;
   273 		}
   269 		}
   274 
   270 
   307 			'FILTER_BESSEL',
   303 			'FILTER_BESSEL',
   308 			'FILTER_SINC',
   304 			'FILTER_SINC',
   309 		);
   305 		);
   310 
   306 
   311 		/**
   307 		/**
   312 		 * Set the filter value if '$filter_name' name is in our whitelist and the related
   308 		 * Set the filter value if '$filter_name' name is in the allowed list and the related
   313 		 * Imagick constant is defined or fall back to our default filter.
   309 		 * Imagick constant is defined or fall back to the default filter.
   314 		 */
   310 		 */
   315 		if ( in_array( $filter_name, $allowed_filters ) && defined( 'Imagick::' . $filter_name ) ) {
   311 		if ( in_array( $filter_name, $allowed_filters, true ) && defined( 'Imagick::' . $filter_name ) ) {
   316 			$filter = constant( 'Imagick::' . $filter_name );
   312 			$filter = constant( 'Imagick::' . $filter_name );
   317 		} else {
   313 		} else {
   318 			$filter = defined( 'Imagick::FILTER_TRIANGLE' ) ? Imagick::FILTER_TRIANGLE : false;
   314 			$filter = defined( 'Imagick::FILTER_TRIANGLE' ) ? Imagick::FILTER_TRIANGLE : false;
   319 		}
   315 		}
   320 
   316 
   359 			} else {
   355 			} else {
   360 				$this->image->scaleImage( $dst_w, $dst_h );
   356 				$this->image->scaleImage( $dst_w, $dst_h );
   361 			}
   357 			}
   362 
   358 
   363 			// Set appropriate quality settings after resizing.
   359 			// Set appropriate quality settings after resizing.
   364 			if ( 'image/jpeg' == $this->mime_type ) {
   360 			if ( 'image/jpeg' === $this->mime_type ) {
   365 				if ( is_callable( array( $this->image, 'unsharpMaskImage' ) ) ) {
   361 				if ( is_callable( array( $this->image, 'unsharpMaskImage' ) ) ) {
   366 					$this->image->unsharpMaskImage( 0.25, 0.25, 8, 0.065 );
   362 					$this->image->unsharpMaskImage( 0.25, 0.25, 8, 0.065 );
   367 				}
   363 				}
   368 
   364 
   369 				$this->image->setOption( 'jpeg:fancy-upsampling', 'off' );
   365 				$this->image->setOption( 'jpeg:fancy-upsampling', 'off' );
   406 			return new WP_Error( 'image_resize_error', $e->getMessage() );
   402 			return new WP_Error( 'image_resize_error', $e->getMessage() );
   407 		}
   403 		}
   408 	}
   404 	}
   409 
   405 
   410 	/**
   406 	/**
   411 	 * Resize multiple images from a single source.
   407 	 * Create multiple smaller images from a single source.
       
   408 	 *
       
   409 	 * Attempts to create all sub-sizes and returns the meta data at the end. This
       
   410 	 * may result in the server running out of resources. When it fails there may be few
       
   411 	 * "orphaned" images left over as the meta data is never returned and saved.
       
   412 	 *
       
   413 	 * As of 5.3.0 the preferred way to do this is with `make_subsize()`. It creates
       
   414 	 * the new images one at a time and allows for the meta data to be saved after
       
   415 	 * each new image is created.
   412 	 *
   416 	 *
   413 	 * @since 3.5.0
   417 	 * @since 3.5.0
   414 	 *
   418 	 *
   415 	 * @param array $sizes {
   419 	 * @param array $sizes {
   416 	 *     An array of image size arrays. Default sizes are 'small', 'medium', 'medium_large', 'large'.
   420 	 *     An array of image size data arrays.
   417 	 *
   421 	 *
   418 	 *     Either a height or width must be provided.
   422 	 *     Either a height or width must be provided.
   419 	 *     If one of the two is set to null, the resize will
   423 	 *     If one of the two is set to null, the resize will
   420 	 *     maintain aspect ratio according to the provided dimension.
   424 	 *     maintain aspect ratio according to the provided dimension.
   421 	 *
   425 	 *
   428 	 *     }
   432 	 *     }
   429 	 * }
   433 	 * }
   430 	 * @return array An array of resized images' metadata by size.
   434 	 * @return array An array of resized images' metadata by size.
   431 	 */
   435 	 */
   432 	public function multi_resize( $sizes ) {
   436 	public function multi_resize( $sizes ) {
   433 		$metadata   = array();
   437 		$metadata = array();
       
   438 
       
   439 		foreach ( $sizes as $size => $size_data ) {
       
   440 			$meta = $this->make_subsize( $size_data );
       
   441 
       
   442 			if ( ! is_wp_error( $meta ) ) {
       
   443 				$metadata[ $size ] = $meta;
       
   444 			}
       
   445 		}
       
   446 
       
   447 		return $metadata;
       
   448 	}
       
   449 
       
   450 	/**
       
   451 	 * Create an image sub-size and return the image meta data value for it.
       
   452 	 *
       
   453 	 * @since 5.3.0
       
   454 	 *
       
   455 	 * @param array $size_data {
       
   456 	 *     Array of size data.
       
   457 	 *
       
   458 	 *     @type int  $width  The maximum width in pixels.
       
   459 	 *     @type int  $height The maximum height in pixels.
       
   460 	 *     @type bool $crop   Whether to crop the image to exact dimensions.
       
   461 	 * }
       
   462 	 * @return array|WP_Error The image data array for inclusion in the `sizes` array in the image meta,
       
   463 	 *                        WP_Error object on error.
       
   464 	 */
       
   465 	public function make_subsize( $size_data ) {
       
   466 		if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
       
   467 			return new WP_Error( 'image_subsize_create_error', __( 'Cannot resize the image. Both width and height are not set.' ) );
       
   468 		}
       
   469 
   434 		$orig_size  = $this->size;
   470 		$orig_size  = $this->size;
   435 		$orig_image = $this->image->getImage();
   471 		$orig_image = $this->image->getImage();
   436 
   472 
   437 		foreach ( $sizes as $size => $size_data ) {
   473 		if ( ! isset( $size_data['width'] ) ) {
   438 			if ( ! $this->image ) {
   474 			$size_data['width'] = null;
   439 				$this->image = $orig_image->getImage();
   475 		}
   440 			}
   476 
   441 
   477 		if ( ! isset( $size_data['height'] ) ) {
   442 			if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
   478 			$size_data['height'] = null;
   443 				continue;
   479 		}
   444 			}
   480 
   445 
   481 		if ( ! isset( $size_data['crop'] ) ) {
   446 			if ( ! isset( $size_data['width'] ) ) {
   482 			$size_data['crop'] = false;
   447 				$size_data['width'] = null;
   483 		}
   448 			}
   484 
   449 			if ( ! isset( $size_data['height'] ) ) {
   485 		$resized = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
   450 				$size_data['height'] = null;
   486 
   451 			}
   487 		if ( is_wp_error( $resized ) ) {
   452 
   488 			$saved = $resized;
   453 			if ( ! isset( $size_data['crop'] ) ) {
   489 		} else {
   454 				$size_data['crop'] = false;
   490 			$saved = $this->_save( $this->image );
   455 			}
   491 
   456 
   492 			$this->image->clear();
   457 			$resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
   493 			$this->image->destroy();
   458 			$duplicate     = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) );
   494 			$this->image = null;
   459 
   495 		}
   460 			if ( ! is_wp_error( $resize_result ) && ! $duplicate ) {
   496 
   461 				$resized = $this->_save( $this->image );
   497 		$this->size  = $orig_size;
   462 
       
   463 				$this->image->clear();
       
   464 				$this->image->destroy();
       
   465 				$this->image = null;
       
   466 
       
   467 				if ( ! is_wp_error( $resized ) && $resized ) {
       
   468 					unset( $resized['path'] );
       
   469 					$metadata[ $size ] = $resized;
       
   470 				}
       
   471 			}
       
   472 
       
   473 			$this->size = $orig_size;
       
   474 		}
       
   475 
       
   476 		$this->image = $orig_image;
   498 		$this->image = $orig_image;
   477 
   499 
   478 		return $metadata;
   500 		if ( ! is_wp_error( $saved ) ) {
       
   501 			unset( $saved['path'] );
       
   502 		}
       
   503 
       
   504 		return $saved;
   479 	}
   505 	}
   480 
   506 
   481 	/**
   507 	/**
   482 	 * Crops Image.
   508 	 * Crops Image.
   483 	 *
   509 	 *
   484 	 * @since 3.5.0
   510 	 * @since 3.5.0
   485 	 *
   511 	 *
   486 	 * @param int  $src_x The start x position to crop from.
   512 	 * @param int  $src_x   The start x position to crop from.
   487 	 * @param int  $src_y The start y position to crop from.
   513 	 * @param int  $src_y   The start y position to crop from.
   488 	 * @param int  $src_w The width to crop.
   514 	 * @param int  $src_w   The width to crop.
   489 	 * @param int  $src_h The height to crop.
   515 	 * @param int  $src_h   The height to crop.
   490 	 * @param int  $dst_w Optional. The destination width.
   516 	 * @param int  $dst_w   Optional. The destination width.
   491 	 * @param int  $dst_h Optional. The destination height.
   517 	 * @param int  $dst_h   Optional. The destination height.
   492 	 * @param bool $src_abs Optional. If the source crop points are absolute.
   518 	 * @param bool $src_abs Optional. If the source crop points are absolute.
   493 	 * @return bool|WP_Error
   519 	 * @return bool|WP_Error
   494 	 */
   520 	 */
   495 	public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
   521 	public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
   496 		if ( $src_abs ) {
   522 		if ( $src_abs ) {
   501 		try {
   527 		try {
   502 			$this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
   528 			$this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
   503 			$this->image->setImagePage( $src_w, $src_h, 0, 0 );
   529 			$this->image->setImagePage( $src_w, $src_h, 0, 0 );
   504 
   530 
   505 			if ( $dst_w || $dst_h ) {
   531 			if ( $dst_w || $dst_h ) {
   506 				// If destination width/height isn't specified, use same as
   532 				// If destination width/height isn't specified,
   507 				// width/height from source.
   533 				// use same as width/height from source.
   508 				if ( ! $dst_w ) {
   534 				if ( ! $dst_w ) {
   509 					$dst_w = $src_w;
   535 					$dst_w = $src_w;
   510 				}
   536 				}
   511 				if ( ! $dst_h ) {
   537 				if ( ! $dst_h ) {
   512 					$dst_h = $src_h;
   538 					$dst_h = $src_h;
   539 		 * (GD rotates counter-clockwise)
   565 		 * (GD rotates counter-clockwise)
   540 		 */
   566 		 */
   541 		try {
   567 		try {
   542 			$this->image->rotateImage( new ImagickPixel( 'none' ), 360 - $angle );
   568 			$this->image->rotateImage( new ImagickPixel( 'none' ), 360 - $angle );
   543 
   569 
   544 			// Normalise Exif orientation data so that display is consistent across devices.
   570 			// Normalise EXIF orientation data so that display is consistent across devices.
   545 			if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
   571 			if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
   546 				$this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
   572 				$this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
   547 			}
   573 			}
   548 
   574 
   549 			// Since this changes the dimensions of the image, update the size.
   575 			// Since this changes the dimensions of the image, update the size.
   575 			}
   601 			}
   576 
   602 
   577 			if ( $vert ) {
   603 			if ( $vert ) {
   578 				$this->image->flopImage();
   604 				$this->image->flopImage();
   579 			}
   605 			}
       
   606 
       
   607 			// Normalise EXIF orientation data so that display is consistent across devices.
       
   608 			if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
       
   609 				$this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
       
   610 			}
   580 		} catch ( Exception $e ) {
   611 		} catch ( Exception $e ) {
   581 			return new WP_Error( 'image_flip_error', $e->getMessage() );
   612 			return new WP_Error( 'image_flip_error', $e->getMessage() );
   582 		}
   613 		}
       
   614 
   583 		return true;
   615 		return true;
       
   616 	}
       
   617 
       
   618 	/**
       
   619 	 * Check if a JPEG image has EXIF Orientation tag and rotate it if needed.
       
   620 	 *
       
   621 	 * As ImageMagick copies the EXIF data to the flipped/rotated image, proceed only
       
   622 	 * if EXIF Orientation can be reset afterwards.
       
   623 	 *
       
   624 	 * @since 5.3.0
       
   625 	 *
       
   626 	 * @return bool|WP_Error True if the image was rotated. False if no EXIF data or if the image doesn't need rotation.
       
   627 	 *                       WP_Error if error while rotating.
       
   628 	 */
       
   629 	public function maybe_exif_rotate() {
       
   630 		if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
       
   631 			return parent::maybe_exif_rotate();
       
   632 		} else {
       
   633 			return new WP_Error( 'write_exif_error', __( 'The image cannot be rotated because the embedded meta data cannot be updated.' ) );
       
   634 		}
   584 	}
   635 	}
   585 
   636 
   586 	/**
   637 	/**
   587 	 * Saves current image to file.
   638 	 * Saves current image to file.
   588 	 *
   639 	 *
   609 		return $saved;
   660 		return $saved;
   610 	}
   661 	}
   611 
   662 
   612 	/**
   663 	/**
   613 	 * @param Imagick $image
   664 	 * @param Imagick $image
   614 	 * @param string $filename
   665 	 * @param string  $filename
   615 	 * @param string $mime_type
   666 	 * @param string  $mime_type
   616 	 * @return array|WP_Error
   667 	 * @return array|WP_Error
   617 	 */
   668 	 */
   618 	protected function _save( $image, $filename = null, $mime_type = null ) {
   669 	protected function _save( $image, $filename = null, $mime_type = null ) {
   619 		list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
   670 		list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
   620 
   671 
   621 		if ( ! $filename ) {
   672 		if ( ! $filename ) {
   622 			$filename = $this->generate_filename( null, null, $extension );
   673 			$filename = $this->generate_filename( null, null, $extension );
   623 		}
   674 		}
   624 
   675 
   625 		try {
   676 		try {
   626 			// Store initial Format
   677 			// Store initial format.
   627 			$orig_format = $this->image->getImageFormat();
   678 			$orig_format = $this->image->getImageFormat();
   628 
   679 
   629 			$this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
   680 			$this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
   630 			$this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
   681 			$this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
   631 
   682 
   632 			// Reset original Format
   683 			// Reset original format.
   633 			$this->image->setImageFormat( $orig_format );
   684 			$this->image->setImageFormat( $orig_format );
   634 		} catch ( Exception $e ) {
   685 		} catch ( Exception $e ) {
   635 			return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
   686 			return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
   636 		}
   687 		}
   637 
   688 
   638 		// Set correct file permissions
   689 		// Set correct file permissions.
   639 		$stat  = stat( dirname( $filename ) );
   690 		$stat  = stat( dirname( $filename ) );
   640 		$perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
   691 		$perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits.
   641 		@ chmod( $filename, $perms );
   692 		chmod( $filename, $perms );
   642 
   693 
   643 		/** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
       
   644 		return array(
   694 		return array(
   645 			'path'      => $filename,
   695 			'path'      => $filename,
       
   696 			/** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
   646 			'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
   697 			'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
   647 			'width'     => $this->size['width'],
   698 			'width'     => $this->size['width'],
   648 			'height'    => $this->size['height'],
   699 			'height'    => $this->size['height'],
   649 			'mime-type' => $mime_type,
   700 			'mime-type' => $mime_type,
   650 		);
   701 		);
   660 	 */
   711 	 */
   661 	public function stream( $mime_type = null ) {
   712 	public function stream( $mime_type = null ) {
   662 		list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
   713 		list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
   663 
   714 
   664 		try {
   715 		try {
   665 			// Temporarily change format for stream
   716 			// Temporarily change format for stream.
   666 			$this->image->setImageFormat( strtoupper( $extension ) );
   717 			$this->image->setImageFormat( strtoupper( $extension ) );
   667 
   718 
   668 			// Output stream of image content
   719 			// Output stream of image content.
   669 			header( "Content-Type: $mime_type" );
   720 			header( "Content-Type: $mime_type" );
   670 			print $this->image->getImageBlob();
   721 			print $this->image->getImageBlob();
   671 
   722 
   672 			// Reset Image to original Format
   723 			// Reset image to original format.
   673 			$this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
   724 			$this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
   674 		} catch ( Exception $e ) {
   725 		} catch ( Exception $e ) {
   675 			return new WP_Error( 'image_stream_error', $e->getMessage() );
   726 			return new WP_Error( 'image_stream_error', $e->getMessage() );
   676 		}
   727 		}
   677 
   728 
   686 	 * @return true|WP_Error True if stripping metadata was successful. WP_Error object on error.
   737 	 * @return true|WP_Error True if stripping metadata was successful. WP_Error object on error.
   687 	 */
   738 	 */
   688 	protected function strip_meta() {
   739 	protected function strip_meta() {
   689 
   740 
   690 		if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) {
   741 		if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) {
   691 			/* translators: %s: ImageMagick method name */
   742 			/* translators: %s: ImageMagick method name. */
   692 			return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::getImageProfiles()</code>' ) );
   743 			return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::getImageProfiles()</code>' ) );
   693 		}
   744 		}
   694 
   745 
   695 		if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) {
   746 		if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) {
   696 			/* translators: %s: ImageMagick method name */
   747 			/* translators: %s: ImageMagick method name. */
   697 			return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::removeImageProfile()</code>' ) );
   748 			return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::removeImageProfile()</code>' ) );
   698 		}
   749 		}
   699 
   750 
   700 		/*
   751 		/*
   701 		 * Protect a few profiles from being stripped for the following reasons:
   752 		 * Protect a few profiles from being stripped for the following reasons:
   715 		);
   766 		);
   716 
   767 
   717 		try {
   768 		try {
   718 			// Strip profiles.
   769 			// Strip profiles.
   719 			foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) {
   770 			foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) {
   720 				if ( ! in_array( $key, $protected_profiles ) ) {
   771 				if ( ! in_array( $key, $protected_profiles, true ) ) {
   721 					$this->image->removeImageProfile( $key );
   772 					$this->image->removeImageProfile( $key );
   722 				}
   773 				}
   723 			}
   774 			}
   724 		} catch ( Exception $e ) {
   775 		} catch ( Exception $e ) {
   725 			return new WP_Error( 'image_strip_meta_error', $e->getMessage() );
   776 			return new WP_Error( 'image_strip_meta_error', $e->getMessage() );
   740 		try {
   791 		try {
   741 			// By default, PDFs are rendered in a very low resolution.
   792 			// By default, PDFs are rendered in a very low resolution.
   742 			// We want the thumbnail to be readable, so increase the rendering DPI.
   793 			// We want the thumbnail to be readable, so increase the rendering DPI.
   743 			$this->image->setResolution( 128, 128 );
   794 			$this->image->setResolution( 128, 128 );
   744 
   795 
       
   796 			// When generating thumbnails from cropped PDF pages, Imagemagick uses the uncropped
       
   797 			// area (resulting in unnecessary whitespace) unless the following option is set.
       
   798 			$this->image->setOption( 'pdf:use-cropbox', true );
       
   799 
   745 			// Only load the first page.
   800 			// Only load the first page.
   746 			return $this->file . '[0]';
   801 			return $this->file . '[0]';
   747 		} catch ( Exception $e ) {
   802 		} catch ( Exception $e ) {
   748 			return new WP_Error( 'pdf_setup_failed', $e->getMessage(), $this->file );
   803 			return new WP_Error( 'pdf_setup_failed', $e->getMessage(), $this->file );
   749 		}
   804 		}