http://www.perlmonks.org?node_id=1214407

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Does anyone know how to set the compression level for writing png files in Imager, or if it's even possible? So far Super Search and Google have been no help, so I tried many guesses of possible argument names, but no joy. All I've learned is that Imager doesn't complain about invalid args. I would really prefer not to use a different image module as it would involve a substantial rewrite of the program. Thanks for any help with this.

$img->write( file => $outfile, type => 'png', compression => 0 ); $img->write( file => $outfile, type => 'png', compress => 0 ); $img->write( file => $outfile, type => 'png', level => 0 ); $img->write( file => $outfile, type => 'png', quality => 0 ); ... I've tried these and many more, including every variation or combi +nation I could think of

Replies are listed 'Best First'.
Re: Setting png compression level in Imager
by haukex (Archbishop) on May 12, 2018 at 09:05 UTC

    This Readme says that Imager uses libpng. The libpng Manual says that it offers several functions to control the compression options, named png_set_compression_*, the central one being png_set_compression_level. The Imager source does not seem to call these functions at all. So unfortunately the answer to your question seems to be no, there is currently no way to set the compression level. You may want to file a wishlist item in the issue tracker, or perhaps even hack the source yourself.

Re: Setting png compression level in Imager -- GD
by Discipulus (Canon) on May 12, 2018 at 12:52 UTC
    Hello Anonymous Monk,

    GD uses png compression.

    > The optional $compression_level argument controls the amount of compression to apply to the output PNG image. Values range from 0-9, where 0 means no compression (largest files, highest quality) and 9 means maximum compression (smallest files, worst quality). A compression level of -1 uses the default compression level selected when zlib was compiled on your system, and is the same as calling png() with no argument.

    See it explained GD docs.

    I used png compression in my picwoodpecker -- a Tk program to choose and modify your photos (<- upvote here ;) that now is on github (<- newer version here ;)

    Iirc the compression worked but in the windows machine where I'm now seems to not to have effect on the size/quality of the output (missing zlib?).

    Try my (wonderful!) program above perl picwoodpecker.pl -pngcompression 0 go to advanced and set the output to png then use copy this photo from the main ineterface. Retry with -pngcompression 9

    HtH

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Setting png compression level in Imager
by Your Mother (Archbishop) on May 12, 2018 at 05:10 UTC

    I poked around a bit. I expect it is possible but I didn't see where/how. It looks like Imager harnesses pnglib which primarily does compression via zlib. I went through the pnglib docs quite a lot and it all seemed a mismatch for things I would call intuitive Perl; like setting the compression level or quality in the write call as you were trying. I also tried your code and quite a few other variations as guesses from the libpng doc and had no luck. I was also a little irritated and surprised by the arbitrary argument acceptance. :| If you figure it out or get an answer, please submit a doc patch. This would be really good to have documented.

    You could maybe switch to jpg(?) in the short term. If you're not doing things that are either text oriented or clean deco-ish, jpg is a much better match for file size optimization than png.

      Thanks. The program is designed to convert thousands of tiff files to png after performing several operations on them, and the compression needs to be lossless, so no jpegs.

      I did finally find a set of default arg values in the Imager source code, and one of them is compress => 1, but changing it makes no difference in file size. I saved a few images with GIMP using different settings, and judging by file size Imager uses png compression level 6. Also, from the same tests it looks like going to maximum compression from 6 will not give more than 4% improvement. Since I'm already getting better than 3:1 reduction in file size by converting to png's I'll just be happy with that.

Re: Setting png compression level in Imager
by vr (Curate) on May 13, 2018 at 11:45 UTC

    Well, if execution time can be sacrificed...

    As you already found, Imager (through libpng) uses zlib's level 6, which is "good enough", because zlib's max (9) delivers compression only marginally better. But we can beat this "level 9" using more robust algorithm.

    use strict; use warnings; use feature 'say'; use Imager; use Image::PNG::Rewriter; use IO::Compress::Deflate 'deflate', ':constants'; use Compress::Zopfli::ZLIB 'compress'; my $name = 'PNG_transparency_demonstration_1.png'; open my $fh, '<', $name; binmode $fh; Imager-> new( fh => $fh ) -> write( type => 'png', data => \my $png_imager ); seek $fh, 0, 0; my $rw = Image::PNG::Rewriter-> new( handle => $fh, zlib => sub { my $input = shift; deflate \$input => \my $output, -Level => 9, -Strategy => Z_FILTERED; return $output } ); $rw-> refilter( $rw-> original_filters ); # no-op my $png_zlib9 = $rw-> as_png; seek $fh, 0, 0; $rw = Image::PNG::Rewriter-> new( handle => $fh, zlib => \&compress, ); $rw-> refilter( $rw-> original_filters ); # no-op my $png_zopfli = $rw-> as_png; my $f = "%-24s%s\n"; printf $f, 'Original file', -s $name; printf $f, 'Re-saved with Imager', length $png_imager; printf $f, 'Compressed with zlib', length $png_zlib9; printf $f, 'Compressed with zopfli', length $png_zopfli; __END__ Original file 179559 Re-saved with Imager 203632 Compressed with zlib 198642 Compressed with zopfli 179618

    The test subject is this image. Note the change history (disregard early versions, they are of low quality) -- last guy who uploaded final copy was, perhaps, using same Zopfli compressor.

    I'm lazy to handle IDAT chunk(s) by hand, so there's Image::PNG::Rewriter DIY used. To kick-start compressor (passed as sub to constructor), it looks like some "is_dirty" flag needs to be set, or otherwise original IDAT is output as it was. Hence, the "no_op" line above, which filters scan-lines with the same values as original. I didn't investigate if Zopfli parameters can deliver (a) better compression or (b) faster execution with almost the same compression.

    Heh, BTW, we can check if libpng is smart enough to find out itself, which filter to use with each scan-line:

    use strict; use warnings; use feature 'say'; use Imager; use Image::PNG::Rewriter; my $i = Imager-> new( # defaults are RGB, 8 bits per channel xsize => 256, ysize => 2, filetype => 'raw', interleave => 1, # RRRR...GGGG...BBBB... data => \( pack 'C*', ( 0 .. 255 ) x 3, # 1st scanline is gradient ( 0 ) x ( 3 * 256 )), # 2nd scanline is flat black ) or die; $i-> write( data => \my $png, type => 'png' ); open my $fh, '<', \$png; my $re = Image::PNG::Rewriter-> new( handle => $fh ); say for $re-> original_filters; __END__ 1 0

    BTW, all of the above is assuming, that you are working with true-color 24 bpp images, which cannot be (losslessly) converted to 8 bpp palletized PNG images.

Re: Setting png compression level in Imager
by pryrt (Abbot) on May 11, 2018 at 23:27 UTC

    Looking at Imager, I searched for "PNG". A few matches down, I found PNG files - Imager::Files, "PNG" in Imager::Files. I see some mentions there of "compressed"; there might be something in that section to help you.... or there might not. If not, you might be able to find something in the source code. For future reference, official documentation and source code is probably a better first step then trying "every variation or combination I could think of"

      Ok thanks, and I should have mentioned that I did search the documentation too. The reference to compression you mention is for compressing embedded metadata, not the actual image. According to the png specs, there are 10 levels of image compression, 0-9, and I want to be able to explicitly set that rather than accepting whatever, apparently undocumented, default is set in the module.

Re: Setting png compression level in Imager
by Anonymous Monk on May 13, 2018 at 06:06 UTC

    Thanks for the help & info everyone. With that and a few tests, it appears that there is not enough room for improvement in compression to put much more effort into this. Apparently you cannot change the png compression in Imager, which defaults to level 6, and compressing to level 9 (using GIMP) only gives a small reduction in file size with a noticeable increase in processing time while saving the file.

      Ive been using Pngcrush bruteforce for a while, best savings i ever was 300kb on a bmp drawing i did in pbrush, there just aint that much reduction to be had for photos