#!/usr/bin/perl
###################
#
# house/transform.cgi
#
# This CGI script uses the GD graphics package 
# to output a picture of a house, including
# various rotations and scalings as chosen by the user.
#
# Usage: point your browser at this file, fill in the form params, 
# and click "submit".
#
# (Or any of the globals in the %g hash below in the code
#  may be given values in the URL.)
#
# For details on GD see http://stein.cshl.org/WWW/software/GD/
#
# Jim Mahoney (mahoney@marlboro.edu)
# Copyright 2003.  This is free software; you can redistribute it 
# and/or modify it under the same terms as Perl itself.
#
#   v 1.1 Feb 26 - fixed web debug display in Thurs class 
#   v 1.0 Febuary 24 2004 - did for Tues 22 class
###################################################
use strict;
use warnings;
my $DEBUG = 0;          #    set to 1 to print diagnostics and exit.
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use GD;

# Make sure web access doesn't barf if debugging is on.
print "Content-type: text/plain\n\n" if $DEBUG;

# Constants
use constant pi                    => 4*atan2(1,1);
use constant radians_per_degree    => pi/180;

# Default values for the globals.
my %g = ( xsize    => 200,           # x window width         | in pixels
          ysize    => 200,           # y window height        |
          border   =>  10,           # blank edge of window   |
          xmax     =>   1,           #   drawing region |
          xmin     =>  -1,           #                  | in (x,y) coords
          ymax     =>   1,           #                  |
          ymin     =>  -1,           #                  |
          dark     => '0,0,255',     # dark RGB color ; blue by default
          grey     => '100,100,100', # grey RGB color
          type     => 'png',         # type of output image (png or jpeg)
                               
          scale    => 1.0,           #            | affine transformation 
          angle    => 0,             # (degrees)  |
          x_offset => 0,             #            |
          y_offset => 0,             #            |
          );

# Set the globals from name=value pairs passed in the URL or
# set by the values passed in the form.
for my $global (keys %g){
  $g{$global} = param($global) if defined param($global);
}

# Reality check for the globals.
die "Oops - xsize must be positive - "          if $g{xsize}<0;
die "Oops - ysize must be positive - "          if $g{ysize}<0;
die "Oops - border must be positive - "         if $g{border}<0;
die "Oops - image type must be 'jpeg' or 'png'" unless $g{type} =~ /jpeg|png/;

# Coordinates for an outline of a house, in xy coords.
my @house = ( [0,  0],
              [.4, 0],
              [.4,.4],
              [.2,.6],
              [0, .4],
            );

# Globals based on previous parameters.
my $N           = scalar(@house);       # number of points in the house.
my $x_convert   = $g{xsize}/( $g{xmax} - $g{xmin} );
my $y_convert   = $g{ysize}/( $g{ymax} - $g{ymin} );
my $bottom_left = '('.$g{xmin}.','.$g{ymin}.')';
my $top_right   = '('.$g{xmax}.','.$g{ymax}.')';
my @indeces     = (0..($N-1));

# These fill in the form below, to keep it the same as last passed values.
# (The named variables are easier to insert in the qq{ } text block.)
my $angle    = $g{angle};
my $scale    = $g{scale};
my $x_offset = $g{x_offset};
my $y_offset = $g{y_offset};

if ($DEBUG){
  print " x_convert pixel/coord = $x_convert \n";
  print " y_convert pixel/coord = $y_convert \n\n";
  print " house is " if $DEBUG;
  print "(" . $house[$_][0] . "," . $house[$_][1] . ") " for @indeces;
  print "\n";
}

# Apply the transformation to each point.
for (my $i=0; $i<$N; $i++){
  $house[$i] = [ affine_transform( $g{angle},     $g{scale},
                                   $g{x_offset},  $g{y_offset},
                                   $house[$i][0], $house[$i][1])
               ]; 
}
if ($DEBUG){
  print " house after transform is ";
  print "(" . $house[$_][0] . "," . $house[$_][1] . ") " for @indeces;
  print "\n";
}


# Output file for image.
my $outfile  = "house." . $g{type}; # 'house.jpeg' or 'house.png'
my $url      = url();               # Ask CGI.pm for the name of this file

# Create a blank image
my $image = GD::Image->new( $g{xsize}, $g{ysize}); # width,height in pixels
my $white = $image->colorAllocate(255,255,255);    # background is first color
my $dark  = $image->colorAllocate( split(',',$g{dark}) );
my $grey  = $image->colorAllocate( split(',',$g{grey}) );
$image->transparent($white);
$image->interlaced('true');
if ($DEBUG){
  print(" line: @{$house[$_]}, @{$house[($_+1)%$N]},$dark \n") for @indeces;
}

# Convert xy coords to screen pixels.
$house[$_] = [ xpix($house[$_][0]), ypix($house[$_][1]) ] for @indeces;

if ($DEBUG){
  print " house in pixels is ";
  print "(" . $house[$_][0] . "," . $house[$_][1] . ") " for @indeces;
  print "\n";
  exit;
}

# Draw the house in the image.
$image->line(@{$house[$_]}, @{$house[($_+1)%$N]},$dark)   for @indeces;

# Draw the frame.
# (The pen hangs below/right the given point, so right/bottom are in by one.)
$image->line(0,0,$g{xsize}-1,0,$grey);
$image->line($g{xsize}-1,0,$g{xsize}-1,$g{ysize}-1,$grey);
$image->line($g{xsize}-1,$g{ysize}-1,0,$g{ysize}-1,$grey);
$image->line(0,$g{ysize}-1,0,0,$grey);

# Write the picture to a file.
unlink $outfile;                # remove image if there was one.
open(IMG, ">$outfile") or die "couldn't open '$outfile': $!";
binmode IMG;
print IMG $image->png  if $g{type} =~ /png/;
print IMG $image->jpeg if $g{type} =~ /jpeg/;
close(IMG);

# And finally, output the web page that displays the file,
# including the <img> pointing at the picture of the house at the top.
print "Content-type: text/html\n\n";
print qq{
<html>
 <head><title>House Affine Transform</title></head>
 <body bgcolor="white">
  <h2>House Affine Transformation</h2>
  <hr noshade size=1>
  <img src="$outfile">
  <hr noshade size=1>
  <form action="$url" method=GET>
  The frame runs from a bottom-left (x,y) = $bottom_left
  to a top-right (x,y) = $top_right.<br>
  So choose a new size, angle, and position and click the button.<p>
    <table border="0">
    <tr>
     <td>Angle (in degrees):</td>  
     <td><input type="text" name="angle" value="$angle"></td>
    </tr><tr>
     <td>Scale factor:</td>
     <td><input type="text" name="scale" value="$scale"></td>
    </tr><tr>
     <td>X offset (in xy coords):</td>
     <td><input type="text" name="x_offset" value="$x_offset"></td>
    </tr><tr>
     <td>Y offset (in xy coords):</td>
     <td><input type="text" name="y_offset" value="$y_offset"></td>
    </tr>
    </table>
    <input type="submit" value="Draw">
  </form>
  <hr noshade size=1>
  <div align="right">
  <small>
    Jim Mahoney<br>
    <a href="mailto:mahoney\@marlboro.edu">(mahoney\@marlboro.edu)</a><br>
    <a href="${url}_html">source code</a>
  </small>
  </div>
  </body>
</html>
};


# -------------------------------------------

# Rotate (counter clockwise), stretch, and shift a given (x,y) point.
# Usage: ($x,$y) = affine_transform( $degrees, $scale, $x_off, $y_off, $x, $y);
sub affine_transform {
  my ($degrees, $scale, $x_offset, $y_offset, $x, $y) = @_;
  my $theta = $degrees * radians_per_degree;
  print " in: ($degrees, $scale, $x_offset, $y_offset, $x, $y)\n" if $DEBUG;
  # Rotate
  ($x, $y) = ( cos($theta) * $x - sin($theta) * $y,
               sin($theta) * $x + cos($theta) * $y );
  print "     rotate: (x,y) = ($x,$y)\n" if $DEBUG;
  # Scale
  ($x, $y) = ( $scale * $x, $scale * $y );
  print "     scale:  (x,y) = ($x,$y)\n" if $DEBUG;
  # Translate
  ($x, $y) = ( $x_offset + $x, $y_offset + $y );
  print "     offset: (x,y) = ($x,$y)\n" if $DEBUG;
  return ($x,$y);
}

# Coordinates:
#
#            (0,0)
#  pixels :    +-------------+
#              |             |
#              |             |
#              +-------------+
#                            (xsize,ysize)
#
# The the x,y min,max points are inside the border of the 
# pixel coordinates.  That is, I leave a bit blank at the edges.
#
#                   ymax
#  xy coords:       |
#                   |
#                   |
#                   |(0,0)
#         xmin -----+----------- xmax
#                   |
#                   |
#                   ymin

# convert x coord to pixel value; uses globals in %g and $x_convert
# Usage: $x_in_pixels = xpix( $x_coord );
sub xpix {
  my ($x) = @_;
  return $g{border} + ( $x - $g{xmin} ) * $x_convert;
}

# convert y coords to y pixel value; uses globals in %g and $y_convert
# Usage: $y_in_pixels = ypix( $y_coord );
sub ypix {
  my ($y) = @_;
  return $g{border} + ( $g{ymax} - $y ) * $y_convert;
}
