--- a/wp/wp-includes/class-wp-image-editor-imagick.php Tue Oct 22 16:11:46 2019 +0200
+++ b/wp/wp-includes/class-wp-image-editor-imagick.php Tue Dec 15 13:49:49 2020 +0100
@@ -23,7 +23,7 @@
public function __destruct() {
if ( $this->image instanceof Imagick ) {
- // we don't need the original in memory anymore
+ // We don't need the original in memory anymore.
$this->image->clear();
$this->image->destroy();
}
@@ -83,11 +83,6 @@
return false;
}
- // HHVM Imagick does not support loading from URL, so fail to allow fallback to GD.
- if ( defined( 'HHVM_VERSION' ) && isset( $args['path'] ) && preg_match( '|^https?://|', $args['path'] ) ) {
- return false;
- }
-
return true;
}
@@ -108,11 +103,12 @@
// setIteratorIndex is optional unless mime is an animated format.
// Here, we just say no if you are missing it and aren't loading a jpeg.
- if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && $mime_type != 'image/jpeg' ) {
+ if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && 'image/jpeg' !== $mime_type ) {
return false;
}
try {
+ // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
} catch ( Exception $e ) {
return false;
@@ -146,7 +142,7 @@
$file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
$filename = $this->file;
- if ( 'pdf' == $file_extension ) {
+ if ( 'pdf' === $file_extension ) {
$filename = $this->pdf_setup();
}
@@ -158,7 +154,7 @@
return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
}
- // Select the first frame to handle animated images properly
+ // Select the first frame to handle animated images properly.
if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) {
$this->image->setIteratorIndex( 0 );
}
@@ -193,7 +189,7 @@
}
try {
- if ( 'image/jpeg' == $this->mime_type ) {
+ if ( 'image/jpeg' === $this->mime_type ) {
$this->image->setImageCompressionQuality( $quality );
$this->image->setImageCompression( imagick::COMPRESSION_JPEG );
} else {
@@ -213,7 +209,6 @@
*
* @param int $width
* @param int $height
- *
* @return true|WP_Error
*/
protected function update_size( $width = null, $height = null ) {
@@ -246,9 +241,9 @@
*
* @since 3.5.0
*
- * @param int|null $max_w Image width.
- * @param int|null $max_h Image height.
- * @param bool $crop
+ * @param int|null $max_w Image width.
+ * @param int|null $max_h Image height.
+ * @param bool $crop
* @return bool|WP_Error
*/
public function resize( $max_w, $max_h, $crop = false ) {
@@ -260,13 +255,14 @@
if ( ! $dims ) {
return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions' ) );
}
+
list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
if ( $crop ) {
return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
}
- // Execute the resize
+ // Execute the resize.
$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
if ( is_wp_error( $thumb_result ) ) {
return $thumb_result;
@@ -309,10 +305,10 @@
);
/**
- * Set the filter value if '$filter_name' name is in our whitelist and the related
- * Imagick constant is defined or fall back to our default filter.
+ * Set the filter value if '$filter_name' name is in the allowed list and the related
+ * Imagick constant is defined or fall back to the default filter.
*/
- if ( in_array( $filter_name, $allowed_filters ) && defined( 'Imagick::' . $filter_name ) ) {
+ if ( in_array( $filter_name, $allowed_filters, true ) && defined( 'Imagick::' . $filter_name ) ) {
$filter = constant( 'Imagick::' . $filter_name );
} else {
$filter = defined( 'Imagick::FILTER_TRIANGLE' ) ? Imagick::FILTER_TRIANGLE : false;
@@ -361,7 +357,7 @@
}
// Set appropriate quality settings after resizing.
- if ( 'image/jpeg' == $this->mime_type ) {
+ if ( 'image/jpeg' === $this->mime_type ) {
if ( is_callable( array( $this->image, 'unsharpMaskImage' ) ) ) {
$this->image->unsharpMaskImage( 0.25, 0.25, 8, 0.065 );
}
@@ -408,12 +404,20 @@
}
/**
- * Resize multiple images from a single source.
+ * Create multiple smaller images from a single source.
+ *
+ * Attempts to create all sub-sizes and returns the meta data at the end. This
+ * may result in the server running out of resources. When it fails there may be few
+ * "orphaned" images left over as the meta data is never returned and saved.
+ *
+ * As of 5.3.0 the preferred way to do this is with `make_subsize()`. It creates
+ * the new images one at a time and allows for the meta data to be saved after
+ * each new image is created.
*
* @since 3.5.0
*
* @param array $sizes {
- * An array of image size arrays. Default sizes are 'small', 'medium', 'medium_large', 'large'.
+ * An array of image size data arrays.
*
* Either a height or width must be provided.
* If one of the two is set to null, the resize will
@@ -430,52 +434,74 @@
* @return array An array of resized images' metadata by size.
*/
public function multi_resize( $sizes ) {
- $metadata = array();
+ $metadata = array();
+
+ foreach ( $sizes as $size => $size_data ) {
+ $meta = $this->make_subsize( $size_data );
+
+ if ( ! is_wp_error( $meta ) ) {
+ $metadata[ $size ] = $meta;
+ }
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * Create an image sub-size and return the image meta data value for it.
+ *
+ * @since 5.3.0
+ *
+ * @param array $size_data {
+ * Array of size data.
+ *
+ * @type int $width The maximum width in pixels.
+ * @type int $height The maximum height in pixels.
+ * @type bool $crop Whether to crop the image to exact dimensions.
+ * }
+ * @return array|WP_Error The image data array for inclusion in the `sizes` array in the image meta,
+ * WP_Error object on error.
+ */
+ public function make_subsize( $size_data ) {
+ if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
+ return new WP_Error( 'image_subsize_create_error', __( 'Cannot resize the image. Both width and height are not set.' ) );
+ }
+
$orig_size = $this->size;
$orig_image = $this->image->getImage();
- foreach ( $sizes as $size => $size_data ) {
- if ( ! $this->image ) {
- $this->image = $orig_image->getImage();
- }
-
- if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
- continue;
- }
-
- if ( ! isset( $size_data['width'] ) ) {
- $size_data['width'] = null;
- }
- if ( ! isset( $size_data['height'] ) ) {
- $size_data['height'] = null;
- }
+ if ( ! isset( $size_data['width'] ) ) {
+ $size_data['width'] = null;
+ }
- if ( ! isset( $size_data['crop'] ) ) {
- $size_data['crop'] = false;
- }
-
- $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
- $duplicate = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) );
-
- if ( ! is_wp_error( $resize_result ) && ! $duplicate ) {
- $resized = $this->_save( $this->image );
+ if ( ! isset( $size_data['height'] ) ) {
+ $size_data['height'] = null;
+ }
- $this->image->clear();
- $this->image->destroy();
- $this->image = null;
-
- if ( ! is_wp_error( $resized ) && $resized ) {
- unset( $resized['path'] );
- $metadata[ $size ] = $resized;
- }
- }
-
- $this->size = $orig_size;
+ if ( ! isset( $size_data['crop'] ) ) {
+ $size_data['crop'] = false;
}
+ $resized = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
+
+ if ( is_wp_error( $resized ) ) {
+ $saved = $resized;
+ } else {
+ $saved = $this->_save( $this->image );
+
+ $this->image->clear();
+ $this->image->destroy();
+ $this->image = null;
+ }
+
+ $this->size = $orig_size;
$this->image = $orig_image;
- return $metadata;
+ if ( ! is_wp_error( $saved ) ) {
+ unset( $saved['path'] );
+ }
+
+ return $saved;
}
/**
@@ -483,12 +509,12 @@
*
* @since 3.5.0
*
- * @param int $src_x The start x position to crop from.
- * @param int $src_y The start y position to crop from.
- * @param int $src_w The width to crop.
- * @param int $src_h The height to crop.
- * @param int $dst_w Optional. The destination width.
- * @param int $dst_h Optional. The destination height.
+ * @param int $src_x The start x position to crop from.
+ * @param int $src_y The start y position to crop from.
+ * @param int $src_w The width to crop.
+ * @param int $src_h The height to crop.
+ * @param int $dst_w Optional. The destination width.
+ * @param int $dst_h Optional. The destination height.
* @param bool $src_abs Optional. If the source crop points are absolute.
* @return bool|WP_Error
*/
@@ -503,8 +529,8 @@
$this->image->setImagePage( $src_w, $src_h, 0, 0 );
if ( $dst_w || $dst_h ) {
- // If destination width/height isn't specified, use same as
- // width/height from source.
+ // If destination width/height isn't specified,
+ // use same as width/height from source.
if ( ! $dst_w ) {
$dst_w = $src_w;
}
@@ -541,7 +567,7 @@
try {
$this->image->rotateImage( new ImagickPixel( 'none' ), 360 - $angle );
- // Normalise Exif orientation data so that display is consistent across devices.
+ // Normalise EXIF orientation data so that display is consistent across devices.
if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
$this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
}
@@ -577,13 +603,38 @@
if ( $vert ) {
$this->image->flopImage();
}
+
+ // Normalise EXIF orientation data so that display is consistent across devices.
+ if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
+ $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
+ }
} catch ( Exception $e ) {
return new WP_Error( 'image_flip_error', $e->getMessage() );
}
+
return true;
}
/**
+ * Check if a JPEG image has EXIF Orientation tag and rotate it if needed.
+ *
+ * As ImageMagick copies the EXIF data to the flipped/rotated image, proceed only
+ * if EXIF Orientation can be reset afterwards.
+ *
+ * @since 5.3.0
+ *
+ * @return bool|WP_Error True if the image was rotated. False if no EXIF data or if the image doesn't need rotation.
+ * WP_Error if error while rotating.
+ */
+ public function maybe_exif_rotate() {
+ if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
+ return parent::maybe_exif_rotate();
+ } else {
+ return new WP_Error( 'write_exif_error', __( 'The image cannot be rotated because the embedded meta data cannot be updated.' ) );
+ }
+ }
+
+ /**
* Saves current image to file.
*
* @since 3.5.0
@@ -611,8 +662,8 @@
/**
* @param Imagick $image
- * @param string $filename
- * @param string $mime_type
+ * @param string $filename
+ * @param string $mime_type
* @return array|WP_Error
*/
protected function _save( $image, $filename = null, $mime_type = null ) {
@@ -623,26 +674,26 @@
}
try {
- // Store initial Format
+ // Store initial format.
$orig_format = $this->image->getImageFormat();
$this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
$this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
- // Reset original Format
+ // Reset original format.
$this->image->setImageFormat( $orig_format );
} catch ( Exception $e ) {
return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
}
- // Set correct file permissions
+ // Set correct file permissions.
$stat = stat( dirname( $filename ) );
- $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
- @ chmod( $filename, $perms );
+ $perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits.
+ chmod( $filename, $perms );
- /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
return array(
'path' => $filename,
+ /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
'width' => $this->size['width'],
'height' => $this->size['height'],
@@ -662,14 +713,14 @@
list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
try {
- // Temporarily change format for stream
+ // Temporarily change format for stream.
$this->image->setImageFormat( strtoupper( $extension ) );
- // Output stream of image content
+ // Output stream of image content.
header( "Content-Type: $mime_type" );
print $this->image->getImageBlob();
- // Reset Image to original Format
+ // Reset image to original format.
$this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
} catch ( Exception $e ) {
return new WP_Error( 'image_stream_error', $e->getMessage() );
@@ -688,12 +739,12 @@
protected function strip_meta() {
if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) {
- /* translators: %s: ImageMagick method name */
+ /* translators: %s: ImageMagick method name. */
return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::getImageProfiles()</code>' ) );
}
if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) {
- /* translators: %s: ImageMagick method name */
+ /* translators: %s: ImageMagick method name. */
return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::removeImageProfile()</code>' ) );
}
@@ -717,7 +768,7 @@
try {
// Strip profiles.
foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) {
- if ( ! in_array( $key, $protected_profiles ) ) {
+ if ( ! in_array( $key, $protected_profiles, true ) ) {
$this->image->removeImageProfile( $key );
}
}
@@ -742,6 +793,10 @@
// We want the thumbnail to be readable, so increase the rendering DPI.
$this->image->setResolution( 128, 128 );
+ // When generating thumbnails from cropped PDF pages, Imagemagick uses the uncropped
+ // area (resulting in unnecessary whitespace) unless the following option is set.
+ $this->image->setOption( 'pdf:use-cropbox', true );
+
// Only load the first page.
return $this->file . '[0]';
} catch ( Exception $e ) {