home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / class-wp-image-editor-imagick.php < prev    next >
Encoding:
PHP Script  |  2017-08-22  |  21.2 KB  |  753 lines

  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.      * @static
  41.      *
  42.      * @param array $args
  43.      * @return bool
  44.      */
  45.     public static function test( $args = array() ) {
  46.  
  47.         // First, test Imagick's extension and classes.
  48.         if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick', false ) || ! class_exists( 'ImagickPixel', false ) )
  49.             return false;
  50.  
  51.         if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) )
  52.             return false;
  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.         $class_methods = array_map( 'strtolower', get_class_methods( 'Imagick' ) );
  81.         if ( array_diff( $required_methods, $class_methods ) ) {
  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.         }
  89.  
  90.         return true;
  91.     }
  92.  
  93.     /**
  94.      * Checks to see if editor supports the mime-type specified.
  95.      *
  96.      * @since 3.5.0
  97.      *
  98.      * @static
  99.      *
  100.      * @param string $mime_type
  101.      * @return bool
  102.      */
  103.     public static function supports_mime_type( $mime_type ) {
  104.         $imagick_extension = strtoupper( self::get_extension( $mime_type ) );
  105.  
  106.         if ( ! $imagick_extension )
  107.             return false;
  108.  
  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.
  111.         if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && $mime_type != 'image/jpeg' )
  112.                 return false;
  113.  
  114.         try {
  115.             return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
  116.         }
  117.         catch ( Exception $e ) {
  118.             return false;
  119.         }
  120.     }
  121.  
  122.     /**
  123.      * Loads image from $this->file into new Imagick Object.
  124.      *
  125.      * @since 3.5.0
  126.      *
  127.      * @return true|WP_Error True if loaded; WP_Error on failure.
  128.      */
  129.     public function load() {
  130.         if ( $this->image instanceof Imagick )
  131.             return true;
  132.  
  133.         if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
  134.             return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file );
  135.  
  136.         /*
  137.          * Even though Imagick uses less PHP memory than GD, set higher limit
  138.          * for users that have low PHP.ini limits.
  139.          */
  140.         wp_raise_memory_limit( 'image' );
  141.  
  142.         try {
  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() )
  156.                 return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file);
  157.  
  158.             // Select the first frame to handle animated images properly
  159.             if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) )
  160.                 $this->image->setIteratorIndex(0);
  161.  
  162.             $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
  163.         }
  164.         catch ( Exception $e ) {
  165.             return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
  166.         }
  167.  
  168.         $updated_size = $this->update_size();
  169.         if ( is_wp_error( $updated_size ) ) {
  170.             return $updated_size;
  171.         }
  172.  
  173.         return $this->set_quality();
  174.     }
  175.  
  176.     /**
  177.      * Sets Image Compression quality on a 1-100% scale.
  178.      *
  179.      * @since 3.5.0
  180.      *
  181.      * @param int $quality Compression Quality. Range: [1,100]
  182.      * @return true|WP_Error True if set successfully; WP_Error on failure.
  183.      */
  184.     public function set_quality( $quality = null ) {
  185.         $quality_result = parent::set_quality( $quality );
  186.         if ( is_wp_error( $quality_result ) ) {
  187.             return $quality_result;
  188.         } else {
  189.             $quality = $this->get_quality();
  190.         }
  191.  
  192.         try {
  193.             if ( 'image/jpeg' == $this->mime_type ) {
  194.                 $this->image->setImageCompressionQuality( $quality );
  195.                 $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
  196.             }
  197.             else {
  198.                 $this->image->setImageCompressionQuality( $quality );
  199.             }
  200.         }
  201.         catch ( Exception $e ) {
  202.             return new WP_Error( 'image_quality_error', $e->getMessage() );
  203.         }
  204.  
  205.         return true;
  206.     }
  207.  
  208.     /**
  209.      * Sets or updates current image size.
  210.      *
  211.      * @since 3.5.0
  212.      *
  213.      * @param int $width
  214.      * @param int $height
  215.      *
  216.      * @return true|WP_Error
  217.      */
  218.     protected function update_size( $width = null, $height = null ) {
  219.         $size = null;
  220.         if ( !$width || !$height ) {
  221.             try {
  222.                 $size = $this->image->getImageGeometry();
  223.             }
  224.             catch ( Exception $e ) {
  225.                 return new WP_Error( 'invalid_image', __( 'Could not read image size.' ), $this->file );
  226.             }
  227.         }
  228.  
  229.         if ( ! $width )
  230.             $width = $size['width'];
  231.  
  232.         if ( ! $height )
  233.             $height = $size['height'];
  234.  
  235.         return parent::update_size( $width, $height );
  236.     }
  237.  
  238.     /**
  239.      * Resizes current image.
  240.      *
  241.      * At minimum, either a height or width must be provided.
  242.      * If one of the two is set to null, the resize will
  243.      * maintain aspect ratio according to the provided dimension.
  244.      *
  245.      * @since 3.5.0
  246.      *
  247.      * @param  int|null $max_w Image width.
  248.      * @param  int|null $max_h Image height.
  249.      * @param  bool     $crop
  250.      * @return bool|WP_Error
  251.      */
  252.     public function resize( $max_w, $max_h, $crop = false ) {
  253.         if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
  254.             return true;
  255.  
  256.         $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
  257.         if ( ! $dims )
  258.             return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
  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 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.
  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.  
  402.         }
  403.         catch ( Exception $e ) {
  404.             return new WP_Error( 'image_resize_error', $e->getMessage() );
  405.         }
  406.     }
  407.  
  408.     /**
  409.      * Resize multiple images from a single source.
  410.      *
  411.      * @since 3.5.0
  412.      *
  413.      * @param array $sizes {
  414.      *     An array of image size arrays. Default sizes are 'small', 'medium', 'medium_large', 'large'.
  415.      *
  416.      *     Either a height or width must be provided.
  417.      *     If one of the two is set to null, the resize will
  418.      *     maintain aspect ratio according to the provided dimension.
  419.      *
  420.      *     @type array $size {
  421.      *         Array of height, width values, and whether to crop.
  422.      *
  423.      *         @type int  $width  Image width. Optional if `$height` is specified.
  424.      *         @type int  $height Image height. Optional if `$width` is specified.
  425.      *         @type bool $crop   Optional. Whether to crop the image. Default false.
  426.      *     }
  427.      * }
  428.      * @return array An array of resized images' metadata by size.
  429.      */
  430.     public function multi_resize( $sizes ) {
  431.         $metadata = array();
  432.         $orig_size = $this->size;
  433.         $orig_image = $this->image->getImage();
  434.  
  435.         foreach ( $sizes as $size => $size_data ) {
  436.             if ( ! $this->image )
  437.                 $this->image = $orig_image->getImage();
  438.  
  439.             if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
  440.                 continue;
  441.             }
  442.  
  443.             if ( ! isset( $size_data['width'] ) ) {
  444.                 $size_data['width'] = null;
  445.             }
  446.             if ( ! isset( $size_data['height'] ) ) {
  447.                 $size_data['height'] = null;
  448.             }
  449.  
  450.             if ( ! isset( $size_data['crop'] ) ) {
  451.                 $size_data['crop'] = false;
  452.             }
  453.  
  454.             $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'] ) );
  456.  
  457.             if ( ! is_wp_error( $resize_result ) && ! $duplicate ) {
  458.                 $resized = $this->_save( $this->image );
  459.  
  460.                 $this->image->clear();
  461.                 $this->image->destroy();
  462.                 $this->image = null;
  463.  
  464.                 if ( ! is_wp_error( $resized ) && $resized ) {
  465.                     unset( $resized['path'] );
  466.                     $metadata[$size] = $resized;
  467.                 }
  468.             }
  469.  
  470.             $this->size = $orig_size;
  471.         }
  472.  
  473.         $this->image = $orig_image;
  474.  
  475.         return $metadata;
  476.     }
  477.  
  478.     /**
  479.      * Crops Image.
  480.      *
  481.      * @since 3.5.0
  482.      *
  483.      * @param int  $src_x The start x position to crop from.
  484.      * @param int  $src_y The start y position to crop from.
  485.      * @param int  $src_w The width to crop.
  486.      * @param int  $src_h The height to crop.
  487.      * @param int  $dst_w Optional. The destination width.
  488.      * @param int  $dst_h Optional. The destination height.
  489.      * @param bool $src_abs Optional. If the source crop points are absolute.
  490.      * @return bool|WP_Error
  491.      */
  492.     public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
  493.         if ( $src_abs ) {
  494.             $src_w -= $src_x;
  495.             $src_h -= $src_y;
  496.         }
  497.  
  498.         try {
  499.             $this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
  500.             $this->image->setImagePage( $src_w, $src_h, 0, 0);
  501.  
  502.             if ( $dst_w || $dst_h ) {
  503.                 // If destination width/height isn't specified, use same as
  504.                 // width/height from source.
  505.                 if ( ! $dst_w )
  506.                     $dst_w = $src_w;
  507.                 if ( ! $dst_h )
  508.                     $dst_h = $src_h;
  509.  
  510.                 $thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
  511.                 if ( is_wp_error( $thumb_result ) ) {
  512.                     return $thumb_result;
  513.                 }
  514.  
  515.                 return $this->update_size();
  516.             }
  517.         }
  518.         catch ( Exception $e ) {
  519.             return new WP_Error( 'image_crop_error', $e->getMessage() );
  520.         }
  521.         return $this->update_size();
  522.     }
  523.  
  524.     /**
  525.      * Rotates current image counter-clockwise by $angle.
  526.      *
  527.      * @since 3.5.0
  528.      *
  529.      * @param float $angle
  530.      * @return true|WP_Error
  531.      */
  532.     public function rotate( $angle ) {
  533.         /**
  534.          * $angle is 360-$angle because Imagick rotates clockwise
  535.          * (GD rotates counter-clockwise)
  536.          */
  537.         try {
  538.             $this->image->rotateImage( new ImagickPixel('none'), 360-$angle );
  539.  
  540.             // Normalise Exif orientation data so that display is consistent across devices.
  541.             if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
  542.                 $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
  543.             }
  544.  
  545.             // Since this changes the dimensions of the image, update the size.
  546.             $result = $this->update_size();
  547.             if ( is_wp_error( $result ) )
  548.                 return $result;
  549.  
  550.             $this->image->setImagePage( $this->size['width'], $this->size['height'], 0, 0 );
  551.         }
  552.         catch ( Exception $e ) {
  553.             return new WP_Error( 'image_rotate_error', $e->getMessage() );
  554.         }
  555.         return true;
  556.     }
  557.  
  558.     /**
  559.      * Flips current image.
  560.      *
  561.      * @since 3.5.0
  562.      *
  563.      * @param bool $horz Flip along Horizontal Axis
  564.      * @param bool $vert Flip along Vertical Axis
  565.      * @return true|WP_Error
  566.      */
  567.     public function flip( $horz, $vert ) {
  568.         try {
  569.             if ( $horz )
  570.                 $this->image->flipImage();
  571.  
  572.             if ( $vert )
  573.                 $this->image->flopImage();
  574.         }
  575.         catch ( Exception $e ) {
  576.             return new WP_Error( 'image_flip_error', $e->getMessage() );
  577.         }
  578.         return true;
  579.     }
  580.  
  581.     /**
  582.      * Saves current image to file.
  583.      *
  584.      * @since 3.5.0
  585.      *
  586.      * @param string $destfilename
  587.      * @param string $mime_type
  588.      * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
  589.      */
  590.     public function save( $destfilename = null, $mime_type = null ) {
  591.         $saved = $this->_save( $this->image, $destfilename, $mime_type );
  592.  
  593.         if ( ! is_wp_error( $saved ) ) {
  594.             $this->file = $saved['path'];
  595.             $this->mime_type = $saved['mime-type'];
  596.  
  597.             try {
  598.                 $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) );
  599.             }
  600.             catch ( Exception $e ) {
  601.                 return new WP_Error( 'image_save_error', $e->getMessage(), $this->file );
  602.             }
  603.         }
  604.  
  605.         return $saved;
  606.     }
  607.  
  608.     /**
  609.      *
  610.      * @param Imagick $image
  611.      * @param string $filename
  612.      * @param string $mime_type
  613.      * @return array|WP_Error
  614.      */
  615.     protected function _save( $image, $filename = null, $mime_type = null ) {
  616.         list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
  617.  
  618.         if ( ! $filename )
  619.             $filename = $this->generate_filename( null, null, $extension );
  620.  
  621.         try {
  622.             // Store initial Format
  623.             $orig_format = $this->image->getImageFormat();
  624.  
  625.             $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
  626.             $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
  627.  
  628.             // Reset original Format
  629.             $this->image->setImageFormat( $orig_format );
  630.         }
  631.         catch ( Exception $e ) {
  632.             return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
  633.         }
  634.  
  635.         // Set correct file permissions
  636.         $stat = stat( dirname( $filename ) );
  637.         $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
  638.         @ chmod( $filename, $perms );
  639.  
  640.         /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
  641.         return array(
  642.             'path'      => $filename,
  643.             'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
  644.             'width'     => $this->size['width'],
  645.             'height'    => $this->size['height'],
  646.             'mime-type' => $mime_type,
  647.         );
  648.     }
  649.  
  650.     /**
  651.      * Streams current image to browser.
  652.      *
  653.      * @since 3.5.0
  654.      *
  655.      * @param string $mime_type The mime type of the image.
  656.      * @return bool|WP_Error True on success, WP_Error object on failure.
  657.      */
  658.     public function stream( $mime_type = null ) {
  659.         list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
  660.  
  661.         try {
  662.             // Temporarily change format for stream
  663.             $this->image->setImageFormat( strtoupper( $extension ) );
  664.  
  665.             // Output stream of image content
  666.             header( "Content-Type: $mime_type" );
  667.             print $this->image->getImageBlob();
  668.  
  669.             // Reset Image to original Format
  670.             $this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
  671.         }
  672.         catch ( Exception $e ) {
  673.             return new WP_Error( 'image_stream_error', $e->getMessage() );
  674.         }
  675.  
  676.         return true;
  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.  
  752. }
  753.