Make WordPress Core


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