diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 66085ac503e9c..2e7c7039d5ffe 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -484,37 +484,28 @@ protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIAN $this->image->setOption( 'png:compression-filter', '5' ); $this->image->setOption( 'png:compression-level', '9' ); $this->image->setOption( 'png:compression-strategy', '1' ); - // Check to see if a PNG is indexed, and find the pixel depth. - if ( is_callable( array( $this->image, 'getImageDepth' ) ) ) { - $indexed_pixel_depth = $this->image->getImageDepth(); - - // Indexed PNG files get some additional handling. - if ( 0 < $indexed_pixel_depth && 8 >= $indexed_pixel_depth ) { - // Check for an alpha channel. - if ( - is_callable( array( $this->image, 'getImageAlphaChannel' ) ) - && $this->image->getImageAlphaChannel() - ) { - $this->image->setOption( 'png:include-chunk', 'tRNS' ); - } else { - $this->image->setOption( 'png:exclude-chunk', 'all' ); - } - - // Reduce colors in the images to maximum needed, using the global colorspace. - $max_colors = pow( 2, $indexed_pixel_depth ); - if ( is_callable( array( $this->image, 'getImageColors' ) ) ) { - $current_colors = $this->image->getImageColors(); - $max_colors = min( $max_colors, $current_colors ); - } - $this->image->quantizeImage( $max_colors, $this->image->getColorspace(), 0, false, false ); - - /** - * If the colorspace is 'gray', use the png8 format to ensure it stays indexed. - */ - if ( Imagick::COLORSPACE_GRAY === $this->image->getImageColorspace() ) { - $this->image->setOption( 'png:format', 'png8' ); - } + + // Indexed PNG files get some additional handling. + // See #63448 for details. + if ( + is_callable( array( $this->image, 'getImageProperty' ) ) + && '3' === $this->image->getImageProperty( 'png:IHDR.color-type-orig' ) + ) { + + // Check for an alpha channel. + if ( + is_callable( array( $this->image, 'getImageAlphaChannel' ) ) + && $this->image->getImageAlphaChannel() + ) { + $this->image->setOption( 'png:include-chunk', 'tRNS' ); + } else { + $this->image->setOption( 'png:exclude-chunk', 'all' ); } + // Set the image format to Indexed PNG. + $this->image->setOption( 'png:format', 'png8' ); + + } else { + $this->image->setOption( 'png:exclude-chunk', 'all' ); } } diff --git a/tests/phpunit/data/images/png-tests/grayscale-test-image.png b/tests/phpunit/data/images/png-tests/grayscale-test-image.png new file mode 100644 index 0000000000000..10f537a71b67d Binary files /dev/null and b/tests/phpunit/data/images/png-tests/grayscale-test-image.png differ diff --git a/tests/phpunit/data/images/png-tests/vivid-green-bird.png b/tests/phpunit/data/images/png-tests/vivid-green-bird.png new file mode 100644 index 0000000000000..7b55b3d5e7a03 Binary files /dev/null and b/tests/phpunit/data/images/png-tests/vivid-green-bird.png differ diff --git a/tests/phpunit/tests/image/editorImagick.php b/tests/phpunit/tests/image/editorImagick.php index 8b00f3ac1240f..1edbf579c3952 100644 --- a/tests/phpunit/tests/image/editorImagick.php +++ b/tests/phpunit/tests/image/editorImagick.php @@ -770,6 +770,8 @@ public function __return_eight() { * @ticket 36477 * * @dataProvider data_resizes_are_small_for_16bit_images + * + * @param string $file Path to the image file. */ public function test_resizes_are_small_for_16bit_images( $file ) { @@ -783,7 +785,7 @@ public function test_resizes_are_small_for_16bit_images( $file ) { $imagick_image_editor->resize( $size['width'] * .5, $size['height'] * .5 ); - $saved = $imagick_image_editor->save( $temp_file ); + $imagick_image_editor->save( $temp_file ); $new_filesize = filesize( $temp_file ); @@ -793,7 +795,7 @@ public function test_resizes_are_small_for_16bit_images( $file ) { } /** - * data_test_resizes_are_small_for_16bit + * Data provider for test_resizes_are_small_for_16bit_images. * * @return array[] */ @@ -816,4 +818,59 @@ public static function data_resizes_are_small_for_16bit_images() { ), ); } + + /** + * Tests that the 'png:IHDR.color-type-orig' property is preserved after resizing + * Used to identify indexed PNG images, see https://www.w3.org/TR/PNG-Chunks.html#C.IHDR. + * + * @ticket 63448 + * @dataProvider data_png_color_type_after_resize + * + * @param string $file_path Path to the image file. + * @param int $expected_color_type The expected original color type. + */ + public function test_png_color_type_is_preserved_after_resize( $file_path, $expected_color_type ) { + + $temp_file = DIR_TESTDATA . '/images/test-temp.png'; + + $imagick_image_editor = new WP_Image_Editor_Imagick( $file_path ); + $imagick_image_editor->load(); + + $size = $imagick_image_editor->get_size(); + $imagick_image_editor->resize( $size['width'] * 0.5, $size['height'] * 0.5 ); + $imagick_image_editor->save( $temp_file ); + + $imagick = new Imagick( $temp_file ); + $actual_color_type = $imagick->getImageProperty( 'png:IHDR.color-type-orig' ); + + unlink( $temp_file ); + + $this->assertSame( (string) $expected_color_type, $actual_color_type, "The PNG original color type should be preserved after resize for {$file_path}." ); + } + + /** + * Data provider for test_png_color_type_is_preserved_after_resize. + * + * @return array[] + */ + public static function data_png_color_type_after_resize() { + return array( + 'vivid-green-bird_color_type_6' => array( + DIR_TESTDATA . '/images/png-tests/vivid-green-bird.png', + 6, // RGBA. + ), + 'grayscale-test-image_color_type_4' => array( + DIR_TESTDATA . '/images/png-tests/grayscale-test-image.png', + 4, // Grayscale with Alpha. + ), + 'rabbit-time-paletted-or8_color_type_3' => array( + DIR_TESTDATA . '/images/png-tests/rabbit-time-paletted-or8.png', + 3, // Paletted. + ), + 'test8_color_type_3' => array( + DIR_TESTDATA . '/images/png-tests/test8.png', + 3, // Paletted. + ), + ); + } }