35 * 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() |
36 * method can be called statically. |
36 * method can be called statically. |
37 * |
37 * |
38 * @since 3.5.0 |
38 * @since 3.5.0 |
39 * |
39 * |
40 * @static |
|
41 * |
|
42 * @param array $args |
40 * @param array $args |
43 * @return bool |
41 * @return bool |
44 */ |
42 */ |
45 public static function test( $args = array() ) { |
43 public static function test( $args = array() ) { |
46 |
44 |
47 // First, test Imagick's extension and classes. |
45 // First, test Imagick's extension and classes. |
48 if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick', false ) || ! class_exists( 'ImagickPixel', false ) ) |
46 if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick', false ) || ! class_exists( 'ImagickPixel', false ) ) { |
49 return false; |
47 return false; |
50 |
48 } |
51 if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) ) |
49 |
|
50 if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) ) { |
52 return false; |
51 return false; |
|
52 } |
53 |
53 |
54 $required_methods = array( |
54 $required_methods = array( |
55 'clear', |
55 'clear', |
56 'destroy', |
56 'destroy', |
57 'valid', |
57 'valid', |
93 /** |
94 /** |
94 * Checks to see if editor supports the mime-type specified. |
95 * Checks to see if editor supports the mime-type specified. |
95 * |
96 * |
96 * @since 3.5.0 |
97 * @since 3.5.0 |
97 * |
98 * |
98 * @static |
|
99 * |
|
100 * @param string $mime_type |
99 * @param string $mime_type |
101 * @return bool |
100 * @return bool |
102 */ |
101 */ |
103 public static function supports_mime_type( $mime_type ) { |
102 public static function supports_mime_type( $mime_type ) { |
104 $imagick_extension = strtoupper( self::get_extension( $mime_type ) ); |
103 $imagick_extension = strtoupper( self::get_extension( $mime_type ) ); |
105 |
104 |
106 if ( ! $imagick_extension ) |
105 if ( ! $imagick_extension ) { |
107 return false; |
106 return false; |
|
107 } |
108 |
108 |
109 // setIteratorIndex is optional unless mime is an animated format. |
109 // 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. |
110 // 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' ) |
111 if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && $mime_type != 'image/jpeg' ) { |
112 return false; |
112 return false; |
|
113 } |
113 |
114 |
114 try { |
115 try { |
115 return ( (bool) @Imagick::queryFormats( $imagick_extension ) ); |
116 return ( (bool) @Imagick::queryFormats( $imagick_extension ) ); |
116 } |
117 } catch ( Exception $e ) { |
117 catch ( Exception $e ) { |
|
118 return false; |
118 return false; |
119 } |
119 } |
120 } |
120 } |
121 |
121 |
122 /** |
122 /** |
125 * @since 3.5.0 |
125 * @since 3.5.0 |
126 * |
126 * |
127 * @return true|WP_Error True if loaded; WP_Error on failure. |
127 * @return true|WP_Error True if loaded; WP_Error on failure. |
128 */ |
128 */ |
129 public function load() { |
129 public function load() { |
130 if ( $this->image instanceof Imagick ) |
130 if ( $this->image instanceof Imagick ) { |
131 return true; |
131 return true; |
132 |
132 } |
133 if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) |
133 |
134 return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file ); |
134 if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) { |
|
135 return new WP_Error( 'error_loading_image', __( 'File doesn’t exist?' ), $this->file ); |
|
136 } |
135 |
137 |
136 /* |
138 /* |
137 * Even though Imagick uses less PHP memory than GD, set higher limit |
139 * Even though Imagick uses less PHP memory than GD, set higher limit |
138 * for users that have low PHP.ini limits. |
140 * for users that have low PHP.ini limits. |
139 */ |
141 */ |
140 wp_raise_memory_limit( 'image' ); |
142 wp_raise_memory_limit( 'image' ); |
141 |
143 |
142 try { |
144 try { |
143 $this->image = new Imagick(); |
145 $this->image = new Imagick(); |
144 $file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) ); |
146 $file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) ); |
145 $filename = $this->file; |
147 $filename = $this->file; |
146 |
148 |
147 if ( 'pdf' == $file_extension ) { |
149 if ( 'pdf' == $file_extension ) { |
148 $filename = $this->pdf_setup(); |
150 $filename = $this->pdf_setup(); |
149 } |
151 } |
150 |
152 |
151 // Reading image after Imagick instantiation because `setResolution` |
153 // Reading image after Imagick instantiation because `setResolution` |
152 // only applies correctly before the image is read. |
154 // only applies correctly before the image is read. |
153 $this->image->readImage( $filename ); |
155 $this->image->readImage( $filename ); |
154 |
156 |
155 if ( ! $this->image->valid() ) |
157 if ( ! $this->image->valid() ) { |
156 return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file); |
158 return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); |
|
159 } |
157 |
160 |
158 // Select the first frame to handle animated images properly |
161 // Select the first frame to handle animated images properly |
159 if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) |
162 if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) { |
160 $this->image->setIteratorIndex(0); |
163 $this->image->setIteratorIndex( 0 ); |
|
164 } |
161 |
165 |
162 $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() ); |
166 $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() ); |
163 } |
167 } catch ( Exception $e ) { |
164 catch ( Exception $e ) { |
|
165 return new WP_Error( 'invalid_image', $e->getMessage(), $this->file ); |
168 return new WP_Error( 'invalid_image', $e->getMessage(), $this->file ); |
166 } |
169 } |
167 |
170 |
168 $updated_size = $this->update_size(); |
171 $updated_size = $this->update_size(); |
169 if ( is_wp_error( $updated_size ) ) { |
172 if ( is_wp_error( $updated_size ) ) { |
215 * |
216 * |
216 * @return true|WP_Error |
217 * @return true|WP_Error |
217 */ |
218 */ |
218 protected function update_size( $width = null, $height = null ) { |
219 protected function update_size( $width = null, $height = null ) { |
219 $size = null; |
220 $size = null; |
220 if ( !$width || !$height ) { |
221 if ( ! $width || ! $height ) { |
221 try { |
222 try { |
222 $size = $this->image->getImageGeometry(); |
223 $size = $this->image->getImageGeometry(); |
223 } |
224 } catch ( Exception $e ) { |
224 catch ( Exception $e ) { |
|
225 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 ); |
226 } |
226 } |
227 } |
227 } |
228 |
228 |
229 if ( ! $width ) |
229 if ( ! $width ) { |
230 $width = $size['width']; |
230 $width = $size['width']; |
231 |
231 } |
232 if ( ! $height ) |
232 |
|
233 if ( ! $height ) { |
233 $height = $size['height']; |
234 $height = $size['height']; |
|
235 } |
234 |
236 |
235 return parent::update_size( $width, $height ); |
237 return parent::update_size( $width, $height ); |
236 } |
238 } |
237 |
239 |
238 /** |
240 /** |
248 * @param int|null $max_h Image height. |
250 * @param int|null $max_h Image height. |
249 * @param bool $crop |
251 * @param bool $crop |
250 * @return bool|WP_Error |
252 * @return bool|WP_Error |
251 */ |
253 */ |
252 public function resize( $max_w, $max_h, $crop = false ) { |
254 public function resize( $max_w, $max_h, $crop = false ) { |
253 if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) |
255 if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) { |
254 return true; |
256 return true; |
|
257 } |
255 |
258 |
256 $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); |
259 $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); |
257 if ( ! $dims ) |
260 if ( ! $dims ) { |
258 return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') ); |
261 return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions' ) ); |
|
262 } |
259 list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; |
263 list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; |
260 |
264 |
261 if ( $crop ) { |
265 if ( $crop ) { |
262 return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h ); |
266 return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h ); |
263 } |
267 } |
333 * To be more efficient, resample large images to 5x the destination size before resizing |
337 * 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), |
338 * 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. |
339 * unless we would be resampling to a scale smaller than 128x128. |
336 */ |
340 */ |
337 if ( is_callable( array( $this->image, 'sampleImage' ) ) ) { |
341 if ( is_callable( array( $this->image, 'sampleImage' ) ) ) { |
338 $resize_ratio = ( $dst_w / $this->size['width'] ) * ( $dst_h / $this->size['height'] ); |
342 $resize_ratio = ( $dst_w / $this->size['width'] ) * ( $dst_h / $this->size['height'] ); |
339 $sample_factor = 5; |
343 $sample_factor = 5; |
340 |
344 |
341 if ( $resize_ratio < .111 && ( $dst_w * $sample_factor > 128 && $dst_h * $sample_factor > 128 ) ) { |
345 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 ); |
346 $this->image->sampleImage( $dst_w * $sample_factor, $dst_h * $sample_factor ); |
343 } |
347 } |
426 * } |
428 * } |
427 * } |
429 * } |
428 * @return array An array of resized images' metadata by size. |
430 * @return array An array of resized images' metadata by size. |
429 */ |
431 */ |
430 public function multi_resize( $sizes ) { |
432 public function multi_resize( $sizes ) { |
431 $metadata = array(); |
433 $metadata = array(); |
432 $orig_size = $this->size; |
434 $orig_size = $this->size; |
433 $orig_image = $this->image->getImage(); |
435 $orig_image = $this->image->getImage(); |
434 |
436 |
435 foreach ( $sizes as $size => $size_data ) { |
437 foreach ( $sizes as $size => $size_data ) { |
436 if ( ! $this->image ) |
438 if ( ! $this->image ) { |
437 $this->image = $orig_image->getImage(); |
439 $this->image = $orig_image->getImage(); |
|
440 } |
438 |
441 |
439 if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) { |
442 if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) { |
440 continue; |
443 continue; |
441 } |
444 } |
442 |
445 |
450 if ( ! isset( $size_data['crop'] ) ) { |
453 if ( ! isset( $size_data['crop'] ) ) { |
451 $size_data['crop'] = false; |
454 $size_data['crop'] = false; |
452 } |
455 } |
453 |
456 |
454 $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); |
457 $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); |
455 $duplicate = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) ); |
458 $duplicate = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) ); |
456 |
459 |
457 if ( ! is_wp_error( $resize_result ) && ! $duplicate ) { |
460 if ( ! is_wp_error( $resize_result ) && ! $duplicate ) { |
458 $resized = $this->_save( $this->image ); |
461 $resized = $this->_save( $this->image ); |
459 |
462 |
460 $this->image->clear(); |
463 $this->image->clear(); |
461 $this->image->destroy(); |
464 $this->image->destroy(); |
462 $this->image = null; |
465 $this->image = null; |
463 |
466 |
464 if ( ! is_wp_error( $resized ) && $resized ) { |
467 if ( ! is_wp_error( $resized ) && $resized ) { |
465 unset( $resized['path'] ); |
468 unset( $resized['path'] ); |
466 $metadata[$size] = $resized; |
469 $metadata[ $size ] = $resized; |
467 } |
470 } |
468 } |
471 } |
469 |
472 |
470 $this->size = $orig_size; |
473 $this->size = $orig_size; |
471 } |
474 } |
495 $src_h -= $src_y; |
498 $src_h -= $src_y; |
496 } |
499 } |
497 |
500 |
498 try { |
501 try { |
499 $this->image->cropImage( $src_w, $src_h, $src_x, $src_y ); |
502 $this->image->cropImage( $src_w, $src_h, $src_x, $src_y ); |
500 $this->image->setImagePage( $src_w, $src_h, 0, 0); |
503 $this->image->setImagePage( $src_w, $src_h, 0, 0 ); |
501 |
504 |
502 if ( $dst_w || $dst_h ) { |
505 if ( $dst_w || $dst_h ) { |
503 // If destination width/height isn't specified, use same as |
506 // If destination width/height isn't specified, use same as |
504 // width/height from source. |
507 // width/height from source. |
505 if ( ! $dst_w ) |
508 if ( ! $dst_w ) { |
506 $dst_w = $src_w; |
509 $dst_w = $src_w; |
507 if ( ! $dst_h ) |
510 } |
|
511 if ( ! $dst_h ) { |
508 $dst_h = $src_h; |
512 $dst_h = $src_h; |
|
513 } |
509 |
514 |
510 $thumb_result = $this->thumbnail_image( $dst_w, $dst_h ); |
515 $thumb_result = $this->thumbnail_image( $dst_w, $dst_h ); |
511 if ( is_wp_error( $thumb_result ) ) { |
516 if ( is_wp_error( $thumb_result ) ) { |
512 return $thumb_result; |
517 return $thumb_result; |
513 } |
518 } |
514 |
519 |
515 return $this->update_size(); |
520 return $this->update_size(); |
516 } |
521 } |
517 } |
522 } catch ( Exception $e ) { |
518 catch ( Exception $e ) { |
|
519 return new WP_Error( 'image_crop_error', $e->getMessage() ); |
523 return new WP_Error( 'image_crop_error', $e->getMessage() ); |
520 } |
524 } |
521 return $this->update_size(); |
525 return $this->update_size(); |
522 } |
526 } |
523 |
527 |
533 /** |
537 /** |
534 * $angle is 360-$angle because Imagick rotates clockwise |
538 * $angle is 360-$angle because Imagick rotates clockwise |
535 * (GD rotates counter-clockwise) |
539 * (GD rotates counter-clockwise) |
536 */ |
540 */ |
537 try { |
541 try { |
538 $this->image->rotateImage( new ImagickPixel('none'), 360-$angle ); |
542 $this->image->rotateImage( new ImagickPixel( 'none' ), 360 - $angle ); |
539 |
543 |
540 // Normalise Exif orientation data so that display is consistent across devices. |
544 // Normalise Exif orientation data so that display is consistent across devices. |
541 if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) { |
545 if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) { |
542 $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT ); |
546 $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT ); |
543 } |
547 } |
544 |
548 |
545 // Since this changes the dimensions of the image, update the size. |
549 // Since this changes the dimensions of the image, update the size. |
546 $result = $this->update_size(); |
550 $result = $this->update_size(); |
547 if ( is_wp_error( $result ) ) |
551 if ( is_wp_error( $result ) ) { |
548 return $result; |
552 return $result; |
|
553 } |
549 |
554 |
550 $this->image->setImagePage( $this->size['width'], $this->size['height'], 0, 0 ); |
555 $this->image->setImagePage( $this->size['width'], $this->size['height'], 0, 0 ); |
551 } |
556 } catch ( Exception $e ) { |
552 catch ( Exception $e ) { |
|
553 return new WP_Error( 'image_rotate_error', $e->getMessage() ); |
557 return new WP_Error( 'image_rotate_error', $e->getMessage() ); |
554 } |
558 } |
555 return true; |
559 return true; |
556 } |
560 } |
557 |
561 |
589 */ |
594 */ |
590 public function save( $destfilename = null, $mime_type = null ) { |
595 public function save( $destfilename = null, $mime_type = null ) { |
591 $saved = $this->_save( $this->image, $destfilename, $mime_type ); |
596 $saved = $this->_save( $this->image, $destfilename, $mime_type ); |
592 |
597 |
593 if ( ! is_wp_error( $saved ) ) { |
598 if ( ! is_wp_error( $saved ) ) { |
594 $this->file = $saved['path']; |
599 $this->file = $saved['path']; |
595 $this->mime_type = $saved['mime-type']; |
600 $this->mime_type = $saved['mime-type']; |
596 |
601 |
597 try { |
602 try { |
598 $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) ); |
603 $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) ); |
599 } |
604 } catch ( Exception $e ) { |
600 catch ( Exception $e ) { |
|
601 return new WP_Error( 'image_save_error', $e->getMessage(), $this->file ); |
605 return new WP_Error( 'image_save_error', $e->getMessage(), $this->file ); |
602 } |
606 } |
603 } |
607 } |
604 |
608 |
605 return $saved; |
609 return $saved; |
606 } |
610 } |
607 |
611 |
608 /** |
612 /** |
609 * |
|
610 * @param Imagick $image |
613 * @param Imagick $image |
611 * @param string $filename |
614 * @param string $filename |
612 * @param string $mime_type |
615 * @param string $mime_type |
613 * @return array|WP_Error |
616 * @return array|WP_Error |
614 */ |
617 */ |
615 protected function _save( $image, $filename = null, $mime_type = null ) { |
618 protected function _save( $image, $filename = null, $mime_type = null ) { |
616 list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); |
619 list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); |
617 |
620 |
618 if ( ! $filename ) |
621 if ( ! $filename ) { |
619 $filename = $this->generate_filename( null, null, $extension ); |
622 $filename = $this->generate_filename( null, null, $extension ); |
|
623 } |
620 |
624 |
621 try { |
625 try { |
622 // Store initial Format |
626 // Store initial Format |
623 $orig_format = $this->image->getImageFormat(); |
627 $orig_format = $this->image->getImageFormat(); |
624 |
628 |
625 $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) ); |
629 $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) ); |
626 $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) ); |
630 $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) ); |
627 |
631 |
628 // Reset original Format |
632 // Reset original Format |
629 $this->image->setImageFormat( $orig_format ); |
633 $this->image->setImageFormat( $orig_format ); |
630 } |
634 } catch ( Exception $e ) { |
631 catch ( Exception $e ) { |
|
632 return new WP_Error( 'image_save_error', $e->getMessage(), $filename ); |
635 return new WP_Error( 'image_save_error', $e->getMessage(), $filename ); |
633 } |
636 } |
634 |
637 |
635 // Set correct file permissions |
638 // Set correct file permissions |
636 $stat = stat( dirname( $filename ) ); |
639 $stat = stat( dirname( $filename ) ); |
637 $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits |
640 $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits |
638 @ chmod( $filename, $perms ); |
641 @ chmod( $filename, $perms ); |
639 |
642 |
640 /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */ |
643 /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */ |
641 return array( |
644 return array( |
741 // We want the thumbnail to be readable, so increase the rendering DPI. |
742 // We want the thumbnail to be readable, so increase the rendering DPI. |
742 $this->image->setResolution( 128, 128 ); |
743 $this->image->setResolution( 128, 128 ); |
743 |
744 |
744 // Only load the first page. |
745 // Only load the first page. |
745 return $this->file . '[0]'; |
746 return $this->file . '[0]'; |
746 } |
747 } catch ( Exception $e ) { |
747 catch ( Exception $e ) { |
|
748 return new WP_Error( 'pdf_setup_failed', $e->getMessage(), $this->file ); |
748 return new WP_Error( 'pdf_setup_failed', $e->getMessage(), $this->file ); |
749 } |
749 } |
750 } |
750 } |
751 |
751 |
752 } |
752 } |