#!/usr/bin/perl -T

#[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
#Plots a Mandelbrot Set.
#    z(n) = z(n-1)^2+C
#      translates into a computer algorithm something like this: (pseudocode)
#
#   for each x,y coordinate
#    x = 0; y = 0; count = 0;
#     loop: count++
#      x' = x^2 - y^2 + x(0)
#      y' = 2xy + y(0)
#      test is (x'^2 + y'^2) > 4? (4 = the square magnitude of numbers oustide the set)
#        yes? then color_of_pixel = colormap(count)
#        no? then is count = max_iterations? then color_of_pixel = black
#      x = x'; y= y'
#
#Look at http://www.pjde.demon.co.uk/answers/mandel.htm
#
#By Ian Smith-Heisters, February 17th, 2004
#    ian@0x09.com
#]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

use strict;
use warnings;
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use GD;

my %g = (maxit     => 100, #maximum number of iterations
	 size      => 400, #size of main window in pixels
	 imax      => 2,   #maximum number on the imaginary plane
	 imin      => -2,  #minimum number on the imaginary plane
#	 colormap  => 1,
	 type      => 'png',
	);

for my $global (keys %g){
  $g{$global} = param($global) if defined param($global);
}

die "maxit must be at least one" if $g{maxit}<1;
die "size must be at lease one"  if $g{size}<1;
#die "colormap must be 1 or 2."   if $g{colormap}!=1||$g{colormap}!=2;

my $im = new GD::Image($g{size}, $g{size});

my $white  = $im -> colorAllocate(255,255,255);
my $black  = $im -> colorAllocate(  0,  0,  0);
my $red    = $im -> colorAllocate(255,  0,  0);
my $blue   = $im -> colorAllocate(  0,  0,255);
my $indigo = $im -> colorAllocate( 40, 10, 95);
my $dkgreen= $im -> colorAllocate(  0, 92,  1);
my $grey1  = $im -> colorAllocate(200,200,200);
my $grey2  = $im -> colorAllocate(150,150,150);
my $grey3  = $im -> colorAllocate(100,100,100);
my $grey4  = $im -> colorAllocate( 50, 50, 50);
my $grey5  = $im -> colorAllocate( 25, 25, 25);

$im->transparent($white);
$im->interlaced('true');

&draw;

if ($g{type} eq 'png'){
  print "Content-type: image/png\n\n";
  print $im->png;
}
elsif ($g{type} eq 'jpeg'){
  print "Content-type: image/jpg\n\n";
  print $im->jpeg;
}
else {
  die " Oops - image type must be 'png' or 'jpeg'.";
}

##############Sub definitions:

# ---- Draw: draws the pixels to an image and throws it on the window

sub draw {
  for (my $x=0; $x<$g{size}; $x++){
    for (my $y=0; $y<$g{size}; $y++){
      my $color  =  colorize($x, $y);
      $im -> setPixel ($x, $y, $color);
    }
  }
}#end draw

# ---- Colorize: takes a return from plot and applies a color map to it.
#       returns a color for use by GD setPixel

sub colorize {
  my $x = shift;
  my $y = shift;
  my $iterations = plot($x, $y);
  my $color;

  if ($iterations >=  0 && $iterations <= 5){
    $color = $grey5;
  } elsif ($iterations > 5 && $iterations <= 10){
    $color = $grey4;
  } elsif ($iterations > 10 && $iterations <= 15){
    $color = $grey3;
  } elsif ($iterations > 15 && $iterations <= 25){
    $color = $grey2;
  } elsif ($iterations > 25 && $iterations <= 99){
    $color = $grey1;
  } elsif ($iterations == $g{maxit}){
    $color = $black;
  }
  return $color;
}

# ---- Plot: returns the number of iterations it takes a point to go over 2,
#        as it is provable that any point that goes over 2 (really 1.56 or
#        something) approaches infinity and is thus not part of the set.
#        If the point remains under 2 for $g{maxit} it is part of the set.

sub plot {
  my @newArgs = convertCoords(@_);
  my $xCoord = shift(@newArgs);
  my $yCoord = shift(@newArgs);
  my $oldX = 0;
  my $oldY = 0;
  my $newX = 0;
  my $newY = 0;
  my $count = 1;

  #as long as the number of iterations is below $g{maxit} and the point hasn't
  #    exceeded 2^2.
  for (;($count < $g{maxit})&&(($newX*$newX + $newY*$newY)<4);$count++){
    $newX = (($oldX*$oldX) - ($oldY*$oldY)) + $xCoord;
    $newY = (2*$oldX*$oldY) + $yCoord;
    $oldX = $newX;
    $oldY = $newY;
  }

  return $count;
}

# ---- ConvertCoords: converts coordinates from pixel to imaginary plane.

sub convertCoords {
  my $pX = shift;
  my $pY = shift;

  my $multiplier = ($g{imax} - $g{imin}) / $g{size};#each pixel is one of these.
  my $origin = ($g{size} / 2)*$multiplier;

  my $iX = ($pX * $multiplier) - $origin;
  my $iY = ($pY * $multiplier) - $origin;

  my @result = ($iX, $iY);
  return @result;
}
