The Perl Advent Calendar needs more articles for 2022. Submit your idea today!
#!perl -w
use strict;
use Imager;
use Imager::Fountain;
use Getopt::Long;

Getopt::Long::Configure("bundling");

# see usage() for a description of the parameters we accept
my $border_width = 10;
my $border_height = 10;
my $border_thickness; # sets width and height and overrides them
my $fountain;
my $color = 'red';
GetOptions('width|w=i' => \$border_width,
	   'height|h=i' => \$border_height,
	   'thickness|t=i' => \$border_thickness,
	   'fountain|f=s' => \$fountain,
	   'color|c=s' => \$color)
  or usage();

# make sure we got sane values
if (defined $border_thickness) {
  if ($border_thickness <= 0) {
    die "--thickness must be positive\n";
  }
  $border_width = $border_height = $border_thickness;
}
elsif ($border_width < 0) {
  die "--width must non-negative\n";
}
elsif ($border_height < 0) {
  die "--height must be non-negative\n";
}
elsif ($border_width == 0 && $border_height == 0) {
  # not much point if both are zero
  die "One of --width or --height must be positive\n";
}

my $src_name = shift;
my $out_name = shift
  or usage();

# treat extras as an error
@ARGV
  and usage(); 

# load the source, let Imager work out the name
my $src_image = Imager->new;
$src_image->read(file=>$src_name)
  or die "Cannot read source image $src_name: ", $src_image->errstr, "\n";

my $out_image;
if ($fountain) {
  # add a fountain fill border
  my ($out_color, $in_color) = split /,/, $fountain, 2;
  $in_color
    or die "--fountain '$fountain' invalid\n";
  $out_image = fountain_border($src_image, $out_color, $in_color, 
			       $border_width, $border_height);
}
else {
  $out_image = solid_border($src_image, $color, 
			    $border_width, $border_height);
}

# write it out, and let Imager work out the output format from the
# filename
$out_image->write(file=>$out_name)
  or die "Cannot save $out_name: ", $out_image->errstr, "\n";

sub fountain_border {
  my ($src_image, $out_color_name, $in_color_name, 
      $border_width, $border_height) = @_;

  my $out_color = Imager::Color->new($out_color_name)
    or die "Cannot translate color $out_color_name: ", Imager->errstr, "\n";
  my $in_color = Imager::Color->new($in_color_name)
    or die "Cannot translate color $in_color_name: ", Imager->errstr, "\n";
  my $fountain = Imager::Fountain->new;
  $fountain->add
	(
	 c0 => $out_color,
	 c1 => $in_color,
	);

  my $out = Imager->new(xsize => $src_image->getwidth() + 2 * $border_width,
                        ysize => $src_image->getheight() + 2 * $border_height,
                        bits => $src_image->bits,
                        channels => $src_image->getchannels);

  my $width = $out->getwidth;
  my $height = $out->getheight;
  # these mark the corners of the inside rectangle, done here
  # to reduce the redundancy below
  my $in_left = $border_width - 1;
  my $in_right = $width - $border_width;
  my $in_top = $border_height - 1;
  my $in_bottom = $height - $border_height;

  # four linear fountain fills, one for each side
  # Note: we overlap the sides with the top and bottom to avoid
  # having them both anti-alias against the black background where x==y
  # (and the other corners)
  # top
  $out->polygon(x => [ 0, $width-1, $width-1, 0  ],
		y => [ 0, 0,        $in_top,  $in_top ],
		fill => { fountain => 'linear',
			  segments => $fountain,
			  xa => 0, ya => 0,
			  xb => 0, yb => $border_height });
  # bottom
  $out->polygon(x => [ 0,         $width-1,  $width-1,  0 ],
		y => [ $height-1, $height-1, $in_bottom, $in_bottom ],
		fill => { fountain => 'linear',
			  segments => $fountain,
			  xa => 0, ya => $height-1,
			  xb => 0, yb => $height-$border_height });
  # left
  $out->polygon(x => [ 0, 0,         $in_left,   $in_left ],
		y => [ 0, $height-1, $in_bottom, $in_top ],
		fill => { fountain => 'linear',
			  segments => $fountain,
			  xa => 0, ya => 0, 
			  xb => $border_width, yb => 0 });
  # right
  $out->polygon(x => [ $width-1, $width-1,  $in_right,  $in_right ],
		y => [ 0,        $height-1, $in_bottom, $in_top ],
		fill => { fountain => 'linear',
			  segments => $fountain,
			  xa => $width-1, ya => 0,
			  xb => $width-$border_width, yb => 0 });

  # and put the source in
  $out->paste(left => $border_width,
              top => $border_height,
              img => $src_image);

  return $out;
}

sub solid_border {
  my ($source, $color, $border_width, $border_height) = @_;

  my $out = Imager->new(xsize => $source->getwidth() + 2 * $border_width,
                        ysize => $source->getheight() + 2 * $border_height,
                        bits => $source->bits,
                        channels => $source->getchannels);

  # we can do it the lazy way for a solid border - just fill the whole image
  $out->box(filled => 1, color=>$color)
    or die "Invalid color '$color':", $out->errstr, "\n";

  $out->paste(left => $border_width,
              top => $border_height,
              img => $source);

  return $out;
}

sub usage {
  print <<EOS;
Usage: $0 [options] sourceimage outimage
Options are:
  --width <pixels> | -w <pixels>
    Set width of border (default 10)
      eg. --width 25
  --height <pixels> | -h <pixels>
    Set height of border (default 10)
      eg. --height 30
  --thickness <pixels> | -t <pixels>
    Sets width and height of border, overrides -w and -h
      eg. --thickness 20
  --fountain <outcolor>,<incolor> | -f outcolor,incolor
    Creates a border that's a linear fountain fill with outcolor at the
    outside and incolor at the inside.
      eg. --fountain red,black
  --color <color>
    Sets the color of the default solid border.  Ignored if --fountain
    is supplied.  (default red)
      eg. --color blue
EOS
  exit 1;
}

=head1 NAME

border.pl - sample to add borders to an image

=head1 SYNOPSIS

  perl border.pl [options] input output

=head1 DESCRIPTION

Simple sample of adding borders to an image.

=head1 AUTHOR

Tony Cook <tony@develop-help.com>

=head1 REVISION

$Revision$

=cut