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’t exist?'), $this->file ); |
134 return new WP_Error( 'error_loading_image', __('File doesn’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); |
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 */ |
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 } |