Closed nosilver4u closed 5 months ago
Oh, and here are a couple images I've used for testing: https://ewwwio-downloads.b-cdn.net/png-tests/deskcat8.png https://ewwwio-downloads.b-cdn.net/png-tests/test8.png https://ewwwio-downloads.b-cdn.net/png-tests/Palette_icon-or8.png https://ewwwio-downloads.b-cdn.net/png-tests/rabbit-time-paletted-or8.png
Please can you give me a standalone piece of code that shows the problem? i.e. some code that can be run from the command line.
Although I might be able to reproduce your problem from your description, it might take me quite a while, or the problem might not show itself with different code running on my system.
I was thinking of that last night, but ran out of time, so I'll work up a simple script to replicate it.
Ok, I wrote a script locally, where I have the latest ImageMagick (7.1.1-32 Q16-HDRI x86_64 22207), and Imagick 3.7.0. palette-problem.php.zip
It's kind of long as I put in all the custom code for reading pixel depth and color type, as that doesn't seem to work great in Imagick directly. But if you'd rather copy it than use the zip, here you go:
<?php
$filename = 'deskcat8.png';
$newname = 'deskcat8-test.png';
get_png_color_depth( $filename );
$image = new Imagick( $filename );
$current_colors = $image->getImageColors();
echo "started with $current_colors colors\n";
$image->resizeImage( 1140, 1102, Imagick::FILTER_TRIANGLE, 1 );
$image->setOption( 'png:compression-filter', '5' );
$image->setOption( 'png:compression-level', '9' );
$image->setOption( 'png:compression-strategy', '1' );
$image->setOption( 'png:exclude-chunk', 'all' );
if ( $image->getImageAlphaChannel() === Imagick::ALPHACHANNEL_UNDEFINED ) {
echo "hollder\n";
$image->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE );
}
$max_colors = 255;
$image->quantizeImage( $max_colors, $image->getColorspace(), 0, false, false );
$new_colors = $image->getImageColors();
echo "ended up with $new_colors colors\n";
// Here are all the things I've tried:
// $image->setOption( 'png:format', 'png8' );
// $image->setImageType( imagick::IMGTYPE_PALETTEMATTE );
// $image->setOption( 'png:color-type', 3 );
if ( 8 < $image->getImageDepth() ) {
echo "deeeep\n";
$image->setImageDepth( 8 );
}
// Or if we try to prefix the filename directly in writeImage():
// $write_image_result = $image->writeImage( 'PNG8:' . $newname );
$write_image_result = $image->writeImage( $newname );
if ( $write_image_result ) {
get_png_color_depth( $newname );
}
function get_png_color_depth( $filename ) {
if ( ! is_file( $filename ) ) {
return;
}
$size = filesize( $filename );
echo "size of $filename is $size\n";
$file_handle = fopen( $filename, 'rb' );
if ( ! $file_handle ) {
return;
}
$png_header = fread( $file_handle, 4 );
if ( chr( 0x89 ) . 'PNG' !== $png_header ) {
return;
}
// Move forward 8 bytes.
fread( $file_handle, 8 );
$png_ihdr = fread( $file_handle, 4 );
// Make sure we have an IHDR.
if ( 'IHDR' !== $png_ihdr ) {
return;
}
// Skip past the dimensions.
$dimensions = fread( $file_handle, 8 );
// Bit depth: 1 byte
// Bit depth is a single-byte integer giving the number of bits per sample or
// per palette index (not per pixel).
//
// Valid values are 1, 2, 4, 8, and 16, although not all values are allowed for all color types.
$pixel_depth = ord( (string) fread( $file_handle, 1 ) );
echo "pixel depth of $filename is $pixel_depth\n";
// Color type is a single-byte integer that describes the interpretation of the image data.
// Color type codes represent sums of the following values:
// 1 (palette used), 2 (color used), and 4 (alpha channel used).
// The valid color types are:
// 0 => Grayscale
// 2 => Truecolor
// 3 => Indexed
// 4 => Greyscale with alpha
// 6 => Truecolour with alpha
$color_type = ord( (string) fread( $file_handle, 1 ) );
echo "color type of $filename is $color_type\n";
fclose( $file_handle );
}
Thanks.
While I attempt to investigate what is going wrong, if you need a fix, then just calling ImageMagick through the command line with something like:
convert deskcat8.png -resize 1140x1102 PNG8:im_output_png8.png
Does seem to give the desired result.
So. Try commenting out the line:
$image->setOption( 'png:exclude-chunk', 'all' );
and comment in the lines:
// Here are all the things I've tried:
$image->setOption( 'png:format', 'png8' );
$image->setImageType( imagick::IMGTYPE_PALETTEMATTE );
$image->setOption( 'png:color-type', 3 );
Does your code now behave as you wish?
If it does, then maybe use $image->stripImage
rather than excluding chunks. I think that should prevent any 'extra' chunks from being saved, though that is just a guess rather than knowledge.
Aha, you nailed it with the exclude-chunk option!
From reading the docs on that define, there is this useful bit:
If the ancillary PNG tRNS chunk is excluded and the image has transparency, the PNG colortype is forced to be 4 or 6 (GRAY_ALPHA or RGBA).
Thank you so much!
So I ran into a quirk with one of the test images: https://ewwwio-downloads.b-cdn.net/png-tests/test8.png
With all the other test/sample images, so long as I preserve the tRNS chunk, and quantize them to 255 colors (or less), I get an image that is indexed (palette or palettematte).
But with test8.png, IM always saves it as grayscale+alpha, which vastly increases the file size. I took a look with identify -verbose test8.png
:
Image:
Filename: test8.png
Permissions: rw-r--r--
Format: PNG (Portable Network Graphics)
Mime type: image/png
Class: PseudoClass
Geometry: 1405x816+0+0
Units: Undefined
Colorspace: sRGB
Type: GrayscaleAlpha
Base type: PaletteAlpha
Endianness: Undefined
Depth: 8-bit
...
Note that IM shows a Base type of PaletteAlpha, but a Type of GrayscaleAlpha, which is different from all the other sample images, which simply say Type: PaletteAlpha
.
Even stranger, if I do $image->getImageType()
prior to saving the file, it says imagick::IMGTYPE_PALETTEMATTE
, but as soon as I do writeOutput(), it's GrayscaleAlpha--and not just "spoofing" like the original, but actual color type 4 with no palette.
I went through some of the ways to force it into color type 3 (indexed), and only 1 works on this image:
$image->setOption( 'png:format', 'png8' );
Unfortunately, that isn't ideal for the other images, as it "re-indexes" them, and degrades the quality. Two possibilities come to mind:
Any ideas?
Please can you submit it as a new issue. If nothing else, keeping issues closed gives me a marginal sense of accomplishment.
will do!
Hi all, We've been attempting to improve the resizing of PNG images in WordPress and have run into a bit of a roadblock. But I'm not sure if we're missing something, or if this is a bug.
In short, we're specifically looking at indexed PNG images with a bit depth of 8 (or lower). When an image is scaled down, more colors get added, and often the scaled image ends up larger than the original. To fix this, I've used the
quantizeImage()
method to constrain the total number of colors, so that it is closer to the original and can be saved with a palette in indexed mode (color type 3). You can see what I've done so far here: https://github.com/WordPress/wordpress-develop/commit/40eb2d533f6068f5a4b6da25a6d29c85514bfe43Unfortunately, even though the number of colors has been reduced to be below 256, the image doesn't get saved as indexed. I tried this:
$image->setOption( 'png:color-type', 3 );
and nothing changes. I also tried$image->setImageType( imagick::IMGTYPE_PALETTE )
or$image->setImageType( imagick::IMGTYPE_PALETTEMATTE )
and that doesn't seem to help either.At https://stackoverflow.com/questions/10348743/how-to-convert-png32-to-png8-via-imagick-in-php it is suggested to use
$image->writeImage( "png8:$filename" );
and apparently at some point that worked, but I'm getting fatal errors when attempting that.PHP Fatal error: Uncaught ImagickException: Cannot write PNG8 or color-type 3; colormap is NULL '/sites/test.exactlywww.com/files/wp-content/uploads/2024/05/cat-desk8-10-300x290.png' @ error/png.c/WriteOnePNGImage/9666
Likewise,
$image->setOption( 'png:format', 'png8' )
also throws the same error, which I think might work better for our purposes--if it worked at all.So, is there anything else we can do to try and get Imagick to save PNG images as indexed? Is this a bug or how is it that it used to work, and no longer does?
Thanks for your time and any help you can offer!
Edit: I'm primarily testing with ImageMagick 6.9.11-60 Q16 x86_64 2021-01-25 https://imagemagick.org Imagick version 3.7.0
and also with ImageMagick 7.1.1-19 Q16-HDRI x86_64 21601 https://imagemagick.org Imagick version 3.7.0