#!/usr/bin/perl -w use strict; use Imager; use Getopt::Long; use File::Spec; use File::Basename qw(basename); use vars qw($VERSION); $VERSION="0.01_00"; my $help; my $verbose; my $backup; # backup extension name my $directory; # output directory my $output; my $type; my %write_opts; # options supplied to write() my $understand; my @collection; # actions/options in order to allow us to set values as needed # each entry consists of: # - ref to action/option handler function # - help text # - optional ref to value parser function my %funcs = im_functions(); my %options = im_options(); my %all = ( %funcs, %options ); my $action_func = sub { my ($option, $value) = @_; if ($all{$option}[1] && ref $all{$option}[1]) { $value = $all{$option}[1]->($option, $value); } push @collection, [ $option, $value ] }; my $did_help; my $help_func = sub { my ($option) = @_; $option =~ s/help-//; help_on($option); ++$did_help; }; my @getoptions; for my $option_name (keys %all) { my $option = $all{$option_name}; my @names = ( $option_name ); my @other_names = split /\|/, $option->[2] if $option->[2]; push @names, @other_names; my $code = ''; if ($option->[1]) { $code = ref $option->[1] ? "=s" : "=".$option->[1]; } push @getoptions, join("|", @names) . $code => $action_func; # this would be evil $all{$_} = $option for @other_names; push @getoptions, join("|", map "help-$_", @names) => $help_func; } GetOptions('help' => sub { $help_func->("synopsis") }, 'verbose|v+' => \$verbose, 'backup|i=s' => \$backup, 'directory|d=s' => \$directory, 'type|t=s' => \$type, # output file type 'write-option|wo=s' => \%write_opts, 'output|o=s' => \$output, 'understand' => \$understand, @getoptions, 'help-color-spec' => sub { $help_func->("color specifications") }, 'help-actions' => sub { $help_func->("actions") }, 'help-options' => sub { $help_func->("processing options") }, 'help-general' => sub { $help_func->("general options") }, ) or usage(); $did_help and exit; unless ($understand) { die <[0]}, @collection; if ($help) { if (@actions) { print $funcs{$_}[1] for map $_->[0], @actions; exit; } else { usage(); } } if (!@actions && !@ARGV) { usage(); } unless (@ARGV) { die "No files to process\n"; } unless (@actions) { die "Nothing to do, supply at least one action, see $0 --help\n"; } my @type; push @type, type => $type if $type; for my $name (@ARGV) { my $im = Imager->new; if ($im->read(file=>$name)) { my %state = ( filename => $name ); for my $action (@collection) { $im = $all{$action->[0]}[0]->($im, $action->[1], \%state); last unless $im; } if ($im) { my $outname = $name; if ($directory) { my $file; (undef, undef, $file) = File::Spec->split_path($outname); $outname = File::Spec->catfile($directory, $file); } if ($backup) { my $backfile = $name . $backup; rename $name, $backfile or die "Couldn't rename source '$name' to backup '$backfile': $!\n"; } unless ($im->write(file=>$outname, @type)) { die "Could not write result from '$name' to '$outname': ", $im->errstr,"\n"; } } } else { print STDERR "Failed reading $name: ",$im->errstr,"\n"; } } sub _replace_codes { my ($im, $state, $format) = @_; my %replace = ( f => [ 's', $state->{filename} ], b => [ 's', basename($state->{filename}) ], w => [ 'd', $im->getwidth ], h => [ 'd', $im->getheight ], c => [ 'd', $im->getchannels ], t => [ 's', $im->type ], n => [ 'c', ord("\n") ], # a bit of a hack '%' => [ '%' ], ); my @values; $format =~ s{%(-?(?:\d+(?:\.\d*)?|\.\d+)?)([fwhctbn%])} { my $which = $replace{$2}; push @values, @$which[1..$#$which]; "%$1$which->[0]" }eg; return sprintf $format, @values; } sub req_info { my ($im, $ignored, $state) = @_; my $format = $state->{info_format} || <{info_format} = $value; $im; } sub req_tags { my ($im, $ignored, $state) = @_; print $state->{filename},"\n"; my @tags = $im->tags; for my $tag (sort { $a->[0] cmp $b->[0] } @tags) { my $name = shift @$tag; print " $name: @$tag\n"; } return; } sub req_palette { my ($im, $ignored, $state) = @_; print $state->{filename},"\n"; if ($im->type eq 'direct') { print " No palette - this is a direct color image\n"; } else { my @colors = $im->getcolors; for my $index (0..$#colors) { printf "%3d: (%3d, %3d, %3d)\n", $index, ($colors[$index]->rgba)[0..2]; } } return; } sub val_scale { my ($option, $value) = @_; my %options; if ($option =~ /^(\d+)\s*x\s*(\d+)$/i) { return { xpixels=>$1, ypixels=>$2 }; } elsif ($option =~ /^(\d+(?:\.\d*)?|\.\d+)$/) { return { scalefactor => $option }; } elsif ($option =~ /^(\d+)\s*x\s*(\d+)\s*min$/i) { return { xpixels=>$1, ypixels=>$2, type=>'min' }; } elsif ($option =~ /^(\d+)\s*(?:w|wide)$/i) { return { xpixels => $1 }; } elsif ($option =~ /^(\d+)\s*(?:h|high)$/i) { return { ypixels => $1 }; } else { die "Invalid parameter to --scale, try $0 --help-scale\n"; } } sub req_scale { my ($im, $args) = @_; return $im->scale(%$args); } sub val_rotate { my ($option, $value) = @_; if ($value =~ /^[-+]?(?:\d+(?:\.\d*)|\.\d+)$/) { return { degrees => $value }; } elsif ($value =~ /^([-+]?(?:\d+(?:\.\d*)|\.\d+))\s*(?:r|radians)$/i) { return { radians => $1 }; } else { die "Invalid parameter to --rotate, try $0 --help-rotate\n"; } } sub req_rotate { my ($im, $args, $state) = @_; my @moreargs; if ($state->{background}) { push @moreargs, back => $state->{background}; } return $im->rotate(%$args, @moreargs); } sub req_bg { my ($im, $value, $state) = @_; $state->{background} = $value; $im; } sub req_fg { my ($im, $value, $state) = @_; $state->{foreground} = $value; $im; } sub req_font { my ($im, $value, $state) = @_; $state->{font} = Imager::Font->new(file=>$value) or die "Could not create font from $value: ", Imager->errstr,"\n"; $im; } sub val_font_size { my ($option, $value) = @_; unless ($value =~ /^\d+$/ && $value > 0) { die "$option must be a positive integer\n"; } $value; } sub req_font_size { my ($im, $value, $state) = @_; $state->{font_size} = $value; $im; } sub req_caption { my ($im, $format, $state) = @_; my $text = _replace_codes($im, $state, $format); my $font = $state->{font} or die "You must supply a --font option before the --caption command\n"; my $size = $state->{font_size} || 16; my $box = $font->bounding_box(size=>$size); $box->total_width <= $im->getwidth or die "Caption text '$text' is wider (", $box->total_width, ") than the image (",$im->getwidth,")\n"; die "not implemented yet"; } sub usage { help_on("SYNOPSIS"); exit 1; } sub im_functions { return ( info => [ \&req_info ], tags => [ \&req_tags ], palette => [ \&req_palette ], scale => [ \&req_scale, \&val_scale ], rotate => [ \&req_rotate, \&val_rotate ], # caption => [ \&req_caption ], # not done yet ); } sub val_color { my ($option, $value) = @_; if ($value =~ /^rgba\((\d+),(\d+),(\d+),(\d+)\)$/i) { return Imager::Color->new($1,$2,$3,$4); } elsif ($value =~ /^rgb\((\d+),(\d+),(\d+)\)$/i) { return Imager::Color->new($1,$2,$3); } elsif ($value =~ /^\#[\da-f]{3}([\da-f]{3})?$/) { return Imager::Color->new(web=>$value); } elsif ($value =~ /^hsv\((\d+(?:\.\d*)),(\d+\.\d*|\.\d+),(\d+\.\d*|\.\d+)\)$/) { return Imager::Color->new(hsv => [ $1, $2, $3 ]); } elsif ($value =~ /^hsva\((\d+(?:\.\d*)),(\d+\.\d*|\.\d+),(\d+\.\d*|\.\d+),(\d+)\)$/) { return Imager::Color->new(hsv => [ $1, $2, $3 ], alpha=>$4); } else { my $color = Imager::Color->new(name=>$value); return $color if $color; die "Unrecognized color specification $value supplied to --$option\n"; } } sub im_options { return ( background => [ \&req_bg, \&val_color, 'bg' ], foreground => [ \&req_fg, \&val_color, 'fg' ], 'info-format' => [ \&req_info_format, 's'], font => [ \&req_font, \&val_font ], 'font-size' => [ \&req_font_size, \&val_font_size, 'fs' ], ); } sub help_on { my ($topic) = @_; open SOURCE, "< $0" or die "Cannot read source for help text: $!\n"; my @lines; while () { # don't chomp it if (/^=item --$topic\s/) { push @lines, $_; # read any more =items then read text until we see =item or =back while () { last unless /^\s*$/ or /^=item /; push @lines, $_; } push @lines, $_; # and any other until another option or =back while () { last if /^=(item|cut|back)/; push @lines, $_; } print @lines; return; } elsif (/^=head(\d) $topic\s*$/i) { my $level = $1; push @lines, $_; while () { last if /=head[1-$level]/; push @lines, $_; } print @lines; return; } } close SOURCE; die "No help topic $topic found\n"; } sub help_color_spec { print <] [--fs ] [--background ] [--bg ] [--foreground ] [--fg ] [--rotate ] [--scale ] [--caption ] [--info] [--tags] [--font fontfile] files ... imager --help-I