6 use File::Basename qw(basename);
13 my $backup; # backup extension name
14 my $directory; # output directory
17 my %write_opts; # options supplied to write()
20 my @collection; # actions/options in order to allow us to set values as needed
22 # each entry consists of:
23 # - ref to action/option handler function
25 # - optional ref to value parser function
27 my %funcs = im_functions();
28 my %options = im_options();
29 my %all = ( %funcs, %options );
33 my ($option, $value) = @_;
34 if ($all{$option}[1] && ref $all{$option}[1]) {
35 $value = $all{$option}[1]->($option, $value);
37 push @collection, [ $option, $value ]
51 for my $option_name (keys %all) {
52 my $option = $all{$option_name};
53 my @names = ( $option_name );
54 my @other_names = split /\|/, $option->[2] if $option->[2];
55 push @names, @other_names;
58 $code = ref $option->[1] ? "=s" : "=".$option->[1];
60 push @getoptions, join("|", @names) . $code => $action_func;
61 # this would be evil $all{$_} = $option for @other_names;
62 push @getoptions, join("|", map "help-$_", @names) => $help_func;
65 GetOptions('help' => sub { $help_func->("synopsis") },
66 'verbose|v+' => \$verbose,
67 'backup|i=s' => \$backup,
68 'directory|d=s' => \$directory,
69 'type|t=s' => \$type, # output file type
70 'write-option|wo=s' => \%write_opts,
71 'output|o=s' => \$output,
72 'understand' => \$understand,
76 'help-color-spec' => sub { $help_func->("color specifications") },
77 'help-actions' => sub { $help_func->("actions") },
78 'help-options' => sub { $help_func->("processing options") },
79 'help-general' => sub { $help_func->("general options") },
85 unless ($understand) {
87 This tool is under-tested and will probably destroy your data.
89 If you understand and agree with this use the --understand option.
91 In fact, only the --info and --tags actions have been used at all.
96 exists $write_opts{file}
97 and die "Illegal write option 'file'\n";
98 exists $write_opts{type}
99 and die "Use the --type option to set the output format\n";
101 delete $write_opts{qw/file fd fh data callback/};
103 my @actions = grep $funcs{$_->[0]}, @collection;
107 print $funcs{$_}[1] for map $_->[0], @actions;
115 if (!@actions && !@ARGV) {
120 die "No files to process\n";
124 die "Nothing to do, supply at least one action, see $0 --help\n";
128 push @type, type => $type if $type;
130 for my $name (@ARGV) {
131 my $im = Imager->new;
132 if ($im->read(file=>$name)) {
133 my %state = ( filename => $name );
135 for my $action (@collection) {
136 $im = $all{$action->[0]}[0]->($im, $action->[1], \%state);
144 (undef, undef, $file) = File::Spec->split_path($outname);
145 $outname = File::Spec->catfile($directory, $file);
148 my $backfile = $name . $backup;
149 rename $name, $backfile
150 or die "Couldn't rename source '$name' to backup '$backfile': $!\n";
153 unless ($im->write(file=>$outname, @type)) {
154 die "Could not write result from '$name' to '$outname': ", $im->errstr,"\n";
159 print STDERR "Failed reading $name: ",$im->errstr,"\n";
164 my ($im, $state, $format) = @_;
168 f => [ 's', $state->{filename} ],
169 b => [ 's', basename($state->{filename}) ],
170 w => [ 'd', $im->getwidth ],
171 h => [ 'd', $im->getheight ],
172 c => [ 'd', $im->getchannels ],
173 t => [ 's', $im->type ],
174 n => [ 'c', ord("\n") ], # a bit of a hack
178 $format =~ s{%(-?(?:\d+(?:\.\d*)?|\.\d+)?)([fwhctbn%])}
180 my $which = $replace{$2};
181 push @values, @$which[1..$#$which];
185 return sprintf $format, @values;
189 my ($im, $ignored, $state) = @_;
191 my $format = $state->{info_format} || <<EOS;
193 Dimensions: %ww x %hh
198 print _replace_codes($im, $state, $format);
203 sub req_info_format {
204 my ($im, $value, $state) = @_;
206 $state->{info_format} = $value;
212 my ($im, $ignored, $state) = @_;
214 print $state->{filename},"\n";
215 my @tags = $im->tags;
216 for my $tag (sort { $a->[0] cmp $b->[0] } @tags) {
217 my $name = shift @$tag;
218 print " $name: @$tag\n";
225 my ($option, $value) = @_;
228 if ($option =~ /^(\d+)\s*x\s*(\d+)$/i) {
229 return { xpixels=>$1, ypixels=>$2 };
231 elsif ($option =~ /^(\d+(?:\.\d*)?|\.\d+)$/) {
232 return { scalefactor => $option };
234 elsif ($option =~ /^(\d+)\s*x\s*(\d+)\s*min$/i) {
235 return { xpixels=>$1, ypixels=>$2, type=>'min' };
237 elsif ($option =~ /^(\d+)\s*(?:w|wide)$/i) {
238 return { xpixels => $1 };
240 elsif ($option =~ /^(\d+)\s*(?:h|high)$/i) {
241 return { ypixels => $1 };
244 die "Invalid parameter to --scale, try $0 --help-scale\n";
249 my ($im, $args) = @_;
251 return $im->scale(%$args);
255 my ($option, $value) = @_;
257 if ($value =~ /^[-+]?(?:\d+(?:\.\d*)|\.\d+)$/) {
258 return { degrees => $value };
260 elsif ($value =~ /^([-+]?(?:\d+(?:\.\d*)|\.\d+))\s*(?:r|radians)$/i) {
261 return { radians => $1 };
264 die "Invalid parameter to --rotate, try $0 --help-rotate\n";
269 my ($im, $args, $state) = @_;
272 if ($state->{background}) {
273 push @moreargs, back => $state->{background};
275 return $im->rotate(%$args, @moreargs);
279 my ($im, $value, $state) = @_;
281 $state->{background} = $value;
287 my ($im, $value, $state) = @_;
289 $state->{foreground} = $value;
295 my ($im, $value, $state) = @_;
297 $state->{font} = Imager::Font->new(file=>$value)
298 or die "Could not create font from $value: ", Imager->errstr,"\n";
304 my ($option, $value) = @_;
306 unless ($value =~ /^\d+$/ && $value > 0) {
307 die "$option must be a positive integer\n";
314 my ($im, $value, $state) = @_;
316 $state->{font_size} = $value;
322 my ($im, $format, $state) = @_;
324 my $text = _replace_codes($im, $state, $format);
326 my $font = $state->{font}
327 or die "You must supply a --font option before the --caption command\n";
329 my $size = $state->{font_size} || 16;
331 my $box = $font->bounding_box(size=>$size);
332 $box->total_width <= $im->getwidth
333 or die "Caption text '$text' is wider (", $box->total_width,
334 ") than the image (",$im->getwidth,")\n";
336 die "not implemented yet";
347 info => [ \&req_info ],
348 tags => [ \&req_tags ],
349 scale => [ \&req_scale, \&val_scale ],
350 rotate => [ \&req_rotate, \&val_rotate ],
351 caption => [ \&req_caption ],
356 my ($option, $value) = @_;
358 if ($value =~ /^rgba\((\d+),(\d+),(\d+),(\d+)\)$/i) {
359 return Imager::Color->new($1,$2,$3,$4);
361 elsif ($value =~ /^rgb\((\d+),(\d+),(\d+)\)$/i) {
362 return Imager::Color->new($1,$2,$3);
364 elsif ($value =~ /^\#[\da-f]{3}([\da-f]{3})?$/) {
365 return Imager::Color->new(web=>$value);
367 elsif ($value =~ /^hsv\((\d+(?:\.\d*)),(\d+\.\d*|\.\d+),(\d+\.\d*|\.\d+)\)$/) {
368 return Imager::Color->new(hsv => [ $1, $2, $3 ]);
370 elsif ($value =~ /^hsva\((\d+(?:\.\d*)),(\d+\.\d*|\.\d+),(\d+\.\d*|\.\d+),(\d+)\)$/) {
371 return Imager::Color->new(hsv => [ $1, $2, $3 ], alpha=>$4);
374 my $color = Imager::Color->new(name=>$value);
375 return $color if $color;
377 die "Unrecognized color specification $value supplied to --$option\n";
384 background => [ \&req_bg, \&val_color, 'bg' ],
385 foreground => [ \&req_fg, \&val_color, 'fg' ],
386 'info-format' => [ \&req_info_format, 's'],
387 font => [ \&req_font, \&val_font ],
388 'font-size' => [ \&req_font_size, \&val_font_size, 'fs' ],
395 open SOURCE, "< $0" or die "Cannot read source for help text: $!\n";
399 if (/^=item --$topic\s/) {
401 # read any more =items then read text until we see =item or =back
403 last unless /^\s*$/ or /^=item /;
407 # and any other until another option or =back
409 last if /^=(item|cut|back)/;
415 elsif (/^=head(\d) $topic\s*$/i) {
419 last if /=head[1-$level]/;
429 die "No help topic $topic found\n";
433 sub help_color_spec {
440 imager - Imager command-line image manipulation tool
445 imager [--font-size <size>] [--fs <size>] [--background <color>]
446 [--bg <color>] [--foreground <color>] [--fg <color]
447 [--info-format <format>] [--rotate <angle>] [--scale <scale-spec>]
448 [--caption <text>] [--info] [--tags] [--font fontfile] files ...
449 imager --help-I<option>
450 imager --help-I<operation>
451 imager --help-options
452 imager --help-actions
453 imager --help-general
463 Displays the width, height, channels, type for each image, and any tags
464 Imager picks up. No options.
466 Note: Imager still converts many files into direct images when the source
467 is a paletted image, so the displayed image type may not match the
470 No output image file is produced.
474 Displays all the tags the Imager reader for that format sets for each
477 See L<Imager::Files> for file format specific tags and
478 L<Imager::ImageTypes> for common tags.
480 =item --scale <scalefactor>
482 =item --scale <width>x<height>
484 =item --scale <width>x<height>min
486 =item --scale <width>w
488 =item --scale <height>h
490 Scale either by the given scaling factor, given as a floating point number,
491 or to a given dimension.
493 The scaling is always proportional, if a dimension is given then the
494 scalefactor that results in the larger image that matches either the
495 specified width or height is chosen, unless the word "min" is present".
497 --scale 0.5 # half size image
498 --scale 100x100 # aim for 100 pixel x 100 pixel image
499 --scale 100x100min # image that fits in 100 x 100 pixel box
500 --scale 100w # 100 pixel wide image
501 --scale 100h # 100 pixel high image
503 =item --rotate <degrees>
505 =item --rotate <radians>r
507 Rotate the image by the given number of degrees or radians.
511 Displays the usage message if no extra parameter is found, otherwise displays
512 more detailed help for the given function, if any.
516 Expands the image to create a caption area and draws the given text in the
519 You must set a font with --font before this.
521 imager --font arial.ttf --caption "my silly picture"
523 The text has the same replacements done as the --info command.
525 imager --font arial.ttf --caption '%b - %w x %h'
527 If the caption text is too wide for the image an error is produced.
529 Any newlines that aren't at the beginning or end of the caption cause
530 multiple lines of text to be produced.
532 The --foreground and --background options can be used to set colors
533 for this. By default black text on a white background is produced.
537 =head1 GENERAL OPTIONS
543 Display the SYNOPSIS from this POD
549 Increase the verbosity level.
551 =item --backup <extension>
555 Input files are renamed to I<filename><extension> before the output
558 =item --directory <directory>
562 If this is supplied the output files are written to this directory
565 =item --type <fileformat>
567 Specifies an output file format
569 =item --write-option name=value
571 =item --wo name=value
573 Sets the value of an option supplied to the Imager write() function.
574 The options available depend on the file format, see
575 L<Imager::Files/TYPE SPECIFIC INFORMATION> for file format specific
578 You can also supply the L<Imager::ImageTypes/Common Tags>.
580 If you're writing to a gif file you can also supply the options
581 documented as tags under L<Imager::ImageTypes/Quantization options>.
585 =head1 PROCESSING OPTIONS
587 These supply extra parameters to the actions
591 =item --background <color-spec>
593 =item --bg <color-spec>
595 Sets the background color for the --rotate and --caption actions, and
596 possibly other actions in the future.
598 See $0 --help-color-spec for possible color specifications.
603 =item --foreground <color-spec>
605 =item --fg <color-spec>
607 Sets the foreground color for the --caption action, and possibly other
608 actions in the future.
610 See $0 --help-color-spec for possible color specifications.
615 =item --font-size size
619 Set the font size used by the --caption command, in pixels.
621 --fs 16 # 16 pixels from baseline to top
622 --font-size 40 # a bit bigger
624 =item --info-format format
626 Sets the format for the output of the --info command.
628 The format can contain printf style replacement codes, each value is %
629 followed by a sprintf() field width/precision, followed by the value
632 The following values can be output:
633 w - image width in pixels
634 h - image height in pixels
635 f - full image filename
636 b - base image filename
637 c - number of channels
638 t - image type (direct vs paletted)
639 n - inserts a newline
640 % - inserts a '%' symbol
642 The default format is:
644 Image: %f%nDimensions: %ww x %hh%nChannels: %c%nType: %t%n
646 You can use field widths to produce a more table like appearance:
648 im --info-format '%4w %4h %4c %-8t %b%n' --info *.jpg
650 =item --font filename
652 Gives the name of a font file. Required by actions that render text.
659 =head1 COLOR SPECIFICATIONS
661 Possible color specifications:
662 color-name - the name of a built-in color
663 rgb(red,green,blue) - as an RGB triplet
664 #RRGGBB - as a HTML RGB hex triple
665 #RGB - as a HTML CSS RGB hex triple
666 rgba(red,green,blue,alpha) - as an RGBA quad
667 hsv(hue,sat,value) - as an HSV triplet
668 hsva(hue,sat,value,alpha) as an HSVA quad
677 If you use either of the HTML color specifications, or a specification
678 using parentheses from a Unix shell you will need to quote it, for
682 --bg 'rgb(255,0,255)'
686 Tony Cook <tony@develop-help.com>