#!/usr/bin/perl -w
# Expand pixels in a PPM to make mosaic like images.
#
# To do: separate color choices for each border.
#        user-defined pixel manipulation function.
#
# Benjamin Elijah Griffin	28 Feb 2003
use strict;
use integer;
use Image::PPMlib;
use vars qw( $id $arg $err $i $j $k $r $g $b $br $bg $bb $tri
	     $inforef @line @out $pc $pix $mpix
             $file $bmode $bpixels $bpT $bpB $bpL $bpR $pixels 
	     $VERSION
	   );

$id = $0;
$id =~ s:.*/::;
$VERSION = "0.5";

$bmode   = 'none';
$bpT = $bpB = $bpL = $bpR = 1;
$pixels  = 10;

while (defined($ARGV[0]) and $ARGV[0] =~ /^-(.*)/) {
  $arg = $1; # stripped of first hyphen
  shift;
  if ($arg eq '-help' or $arg eq '-version' or $arg eq 'v') {
    &usage(0);

  } elsif ($arg eq 'b' or $arg eq '-border') {
    $bmode = shift;
    if ($bmode eq 'none' or $bmode eq 'dark' or $bmode eq 'light') {
      1; # valid mode that requires no processing now
    } elsif ($bmode eq 'darker' or $bmode eq 'lighter') {
      $bmode =~ s/er$//;

    } elsif ($bmode eq 'black') {
      ($br, $bg, $bb) = (   0,   0,   0 );

    } elsif ($bmode eq 'white') {
      ($br, $bg, $bb) = ( 255, 255, 255 );

    } elsif ($bmode eq 'red') {
      ($br, $bg, $bb) = ( 255,   0,   0 );

    } elsif ($bmode eq 'green') {
      ($br, $bg, $bb) = (   0, 255,   0 );

    } elsif ($bmode eq 'blue') {
      ($br, $bg, $bb) = (   0,   0, 255 );

    } elsif ($bmode =~ /^fixed:(\d+:\d+:\d+)$/) {
      $tri = $1;
      $bmode = 'fixed';
      ($br, $bg, $bb) = split(/:/, $tri, 3)

    } elsif ($bmode =~ /^fixed:([0-9a-fA-F]{6})$/) {
      $tri = $1;
      $bmode = 'fixed';
      ($br, $bg, $bb) = hextriplettoraw($tri);
      $br = ord($br);
      $bg = ord($bg);
      $bb = ord($bb);

    } else {
      $err = 1;
      warn "$id: Invalid mode for -b (--border)\n";
    }

  } elsif ($arg eq 'e' or $arg eq '-borderpixels') {
    $bpixels = shift;
    if ($bpixels =~ /^(\d+),(\d+),(\d+),(\d+)$/) {
      $bpT = $1;
      $bpB = $2;
      $bpL = $3;
      $bpR = $4;
      $bpixels = '';
    }
    elsif ($bpixels =~ /^[1-9]\d*$/) {
      $bpT = $bpB = int($bpixels / 2);
      if ($bpixels % 2) {
	$bpT ++;
      }
      $bpL = $bpT;
      $bpR = $bpR;
    }
    else {
      $err = 1;
      warn "$id: Invalid pixel value for -e (--borderpixels)\n";
    }

  } elsif ($arg eq 'p' or $arg eq '-pixels') {
    $pixels = shift;
    if ($pixels !~ /^[1-9]\d*$/) {
      $err = 1;
      warn "$id: Invalid pixel value for -p (--pixels)\n";
    }

  } elsif ($arg eq '-') {
    last;

  } else {
    $err = 1;
    warn "$id: unrecognized argument: -$arg\n";
  }
}

if ($err) {
  die "Use --help for usage\n";
}

$file = shift;

if(!defined($file)) { $file = '-'; }

if(!open(PPM, "< $file")) {
  die "$id: Cannot open $file: $!\n";
}

$inforef = readppmheader(\*PPM);

if ($$inforef{error}) {
  die "$id: $file: $$inforef{error}\n";
}
if ($$inforef{bgp} ne 'p') {
  die "$id: $file is not a ppm file\n";
}

# No border overrides any specified border size
if ($bmode eq 'none') {
  $bpT = $bpB = $bpL = $bpR = 0;
}

printf("P6\n%d %d\n%d\n", $$inforef{width}  * ($bpL + $bpR + $pixels),
                          $$inforef{height} * ($bpT + $bpB + $pixels),
			  255);


# If we can, calculate the border stuff now.
if($bmode ne 'none') {
  $pc = $bpL + $bpR + $pixels;	# width of border + cell

  if(($bmode ne 'light') and ($bmode ne 'dark')) {
    $pix = $Image::PPMlib::decraw{$br} .
	   $Image::PPMlib::decraw{$bg} .
	   $Image::PPMlib::decraw{$bb};
  }
}


# loop i: each line of height
for ($i = 0; $i < $$inforef{height}; $i ++) {
  @line = readpixels_dec(\*PPM, $$inforef{type}, $$inforef{width});

  for($k = 0; $k < $bpT + $bpB + $pixels; $k ++) {
    $out[$k] = '';
  }

  # loop j: each pixel in the line
  for($j = 0; $j < @line; $j ++) {

    # figure out per-pixel borders
    if ($bmode eq 'light') {
      $br = $line[$j][0] * 11 / 10; # mult by 1.1 with 'use integer'
      $bg = $line[$j][1] * 11 / 10; # mult by 1.1 with 'use integer'
      $bb = $line[$j][2] * 11 / 10; # mult by 1.1 with 'use integer'
      $br = ($br>255)? 255 : $br;
      $bg = ($bg>255)? 255 : $bg;
      $bb = ($bb>255)? 255 : $bb;

      $pix = $Image::PPMlib::decraw{$br} .
	     $Image::PPMlib::decraw{$bg} .
	     $Image::PPMlib::decraw{$bb};
    } elsif ($bmode eq 'dark') {
      $br = $line[$j][0] *  9 / 10; # mult by 0.9 with 'use integer'
      $bg = $line[$j][1] *  9 / 10; # mult by 0.9 with 'use integer'
      $bb = $line[$j][2] *  9 / 10; # mult by 0.9 with 'use integer'

      $pix = $Image::PPMlib::decraw{$br} .
	     $Image::PPMlib::decraw{$bg} .
	     $Image::PPMlib::decraw{$bb};
    }

    # if any border, apply top and left now
    if($bmode ne 'none' and ($bpT or $bpL)) {

      for ($k = 0; $k < $bpT; $k ++) {	# top border, top $bpT rows
        $out[$k] .= $pix x $pc;
      }

      for ($k = 0; $k < $pixels; $k ++) {	# left border, after $bpT rpws
        $out[$k + $bpT] .= $pix x $bpL;
      }
    }

    # main pixel body
    $mpix = $Image::PPMlib::decraw{$line[$j][0]} .
	    $Image::PPMlib::decraw{$line[$j][1]} .
	    $Image::PPMlib::decraw{$line[$j][2]};
    for ($k = 0; $k < $pixels; $k ++) {	# inside main pixel, after $bpT rows
      $out[$k + $bpT] .= $mpix x $pixels;
    }


    # if any border, apply bottom and right now
    if($bmode ne 'none' and ($bpB or $bpR)) {
      
      for ($k = 0; $k < $bpB; $k ++) {	# bottom border, after $bpT + $pixels
        $out[$k + $pixels + $bpT] .= $pix x $pc;
      }

      for ($k = 0; $k < $pixels; $k ++) { # right border, after $bpT rows
        $out[$k + $bpT] .= $pix x $bpR;
      }
    }

  } # end j: per pixel loop

  for($k = 0; $k < $bpT + $bpB + $pixels; $k ++) {
    # if no border, those strings will still be '' and are safe to print
    print $out[$k];
  }


  if(@line != $$inforef{width}) {
    warn "$id: ran out of pixels prematurely\n";
    last;
  }

} # for line of height


sub usage($) {
  my $rc = shift;

  print STDERR <<"ProgramUsage";
$id version $VERSION usage:

  ppmmosaic reads PPM(5) files and makes larger pixelated versions of them,
  with several border options.

Arguments:

  -b MODE    --border MODE	     MODE can be black, white, red, green,
  				     blue, fixed:RGB, lighter, darker, or
				     none. RGB can 6 hex digits or a colon-
				     seperated decimal triple. 'lighter' and
				     'darker' are 10% changed from inner pixel
  -e SIZE    --borderpixels SIZE     make the border be SIZE pixels (total, if
  				     odd top and left borders will be larger)
  -e T,B,L,R --borderpixels T,B,L,R  set sizes for Top, Bottom, Left, and
                                     Right borders
  -p SIZE    --pixels SIZE	     make the tile be SIZE pixels (plus border)
  --				     end arguments

bordermode defaults to none, borderpixels defaults to 1, pixels defaults to 10
Examples:

  ppmmosaic -e 5 -p 20 -b fixed:255:150:200 foo.ppm > new-foo.ppm
	If foo.ppm was 10x10, the new image will be 250x250, with pink
	borders 3 pixels on top and left, 2 pixels on bottom and right
	around each of the original pixels, which will now be 20x20 in
	size.

  ppmmosaic --borderpixels 2 --border fixed:808080 --pixels 6 bar.ppm \
  > new-bar.ppm
  	Image new-bar.ppm will be 8 times larger than bar.ppm with 1 pixel
	all around gray borders on the 6x6 tiles from bar.ppm, looking
	something like a mosaic complete with grout.

  ppmmosaic -e 1,0,0,0 --border black --pixels 1 bar.ppm > new-bar.ppm
  	Image new-bar.ppm will be twice as tall with a black line between
	each row. If bar.ppm had previously been compressed 50% vertically,
	it now has an early CRT scan-line look to it.
ProgramUsage
  exit $rc;
}

__END__

=pod

=head1 NAME

ppmmosaic - make mosaic images by expanding pixels into bordered tiles

=head1 DESCRIPTION

ppmmosaic reads PPM(5) files and makes larger pixelated versions of them,
with several border options. Such images can look like simple block mosaics,
eg pick a big value for inner pixel size, and small border evenly sized on
all edges. Or venetian blind effects can be created by having borders in
one dimension only.

Options:

=over 4

=item *

-b MODE    --border MODE

MODE can be C<black>, C<white>, C<red>, C<green>,
C<blue>, C<fixed:>I<RGB>, C<lighter>, C<darker>, or
C<none>.

For fixed color borders, the RGB value can 6 hex digits (eg "99CC33")
or a colon-seperated decimal triple (eg "153:204:51"). The C<black>,
C<white>, etc, border options are short hand for C<fixed:000000>,
C<fixed:FFFFFF>, etc.

The 'lighter' and 'darker' options are 10% changed from inner pixel.

Default is C<none>.

=item *

-e SIZE    --borderpixels SIZE

Makes the border be SIZE pixels total. If SIZE is
odd, the top and left borders will be larger.
So C<-e 5> is equivilent to C<-e 3,2,3,2>
making the top and left borders three pixels,
and the bottom and right borders two pixels.

Default is equivilent to C<-e 2>.

=item *

-e T,B,L,R   --borderpixels T,B,L,R

Sets the sizes for Top, Bottom, Left, and Right borders
individually. 

Default is equivilent to C<-e 1,1,1,1>

=item *

-p SIZE    --pixels SIZE

Makes the inner pixel tile be SIZE pixels. The border goes around
this inner pixel.

Default is C<10>.

=item *

--

Marks the end of the arguments

=item *

--help                 

Show a help message and exit.

=item *

--version              

Same as C<--help>.

=back

=head1 COPYRIGHT

Copyright 2003 by Eli the Bearded / Benjamin Elijah Griffin.
Released under the same license(s) as Perl.

=head1 AUTHOR

Eli the Bearded originally the Image::PPMlib to make a script
very much like this one easier to code.

=head1 CPAN INFO

=head1 SCRIPT CATEGORIES

Image

=head1 README

ppmmosaic - make mosaic images by expanding pixels into bordered tiles

=head1 PREREQUISITES

This uses the C<strict>, C<vars>, C<integer>, and C<Image::PPMlib> modules.

=head1 COREQUISITES

None.

=head1 OSNAMES

Should be OS independent.

=cut