Make WordPress Core

Changeset 36700


Ignore:
Timestamp:
02/24/2016 10:50:10 PM (11 years ago)
Author:
mikeschroder
Message:

Media: Optimize Imagick settings for quality and filesize

  • Resamples and sharpens larger images before resize.
  • Uses imagick::FILTER_TRIANGLE for a smoother resize.
  • Introduces WP_Image_Editor_Imagick::thumbnail_image() protected method to efficiently resize images. Similar to the functionality of Imagick's thumbnailImage().
  • Introduces WP_Image_Editor_Imagick::strip_meta() protected method and image_strip_meta filter that, by default, strip image profiles to reduce file size, while leaving color profiles intact.

See: #33642, #30402, #28634.
Props: joemcgill, dnewton, mikeschroder.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-image-editor-imagick.php

    r35479 r36700  
    6363                        'getimageblob',
    6464                        'getimagegeometry',
     65                        'getimagedepth',
    6566                        'getimageformat',
    6667                        'setimageformat',
    6768                        'setimagecompression',
    6869                        'setimagecompressionquality',
     70                        'setimagedepth',
    6971                        'setimagepage',
     72                        'setimageproperty',
     73                        'setinterlacescheme',
    7074                        'scaleimage',
    7175                        'cropimage',
    … …  
    7377                        'flipimage',
    7478                        'flopimage',
     79                        'unsharpmaskimage',
    7580                );
    7681
    7782                // Now, test for deep requirements within Imagick.
    78                 if ( ! defined( 'imagick::COMPRESSION_JPEG' ) )
     83                if ( ! ( defined( 'imagick::COMPRESSION_JPEG' ) && defined( 'imagick::FILTER_TRIANGLE' ) ) )
    7984                        return false;
    8085
    … …  
    250255                }
    251256
    252                 try {
     257                // Execute the resize
     258                $thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
     259                if ( is_wp_error( $thumb_result ) ) {
     260                        return $thumb_result;
     261                }
     262
     263                return $this->update_size( $dst_w, $dst_h );
     264        }
     265
     266        /**
     267         * Efficiently resize the current image
     268         *
     269         * This is a WordPress specific implementation of Imagick::thumbnailImage(),
     270         * which resizes an image to given dimensions and removes any associated profiles.
     271         *
     272         * @since 4.5.0
     273         * @access protected
     274         *
     275         * @param int    $dst_w       The destination width.
     276         * @param int    $dst_h       The destination height.
     277         * @param string $filter_name Optional. The Imagick filter to use when resizing. Default 'FILTER_TRIANGLE'.
     278         * @param bool   $strip_meta  Optional. Strip all profiles, excluding color profiles, from the image. Default true.
     279         * @return bool|WP_Error
     280         */
     281        protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIANGLE', $strip_meta = true ) {
     282                $allowed_filters = array(
     283                        'FILTER_POINT',
     284                        'FILTER_BOX',
     285                        'FILTER_TRIANGLE',
     286                        'FILTER_HERMITE',
     287                        'FILTER_HANNING',
     288                        'FILTER_HAMMING',
     289                        'FILTER_BLACKMAN',
     290                        'FILTER_GAUSSIAN',
     291                        'FILTER_QUADRATIC',
     292                        'FILTER_CUBIC',
     293                        'FILTER_CATROM',
     294                        'FILTER_MITCHELL',
     295                        'FILTER_LANCZOS',
     296                        'FILTER_BESSEL',
     297                        'FILTER_SINC',
     298                );
     299
     300                /**
     301                 * Set the filter value if '$filter_name' name is in our whitelist and the related
     302                 * Imagick constant is defined or fall back to our default filter.
     303                 */
     304                if ( in_array( $filter_name, $allowed_filters ) && defined( 'Imagick::' . $filter_name ) ) {
     305                        $filter = constant( 'Imagick::' . $filter_name );
     306                } else {
     307                        $filter = Imagick::FILTER_TRIANGLE;
     308                }
     309
     310                /**
     311                 * Filter to override stripping metadata from images when they're resized.
     312                 *
     313                 * This filter only applies when resizing using the Imagick editor since GD
     314                 * always strips profiles by default.
     315                 *
     316                 * @since 4.5.0
     317                 *
     318                 * @param bool $strip_meta Whether to strip image metadata during resizing. Default true.
     319                 */
     320                $strip_meta = apply_filters( 'image_strip_meta', $strip_meta );
     321
     322                // Strip image meta.
     323                if ( $strip_meta ) {
     324                        $strip_result = $this->strip_meta();
     325
     326                        if ( is_wp_error( $strip_result ) ) {
     327                                return $strip_result;
     328                        }
     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                        $resize_ratio = ( $dst_w / $this->size['width'] ) * ( $dst_h / $this->size['height'] );
     338                        $sample_factor = 5;
     339
     340                        if ( $resize_ratio < .111 && ( $dst_w * $sample_factor > 128 && $dst_h * $sample_factor > 128 ) ) {
     341                                $this->image->sampleImage( $dst_w * $sample_factor, $dst_h * $sample_factor );
     342                        }
     343
     344                        // Resize to the final output size.
     345                        $this->image->setOption( 'filter:support', '2.0' );
     346                        $this->image->resizeImage( $dst_w, $dst_h, $filter, 1 );
     347
     348                        // Set appropriate quality settings after resizing.
     349                        if ( 'image/jpeg' == $this->mime_type ) {
     350                                $this->image->unsharpMaskImage( 0.25, 0.25, 8, 0.065 );
     351                                $this->image->setOption( 'jpeg:fancy-upsampling', 'off' );
     352                        }
     353
     354                        if ( 'image/png' === $this->mime_type ) {
     355                                $this->image->setOption( 'png:compression-filter', '5' );
     356                                $this->image->setOption( 'png:compression-level', '9' );
     357                                $this->image->setOption( 'png:compression-strategy', '1' );
     358                                $this->image->setOption( 'png:exclude-chunk', 'all' );
     359                        }
     360
    253361                        /**
    254                          * @TODO: Thumbnail is more efficient, given a newer version of Imagemagick.
    255                          * $this->image->thumbnailImage( $dst_w, $dst_h );
     362                         * If alpha channel is not defined, set it opaque.
     363                         *
     364                         * Note that Imagick::getImageAlphaChannel() is only available if Imagick
     365                         * has been compiled against ImageMagick version 6.4.0 or newer.
    256366                         */
    257                         $this->image->scaleImage( $dst_w, $dst_h );
     367                        if ( method_exists( $this->image, 'getImageAlphaChannel') && $this->image->getImageAlphaChannel() === Imagick::ALPHACHANNEL_UNDEFINED ) {
     368                                $this->image->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE );
     369                        }
     370
     371                        // Limit the  bit depth of resized images to 8 bits per channel.
     372                        if ( 8 < $this->image->getImageDepth() ) {
     373                                $this->image->setImageDepth( 8 );
     374                        }
     375
     376                        $this->image->setInterlaceScheme( Imagick::INTERLACE_NO );
     377
    258378                }
    259379                catch ( Exception $e ) {
    260380                        return new WP_Error( 'image_resize_error', $e->getMessage() );
    261381                }
    262 
    263                 return $this->update_size( $dst_w, $dst_h );
    264382        }
    265383
    … …  
    368486                                        $dst_h = $src_h;
    369487
    370                                 $this->image->scaleImage( $dst_w, $dst_h );
     488                                $thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
     489                                if ( is_wp_error( $thumb_result ) ) {
     490                                        return $thumb_result;
     491                                }
     492
    371493                                return $this->update_size();
    372494                        }
    … …  
    531653                return true;
    532654        }
     655
     656        /**
     657         * Strip all image meta except color profiles from an image.
     658         *
     659         * @access protected
     660         * @since 4.5.0
     661         */
     662        protected function strip_meta() {
     663                try {
     664                        // Strip profiles.
     665                        foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) {
     666                                if ( $key != 'icc' && $key != 'icm' ) {
     667                                        $this->image->removeImageProfile( $key );
     668                                }
     669                        }
     670
     671                        // Strip image properties.
     672                        if ( method_exists( $this->image, 'deleteImageProperty' ) ) {
     673                                $this->image->deleteImageProperty( 'comment' );
     674                                $this->image->deleteImageProperty( 'Thumb::URI' );
     675                                $this->image->deleteImageProperty( 'Thumb::MTime' );
     676                                $this->image->deleteImageProperty( 'Thumb::Size' );
     677                                $this->image->deleteImageProperty( 'Thumb::Mimetype' );
     678                                $this->image->deleteImageProperty( 'software' );
     679                                $this->image->deleteImageProperty( 'Thumb::Image::Width' );
     680                                $this->image->deleteImageProperty( 'Thumb::Image::Height' );
     681                                $this->image->deleteImageProperty( 'Thumb::Document::Pages' );
     682                        } else {
     683                                $this->image->setImageProperty( 'comment', '' );
     684                                $this->image->setImageProperty( 'Thumb::URI', '' );
     685                                $this->image->setImageProperty( 'Thumb::MTime', '' );
     686                                $this->image->setImageProperty( 'Thumb::Size', '' );
     687                                $this->image->setImageProperty( 'Thumb::Mimetype', '' );
     688                                $this->image->setImageProperty( 'software', '' );
     689                                $this->image->setImageProperty( 'Thumb::Image::Width', '' );
     690                                $this->image->setImageProperty( 'Thumb::Image::Height', '' );
     691                                $this->image->setImageProperty( 'Thumb::Document::Pages', '' );
     692                        }
     693                } catch ( Excpetion $e ) {
     694                        return new WP_Error( 'image_strip_meta_error', $e->getMessage() );
     695                }
     696
     697                return true;
     698        }
     699
    533700}
Note: See TracChangeset for help on using the changeset viewer.