#!/usr/bin/perl
# -*- cperl -*-
# Convert a PostScript file to a 4-bit-depth GIF

use warnings;
use strict;
use Getopt::Long;

use constant S_OPAQUE       => 'opaque';
use constant S_NORMAL       => 'normal';
use constant S_DBL_BLEND    => 'double-blended';
use constant S_ALPHA_MOSTLY => 'alpha-mostly';
use constant S_ALPHA_ONLY   => 'alpha-only';

our (@OUT, $SIZE, $C_DARK, $C_LIGHT, $C_BGND, $DEBUG, $STYLE);

GetOptions( 'size|s=i'         => \$SIZE,
            'dark-color|cd=s'  => \$C_DARK,
            'light-color|cl=s' => \$C_LIGHT,
            'background|cb=s'  => \$C_BGND,

            'opaque|q'         => sub { $STYLE = S_OPAQUE },
            'normal'           => sub { $STYLE = S_NORMAL },
            'double-blended|b' => sub { $STYLE = S_DBL_BLEND },
            'alpha-mostly'     => sub { $STYLE = S_ALPHA_MOSTLY },
            'alpha-only'       => sub { $STYLE = S_ALPHA_ONLY },

            'output|o=s'       => \@OUT,
            'debug|d+'         => \$DEBUG,
          ) or die <<"EndOfUsage";
Usage: $0 [options] file [file...]
Options:
  -s  -size=<px>
          Limit the image size to the specified number of pixels.
  -cd -dark-color=<color>
  -cl -light-color=<color>
          Colorize the image with the specified colors.  Colors can be
          specified as 'rgb:#/#/#' or as color names.
  -cb -background=<color>
          Use specified color for the unpainted background, instead of
          white.  (Black is probably the only other useful color.)

  -q  -opaque
          Generate an opaque image (no transparency).
      -normal
          Make the image transparent where nothing was drawn in black
          or white or any other color.  This usually results in the
          best visual appearance on any viewer.
          [This is the default.]
  -b  -double-blended
          Use both color mixing and alpha (transparency) for the same
          pixels.  This is technically incorrect, but it results in
          good visual appearance even for viewers that don't support
          transparency.
      -alpha-mostly
          Construct an image consisting mostly of a single color, with
          varying alpha (transparency) values.  If the transparency
          value is anything except 100%, the image uses the "dark"
          color.  Where the image is fully transparent, the "light"
          color is used (but will not be shown by viewers which
          support transparency).

          Will display as a "chunky" two-color image on viewers that
          don't support transparency!
      -alpha-only
          Construct an image consisting of only a single color, but
          whith varying alpha (transparency) values.  Will display as
          a solid color on viewers that don't support transparency!

          With this option, the value of -light-color is ignored.

  -o  -output=<file>
          Send output to the specified file (the default is to use a
          name based on the input file).

  -d  -debug
          Enable debugging.
EndOfUsage

@ARGV or die 'No files specified';

use constant PT_PER_IN => 72;

sub convert ( $@ ) {
  my ($input, %param) = @_;

  my $debug  = $param{debug};
  my $size   = $param{size}       || 1200;
  my $dark   = $param{dark}       || 'rgb:0/0/0';
  my $light  = $param{light}      || 'rgb:f/f/f';
  my $bgnd   = $param{background} || 'rgb:f/f/f';
  my $style  = $param{style}      || S_NORMAL;

  my $output = $param{output};
  if (! defined $output) {
    (my $base = $input) =~ s/\.(?:ps|epsf?)$//i;
    $base .= "-$size";
    $output = "$base.png";
  }

  my $F_GSOUT   = "tmp_epsf2png_00.png";
  my $F_GSALPHA = "tmp_epsf2png_01alpha.pgm";
  my $F_C_IMG   = "tmp_epsf2png_11image.ppm";
  my $F_G_MIX   = "tmp_epsf2png_13mixed.pgm";
  my $F_C_MIX   = "tmp_epsf2png_14mixed.ppm";
  my $F_CHUNKY  = "tmp_epsf2png_25chunky.ppm";
  my $F_SOLID   = "tmp_epsf2png_26solid.ppm";
  my @F_TMP     = ($F_GSOUT, $F_GSALPHA, $F_C_IMG, $F_G_MIX, $F_C_MIX,
                   $F_CHUNKY, $F_SOLID);
  unlink $_ for @F_TMP;

  print STDERR "$input -> $output ($style)\n";

  # set up intermediate conversions
  my ($ximg, $xalpha);
 CONVERT:
  {
    $style eq S_OPAQUE
      and $ximg = $F_C_MIX,  last CONVERT;
    $style eq S_NORMAL
      and $ximg = $F_C_IMG,  $xalpha = $F_GSALPHA, last CONVERT;
    $style eq S_DBL_BLEND
      and $ximg = $F_C_MIX,  $xalpha = $F_G_MIX,   last CONVERT;
    $style eq S_ALPHA_MOSTLY
      and $ximg = $F_CHUNKY, $xalpha = $F_G_MIX,   last CONVERT;
    $style eq S_ALPHA_ONLY
      and $ximg = $F_SOLID,  $xalpha = $F_G_MIX,   last CONVERT;
    die "Unknown output style '$style'";
  }
  my %need;
  ++$need{$ximg}   if $ximg;
  ++$need{$xalpha} if $xalpha;
  # dependencies:
  ++$need{$F_G_MIX} if $need{$F_C_MIX} or $need{$F_CHUNKY};

  # generate image
  my $res  = 72;
  my $psz  = $size * PT_PER_IN / $res;

  system(qq[./epsf2ps -q -p $psz,$psz -m 0 -o - "$input" ] .
         qq[| gs -q -dBATCH -dNOPAUSE -sDEVICE=pngalpha -r$res ] .
         qq[    -dBackgroundColor=16#7f0000 ] .
         qq[    -dGraphicsAlphaBits=4 -sOutputFile="$F_GSOUT" -])
    and die "failed while converting PostScript (status $?)";

  if ($need{$F_GSALPHA} or $debug && $debug > 1) {
    # extract alpha (transparency) data from PNG file
    system(qq[pngtopnm -alpha < "$F_GSOUT" > "$F_GSALPHA"])
      and die "failed while extracting alpha data (status $?)";
  }

  if ($need{$F_C_IMG} or $debug && $debug > 1) {
    # extract image (non-transparency) data from PNG file, and colorize
    system(qq[pngtopnm < "$F_GSOUT" | ppmtopgm ] .
           qq[| pgmtoppm $dark-$light > "$F_C_IMG"])
      and die "failed while extracting image data (status $?)";
  }

  if ($need{$F_G_MIX} or $debug && $debug > 1) {
    # extract mixed data (what the partially transparent image would
    # look like when shown against a white background)
    system(qq[pngtopnm -mix -background "$bgnd" < "$F_GSOUT" ] .
           qq[| ppmtopgm | pnminvert > "$F_G_MIX"])
      and die "failed while extracting mixed data (status $?)";
  }

  if ($need{$F_C_MIX} or $debug && $debug > 1) {
    # extract mixed data (as F_G_MIX), and colorize
    ! -r $F_G_MIX and die "Intermediate file (F_G_MIX) not generated";
    system(qq[pgmtoppm $light-$dark < "$F_G_MIX" > "$F_C_MIX"])
      and die "failed while colorizing mixed data (status $?)";
  }

  if ($need{$F_CHUNKY} or $debug && $debug > 1) {
    # extract mixed data (as F_G_MIX), and color all non-transparent
    # pixels "dark" and transparent ones "light"
    ! -r $F_G_MIX and die "Intermediate file (F_G_MIX) not generated";
    system(qq[pamfunc -multiplier 1000 < "$F_G_MIX" ] .
           qq[| pgmtoppm $light-$dark > "$F_CHUNKY"])
      and die "failed while chunkifying mixed data (status $?)";
  }

  if ($need{$F_SOLID} or $debug && $debug > 1) {
    # generate solid data (a single-color image of the dark color)
    system(qq[pngtopnm < "$F_GSOUT" ] .
           qq[| ppmtopgm | pgmtoppm $dark-$dark > "$F_SOLID"])
      and die "failed while extracting solid data (status $?)";
  }

  ! -r $ximg               and die "Image file not generated";
  $xalpha and ! -r $xalpha and die "Alpha file not generated";

  if ($xalpha) {
    system(qq[pnmtopng -alpha "$xalpha" < "$ximg" > "$output"]);
  } else {
    system(qq[pnmtopng < "$ximg" > "$output"]);
  }

  $debug or unlink $_ for @F_TMP;
}

for my $file (@ARGV) {
  my $out = shift @OUT;
  convert $file, output => $out, size => $SIZE, debug => $DEBUG,
    style => $STYLE, dark => $C_DARK, light => $C_LIGHT, background => $C_BGND;
}
