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 |
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 ) { |
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: |
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 } |