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.


Built from https://develop.svn.wordpress.org/trunk@60667


git-svn-id: http://core.svn.wordpress.org/trunk@60003 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Adam Silverstein
2025-08-26 20:57:34 +00:00
parent f8f84a4173
commit 08ef43f295
2 changed files with 71 additions and 12 deletions

View File

@@ -444,6 +444,53 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
}
try {
/*
* We need to perform some special handling for certain types of images:
* 1. For PNG images, we need to specify compression settings and remove unneeded chunks.
* 2. For indexed PNG images, the number of colors must not exceed 256.
* 3. For indexed PNG images with an alpha channel, the tRNS chunk must be preserved.
* 4. For indexed PNG images with true alpha transparency (an alpha channel > 1 bit),
* we need to avoid saving the image using ImageMagick's 'png8' format,
* because that supports only binary (1 bit) transparency.
*
* For #4 we want to check whether the image has a 1-bit alpha channel before resizing,
* because resizing may cause the number of alpha values to multiply due to antialiasing.
* (We're assuming that, if the original image had only a 1-bit alpha channel,
* then a 1-bit alpha channel should be good enough for the resized images too.)
* So we're going to perform all the necessary checks before resizing the image
* and store the results in variables for later use.
*/
$is_png = false;
$is_indexed_png = false;
$is_indexed_png_with_alpha_channel = false;
$is_indexed_png_with_true_alpha_transparency = false;
if ( 'image/png' === $this->mime_type ) {
$is_png = true;
if (
is_callable( array( $this->image, 'getImageProperty' ) )
&& '3' === $this->image->getImageProperty( 'png:IHDR.color-type-orig' )
) {
$is_indexed_png = true;
if (
is_callable( array( $this->image, 'getImageAlphaChannel' ) )
&& $this->image->getImageAlphaChannel()
) {
$is_indexed_png_with_alpha_channel = true;
if (
is_callable( array( $this->image, 'getImageChannelDepth' ) )
&& defined( 'Imagick::CHANNEL_ALPHA' )
&& 1 < $this->image->getImageChannelDepth( Imagick::CHANNEL_ALPHA )
) {
$is_indexed_png_with_true_alpha_transparency = true;
}
}
}
}
/*
* To be more efficient, resample large images to 5x the destination size before resizing
* whenever the output size is less that 1/3 of the original image size (1/3^2 ~= .111),
@@ -480,30 +527,42 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
$this->image->setOption( 'jpeg:fancy-upsampling', 'off' );
}
if ( 'image/png' === $this->mime_type ) {
if ( $is_png ) {
$this->image->setOption( 'png:compression-filter', '5' );
$this->image->setOption( 'png:compression-level', '9' );
$this->image->setOption( 'png:compression-strategy', '1' );
// 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' )
) {
if ( $is_indexed_png ) {
// Check for an alpha channel.
if (
is_callable( array( $this->image, 'getImageAlphaChannel' ) )
&& $this->image->getImageAlphaChannel()
) {
if ( $is_indexed_png_with_alpha_channel ) {
$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' );
$this->image->quantizeImage( 256, $this->image->getColorspace(), 0, false, false );
/*
* If the colorspace is 'gray', use the png8 format to ensure it stays indexed.
* ImageMagick tends to save grayscale images as grayscale PNGs rather than indexed PNGs,
* even though grayscale PNGs usually have considerably larger file sizes.
* But we can force ImageMagick to save the image as an indexed PNG instead,
* by telling it to use png8 format.
*
* Note that we need to first call quantizeImage() before checking getImageColorspace(),
* because only after calling quantizeImage() will the colorspace be COLORSPACE_GRAY for grayscale images
* (and we have not found any other way to identify grayscale images).
*
* We need to avoid forcing indexed format for images with true alpha transparency,
* because ImageMagick does not support saving an image with true alpha transparency as an indexed PNG.
*/
if ( Imagick::COLORSPACE_GRAY === $this->image->getImageColorspace() && ! $is_indexed_png_with_true_alpha_transparency ) {
// Set the image format to Indexed PNG.
$this->image->setOption( 'png:format', 'png8' );
}
} else {
$this->image->setOption( 'png:exclude-chunk', 'all' );
}

View File

@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
$wp_version = '6.9-alpha-60666';
$wp_version = '6.9-alpha-60667';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.