[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> class-wp-image-editor-imagick.php (source)

   1  <?php
   2  /**
   3   * WordPress Imagick Image Editor
   4   *
   5   * @package WordPress
   6   * @subpackage Image_Editor
   7   */
   8  
   9  /**
  10   * WordPress Image Editor Class for Image Manipulation through Imagick PHP Module
  11   *
  12   * @since 3.5.0
  13   *
  14   * @see WP_Image_Editor
  15   */
  16  class WP_Image_Editor_Imagick extends WP_Image_Editor {
  17      /**
  18       * Imagick object.
  19       *
  20       * @var Imagick
  21       */
  22      protected $image;
  23  
  24  	public function __destruct() {
  25          if ( $this->image instanceof Imagick ) {
  26              // We don't need the original in memory anymore.
  27              $this->image->clear();
  28              $this->image->destroy();
  29          }
  30      }
  31  
  32      /**
  33       * Checks to see if current environment supports Imagick.
  34       *
  35       * We require Imagick 2.2.0 or greater, based on whether the queryFormats()
  36       * method can be called statically.
  37       *
  38       * @since 3.5.0
  39       *
  40       * @param array $args
  41       * @return bool
  42       */
  43  	public static function test( $args = array() ) {
  44  
  45          // First, test Imagick's extension and classes.
  46          if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick', false ) || ! class_exists( 'ImagickPixel', false ) ) {
  47              return false;
  48          }
  49  
  50          if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) ) {
  51              return false;
  52          }
  53  
  54          $required_methods = array(
  55              'clear',
  56              'destroy',
  57              'valid',
  58              'getimage',
  59              'writeimage',
  60              'getimageblob',
  61              'getimagegeometry',
  62              'getimageformat',
  63              'setimageformat',
  64              'setimagecompression',
  65              'setimagecompressionquality',
  66              'setimagepage',
  67              'setoption',
  68              'scaleimage',
  69              'cropimage',
  70              'rotateimage',
  71              'flipimage',
  72              'flopimage',
  73              'readimage',
  74          );
  75  
  76          // Now, test for deep requirements within Imagick.
  77          if ( ! defined( 'imagick::COMPRESSION_JPEG' ) ) {
  78              return false;
  79          }
  80  
  81          $class_methods = array_map( 'strtolower', get_class_methods( 'Imagick' ) );
  82          if ( array_diff( $required_methods, $class_methods ) ) {
  83              return false;
  84          }
  85  
  86          return true;
  87      }
  88  
  89      /**
  90       * Checks to see if editor supports the mime-type specified.
  91       *
  92       * @since 3.5.0
  93       *
  94       * @param string $mime_type
  95       * @return bool
  96       */
  97  	public static function supports_mime_type( $mime_type ) {
  98          $imagick_extension = strtoupper( self::get_extension( $mime_type ) );
  99  
 100          if ( ! $imagick_extension ) {
 101              return false;
 102          }
 103  
 104          // setIteratorIndex is optional unless mime is an animated format.
 105          // Here, we just say no if you are missing it and aren't loading a jpeg.
 106          if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && 'image/jpeg' !== $mime_type ) {
 107                  return false;
 108          }
 109  
 110          try {
 111              // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
 112              return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
 113          } catch ( Exception $e ) {
 114              return false;
 115          }
 116      }
 117  
 118      /**
 119       * Loads image from $this->file into new Imagick Object.
 120       *
 121       * @since 3.5.0
 122       *
 123       * @return true|WP_Error True if loaded; WP_Error on failure.
 124       */
 125  	public function load() {
 126          if ( $this->image instanceof Imagick ) {
 127              return true;
 128          }
 129  
 130          if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) {
 131              return new WP_Error( 'error_loading_image', __( 'File doesn&#8217;t exist?' ), $this->file );
 132          }
 133  
 134          /*
 135           * Even though Imagick uses less PHP memory than GD, set higher limit
 136           * for users that have low PHP.ini limits.
 137           */
 138          wp_raise_memory_limit( 'image' );
 139  
 140          try {
 141              $this->image    = new Imagick();
 142              $file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
 143              $filename       = $this->file;
 144  
 145              if ( 'pdf' === $file_extension ) {
 146                  $filename = $this->pdf_setup();
 147              }
 148  
 149              // Reading image after Imagick instantiation because `setResolution`
 150              // only applies correctly before the image is read.
 151              $this->image->readImage( $filename );
 152  
 153              if ( ! $this->image->valid() ) {
 154                  return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
 155              }
 156  
 157              // Select the first frame to handle animated images properly.
 158              if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) {
 159                  $this->image->setIteratorIndex( 0 );
 160              }
 161  
 162              $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
 163          } catch ( Exception $e ) {
 164              return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
 165          }
 166  
 167          $updated_size = $this->update_size();
 168          if ( is_wp_error( $updated_size ) ) {
 169              return $updated_size;
 170          }
 171  
 172          return $this->set_quality();
 173      }
 174  
 175      /**
 176       * Sets Image Compression quality on a 1-100% scale.
 177       *
 178       * @since 3.5.0
 179       *
 180       * @param int $quality Compression Quality. Range: [1,100]
 181       * @return true|WP_Error True if set successfully; WP_Error on failure.
 182       */
 183  	public function set_quality( $quality = null ) {
 184          $quality_result = parent::set_quality( $quality );
 185          if ( is_wp_error( $quality_result ) ) {
 186              return $quality_result;
 187          } else {
 188              $quality = $this->get_quality();
 189          }
 190  
 191          try {
 192              if ( 'image/jpeg' === $this->mime_type ) {
 193                  $this->image->setImageCompressionQuality( $quality );
 194                  $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
 195              } else {
 196                  $this->image->setImageCompressionQuality( $quality );
 197              }
 198          } catch ( Exception $e ) {
 199              return new WP_Error( 'image_quality_error', $e->getMessage() );
 200          }
 201  
 202          return true;
 203      }
 204  
 205      /**
 206       * Sets or updates current image size.
 207       *
 208       * @since 3.5.0
 209       *
 210       * @param int $width
 211       * @param int $height
 212       * @return true|WP_Error
 213       */
 214  	protected function update_size( $width = null, $height = null ) {
 215          $size = null;
 216          if ( ! $width || ! $height ) {
 217              try {
 218                  $size = $this->image->getImageGeometry();
 219              } catch ( Exception $e ) {
 220                  return new WP_Error( 'invalid_image', __( 'Could not read image size.' ), $this->file );
 221              }
 222          }
 223  
 224          if ( ! $width ) {
 225              $width = $size['width'];
 226          }
 227  
 228          if ( ! $height ) {
 229              $height = $size['height'];
 230          }
 231  
 232          return parent::update_size( $width, $height );
 233      }
 234  
 235      /**
 236       * Resizes current image.
 237       *
 238       * At minimum, either a height or width must be provided.
 239       * If one of the two is set to null, the resize will
 240       * maintain aspect ratio according to the provided dimension.
 241       *
 242       * @since 3.5.0
 243       *
 244       * @param int|null $max_w Image width.
 245       * @param int|null $max_h Image height.
 246       * @param bool     $crop
 247       * @return bool|WP_Error
 248       */
 249  	public function resize( $max_w, $max_h, $crop = false ) {
 250          if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) {
 251              return true;
 252          }
 253  
 254          $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
 255          if ( ! $dims ) {
 256              return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions' ) );
 257          }
 258  
 259          list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
 260  
 261          if ( $crop ) {
 262              return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
 263          }
 264  
 265          // Execute the resize.
 266          $thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
 267          if ( is_wp_error( $thumb_result ) ) {
 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 the allowed list and the related
 309           * Imagick constant is defined or fall back to the default filter.
 310           */
 311          if ( in_array( $filter_name, $allowed_filters, true ) && 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.
 336               */
 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          } catch ( Exception $e ) {
 402              return new WP_Error( 'image_resize_error', $e->getMessage() );
 403          }
 404      }
 405  
 406      /**
 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.
 416       *
 417       * @since 3.5.0
 418       *
 419       * @param array $sizes {
 420       *     An array of image size data arrays.
 421       *
 422       *     Either a height or width must be provided.
 423       *     If one of the two is set to null, the resize will
 424       *     maintain aspect ratio according to the provided dimension.
 425       *
 426       *     @type array $size {
 427       *         Array of height, width values, and whether to crop.
 428       *
 429       *         @type int  $width  Image width. Optional if `$height` is specified.
 430       *         @type int  $height Image height. Optional if `$width` is specified.
 431       *         @type bool $crop   Optional. Whether to crop the image. Default false.
 432       *     }
 433       * }
 434       * @return array An array of resized images' metadata by size.
 435       */
 436  	public function multi_resize( $sizes ) {
 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  
 470          $orig_size  = $this->size;
 471          $orig_image = $this->image->getImage();
 472  
 473          if ( ! isset( $size_data['width'] ) ) {
 474              $size_data['width'] = null;
 475          }
 476  
 477          if ( ! isset( $size_data['height'] ) ) {
 478              $size_data['height'] = null;
 479          }
 480  
 481          if ( ! isset( $size_data['crop'] ) ) {
 482              $size_data['crop'] = false;
 483          }
 484  
 485          $resized = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
 486  
 487          if ( is_wp_error( $resized ) ) {
 488              $saved = $resized;
 489          } else {
 490              $saved = $this->_save( $this->image );
 491  
 492              $this->image->clear();
 493              $this->image->destroy();
 494              $this->image = null;
 495          }
 496  
 497          $this->size  = $orig_size;
 498          $this->image = $orig_image;
 499  
 500          if ( ! is_wp_error( $saved ) ) {
 501              unset( $saved['path'] );
 502          }
 503  
 504          return $saved;
 505      }
 506  
 507      /**
 508       * Crops Image.
 509       *
 510       * @since 3.5.0
 511       *
 512       * @param int  $src_x   The start x position to crop from.
 513       * @param int  $src_y   The start y position to crop from.
 514       * @param int  $src_w   The width to crop.
 515       * @param int  $src_h   The height to crop.
 516       * @param int  $dst_w   Optional. The destination width.
 517       * @param int  $dst_h   Optional. The destination height.
 518       * @param bool $src_abs Optional. If the source crop points are absolute.
 519       * @return bool|WP_Error
 520       */
 521  	public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
 522          if ( $src_abs ) {
 523              $src_w -= $src_x;
 524              $src_h -= $src_y;
 525          }
 526  
 527          try {
 528              $this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
 529              $this->image->setImagePage( $src_w, $src_h, 0, 0 );
 530  
 531              if ( $dst_w || $dst_h ) {
 532                  // If destination width/height isn't specified,
 533                  // use same as width/height from source.
 534                  if ( ! $dst_w ) {
 535                      $dst_w = $src_w;
 536                  }
 537                  if ( ! $dst_h ) {
 538                      $dst_h = $src_h;
 539                  }
 540  
 541                  $thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
 542                  if ( is_wp_error( $thumb_result ) ) {
 543                      return $thumb_result;
 544                  }
 545  
 546                  return $this->update_size();
 547              }
 548          } catch ( Exception $e ) {
 549              return new WP_Error( 'image_crop_error', $e->getMessage() );
 550          }
 551          return $this->update_size();
 552      }
 553  
 554      /**
 555       * Rotates current image counter-clockwise by $angle.
 556       *
 557       * @since 3.5.0
 558       *
 559       * @param float $angle
 560       * @return true|WP_Error
 561       */
 562  	public function rotate( $angle ) {
 563          /**
 564           * $angle is 360-$angle because Imagick rotates clockwise
 565           * (GD rotates counter-clockwise)
 566           */
 567          try {
 568              $this->image->rotateImage( new ImagickPixel( 'none' ), 360 - $angle );
 569  
 570              // Normalise EXIF orientation data so that display is consistent across devices.
 571              if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
 572                  $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
 573              }
 574  
 575              // Since this changes the dimensions of the image, update the size.
 576              $result = $this->update_size();
 577              if ( is_wp_error( $result ) ) {
 578                  return $result;
 579              }
 580  
 581              $this->image->setImagePage( $this->size['width'], $this->size['height'], 0, 0 );
 582          } catch ( Exception $e ) {
 583              return new WP_Error( 'image_rotate_error', $e->getMessage() );
 584          }
 585          return true;
 586      }
 587  
 588      /**
 589       * Flips current image.
 590       *
 591       * @since 3.5.0
 592       *
 593       * @param bool $horz Flip along Horizontal Axis
 594       * @param bool $vert Flip along Vertical Axis
 595       * @return true|WP_Error
 596       */
 597  	public function flip( $horz, $vert ) {
 598          try {
 599              if ( $horz ) {
 600                  $this->image->flipImage();
 601              }
 602  
 603              if ( $vert ) {
 604                  $this->image->flopImage();
 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              }
 611          } catch ( Exception $e ) {
 612              return new WP_Error( 'image_flip_error', $e->getMessage() );
 613          }
 614  
 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          }
 635      }
 636  
 637      /**
 638       * Saves current image to file.
 639       *
 640       * @since 3.5.0
 641       *
 642       * @param string $destfilename
 643       * @param string $mime_type
 644       * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
 645       */
 646  	public function save( $destfilename = null, $mime_type = null ) {
 647          $saved = $this->_save( $this->image, $destfilename, $mime_type );
 648  
 649          if ( ! is_wp_error( $saved ) ) {
 650              $this->file      = $saved['path'];
 651              $this->mime_type = $saved['mime-type'];
 652  
 653              try {
 654                  $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) );
 655              } catch ( Exception $e ) {
 656                  return new WP_Error( 'image_save_error', $e->getMessage(), $this->file );
 657              }
 658          }
 659  
 660          return $saved;
 661      }
 662  
 663      /**
 664       * @param Imagick $image
 665       * @param string  $filename
 666       * @param string  $mime_type
 667       * @return array|WP_Error
 668       */
 669  	protected function _save( $image, $filename = null, $mime_type = null ) {
 670          list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
 671  
 672          if ( ! $filename ) {
 673              $filename = $this->generate_filename( null, null, $extension );
 674          }
 675  
 676          try {
 677              // Store initial format.
 678              $orig_format = $this->image->getImageFormat();
 679  
 680              $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
 681              $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
 682  
 683              // Reset original format.
 684              $this->image->setImageFormat( $orig_format );
 685          } catch ( Exception $e ) {
 686              return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
 687          }
 688  
 689          // Set correct file permissions.
 690          $stat  = stat( dirname( $filename ) );
 691          $perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits.
 692          chmod( $filename, $perms );
 693  
 694          return array(
 695              'path'      => $filename,
 696              /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
 697              'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
 698              'width'     => $this->size['width'],
 699              'height'    => $this->size['height'],
 700              'mime-type' => $mime_type,
 701          );
 702      }
 703  
 704      /**
 705       * Streams current image to browser.
 706       *
 707       * @since 3.5.0
 708       *
 709       * @param string $mime_type The mime type of the image.
 710       * @return bool|WP_Error True on success, WP_Error object on failure.
 711       */
 712  	public function stream( $mime_type = null ) {
 713          list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
 714  
 715          try {
 716              // Temporarily change format for stream.
 717              $this->image->setImageFormat( strtoupper( $extension ) );
 718  
 719              // Output stream of image content.
 720              header( "Content-Type: $mime_type" );
 721              print $this->image->getImageBlob();
 722  
 723              // Reset image to original format.
 724              $this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
 725          } catch ( Exception $e ) {
 726              return new WP_Error( 'image_stream_error', $e->getMessage() );
 727          }
 728  
 729          return true;
 730      }
 731  
 732      /**
 733       * Strips all image meta except color profiles from an image.
 734       *
 735       * @since 4.5.0
 736       *
 737       * @return true|WP_Error True if stripping metadata was successful. WP_Error object on error.
 738       */
 739  	protected function strip_meta() {
 740  
 741          if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) {
 742              /* translators: %s: ImageMagick method name. */
 743              return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::getImageProfiles()</code>' ) );
 744          }
 745  
 746          if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) {
 747              /* translators: %s: ImageMagick method name. */
 748              return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::removeImageProfile()</code>' ) );
 749          }
 750  
 751          /*
 752           * Protect a few profiles from being stripped for the following reasons:
 753           *
 754           * - icc:  Color profile information
 755           * - icm:  Color profile information
 756           * - iptc: Copyright data
 757           * - exif: Orientation data
 758           * - xmp:  Rights usage data
 759           */
 760          $protected_profiles = array(
 761              'icc',
 762              'icm',
 763              'iptc',
 764              'exif',
 765              'xmp',
 766          );
 767  
 768          try {
 769              // Strip profiles.
 770              foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) {
 771                  if ( ! in_array( $key, $protected_profiles, true ) ) {
 772                      $this->image->removeImageProfile( $key );
 773                  }
 774              }
 775          } catch ( Exception $e ) {
 776              return new WP_Error( 'image_strip_meta_error', $e->getMessage() );
 777          }
 778  
 779          return true;
 780      }
 781  
 782      /**
 783       * Sets up Imagick for PDF processing.
 784       * Increases rendering DPI and only loads first page.
 785       *
 786       * @since 4.7.0
 787       *
 788       * @return string|WP_Error File to load or WP_Error on failure.
 789       */
 790  	protected function pdf_setup() {
 791          try {
 792              // By default, PDFs are rendered in a very low resolution.
 793              // We want the thumbnail to be readable, so increase the rendering DPI.
 794              $this->image->setResolution( 128, 128 );
 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  
 800              // Only load the first page.
 801              return $this->file . '[0]';
 802          } catch ( Exception $e ) {
 803              return new WP_Error( 'pdf_setup_failed', $e->getMessage(), $this->file );
 804          }
 805      }
 806  
 807  }


Generated: Wed Aug 12 01:00:03 2020 Cross-referenced by PHPXref 0.7.1