Make WordPress Core


Ignore:
Timestamp:
08/26/2025 08:55:24 PM (5 months ago)
Author:
adamsilverstein
Message:

Media: improve Imagick handling of indexed PNG images with transparency.

Fix an issue where certain transparent PNG images experienced noticeable quality degradation when resized by Imagick.

Follow up to [60246].

Props elvismdev, SirLouen, siliconforks, nosilver4u, iamshashank.

Fixes #63448.

File:
1 edited

Legend:

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

    r60303 r60667  
    446446        try {
    447447            /*
     448             * We need to perform some special handling for certain types of images:
     449             * 1. For PNG images, we need to specify compression settings and remove unneeded chunks.
     450             * 2. For indexed PNG images, the number of colors must not exceed 256.
     451             * 3. For indexed PNG images with an alpha channel, the tRNS chunk must be preserved.
     452             * 4. For indexed PNG images with true alpha transparency (an alpha channel > 1 bit),
     453             *    we need to avoid saving the image using ImageMagick's 'png8' format,
     454             *    because that supports only binary (1 bit) transparency.
     455             *
     456             * For #4 we want to check whether the image has a 1-bit alpha channel before resizing,
     457             * because resizing may cause the number of alpha values to multiply due to antialiasing.
     458             * (We're assuming that, if the original image had only a 1-bit alpha channel,
     459             * then a 1-bit alpha channel should be good enough for the resized images too.)
     460             * So we're going to perform all the necessary checks before resizing the image
     461             * and store the results in variables for later use.
     462             */
     463            $is_png                                      = false;
     464            $is_indexed_png                              = false;
     465            $is_indexed_png_with_alpha_channel           = false;
     466            $is_indexed_png_with_true_alpha_transparency = false;
     467
     468            if ( 'image/png' === $this->mime_type ) {
     469                $is_png = true;
     470
     471                if (
     472                    is_callable( array( $this->image, 'getImageProperty' ) )
     473                    && '3' === $this->image->getImageProperty( 'png:IHDR.color-type-orig' )
     474                ) {
     475                    $is_indexed_png = true;
     476
     477                    if (
     478                        is_callable( array( $this->image, 'getImageAlphaChannel' ) )
     479                        && $this->image->getImageAlphaChannel()
     480                    ) {
     481                        $is_indexed_png_with_alpha_channel = true;
     482
     483                        if (
     484                            is_callable( array( $this->image, 'getImageChannelDepth' ) )
     485                            && defined( 'Imagick::CHANNEL_ALPHA' )
     486                            && 1 < $this->image->getImageChannelDepth( Imagick::CHANNEL_ALPHA )
     487                        ) {
     488                            $is_indexed_png_with_true_alpha_transparency = true;
     489                        }
     490                    }
     491                }
     492            }
     493
     494            /*
    448495             * To be more efficient, resample large images to 5x the destination size before resizing
    449496             * whenever the output size is less that 1/3 of the original image size (1/3^2 ~= .111),
     
    481528            }
    482529
    483             if ( 'image/png' === $this->mime_type ) {
     530            if ( $is_png ) {
    484531                $this->image->setOption( 'png:compression-filter', '5' );
    485532                $this->image->setOption( 'png:compression-level', '9' );
     
    488535                // Indexed PNG files get some additional handling.
    489536                // See #63448 for details.
    490                 if (
    491                     is_callable( array( $this->image, 'getImageProperty' ) )
    492                     && '3' === $this->image->getImageProperty( 'png:IHDR.color-type-orig' )
    493                 ) {
     537                if ( $is_indexed_png ) {
    494538
    495539                    // Check for an alpha channel.
    496                     if (
    497                         is_callable( array( $this->image, 'getImageAlphaChannel' ) )
    498                         && $this->image->getImageAlphaChannel()
    499                     ) {
     540                    if ( $is_indexed_png_with_alpha_channel ) {
    500541                        $this->image->setOption( 'png:include-chunk', 'tRNS' );
    501542                    } else {
    502543                        $this->image->setOption( 'png:exclude-chunk', 'all' );
    503544                    }
    504                     // Set the image format to Indexed PNG.
    505                     $this->image->setOption( 'png:format', 'png8' );
    506 
     545
     546                    $this->image->quantizeImage( 256, $this->image->getColorspace(), 0, false, false );
     547
     548                    /*
     549                     * If the colorspace is 'gray', use the png8 format to ensure it stays indexed.
     550                     * ImageMagick tends to save grayscale images as grayscale PNGs rather than indexed PNGs,
     551                     * even though grayscale PNGs usually have considerably larger file sizes.
     552                     * But we can force ImageMagick to save the image as an indexed PNG instead,
     553                     * by telling it to use png8 format.
     554                     *
     555                     * Note that we need to first call quantizeImage() before checking getImageColorspace(),
     556                     * because only after calling quantizeImage() will the colorspace be COLORSPACE_GRAY for grayscale images
     557                     * (and we have not found any other way to identify grayscale images).
     558                     *
     559                     * We need to avoid forcing indexed format for images with true alpha transparency,
     560                     * because ImageMagick does not support saving an image with true alpha transparency as an indexed PNG.
     561                     */
     562                    if ( Imagick::COLORSPACE_GRAY === $this->image->getImageColorspace() && ! $is_indexed_png_with_true_alpha_transparency ) {
     563                        // Set the image format to Indexed PNG.
     564                        $this->image->setOption( 'png:format', 'png8' );
     565                    }
    507566                } else {
    508567                    $this->image->setOption( 'png:exclude-chunk', 'all' );
Note: See TracChangeset for help on using the changeset viewer.