package App::PNGCrush; use warnings; use strict; our $VERSION = '0.001'; use Carp; use Proc::Reliable; use Devel::TakeHashArgs; use base 'Class::Data::Accessor'; my %Valid_Options = qw( already_size -already bit_depth -bit_depth background -bkgd brute_force -brute color_type -c color_counting -cc output_dir -d double_image_gamma -dou output_extension -e filter -f fix_fatal -fix output_force -force gamma -g itxt -itxt level -l method -m maximum_idat -max no_output -n no_color_counting -no_cc plte_length -plte_len remove -rem replace_gamma -replace_gamma resolution -res save_unknown -save srgb -srgb text -text transparency -trns window_size -w strategy -z insert_ztxt -zitxt ztxt -ztxt verbose -v ); my %No_Arg_Options = map { $_ => 1 } qw( brute_force color_counting double_image_gamma fix_fatal output_force no_output no_color_counting save_unknown verbose ); __PACKAGE__->mk_classaccessors ( qw( proc error results ), keys %Valid_Options ); sub new { my $self = bless {}, shift; get_args_as_hash( \@_, \my %args, { maxtime => 300 } ) or croak $@; my $proc = Proc::Reliable->new; $proc->$_( $args{$_} ) for keys %args; $self->proc( $proc ); return $self; } sub run { my $self = shift; my $in = shift; get_args_as_hash( \@_, \ my %args, { in => $in }, ) or croak $@; $self->$_(undef) for qw(error results); my @options = exists $args{opts} ? @{ $args{opts} } : $self->_make_options; my $proc = $self->proc; my %out; @out{ qw(stdout stderr status msg) } = $proc->run( [ 'pngcrush', @options, $in ] ); return $self->_set_error("Proc::Reliable error: $out{error}") if defined $out{error}; return $self->_set_error("File $in does not seem to exist") if $out{stdout} =~ /Could not find file: \Q$in/; @out{ qw(idat size) } = $out{stdout} =~ /\(([\d.]+)% IDAT reduction\).+?\(([\d.]+)% filesize reduction\)/s; $out{idat} = 0 if not defined $out{idat} and $out{stdout} =~ /\Q(no IDAT change)/; $out{size} = 0 if not defined $out{size} and $out{stdout} =~ /\Q(no filesize change)/; @{ $out{cpu} }{ qw(total decoding encoding other) } = $out{stdout} =~ /CPU \s time \s used \s = \s ([\d.]+) \s seconds \s \(decoding \s ([\d.]+), \s+ encoding \s ([\d.]+), \s other \s ([\d.]+) \s seconds\) /x; ( $out{total_idat_length} ) = $out{stdout} =~ /Total length of data found in IDAT chunks\s+=\s+([\d.]+)/; return $self->results( \%out ); } sub set_options { my $self = shift; get_args_as_hash( \@_, \my %args, {}, [], [ %Valid_Options ] ) or croak $@; $self->reset_options; keys %args; my %shell_args = reverse %Valid_Options; while ( my ( $key, $value ) = each %args ) { $key = $shell_args{$key} unless exists $Valid_Options{$key}; $self->$key( $value ); } return 1; } sub reset_options { my $self = shift; $self->$_(undef) for keys %Valid_Options; return 1; } sub _make_options { my $self = shift; my @options; for my $opt ( keys %Valid_Options ) { my $value = $self->$opt; next unless defined $value; if ( ref $value eq 'ARRAY' ) { if ( $opt eq 'verbose' ) { push @options, ('-v') x @$value; next; } push @options, map { $Valid_Options{$opt} => $_ } @$value; } else { push @options, $Valid_Options{$opt}, exists $No_Arg_Options{$opt} ? () : $value; } } return @options; } sub _set_error { my ( $self, $error ) = @_; $self->error($error); return; } 1; __END__ =head1 NAME App::PNGCrush - Perl wrapper around ``pngcrush'' program =head1 SYNOPSIS use strict; use warnings; use App::PNGCrush; my $crush = App::PNGCrush->new; # let's use best compression and remove a few chunks $crush->set_options( qw( -d OUT_DIR -brute 1 ), remove => [ qw( gAMA cHRM sRGB iCCP ) ], ); my $out_ref = $crush->run('picture.png') or die "Error: " . $crush->error; print "Size reduction: $out_ref->{size}%\n" . "IDAT reduction: $out->{idat}%\n"; =head1 DESCRIPTION The module is a simple wrapper around ``pngcrush'' program. The program is free open source and you can obtain it from L on Debian systems you can find it in the repos: C I needed this module to utilize only little subsection of C's functionality, if you would like some features added, I am more than open for suggestions. =head1 CONSTRUCTOR =head2 C my $crush = App::PNGCrush->new; my $crush = App::PNGCrush->new( max_time => 300 ); Creates a new App::PNGCrush object. Arguments are optional and passed as key/value pairs with keys being L methods and values being the values for those methods, here you can set some options controlling how C will be run. Generally, you'd worry only about C (which B to C<300> seconds in C) and set it to a higher value if you are about to process large images with brute force. =head1 METHODS =head2 C my $results_ref = $crush->run('pic.png') or die $crush->error; my $results_ref = $crush->run('pic.png', opts => [ qw(custom stuff) ] ); Instructs the object to run C. The first argument is mandatory and must be a filename which will be passed to C as input file. Takes one optional argument (so far), which is passed as key/value pair; the key being C and value being an arrayref of custom options you want to give to C (those will bypass shell proccessing). Generally the custom options option is in here "just in case" and B method (see below).> Returns either C or an empty list (depending on the context) if an error occured and the reason for the error will be available via C method. On success returns a hashref with the following keys/values: $VAR1 = { 'total_idat_length' => '1880', 'cpu' => { 'decoding' => '0.010', 'other' => '0.050', 'total' => '0.210', 'encoding' => '0.150' }, 'stderr' => '', 'status' => '0', 'idat' => '0.80', 'stdout' => '| pngcrush 1.6.4 .. blah blah full STDOUT here', 'size' => '1.56' }; =head3 C { 'size' => '1.56', } The C key will contain percentage of filesize reduction. =head3 C { 'idat' => '0.80', } The C key will contain the percentage of IDAT size reduction. =head3 C { 'total_idat_length' => '1880', } The C key will contain total length of data found in IDAT chunks. =head3 C 'cpu' => { 'decoding' => '0.010', 'other' => '0.050', 'total' => '0.210', 'encoding' => '0.150' }, The C key will contain a hashref with with four keys: C, C, C and C with values being number of seconds it took to process. =head3 C { 'stderr' => '', } The C key will contain any collected data from STDERR while C was running. =head3 C { 'stdout' => '| pngcrush 1.6.4 .. blah blah full STDOUT here', } The C key will contain any collected data from STDOUT while C was running. =head3 C { 'status' => '0' } The C key will contain the exit code of C. =head2 C my $ret_ref = $crush->run('some.png') or die $crush->error; If C failed it will return either C or an empty list depending on the context and the reason for failure will be available via C method. Takes no arguments, returns a human parsable error message explaining why C failed. =head2 C my $results_ref = $crush->results; Must be called after a successful call to C. Takes no arguments, returns the exact same hashref last call to C returned. =head2 C $crush->set_options( qw( -d OUT_DIR -brute 1 ), remove => [ qw( gAMA cHRM sRGB iCCP ) ], ); Always returns a true value. Sets the options with which to run C. As argument takes a list of key/value pairs of either standard C options or more verbose names this module offers (see below). If you want to B certain option pass values as B, thus if on a command line you'd write C<< pngcrush -rem gAMA -rem cHRM -rem sRGB ... >> you'd use C<< ->set_options( '-rem' => [ qw( gAMA cHRM sRGB iCPP ) ] ) >>. B if C option does not take an argument you B give it a value of C<1> when setting it via C method. For C<-v> option you can set it to value C<2> to repeat twice (aka uber verbose). B to individual option setting methods. B call to C will call C method (see below) before setting any of your options, thus whatever you don't specify will not be passed to C =head2 C $crush->reset_options; Always returns a true value, takes no arguments. Instructs the object to reset all C options. =head2 individual option methods Module provides methods to set (almost) all C options individually You'd probably would want to use C method (see above) in most cases. See C method which describes how to repeat options and how to set options which take no arguments in C. The following is the list of methods (on the left) and corresponding C options they set (on the right); some options were deemed useless to the module and were not included (this is as of C version 1.6.4): already_size -already bit_depth -bit_depth background -bkgd brute_force -brute color_type -c color_counting -cc output_dir -d double_image_gamma -dou output_extension -e filter -f fix_fatal -fix output_force -force gamma -g itxt -itxt level -l method -m maximum_idat -max no_output -n no_color_counting -no_cc plte_length -plte_len remove -rem replace_gamma -replace_gamma resolution -res save_unknown -save srgb -srgb text -text transparency -trns window_size -w strategy -z insert_ztxt -zitxt ztxt -ztxt verbose -v See C manpage (C or C) for descriptions of these options. Out of those listed above the following C options do not take arguments, thus to set these you'd need to pass C<1> as an argument to the option setting method (except for C which can take a value of C<2> to indicate double verboseness (equivalent to passing C<-v -v> to C) brute_force color_counting double_image_gamma fix_fatal output_force no_output no_color_counting save_unknown verbose =head2 C my $proc_reliable_obj = $crush->proc; $crush->proc( Proc::Reliable->new ); Returns a currently used L object used under the hood, thus you could dynamically set arguments as C<< $crush->proc->max_time(300) >>. When called with an argument it must be a C object which will replace the currently used one (and you just SOO don't wanna do this, do you?) =head1 AUTHOR Zoffix Znet, C<< >> (L, L) =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc App::PNGCrush You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 COPYRIGHT & LICENSE Copyright 2008 Zoffix Znet, all rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut