#!/usr/bin/perl
# Colours! - A colourful demonstration of Perl's GD module.
# Copyright (C) December 2002 Neil Fraser
# http://neil.fraser.name/news/2002/12/30/

# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General
# Public License as published by the Free Software Foundation.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# http://www.gnu.org/

use strict;
use GD;
use CGI qw(:standard);

# Height and width of the image.
# All values work, but 2^n+1 squares are ideal (65x65, 129x129, 257x257, etc).
my $rows = 257;
my $cols = 257;
# Limit the size to avoid denial of service attacks on my server.
$rows = param('y')-2 if param('y');
$rows = 512+1 if $rows > 512+1;
$cols = param('x')-2 if param('x');
$cols = 512+1 if $cols > 512+1;

my $image = GD::Image->new($cols+2, $rows+2, 1);

# The grid of points that make up the image.
my @grid;

# Flip a coin to determine which diagonally opposite corners get the master colours.
if (rand >= .5) {
  $grid[1][1] = 0;
  $grid[$cols][$rows] = 255;
  $grid[$cols][1] = int(rand 255);
  $grid[1][$rows] = int(rand 255);
} else {
  $grid[1][$rows] = 0;
  $grid[$cols][1] = 255;
  $grid[1][1] = int(rand 255);
  $grid[$cols][$rows] = int(rand 255);
}

# Draw the top row and left column.
fillrow(1, 1, $cols);
fillcol(1, 1, $rows);

# Now we have the top edge, the left edge, and the bottom/right dot.
# We are all setup to fill the remaining area.
fillarea(1, $rows, 1, $cols);

# RGB colour declarations for the master colours
my ($c1r, $c1g, $c1b);
my ($c2r, $c2g, $c2b);
do {
  $c1r = int(rand 256);
  $c1g = int(rand 256);
  $c1b = int(rand 256);
  $c2r = int(rand 256);
  $c2g = int(rand 256);
  $c2b = int(rand 256);
  # Keep looping until you get dissimilar colours.
} until ((abs($c1r - $c2r) + abs($c1g - $c2g) + abs($c1b - $c2b)) >= 256);

# Precompute all 256 colours.
my @colourtable;
my ($r, $g, $b);
my ($x, $y);
for ($x=0; $x<=255; $x++) {
  $r = $c1r * $x / 255 + $c2r * (1 - $x / 255);
  $g = $c1g * $x / 255 + $c2g * (1 - $x / 255);
  $b = $c1b * $x / 255 + $c2b * (1 - $x / 255);
  $colourtable[$x] = $image->colorAllocate($r, $g, $b);
}

for ($x=1; $x<=$cols; $x++) {
  for ($y=1; $y<=$rows; $y++) {
    $image->setPixel($x, $y, $colourtable[$grid[$x][$y]+1]);
  }
}

# Add some text.
my $colour = $image->colorResolve(127, 127, 127);
$image->stringFT($colour, '/home/fraser/html/software/colours/tahoma.ttf', 8, 3.14/2, $cols-1, $rows-1,'http://neil.fraser.name/');

# Output the image
print "Content-type: image/jpeg\n\n";
print $image->jpeg;
#print "Content-type: image/png\n\n";
#print $image->png;


# midpoint(end1, end2) => number
# Pick a random number between two end values.
sub midpoint {
  my($end1, $end2) = @_;
  int(rand($end2 - $end1) + $end1);
}


# fillrow(row, endleft, endright)
# Recursively fill one row.
# Used to bootstrap the recursive area fill.
# Also used to fudge our way out of ?x2 rectangles.
sub fillrow {
  my($row, $endleft, $endright) = @_;
  die if $endleft >= $endright; # Can't happen
  if ($endleft + 1 == $endright) {
    return;
  }
  # Set the dot in the middle.
  my ($middleX) = int(($endright - $endleft) / 2 + $endleft);
  $grid[$middleX][$row] = midpoint($grid[$endleft][$row], $grid[$endright][$row]);
  # Go fill the two new sub-rows
  fillrow($row, $endleft, $middleX);
  fillrow($row, $middleX, $endright);
}


# fillcol(col, endtop, endbottom)
# Recursively fill one column.
# Used to bootstrap the recursive area fill.
# Also used to fudge our way out of ?x2 rectangles.
sub fillcol {
  my($col, $endtop, $endbottom) = @_;
  die if $endtop >= $endbottom; # Can't happen
  if ($endtop + 1 == $endbottom) {
    return;
  }
  # Set the dot in the middle.
  my ($middleY) = int(($endbottom - $endtop) / 2 + $endtop);
  $grid[$col][$middleY] = midpoint($grid[$col][$endtop], $grid[$col][$endbottom]);
  # Go fill the two new sub-columns
  fillcol($col, $endtop, $middleY);
  fillcol($col, $middleY, $endbottom);
}


# fillarea(endtop, endbottom, endleft, endright)
# Recursively fill the area.
sub fillarea {
  my($endtop, $endbottom, $endleft, $endright) = @_;
  if (($endleft + 1 == $endright) && ($endtop + 1 == $endbottom)) {
    # This is just a 2x2 square.  Nothing to do.
  } elsif ($endleft + 1 == $endright) {
    # This is just a 2x? rectangle.  Fill in some vertical space.
    fillcol($endright, $endtop, $endbottom);
  } elsif ($endtop + 1 == $endbottom) {
    # This is just a ?x2 rectangle.  Fill in some horizontal space.
    fillrow($endbottom, $endleft, $endright);
  } else {
    # Wide open space; something to sink our recursive teeth into...
    # Set the dot half way along the bottom row.
    my ($middleX) = int(($endright - $endleft) / 2 + $endleft);
    $grid[$middleX][$endbottom] = midpoint($grid[$endleft][$endbottom], $grid[$endright][$endbottom]);

    # Set the dot half way along the right column.
    my ($middleY) = int(($endbottom - $endtop) / 2 + $endtop);
    $grid[$endright][$middleY] = midpoint($grid[$endright][$endtop], $grid[$endright][$endbottom]);

    # Set the dot in the middle (midpoint of two midpoints).
    my ($centre1) = midpoint($grid[$middleX][$endtop], $grid[$middleX][$endbottom]);
    my ($centre2) = midpoint($grid[$endright][$middleY], $grid[$endleft][$middleY]);
    $grid[$middleX][$middleY] = midpoint($centre1, $centre2);

    # Go fill the four new quadrants.
    fillarea($endtop, $middleY, $endleft, $middleX);
    fillarea($middleY, $endbottom, $endleft, $middleX);
    fillarea($endtop, $middleY, $middleX, $endright);
    fillarea($middleY, $endbottom, $middleX, $endright);
  }
}
