Make WordPress Core


Ignore:
Timestamp:
02/24/2016 10:50:10 PM (10 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.