diff -r 34716fd837a4 -r be944660c56a wp/wp-includes/class-wp-image-editor-imagick.php
--- a/wp/wp-includes/class-wp-image-editor-imagick.php Tue Dec 15 15:52:01 2020 +0100
+++ b/wp/wp-includes/class-wp-image-editor-imagick.php Wed Sep 21 18:19:35 2022 +0200
@@ -71,6 +71,7 @@
'flipimage',
'flopimage',
'readimage',
+ 'readimageblob',
);
// Now, test for deep requirements within Imagick.
@@ -127,7 +128,7 @@
return true;
}
- if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) {
+ if ( ! is_file( $this->file ) && ! wp_is_stream( $this->file ) ) {
return new WP_Error( 'error_loading_image', __( 'File doesn’t exist?' ), $this->file );
}
@@ -140,15 +141,21 @@
try {
$this->image = new Imagick();
$file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
- $filename = $this->file;
if ( 'pdf' === $file_extension ) {
- $filename = $this->pdf_setup();
- }
+ $pdf_loaded = $this->pdf_load_source();
- // Reading image after Imagick instantiation because `setResolution`
- // only applies correctly before the image is read.
- $this->image->readImage( $filename );
+ if ( is_wp_error( $pdf_loaded ) ) {
+ return $pdf_loaded;
+ }
+ } else {
+ if ( wp_is_stream( $this->file ) ) {
+ // Due to reports of issues with streams with `Imagick::readImageFile()`, uses `Imagick::readImageBlob()` instead.
+ $this->image->readImageBlob( file_get_contents( $this->file ), $this->file );
+ } else {
+ $this->image->readImage( $this->file );
+ }
+ }
if ( ! $this->image->valid() ) {
return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
@@ -165,6 +172,7 @@
}
$updated_size = $this->update_size();
+
if ( is_wp_error( $updated_size ) ) {
return $updated_size;
}
@@ -189,19 +197,32 @@
}
try {
- if ( 'image/jpeg' === $this->mime_type ) {
- $this->image->setImageCompressionQuality( $quality );
- $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
- } else {
- $this->image->setImageCompressionQuality( $quality );
+ switch ( $this->mime_type ) {
+ case 'image/jpeg':
+ $this->image->setImageCompressionQuality( $quality );
+ $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
+ break;
+ case 'image/webp':
+ $webp_info = wp_get_webp_info( $this->file );
+
+ if ( 'lossless' === $webp_info['type'] ) {
+ // Use WebP lossless settings.
+ $this->image->setImageCompressionQuality( 100 );
+ $this->image->setOption( 'webp:lossless', 'true' );
+ } else {
+ $this->image->setImageCompressionQuality( $quality );
+ }
+ break;
+ default:
+ $this->image->setImageCompressionQuality( $quality );
}
} catch ( Exception $e ) {
return new WP_Error( 'image_quality_error', $e->getMessage() );
}
-
return true;
}
+
/**
* Sets or updates current image size.
*
@@ -244,7 +265,7 @@
* @param int|null $max_w Image width.
* @param int|null $max_h Image height.
* @param bool $crop
- * @return bool|WP_Error
+ * @return true|WP_Error
*/
public function resize( $max_w, $max_h, $crop = false ) {
if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) {
@@ -283,7 +304,7 @@
* @param int $dst_h The destination height.
* @param string $filter_name Optional. The Imagick filter to use when resizing. Default 'FILTER_TRIANGLE'.
* @param bool $strip_meta Optional. Strip all profiles, excluding color profiles, from the image. Default true.
- * @return bool|WP_Error
+ * @return void|WP_Error
*/
protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIANGLE', $strip_meta = true ) {
$allowed_filters = array(
@@ -516,7 +537,7 @@
* @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
+ * @return true|WP_Error
*/
public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
if ( $src_abs ) {
@@ -548,6 +569,7 @@
} catch ( Exception $e ) {
return new WP_Error( 'image_crop_error', $e->getMessage() );
}
+
return $this->update_size();
}
@@ -582,6 +604,7 @@
} catch ( Exception $e ) {
return new WP_Error( 'image_rotate_error', $e->getMessage() );
}
+
return true;
}
@@ -678,8 +701,16 @@
$orig_format = $this->image->getImageFormat();
$this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
- $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
+ } catch ( Exception $e ) {
+ return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
+ }
+ $write_image_result = $this->write_image( $this->image, $filename );
+ if ( is_wp_error( $write_image_result ) ) {
+ return $write_image_result;
+ }
+
+ try {
// Reset original format.
$this->image->setImageFormat( $orig_format );
} catch ( Exception $e ) {
@@ -702,12 +733,62 @@
}
/**
+ * Writes an image to a file or stream.
+ *
+ * @since 5.6.0
+ *
+ * @param Imagick $image
+ * @param string $filename The destination filename or stream URL.
+ * @return true|WP_Error
+ */
+ private function write_image( $image, $filename ) {
+ if ( wp_is_stream( $filename ) ) {
+ /*
+ * Due to reports of issues with streams with `Imagick::writeImageFile()` and `Imagick::writeImage()`, copies the blob instead.
+ * Checks for exact type due to: https://www.php.net/manual/en/function.file-put-contents.php
+ */
+ if ( file_put_contents( $filename, $image->getImageBlob() ) === false ) {
+ return new WP_Error(
+ 'image_save_error',
+ sprintf(
+ /* translators: %s: PHP function name. */
+ __( '%s failed while writing image to stream.' ),
+ 'file_put_contents()
'
+ ),
+ $filename
+ );
+ } else {
+ return true;
+ }
+ } else {
+ $dirname = dirname( $filename );
+
+ if ( ! wp_mkdir_p( $dirname ) ) {
+ return new WP_Error(
+ 'image_save_error',
+ sprintf(
+ /* translators: %s: Directory path. */
+ __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
+ esc_html( $dirname )
+ )
+ );
+ }
+
+ try {
+ return $image->writeImage( $filename );
+ } catch ( Exception $e ) {
+ return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
+ }
+ }
+ }
+
+ /**
* Streams current image to browser.
*
* @since 3.5.0
*
* @param string $mime_type The mime type of the image.
- * @return bool|WP_Error True on success, WP_Error object on failure.
+ * @return true|WP_Error True on success, WP_Error object on failure.
*/
public function stream( $mime_type = null ) {
list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
@@ -739,13 +820,25 @@
protected function strip_meta() {
if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) {
- /* translators: %s: ImageMagick method name. */
- return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), 'Imagick::getImageProfiles()
' ) );
+ return new WP_Error(
+ 'image_strip_meta_error',
+ sprintf(
+ /* translators: %s: ImageMagick method name. */
+ __( '%s is required to strip image meta.' ),
+ 'Imagick::getImageProfiles()
'
+ )
+ );
}
if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) {
- /* translators: %s: ImageMagick method name. */
- return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), 'Imagick::removeImageProfile()
' ) );
+ return new WP_Error(
+ 'image_strip_meta_error',
+ sprintf(
+ /* translators: %s: ImageMagick method name. */
+ __( '%s is required to strip image meta.' ),
+ 'Imagick::removeImageProfile()
'
+ )
+ );
}
/*
@@ -793,10 +886,6 @@
// 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 ) {
@@ -804,4 +893,39 @@
}
}
+ /**
+ * Load the image produced by Ghostscript.
+ *
+ * Includes a workaround for a bug in Ghostscript 8.70 that prevents processing of some PDF files
+ * when `use-cropbox` is set.
+ *
+ * @since 5.6.0
+ *
+ * @return true|WP_error
+ */
+ protected function pdf_load_source() {
+ $filename = $this->pdf_setup();
+
+ if ( is_wp_error( $filename ) ) {
+ return $filename;
+ }
+
+ try {
+ // 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 );
+
+ // Reading image after Imagick instantiation because `setResolution`
+ // only applies correctly before the image is read.
+ $this->image->readImage( $filename );
+ } catch ( Exception $e ) {
+ // Attempt to run `gs` without the `use-cropbox` option. See #48853.
+ $this->image->setOption( 'pdf:use-cropbox', false );
+
+ $this->image->readImage( $filename );
+ }
+
+ return true;
+ }
+
}