#! \usr\bin\perl.exe

################################################################################
################################################################################
#
# Spectrum fill
#
#	by Andrew Hardwick, http://duramecho.com
#
#	Released under GNU Public Licence.
#
################################################################################
################################################################################
# Version 1, 2006/4/29
#  Created to make wallless greyscale 16x16 Cretan maze favicon but looked naff.
# Version 2, 2006/4/29
#  Added (black) walls.
# Version 3, 2006/4/29
#  Added option of simple white path instead of greyscale.
# Version 4, 2006/4/29
#  Added option of spectrum path.
# Version 5, 2006/4/30
#  Added lots more documentation as program changed from disposable to to keep.
#  Added option of different path & wall widths.
# Version 6, 2006/4/30
#  Split large main routine up into 3 subroutines and generalised some parts.
# Version 7, 2006/5/1
#  Added option of output of a series of images with shifted colours.
# Version 8, 2006/6/22
#  Changed hardcoded labyrinth template to one read from an image file.
# Version 9, 2006/6/23
#  Removed 2 stages that cancel out now that a template image is used.
# Version 10, 2006/6/23
#  Replaced remaining sloppy global variable use with parameter passing.
# Version 11, 2006/7/10
#  Corrected bug that corrupted reading of non-square templates.
# Version 12, 2006/7/10
#  Corrected bug that corrupted output from even width or height templates.
# Version 13, 2006/7/16
#  Added option of cycling through spectrum multiple times & chosing start hue.
# Version 14, 2006/7/16
#  Made background & Monochrome (formerly 'White') path colours settable.
# Version 15, 2006/7/20
#  Added support for non-unicursal mazes (equivalent to flood fill).
#  Renamed from 'LabyrinthColourer.pl' to 'SpectrumFill.pl'
#  Improved path walking function not overwrite template array.
# Version 16, 2006/7/20
#  Added stop cells to block flood filling in unwanted directions in circuits.
# Version 17, 2006/7/20
#  Added support for multiple start cells.
# Version 18, 2006/7/23
#  Fixed PGM reading bug (it did not allow surround whitespace on text lines).
# Version 19, 2006/7/23
#  Added optional progress logging ("verbose mode").
# Version 20, 2006/8/9
#  Documentation typo corrections.
# Version 21, 2006/8/16
#  Renamed setting 'StartHue' to 'StartOffset' as it is also used for greyscale.
# Version 22, 2007/9/15
#  Tidied by moving maze output from main routine to subroutines.
#  Tidied by moving colour calculation to a separate function.
#  Changed internal RGB colour format from PPM space separated to generic array.
#  Renamed 'ColourCycles' to 'NumberOfCycles' as works for monochrome too.
#  Removed unnecessary function passing of fixed global parameters.
#  Added 'OutputType' setting instead of guessing from 'NumberOfCycles'.
# Version 23, 2007/9/16
#  Changed internal coordinate system from y=0 at top to y=0 at bottom.
#  Added Javascript/HTML animation output ability.
# Version 24, 2007/9/19
#  Added Id caching to Javascript as lousy IE's getElementById is very slow.
# Version 25, 2007/10/8
#  Changed HTML animation page output to have background colour = maze wall.
#  Stopped path following routine wandering off template when unwalled.
################################################################################
# This program colours in a maze image with spectrum colour progression.
# Additional features include alternative colouring schemes, image expanding
# and expanding the path to a different width to the wall.
################################################################################
# How to use:
#  Set the maze shape & other parameters in the settings. Run it.
# Template format:
#  An image in ASCII PGM format with 256 grey levels (0 to 255).
#  Each pixel is a cell. Grey level of the cell determines what it is.
#    0 = wall cell.
#  128 = start cell (fill starts in and propogates out of this type of cell).
#  192 = stop cell (fill propogates into but not out of this type of cell).
#  255 = path cell (fill propogates into & out of this type of cell).
#  The image must contain at least one start cell.
#  Perferably the path should be 1 pixel wide to get the cleanest colour fill.
#  If it is desired to resize the path to a different width from the wall
#   then all pixels with both x & y even must be walls or be surrounded on
#   all four sides by path cells. (This is because whole rows & columns are
#   resized together).
# Set OutputType to the maze output type required. Options are:
#  Static = one static PPM image.
#  Series = an animation as a series of static PPM images that can be combined.
#  Javascript = an animation as a Javascript animated HTML page.
# Set ColouringScheme to the colour scheme required. Options are:
#  Monochrome = path of single colour.
#  Greyscale = path graduates from black to white.
#  Spectrum = path graduates from red up through the spectrum back to red.
# Set WallColour to the colour for the walls. Colours are represented
#  as ASCII strings of whitespace separated decimal RGB values eaching ranging
#  from 0 to 255.
# Set ColourPath to the colour for the Monochrome path. Colours are represented
#  as ASCII strings of whitespace separated decimal RGB values eaching ranging
#  from 0 to 255. Irrevelant for Spectrum or Greyscale path colouring.
# Set StartHue to where to start in a spectrum (or luminousity if the colouring
#  scheme is Greyscale). Range is 0 to 1. Irrevelant for Monochrome
#  path colouring.
# Set NumberOfCycles to the number of times it to cycle through the spectrum
#  (or colouring greyscale) between the begining & end of the path. Irrevelant
#  for Monochrome path colouring.
# Set WidthPath to the width of the path in pixels. Integer of 1 or more.
# Set WidthWall to the width of the walls in pixels. Integer of 1 or more.
# Set SeriesLength to the number larger of steps in an animation series
#  (which is the number of images output for 'Series' animation). Ignored
#  if the output type is 'Static'.
#  (One can combine the 'Series' animation frames from using Image Magick by
#  "convert -delay <frame spacing in 10 ms units> -loop 0
#  <output filename base>*.ppm <animation name>.gif".
#  If MNG is used instead of GIF then the file will be much
#  better compressed but popular current browsers don't natively support it.)
# Set AnimationStepPeriod to the period between animation steps. Ignored
#  it the output type is not 'Javascript'.
# The template & output filenames do not include the file type suffix.
# The output is an image file in PPM format (Portable Pixel Map, a very
#  inefficiently compressed but very easy to implement format) because it
#  was the easiest for me to do. Most reasonable bitmap image editing
#  and converting programs (including Image Magick, Paint Shop Pro, Photoshop &
#  Gimp) can read PPM and convert it to something more compressed such
#  as PNG.
################################################################################
# Known deficiencies:
#  Settings are not checked for errors.
#  It is not optimised for speed.
#  The file formats are not optimised for compactness.
################################################################################
# This file is formated for 80 character (+linebreak) rows & 4 character tabs.
################################################################################

use strict;

################################################################################
# Settings
################################################################################

my %Settings=(
		# Output type: Static, Series or Javascript
		'OutputType'=>'Javascript',
		# Colouring scheme: Spectrum, Monochrome or Greyscale
		'ColouringScheme'=>'Spectrum',
		# Wall colour: 'R G B' in ASCII
		'ColourWall'=>'0 0 0',
		# Path colour: 'R G B' in ASCII
		'ColourPath'=>'255 255 255',
		# Start offset (hue or grey level): 0 to 1
		'StartOffset'=>0,
		# Number of cycles through the colouration: >= 0
		'NumberOfCycles'=>1,
		# Width of path in pixels: >= 0
		'WidthPath'=>20,
		# Width of wall in pixels: >= 0
		'WidthWall'=>5,
		# Number of frames of to cycle colouration through: >= 1
		'SeriesLength'=>100,
		# Time between animation steps: >= 0
		'AnimationStepPeriod'=>50,
		# Input file name minus extension
		'TemplateFileName'=>'Input',
		# Output file name minus extension
		'OutputFileName'=>'Output',
		# Enable progress logging: 0 = no, 1 = yes
		'Verbose'=>1,
		);

################################################################################
# Main routine
################################################################################

{	# Load the template
	if($Settings{'Verbose'})
	{	print "Loading template '$Settings{'TemplateFileName'}.pgm'.\n";}
	my @Template=@{ReadPgmImageFile("$Settings{'TemplateFileName'}.pgm")};
	# Walk the path numbering the cells
	if($Settings{'Verbose'})
	{	print "Walking the path numbering cells.\n";}
	my @Order=@{NumberPathCells(\@Template)};
	# Create single image, animation as image series or animation as Javascript
	if($Settings{'OutputType'}eq'Static')
	{	# Create single image
		CreateMazeStaticImage(\@Order,\@Template);}
	elsif($Settings{'OutputType'}eq'Series')
	{	# Create image series
		CreateMazeAnimationImageSeries(\@Order,\@Template);}
	elsif($Settings{'OutputType'}eq'Javascript')
	{	# Create Javascript animation
		CreateMazeAnimationJavascript(\@Order,\@Template);}}

################################################################################
# Create maze static image
################################################################################
# Creates & outputs a static image from from a path order and the settings.
################################################################################
# Parameter 0:
#  Pointer to an array containing the cells on the path in order starting
#  with the first one. Cells are represented as pointers to an array containing
#  the cell's x & y coordinates.
# Parameter 1:
#  Pointer to the maze template (2d array of cell grey-values).
################################################################################

sub CreateMazeStaticImage
{	my @PathOrder=@{$_[0]};
	my @Template=@{$_[1]};
	if($Settings{'Verbose'})
	{	print "Creating static image.\n";}
	# Calculate cell colours
	if($Settings{'Verbose'})
	{	print "Calculating colours.\n";}
	my @Colours=@{CalculateCellColours(\@PathOrder,
			scalar(@Template),scalar(@{$Template[0]}),
			$Settings{'StartOffset'})};
	# Output to file with path & wall width scaling
	if($Settings{'Verbose'})
	{	print "Expanding & outputting image.\n";}
	CreatePpmOutputFile("$Settings{'OutputFileName'}.ppm",\@Colours);}
	
################################################################################
# Create maze animation image series
################################################################################
# Creates & outputs a animation as a series of images
#  from from a path order and the settings.
################################################################################
# Parameter 0:
#  Pointer to an array containing the cells on the path in order starting
#  with the first one. Cells are represented as pointers to an array containing
#  the cell's x & y coordinates.
# Parameter 1:
#  Pointer to the maze template (2d array of cell grey-values).
################################################################################

sub CreateMazeAnimationImageSeries
{	my @PathOrder=@{$_[0]};
	my @Template=@{$_[1]};
	if($Settings{'Verbose'})
	{	print "Creating animation image series.\n";}
	# Iterate over series frames
	for(my $c=0;$c<$Settings{'SeriesLength'};$c++)
	{	if($Settings{'Verbose'})
		{	print "Image $c.\n";}
		# Calculate colour offset
		my $ColourOffset=($Settings{'StartOffset'}+
				1-$c/$Settings{'SeriesLength'});
		$ColourOffset-=int($ColourOffset);
		# Calculate cell colours
		if($Settings{'Verbose'})
		{	print " Calculating colours.\n";}
		my @Colours=@{CalculateCellColours(\@PathOrder,
				scalar(@Template),scalar(@{$Template[0]}),
				$ColourOffset)};
		# Create a sequence number suffix (of constant length)
		my $Suffix=('0'x(length($Settings{'SeriesLength'}-1)-
				length($c))).$c;
		if($Settings{'Verbose'})
		{	print " Expanding & outputting image.\n";}
		# Output to file with path & wall width scaling
		CreatePpmOutputFile("$Settings{'OutputFileName'}$Suffix.ppm",
				\@Colours);}}

################################################################################
# Create maze animation as Javascript
################################################################################
# Creates & outputs a animation as a Javascript & HTML
#  from from a path order and the settings.
################################################################################
# Parameter 0:
#  Pointer to an array containing the cells on the path in order starting
#  with the first one. Cells are represented as pointers to an array containing
#  the cell's x & y coordinates.
# Parameter 1:
#  Pointer to the maze template (2d array of cell grey-values).
################################################################################

sub CreateMazeAnimationJavascript
{	my @PathOrder=@{$_[0]};
	my @Template=@{$_[1]};
	if($Settings{'Verbose'})
	{	print "Creating animation as Javascript.\n";}
	# Get the grid dimensions
	my $GridWidth=scalar(@Template);
	my $GridHeight=scalar(@{$Template[0]});
	# Get the wall colour in a format suitable for Javascript
	my $WallColour=join('',
			map({$_=ToHex($_,2)} split('\s+',$Settings{'ColourWall'})));
	# Get the path cell positions in a format suitable for Javascript
	my @PathCells;
	for(my $Step=0;$Step<@PathOrder;$Step++)
	{	push(@PathCells,$PathOrder[$Step][1]*$GridWidth
			+$PathOrder[$Step][0]);}
	# Get the path colours in a format suitable for Javascript
	my @Colours;
	for(my $Step=0;$Step<@PathOrder;$Step++)
	{	push(@Colours,"'#".join('',map({$_=ToHex($_,2)} 
				CalculateColour($Settings{'StartOffset'}+
				$Step/@PathOrder*$Settings{'NumberOfCycles'})))."'");}
	# Create output HTML file with header
	open(File,'>',"$Settings{'OutputFileName'}.html")||
		die("Cannot write to '$Settings{'OutputFileName'}.html'.\n");
	print File "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN' ",
			"'http://www.w3.org/TR/REC-html40/strict.dtd'>",
			"<HTML>\n<HEAD>\n<SCRIPT LANGUAGE='JavaScript'>\n<!--\n\n";
	# Output image & animation data as settings for the Javascript program
	print File
			"GridWidth=$GridWidth;\n",
			"GridHeight=$GridHeight;\n",
			"OddCellWidth=$Settings{'WidthWall'};\n",
			"OddCellHeight=$Settings{'WidthWall'};\n",
			"EvenCellWidth=$Settings{'WidthPath'};\n",
			"EvenCellHeight=$Settings{'WidthPath'};\n",
			"BackgroundColour='#$WallColour';\n",
			"StepPeriod=$Settings{'AnimationStepPeriod'};\n",
			"StepLength=",int(@PathOrder/$Settings{'SeriesLength'}+0.5),";\n",
			"PathCells=new Array(",join(',',@PathCells),");\n",
			"PathColours=new Array(",join(',',@Colours),");\n\n";
	# Output the Javascript program
	print File
			"// Javascript animation program by Andrew J. Hardwick\n",
			"//  Written 2007/9/14-2007/9/19.\n",
			"// Released under GPL.\n\n",
			"// Grid creating function\n",
			"Cells=new Array();\n",
			"function CreateGrid()\n",
			"{\t// Create table\n",
			"\tdocument.write('<TABLE BORDER=0 ",
			"CELLPADDING=0 CELLSPACING=0>');\n",
			"\tfor(Row=0;Row<GridHeight;Row++)\n",
			"\t{\tdocument.write('<TR>');\n",
			"\t\tfor(Column=0;Column<GridWidth;Column++)\n",
			"\t\t{\tdocument.write('<TD WIDTH=\"',",
			"Column%2?EvenCellWidth:OddCellWidth,\n",
			"\t\t\t\t\t'\"HEIGHT=\"',Row%2?EvenCellHeight:OddCellHeight,\n",
			"\t\t\t\t\t'\" ID=\"Cell-',Column,':',",
			"GridHeight-Row-1,'\"></TD>');}\n",
			"\t\tdocument.write('</TR>');}\n",
			"\tdocument.write('</TABLE>');\n",
			"\t// Set table cells initial colour ",
			"(& cache Ids as IE 6 Id look-up is slow)\n",
			"\tfor(x=0;x<GridWidth;x++)\n",
			"\t{\tCells[x]=new Array();\n",
			"\t\tfor(y=0;y<GridHeight;y++)\n",
			"\t\t{\tCells[GridWidth*y+x]=",
			"document.getElementById(\"Cell-\"+x+\":\"+y);\n",
			"\t\t\tCells[GridWidth*y+x].style.backgroundColor=",
			"BackgroundColour;}}}\n\n",
			"// Path colouring function\n",
			"function ColourPath(ColourOffset)\n",
			"{\tfor(CellIndex=0;CellIndex<PathCells.length;CellIndex++)\n",
			"\t{\tColourIndex=",
			"(CellIndex-ColourOffset+PathColours.length)%PathColours.length;\n",
			"\t\tCells[PathCells[CellIndex]].style.backgroundColor=",
			"PathColours[ColourIndex];}}\n\n",
			"// Animating function\n",
			"function Animate(ColourOffset)\n",
			"{\tStartTime=new Date();\n",
			"\tColourPath(ColourOffset);\n",
			"\tColourOffset=(ColourOffset+StepLength)%PathCells.length;\n",
			"\tTimeToNext=StepPeriod-((new Date())-StartTime);\n",
			"\tsetTimeout('Animate('+ColourOffset+')',",
			"TimeToNext>0?TimeToNext:0);}\n\n";
	# Output the rest of the HTML (including that which calls the Javascript)
	print File
			"//-->\n</SCRIPT>\n</HEAD>\n",
			"<BODY BGCOLOR='#$WallColour' ONLOAD='Animate(0);'>\n",
			"<SCRIPT LANGUAGE='JavaScript'>\n<!--\nCreateGrid();\n",
			"//-->\n</SCRIPT>\n",
			"<NOSCRIPT>\nSorry, this requires Javascript ",
			"as it is a Javascript animated HTML artwork.\n</NOSCRIPT>\n",
			"</BODY>\n</HTML>\n";
	close(File);}

################################################################################
# Read PGM Template File
################################################################################
# Reads a template file into an array of pixel colours. The file should be
# be an image in ASCII PGM format.
################################################################################
# Parameter 0:
#  Template file name.
# Returns:
#  Pointer to a 2d array of cell grey-values (0 = black, 255 = white).
################################################################################

sub ReadPgmImageFile
{	my $FileName=$_[0];
	# Open a PGM image file & read in data
	open(File,"<$FileName")||die("Cannot read from '$FileName'.\n");
	my @RawData;
	while(my $DataLine=<File>)
	{	# Discard comments
		$DataLine=~s/\#.*//;
		# Discard surrounding whitespace
		$DataLine=~s/^\s+//;
		$DataLine=~s/\s+$//;
		# Extract data from the line
		push(@RawData,split('\s+',$DataLine));}
	# Process header
	unless(@RawData>4)
	{	die("Insufficent header data in '$FileName'\n");}
	unless(shift(@RawData)eq'P2')
	{	die("'$FileName' is not in ASCII PGM format.\n");}
	my $SizeX=shift(@RawData);
	my $SizeY=shift(@RawData);
	my $Multiplier=255/shift(@RawData);
	# Extract grey values
	unless(@RawData>=$SizeX*$SizeY)
	{	die("Insufficent greyscale data in '$FileName'\n");}
	my @GreyLevels;
	for(my $Y=$SizeY-1;$Y>=0;$Y--)
	{	for(my $X=0;$X<$SizeX;$X++)
		{	$GreyLevels[$X][$Y]=shift(@RawData)*$Multiplier;}}
	close(File);
	return \@GreyLevels;}

################################################################################
# Walk the path numbering the cells by order
################################################################################
# It traces the path and numbers the cells with the order they are visited in.
################################################################################
# Parameter:
#  Pointer to a the template as a 2d array of
#  grey levels (0 = wall, 128 = start point on path, 255 = rest of path).
# Returns:
#  Pointer to an array containing the cells on the path in order starting
#  with the first one. Cells are represented as pointers to an array containing
#  the cell's x & y coordinates.
################################################################################

sub NumberPathCells
{	my @Template=@{$_[0]};
	# Find the start points
	my($X,$Y);
	my @ToVisit;
	my @PathOrder;
	for($X=0;$X<@Template;$X++)
	{	for($Y=0;$Y<@{$Template[$X]};$Y++)
		{	if($Template[$X][$Y]==128)
			{	push(@ToVisit,[$X,$Y]);
				push(@PathOrder,[$X,$Y]);}}}
	unless(@ToVisit)
	{	die("No start point specified in the template.\n");}
	# Flood fill the path recording the order the cells were visited in
	my @Visited;
	my($Direction,$XTest,$YTest);
	while(@ToVisit)
	{	# Get next (oldest unprocessed) cell to propogate the path
		($X,$Y)=@{shift(@ToVisit)};
		# Iterate over directions of propogation
		foreach $Direction ([1,0],[0,-1],[-1,0],[0,1])
		{	# Calculate neighbouring cell coordinates
			$XTest=$X+$$Direction[0];
			$YTest=$Y+$$Direction[1];
			# Skip if this neighbouring cell is off the edge
			if($XTest<0||$XTest>=@Template||$YTest<0||$YTest>=@{$Template[$X]})
			{	next;}
			# Test if it is an unvisited path cell
			if($Template[$XTest][$YTest]!=0&&!$Visited[$XTest][$YTest])
			{	# It is so add to order & mark as visited.
				$Visited[$XTest][$YTest]=1;
				push(@PathOrder,[$XTest,$YTest]);
				# Store it to visit later unless it is a stop cell 
				unless($Template[$XTest][$YTest]==192)
				{	push(@ToVisit,[$XTest,$YTest]);}}}}
	return \@PathOrder;}

################################################################################
# Calculate cell colours
################################################################################
# Calculates the colours of the cells based on their order on the path
# and the specified colouring scheme. Note that, depending on the colouring
# scheme chosen, the values of some of the parameters will not be needed
# for the calculation and so can be given arbitrary values (even null).
################################################################################
# Parameter 0:
#  Pointer to an array containing the cells on the path in order starting
#  with the first one. Cells are represented as pointers to an array containing
#  the cell's x & y coordinates.
# Parameter 1:
#  The width in cells.
# Parameter 2:
#  The height in cells.
# Parameter 3:
#  The colour offset. This is how much (as a fraction of a cycle from 0 to 1)
#  to shift the colours in the cycling colouring schemes.
# Returns:
#  Pointer to a 2d array of cell colours. Colours are represented as ASCII
#  strings of whitespace separated decimal RGB values as used by PPM image
#  format.
################################################################################

sub CalculateCellColours
{	my @PathOrder=@{$_[0]};
	my $SizeX=$_[1];
	my $SizeY=$_[2];
	my $Offset=$_[3];
	# Initialise all cells to wall colour
	my @Colours;
	my($X,$Y);
	for($Y=0;$Y<$SizeY;$Y++)
	{	for($X=0;$X<$SizeX;$X++)
		{	$Colours[$X][$Y]=$Settings{'ColourWall'};}}
	# Colour in the path
	my($Lum,$Hue);
	my $StepsTotal=@PathOrder;
	for(my $Step=0;$Step<$StepsTotal;$Step++)
	{	# Get cell position
		$X=$PathOrder[$Step][0];
		$Y=$PathOrder[$Step][1];
		# Colour the cell (as RGB PPM ASCII triplets of 0-255 range)
		$Colours[$X][$Y]=join(' ',CalculateColour($Offset+
				$Step/$StepsTotal*$Settings{'NumberOfCycles'}));}
	return \@Colours;}

################################################################################
# Calculate colour
################################################################################
# Calculates the colour in the current colouring scheme for the a particular
#  phase through the cycle.
################################################################################
# Parameter:
#  The phase offset from the start of the cycle in cycles.
#  Irrelevant for the 'Monochrome' colouring scheme.
# Returns:
#  Array of 3 members which are the red, green & blue colour components in
#  the 0-255 range.
################################################################################

sub CalculateColour
{	my $Phase=$_[0];
	# Wrap phase to principal region
	$Phase=$Phase-int($Phase);
	# Calculate the colour appropriately
	my(@Colour,$Phase8Bit);
	if($Settings{'ColouringScheme'}eq'Spectrum')
	{	# Spectrum path
		@Colour=HueToRgbBright($Phase);}
	elsif($Settings{'ColouringScheme'}eq'Greyscale')
	{	# Greyscale path
		$Phase8Bit=int($Phase*256);
		@Colour=($Phase8Bit)x3;}
	else
	{	# Monochrome path (a bit inefficient splitting as will be later joined!)
		@Colour=split('\s+',$Settings{'ColourPath'});}
	return @Colour;}

################################################################################
# Convert a hue to RGB
################################################################################
# Convert a hue to RGB with maximum brightness
################################################################################
# Converts hue to RGB with full saturation & maximum brightness
# for gaudy rather than faithful conversion of hue to RGB. Instead of
# maintaining an even luminance throughout the spectrum (which makes yellow
# look like brown on a computer screen), it makes it as bright as possible.
# This makes secondary colours twice as luminous as primary ones.
################################################################################
# Parameter:
#  A hue specified by a number in the range 0 to 1.
# Returns:
#  An array of the red, green and blue components (each in the range 0-255).
################################################################################

sub HueToRgbBright
{	my $Hue=shift;
	my($Red,$Green,$Blue);
	if($Hue<1/3)
	{	# Between red & green
		$Green=$Hue*3;
		$Red=1-$Green;
		$Blue=0;}
	elsif($Hue<2/3)
	{	# Between green & blue
		$Blue=($Hue-1/3)*3;
		$Green=1-$Blue;
		$Red=0;}
	else
	{	# Between blue & red
		$Red=($Hue-2/3)*3;
		$Blue=1-$Red;
		$Green=0;}
	# Maximise the luminance
	my $Top=$Red>$Green?$Red:$Green;
	$Top=$Top>$Blue?$Top:$Blue;
	$Red/=$Top;
	$Green/=$Top;
	$Blue/=$Top;
	# Scale to 255
	return(int($Red*255),int($Green*255),int($Blue*255));}

################################################################################
# Convert an number to hexidecimal
################################################################################
# Converts an number to upper case hexidecimal form and returns it as a string.
################################################################################
# Parameter 0:
#  The number to convert.
# Parameter 1:
#  The minimum number of digits to return.
# Returns:
#  The number as hexidecimal.
################################################################################

sub ToHex
{	return sprintf("%.$_[1]X",$_[0]);}

################################################################################
# Create PPM Output File
################################################################################
# Creates a ASCII PPM image file from an array of cell colours.
################################################################################
# Parameter 0:
#  Output file name
# Parameter 1:
#  Pointer to a 2d array of cell colours (in PPM RGB ASCII format with range
#   0 to 255 per channel).
################################################################################

sub CreatePpmOutputFile
{	my $FileName=$_[0];
	my @Colours=@{$_[1]};
	my $WidthPath=$Settings{'WidthPath'};
	my $WidthWall=$Settings{'WidthWall'};
	# Calculate sizes
	my $SizeCellsX=@Colours;
	my $SizeCellsY=@{$Colours[0]};
	my $SizePixelsX=int($SizeCellsX/2)*$WidthPath+
			int(($SizeCellsX+1)/2)*$WidthWall;
	my $SizePixelsY=int($SizeCellsY/2)*$WidthPath+
			int(($SizeCellsY+1)/2)*$WidthWall;
	# Create PGM image file & output header
	open(File,">$FileName")||die("Cannot write to '$FileName'.\n");
	print File "P3\n"; # Format = RGB ASCII
	print File	"$SizePixelsX $SizePixelsY\n"; # Width & height
	print File "255\n"; # 256 levels per channel
	# Write the pixel colours to the file
	my($X,$Y,$CellSizeX,$CellSizeY,$InCellX,$InCellY);
	for($Y=$SizeCellsY-1;$Y>=0;$Y--)
	{	# Iterate over pixels in cell in y-direction
		$CellSizeY=$Y%2?$WidthPath:$WidthWall;
		for($InCellY=0;$InCellY<$CellSizeY;$InCellY++)
		{	for($X=0;$X<$SizeCellsX;$X++)
			{	# Iterate over pixels in cell in x-direction
				$CellSizeX=$X%2?$WidthPath:$WidthWall;
				for($InCellX=0;$InCellX<$CellSizeX,;$InCellX++)
				{	# Write the pixel colour to the file
					print File "$Colours[$X][$Y]\n";}}}}
	# Finish the file
	close(File);}

################################################################################

