Make WordPress Core

Changeset 60246


Ignore:
Timestamp:
05/22/2025 09:38:22 PM (2 months ago)
Author:
adamsilverstein
Message:

Media: fix degraded handling for 24 bit PNG uploads.

Add additional logic to correctly identify indexed PNG images so output PNGs correctly match the uploaded image depth.

Fix a regression introduced in WordPress 6.8 [59589] where uploaded Truecolor PNG images (meaning > 8 bit) were mis-identified as indexed (8 bit) images, causing output images to be indexed instead of Truecolor resulting in a noticeable visual degradation.

Props elvismdev, wildworks, SirLouen, siliconforks, joemcgill, iamshashank, nosilver4u.
Fixes #63448.

Location:
trunk
Files:
2 added
2 edited

Legend:

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

    r60047 r60246  
    485485                $this->image->setOption( 'png:compression-level', '9' );
    486486                $this->image->setOption( 'png:compression-strategy', '1' );
    487                 // Check to see if a PNG is indexed, and find the pixel depth.
    488                 if ( is_callable( array( $this->image, 'getImageDepth' ) ) ) {
    489                     $indexed_pixel_depth = $this->image->getImageDepth();
    490 
    491                     // Indexed PNG files get some additional handling.
    492                     if ( 0 < $indexed_pixel_depth && 8 >= $indexed_pixel_depth ) {
    493                         // Check for an alpha channel.
    494                         if (
    495                             is_callable( array( $this->image, 'getImageAlphaChannel' ) )
    496                             && $this->image->getImageAlphaChannel()
    497                         ) {
    498                             $this->image->setOption( 'png:include-chunk', 'tRNS' );
    499                         } else {
    500                             $this->image->setOption( 'png:exclude-chunk', 'all' );
    501                         }
    502 
    503                         // Reduce colors in the images to maximum needed, using the global colorspace.
    504                         $max_colors = pow( 2, $indexed_pixel_depth );
    505                         if ( is_callable( array( $this->image, 'getImageColors' ) ) ) {
    506                             $current_colors = $this->image->getImageColors();
    507                             $max_colors     = min( $max_colors, $current_colors );
    508                         }
    509                         $this->image->quantizeImage( $max_colors, $this->image->getColorspace(), 0, false, false );
    510 
    511                         /**
    512                          * If the colorspace is 'gray', use the png8 format to ensure it stays indexed.
    513                          */
    514                         if ( Imagick::COLORSPACE_GRAY === $this->image->getImageColorspace() ) {
    515                             $this->image->setOption( 'png:format', 'png8' );
    516                         }
     487
     488                // Indexed PNG files get some additional handling.
     489                // See #63448 for details.
     490                if (
     491                    is_callable( array( $this->image, 'getImageProperty' ) )
     492                    && '3' === $this->image->getImageProperty( 'png:IHDR.color-type-orig' )
     493                ) {
     494
     495                    // Check for an alpha channel.
     496                    if (
     497                        is_callable( array( $this->image, 'getImageAlphaChannel' ) )
     498                        && $this->image->getImageAlphaChannel()
     499                    ) {
     500                        $this->image->setOption( 'png:include-chunk', 'tRNS' );
     501                    } else {
     502                        $this->image->setOption( 'png:exclude-chunk', 'all' );
    517503                    }
     504                    // Set the image format to Indexed PNG.
     505                    $this->image->setOption( 'png:format', 'png8' );
     506
     507                } else {
     508                    $this->image->setOption( 'png:exclude-chunk', 'all' );
    518509                }
    519510            }
  • trunk/tests/phpunit/tests/image/editorImagick.php

    r60030 r60246  
    771771     *
    772772     * @dataProvider data_resizes_are_small_for_16bit_images
     773     *
     774     * @param string $file Path to the image file.
    773775     */
    774776    public function test_resizes_are_small_for_16bit_images( $file ) {
     
    784786        $imagick_image_editor->resize( $size['width'] * .5, $size['height'] * .5 );
    785787
    786         $saved = $imagick_image_editor->save( $temp_file );
     788        $imagick_image_editor->save( $temp_file );
    787789
    788790        $new_filesize = filesize( $temp_file );
     
    794796
    795797    /**
    796      * data_test_resizes_are_small_for_16bit
     798     * Data provider for test_resizes_are_small_for_16bit_images.
    797799     *
    798800     * @return array[]
     
    817819        );
    818820    }
     821
     822    /**
     823     * Tests that the 'png:IHDR.color-type-orig' property is preserved after resizing
     824     * Used to identify indexed PNG images, see https://www.w3.org/TR/PNG-Chunks.html#C.IHDR.
     825     *
     826     * @ticket 63448
     827     * @dataProvider data_png_color_type_after_resize
     828     *
     829     * @param string $file_path             Path to the image file.
     830     * @param int    $expected_color_type   The expected original color type.
     831     */
     832    public function test_png_color_type_is_preserved_after_resize( $file_path, $expected_color_type ) {
     833
     834        $temp_file = DIR_TESTDATA . '/images/test-temp.png';
     835
     836        $imagick_image_editor = new WP_Image_Editor_Imagick( $file_path );
     837        $imagick_image_editor->load();
     838
     839        $size = $imagick_image_editor->get_size();
     840        $imagick_image_editor->resize( $size['width'] * 0.5, $size['height'] * 0.5 );
     841        $imagick_image_editor->save( $temp_file );
     842
     843        $imagick           = new Imagick( $temp_file );
     844        $actual_color_type = $imagick->getImageProperty( 'png:IHDR.color-type-orig' );
     845
     846        unlink( $temp_file );
     847
     848        $this->assertSame( (string) $expected_color_type, $actual_color_type, "The PNG original color type should be preserved after resize for {$file_path}." );
     849    }
     850
     851    /**
     852     * Data provider for test_png_color_type_is_preserved_after_resize.
     853     *
     854     * @return array[]
     855     */
     856    public static function data_png_color_type_after_resize() {
     857        return array(
     858            'vivid-green-bird_color_type_6'         => array(
     859                DIR_TESTDATA . '/images/png-tests/vivid-green-bird.png',
     860                6, // RGBA.
     861            ),
     862            'grayscale-test-image_color_type_4'     => array(
     863                DIR_TESTDATA . '/images/png-tests/grayscale-test-image.png',
     864                4, // Grayscale with Alpha.
     865            ),
     866            'rabbit-time-paletted-or8_color_type_3' => array(
     867                DIR_TESTDATA . '/images/png-tests/rabbit-time-paletted-or8.png',
     868                3, // Paletted.
     869            ),
     870            'test8_color_type_3'                    => array(
     871                DIR_TESTDATA . '/images/png-tests/test8.png',
     872                3, // Paletted.
     873            ),
     874        );
     875    }
    819876}
Note: See TracChangeset for help on using the changeset viewer.