img2mosaic_threaded.pl
Threaded mosaic image filter:
"turtles" acting in parallel.

The advent of affordable multi-core systems is opening up a new chapter in desktop computing. The performance challenge has clearly shifted to the software front as it strives to exploit such architectures as readily as possible. Whilst pondering these developments, my thoughts drifted first to the photo-mosaic video I recently created (ugolino_640x480_256.avi) and then to the Perl code used to generate it (img2photomosaic_slides.pl). Wouldn't it be great to have all those "turtles" running around simultaneously? The answer is of course a resounding "yes" but how would one go about it?

I opted to start my attempts in parallel processing by revisiting the simpler task of converting an image into a mosaic of rectangular or square "tiles", each of a single color. I first reworked the code for img2mosaic.pl using instead the "turtle" controls from img2photomosaic_slides.pl and then made the turtles act simultaneously instead of each one in turn. Given that the code is written in Perl with ImageMagick's drawing primitives accessed through its PerlMagick interface, the final objective was tackled using Perl's threading implementation called interpreter threads or, more simply, "ithreads". (Achieving true parallel processing is, of course, contingent on the OS running the various threads across different processors.) The following on-line resources are a great starting point in learning about ithreads:

The Perl script img2mosaic_threaded.pl, displayed below, implements a "work crew" model. It is released for personal, non-commercial and non-profit use only. The listing includes the line numbers in order to reference them in the following general remarks.

There remains one nagging question. Invoking the program with the same command line parameters as with the un-threaded version, results in a different mosaic:
Un-threaded version

Mona Lisa mosaic - unthreaded version

Size of this preview: 193 x 300 pixels.
Full resolution (745 x 1,158 pixels).
Threaded version

Mona Lisa mosaic - threaded version

Size of this preview: 193 x 300 pixels.
Full resolution (745 x 1,158 pixels).

Needless to say, despite numerous attempts, I've been unable to eliminate the differences. Could there be subtle issues involving an image blob or other aspect of pixel caching? The answer remains elusive.

In the near future, I hope to acquire a quad-core platform. When that happens, I'll report the computational times versus a single core unit, along with the times for different bale sizes. In the meantime, I'd love to hear from anyone running this code on a multi-core system.

In conclusion, this experiment in threading has been quite interesting. Despite the increase in code size, the turtle paradigm was a natural fit to threads. In fact, I have to wonder if it might not be a worthwhile approach in controlling any sort of threaded analysis over a planar region.

As always, if you have any questions regarding the code or my explanations, please do not hesitate in contacting me.


img2mosaic_threaded.pl -- (Download latest version  - MD5 checksum )
001 use strict;
002 use threads;
003 use threads::shared;
004 use File::Basename;
005 use Image::Magick;
006 use POSIX qw(HUGE_VAL);
007 #===== Copyright 2008, Webpraxis Consulting Ltd. - ALL RIGHTS RESERVED - Email: webpraxis@gmail.com ============================
008 # img2mosaic_threaded.pl: Threaded mosaic image filter.
009 #===============================================================================================================================
010 #           Usage : perl img2mosaic_threaded.pl ImageFile ColorChartFile TileWidth TileHeight Verbose
011 #       Arguments : ImageFile      = path of image file to be filtered.
012 #                   ColorChartFile = path of text file containing the hash declarations for the color chart.
013 #                   TileWidth      = width of a tile in pixels
014 #                   TileHeight     = height of a tile in pixels
015 #                   Verbose        = boolean flag for verbose output
016 #     Input Files : See arguments.
017 #    Output Files : Mosaic GIF file in the form "basename_wxh_px_tiles_threaded.gif" where "basename" denotes the basename of the
018 #                     source image file, "w" the tile width and "h" the tile height.
019 # Temporary Files : None.
020 #         Remarks : Requires PerlMagick. Used module from ImageMagick 6.3.7.
021 #         History : v1.0.0 - July 18, 2008  - Original release.
022 #===============================================================================================================================
023 # 0) INITIALIZE:
024 system( 'cls' ) if $^O =~ /^MSWin/;                                                         #clear screen if running Windows
025 print "$0\n", '=' x length($0), "\n\n";                                                     #display program name
026
027 my $SourceFile  = shift || die 'ERROR: No image file specified';                            #get path of source image file
028 my $ChartFile   = shift || die 'ERROR: No color-chart file specified';                      #get path of color-chart file
029 my $TileWidth   = shift || die 'ERROR: No tile width specified';                            #get pixel width of a tile
030 my $TileHeight  = shift || die 'ERROR: No tile height specified';                           #get pixel height of a tile
031 my $Verbose     = shift;                                                                    #get boolean flag for verbose mode
032 die 'ERROR: Cannot locate image file'       unless -e $SourceFile;
033 die 'ERROR: Cannot locate color-chart file' unless -e $ChartFile;
034
035 fileparse_set_fstype('');                                                                   #force Unix syntax
036 my ( $basename,                                                                             #extract basename &
037      $path                                                                                  #extract path
038    )            = fileparse( $SourceFile, '\..*' );                                         # of source image file
039 my $MosaicFile  = "$path${basename}_${TileWidth}x${TileHeight}_px_tiles_threaded.gif";      #compose filename of mosaic image
040 undef $basename;                                                                            #discard to minimise data copying to threads
041 undef $path;
042
043 my %ColorChart;                                                                             #hash of tile id codes => rgb color values
044 push @ARGV, $ChartFile;                                                                     #set up reading of color chart
045 eval '%ColorChart = (' . join( '', <> ) . ')';                                              #load chart into hash
046 undef $ChartFile;                                                                           #discard to minimise data copying to threads
047
048 my %TileRGB;                                                                                #hash of arrays for each color channel value
049 my %TileColor;                                                                              #hash of rgb fill attributes
050 my $key;                                                                                    # chart hash key
051 my $value;                                                                                  # chart hash value
052 my @rgb;                                                                                    # array of rgb channel colors
053 while( ( $key, $value ) = each %ColorChart ) {                                              # repeat for each chart entry
054     @rgb = split / /, $value;                                                               #  extract hex channel colors
055     push @{ $TileRGB{ $key } }, hex for @rgb;                                               #  create RGB array of decimal values
056     $TileColor{ $key }  = '#' . join '', @rgb;                                              #  create rgb fill attribute
057 }                                                                                           # until all chart entries processed
058 undef %ColorChart;                                                                          #discard to minimise data copying to threads
059 undef $key;
060 undef $value;
061 undef @rgb;
062 #-------------------------------------------------------------------------------------------------------------------------------
063 # 1) SETUP MOSAIC IMAGE:
064 my $source              = Image::Magick->new;                                               #instantiate an image object for the source
065 my ( $sourceWidth,                                                                          #get width &
066      $sourceHeight                                                                          #get height
067    )                    = ( $source->Ping( $SourceFile ) )[0,1];                            #of source
068 undef $source;                                                                              #discard to minimise data copying to threads
069
070 my $No_Tile_Cols        = $sourceWidth / $TileWidth;                                        #compute raw number of tile columns
071    $No_Tile_Cols        = int( ++$No_Tile_Cols )                                            #adjust to an integral number
072                             unless $No_Tile_Cols == int( $No_Tile_Cols );                   # if necessary
073 my $No_Tile_Rows        = $sourceHeight / $TileHeight;                                      #compute raw number of tile rows
074    $No_Tile_Rows        = int( ++$No_Tile_Rows )                                            #adjust to an integral number
075                             unless $No_Tile_Rows == int( $No_Tile_Rows );                   # if necessary
076
077 my $MosaicWidth         = $No_Tile_Cols * $TileWidth;                                       #compute pixel width of mosaic image
078 my $MosaicHeight        = $No_Tile_Rows * $TileHeight;                                      #compute pixel height of mosaic image
079 my $Mosaic              = Image::Magick->new( magick=>"GIF" );                              #instantiate an image object for the mosaic
080    $Mosaic->Read( $SourceFile );                                                            #init mosaic with source image file
081    $Mosaic->Quantize( colors=>scalar keys %TileColor, colorspace=>'RGB' );                  #insure correct colorspace
082    $Mosaic->Scale( geometry=>"${MosaicWidth}x${MosaicHeight}!" );                           #scale mosaic image
083
084 my @MosaicBlob :shared  = $Mosaic->ImageToBlob();                                           #convert mosaic image to a blob
085 undef $Mosaic;                                                                              #thrash object as pixel cache won't survive threading
086
087 print "Source Image:   $SourceFile\n",                                                      #echo initialization results
088       "  Width:        $sourceWidth px\n",
089       "  Height:       $sourceHeight px\n",
090       "Mosaic Image:   $MosaicFile\n",
091       "  Width:        $MosaicWidth px\n",
092       "  Height:       $MosaicHeight px\n",
093       "Mosaic Details: ", $No_Tile_Cols * $No_Tile_Rows, " tiles\n",
094       "  No. of rows:  $No_Tile_Rows\n",
095       "  No. of cols:  $No_Tile_Cols\n",
096       "  Tile width:   $TileWidth px\n",
097       "  Tile height:  $TileHeight px\n\n";
098 print( "Pause. Press the ENTER key to continue..." ), <STDIN> if $Verbose;
099 undef $SourceFile;                                                                          #discard to minimise data copying to threads
100 undef $sourceHeight;
101 undef $sourceWidth;
102 #-------------------------------------------------------------------------------------------------------------------------------
103 # 2) INITIALIZE A TURTLE AT EACH GRID CORNER:
104 my @Bale :shared;                                                                           #array of hashes for the turtle headings & coordinates
105 my %turtle0 :shared = ( HEADING => 'S', X => 0,                 Y => 0                 );   #definition for turtle at top left corner
106 my %turtle1 :shared = ( HEADING => 'W', X => $No_Tile_Cols - 1, Y => 0                 );   #definition for turtle at top right corner
107 my %turtle2 :shared = ( HEADING => 'N', X => $No_Tile_Cols - 1, Y => $No_Tile_Rows - 1 );   #definition for turtle at bottom right corner
108 my %turtle3 :shared = ( HEADING => 'E', X => 0,                 Y => $No_Tile_Rows - 1 );   #definition for turtle at bottom left corner
109 push @Bale, \%turtle0, \%turtle1, \%turtle2, \%turtle3;                                     #init bale
110 #-------------------------------------------------------------------------------------------------------------------------------
111 # 3) DEFINE AN OPERATIONAL ZONE FOR EACH TURTLE:
112 my $midRow = int( $No_Tile_Rows / 2 );                                                      #compute a mid grid row number
113 my $midCol = int( $No_Tile_Cols / 2 );                                                      #compute a mid drid column number
114 my @Zones :shared;                                                                          #array of hashes for defining the zone edges
115 my %edges0 :shared = ( TOP => 0,           BOTTOM => $midRow,           LEFT => 0,           RIGHT => $midCol           );
116 my %edges1 :shared = ( TOP => 0,           BOTTOM => $midRow,           LEFT => $midCol + 1, RIGHT => $No_Tile_Cols - 1 );
117 my %edges2 :shared = ( TOP => $midRow + 1, BOTTOM => $No_Tile_Rows - 1, LEFT => $midCol + 1, RIGHT => $No_Tile_Cols - 1 );
118 my %edges3 :shared = ( TOP => $midRow + 1, BOTTOM => $No_Tile_Rows - 1, LEFT => 0,           RIGHT => $midCol           );
119 push @Zones, \%edges0, \%edges1, \%edges2, \%edges3;                                        #init zones
120 undef $midRow;                                                                              #discard to minimise data copying to threads
121 undef $midCol;
122 #-------------------------------------------------------------------------------------------------------------------------------
123 # 4) RECORD THE INITIAL TURTLE LOCATIONS:
124 my @Tracks :shared;                                                                         #array for bale's locations & the best matching tile names
125 $Tracks[$_] = &share([]) for ( 0..$No_Tile_Cols - 1 );                                      #extend as array of arrays by adding shared leaf nodes
126 $Tracks[$_{X}][$_{Y}] = 1 for ( @Bale );                                                    #record each initial location
127 #-------------------------------------------------------------------------------------------------------------------------------
128 # 5) FIND THE BEST TILE MATCHES:
129 my @No_Consecutive_Turns :shared;                                                           #array for number of consecutive left turns by each turtle
130 my @No_Matches :shared;                                                                     #array for number of tiles matched by each turtle
131 my @threads;                                                                                #array of threads
132 print "Finding the best tile matches...\n";                                                 #inform user
133 $threads[$_] = threads->create( \&launchTurtle, $_ ) for ( 0..$#Bale );                     #launch a thread for each turtle
134 $threads[$_]->join() for ( 0..$#Bale );                                                     #wait for each thread to exit
135 #-------------------------------------------------------------------------------------------------------------------------------
136 # 6) OUTPUT THE MOSAIC:
137 my $best_tile;                                                                              #name of best matching tile
138 my $colNo;                                                                                  #tile column number
139 my $rowNo;                                                                                  #tile row number
140 my $x_top_left;                                                                             #x-coordinate of a tile's top-left corner
141 my $y_top_left;                                                                             #y-coordinate of a tile's top-left corner
142 my $x_bottom_right;                                                                         #x-coordinate of a tile's bottom-right corner
143 my $y_bottom_right;                                                                         #y-coordinate of a tile's bottom-right corner
144
145 print "Creating mosaic image...\n";                                                         #inform user
146 $Mosaic = Image::Magick->new( magick=>"GIF" );                                              #re-instantiate an image object for the mosaic
147 $Mosaic->Set(size=>"${MosaicWidth}x${MosaicHeight}!");                                      #set canvas size
148 $Mosaic->Quantize( colors=>scalar keys %TileColor, colorspace=>'RGB', dither=>0 );          #insure correct colorspace & no dithering
149 $Mosaic->ReadImage('xc:none');                                                              #set no background
150
151 for $rowNo ( 0..$No_Tile_Rows - 1 ) {                                                       #repeat for each tile row
152     $y_top_left     = $rowNo * $TileHeight;                                                 # image y-coordinate of the tile's top-left corner
153     $y_bottom_right = $y_top_left + $TileHeight - 1;                                        # image y-coordinate of the tile's bottom-right corner
154     for $colNo ( 0..$No_Tile_Cols - 1 ) {                                                   # repeat for each tile column
155         $x_top_left     = $colNo * $TileWidth;                                              #  image x-coordinate of the tile's top-left corner
156         $x_bottom_right = $x_top_left + $TileWidth - 1;                                     #  image x-coordinate of the tile's bottom-right corner
157         $best_tile      = $Tracks[$colNo][$rowNo];                                          #  retrieve name of best matching tile
158         $Mosaic->Draw( primitive    => 'rectangle',                                         #  draw the tile
159                        fill         => $TileColor{ $best_tile },
160                        points       => "$x_top_left,$y_top_left $x_bottom_right,$y_bottom_right"
161                      );
162     }                                                                                       # until all tile columns processed
163 }                                                                                           #until all tile rows processed
164 $Mosaic->Write( filename => $MosaicFile );                                                  #save the mosaic to file
165 #-------------------------------------------------------------------------------------------------------------------------------
166 # 7) REPORT THE NUMBER OF TILES MATCHED BY EACH TURTLE:
167 my $totalMatches;                                                                           #total number of tile matches
168 print "\n";
169 for ( 0..$#No_Matches ) {                                                                   #repeat for each turtle
170     print "Turtle #$_ matched $No_Matches[$_] tiles ";                                      # report count
171     printf "(%4.1f\%)\n", int ( 100 * $No_Matches[$_] / ( $No_Tile_Cols * $No_Tile_Rows ) );
172     $totalMatches += $No_Matches[$_];                                                       # update total
173 }                                                                                           #until all turtles processed
174 print "\n\aOOOPS! Number of tile matches ($totalMatches) doesn't equal the tile count!"     #report any discrepancy
175  unless $totalMatches == $No_Tile_Cols * $No_Tile_Rows;
176 exit;                                                                                       #end processing
177 #===== SUBROUTINES =============================================================================================================
178 #            Usage : &launchTurtle( $TURTLE );
179 #          Purpose : Governs the movement of a threaded turtle to find the best matching tiles.
180 #        Arguments : $TURTLE = turtle's array index for @Bale.
181 #        Externals : $TileHeight, $TileRGB, $TileWidth, $Verbose
182 # Shared Externals : @Bale, @MosaicBlob, @No_Consecutive_Turns, @No_Matches, @Tracks, @Zones
183 #             Subs : None.
184 #          Remarks : Names of the best matching tiles are stored in @Tracks.
185 #          History : v1.0.0 - July 18, 2008 - Original release.
186
187 sub launchTurtle {                                                                          #begin sub
188     my $turtle  = shift;                                                                    # turtle index
189     my $mosaic  = Image::Magick->new( magick=>"GIF" );                                      # instantiate an image object for the image
190        $mosaic->BlobToImage( @MosaicBlob );                                                 # init image object
191
192     &findBestTile();                                                                        # find best matching tile at starting location
193     until( &spunAround() ) {                                                                # repeat until end of trek
194         if( &noBoundaryAhead() and &noTracksAhead() ) {                                     #  if next step forward is permitted
195             &stepForward();                                                                 #   move forward one step
196             &findBestTile();                                                                #   find matching best tile at location
197         } else {                                                                            #  else
198             &turnLeft();                                                                    #   turn left
199         }                                                                                   #  end if-else
200     }                                                                                       # until turtle has spun around completely
201     threads->exit();                                                                        #end thread processing
202
203     #            Usage : &spunAround();
204     #          Purpose : Checks if a turtle has done 4 consecutive left turns. If so, returns true, otherwise false.
205     #        Arguments : None.
206     #        Externals : $turtle
207     # Shared Externals : @No_Consecutive_Turns
208     #             Subs : None.
209     #          Remarks : None.
210     #          History : v1.0.0 - July 18, 2008 - Original release.
211
212     sub spunAround {                                                                        # begin sub
213         lock( @No_Consecutive_Turns );                                                      #  lock no-of-turns array for read
214         return $No_Consecutive_Turns[$turtle] == 4;                                         #  return check
215     }                                                                                       # end sub spunAround
216
217     #            Usage : &bestTileMatch( $RED, $GREEN, $BLUE );
218     #          Purpose : Matches a tile to the mean RGB channel values of an image area. Returns the name of the tile.
219     #        Arguments : $RED   = mean 8-bit red color
220     #                    $GREEN = mean 8-bit green color
221     #                    $BLUE  = mean 8-bit blue color
222     #        Externals : $TileRGB, $Verbose
223     # Shared Externals : None.
224     #             Subs : None.
225     #          Remarks : None.
226     #          History : v1.0.0 - July 18, 2008 - Original release.
227
228     sub bestTileMatch {                                                                     # begin sub
229         my ( $red, $green, $blue )  = @_;                                                   #  parametrize the arguments
230         my $bestTile;                                                                       #  name of tile that best matches mean color
231         my $deltaRed;                                                                       #  difference in red color values
232         my $deltaGreen;                                                                     #  difference in green color values
233         my $deltaBlue;                                                                      #  difference in blue color values
234         my $key;                                                                            #  hash key = tile name
235         my $value;                                                                          #  hash value = array of channel color intensities
236         my $metric;                                                                         #  squared Euclidean metric of intensities
237         my $minMetric               = &POSIX::HUGE_VAL;                                     #  minimum value of the metric
238
239         while( ( $key, $value ) = each %TileRGB ) {                                         #  repeat for each possible tile
240             $deltaRed   = $red   - @{$value}[0];                                            #   compute difference in red colors
241             $deltaGreen = $green - @{$value}[1];                                            #   compute difference in green colors
242             $deltaBlue  = $blue  - @{$value}[2];                                            #   compute difference in blue colors
243             $metric     = $deltaRed   * $deltaRed   +                                       #   compute metric
244                           $deltaGreen * $deltaGreen +
245                           $deltaBlue  * $deltaBlue;
246             $minMetric  = $metric, $bestTile = $key if $metric < $minMetric;                #   update minimum metric & possible best tile
247         }                                                                                   #  until all tiles processed
248         print "=> $bestTile\n" if $Verbose;                                                 #  report if requested
249         return $bestTile;                                                                   #  return best matching tile name
250     }                                                                                       # end sub bestTileMatch
251
252     #            Usage : &meanColor( @COLORS );
253     #          Purpose : Returns the mean channel color as an 8-bit value.
254     #        Arguments : @COLORS = list of normalized channel intensities to be averaged.
255     #        Externals : $Verbose
256     # Shared Externals : None.
257     #             Subs : None.
258     #          Remarks : None.
259     #          History : v1.0.0 - July 18, 2008 - Original release.
260
261     sub meanColor {                                                                         # begin sub
262         my $mean;                                                                           #  mean 8-bit color value
263
264         $mean += $_ for @_;                                                                 #  sum all the normalized color values
265         $mean  = 256 * $mean / scalar @_;                                                   #  compute the mean 8-bit value
266         printf "%5.1f ", $mean if $Verbose;                                                 #  report if requested
267         return $mean;                                                                       #  return the mean
268     }                                                                                       # end sub meanColor
269
270     #            Usage : &findBestTile();
271     #          Purpose : Finds the best matching tile for the image area corresponding to a turtle's location.
272     #        Arguments : None.
273     #        Externals : $mosaic, $turtle, $TileHeight, $TileWidth, $Verbose
274     # Shared Externals : @Bale, @No_Matches, @Tracks
275     #             Subs : &bestTileMatch, &meanColor.
276     #          Remarks : The tile name is stored in the shared array @Tracks.
277     #          History : v1.0.0 - July 18 - Original release.
278
279     sub findBestTile {                                                                      # begin sub
280         my $x_top_left;                                                                     #  image x-coordinate of a tile's top-left corner
281         my $y_top_left;                                                                     #  image y-coordinate of a tile's top-left corner
282         {                                                                                   #  start naked block to scope lock
283             lock( @Bale );                                                                  #   lock bale array for read
284             $x_top_left = $Bale[$turtle]{X} * $TileWidth;                                   #   compute x-coordinate of a tile's top-left corner
285             $y_top_left = $Bale[$turtle]{Y} * $TileHeight;                                  #   compute y-coordinate of a tile's top-left corner
286         }                                                                                   #  end naked block, releasing lock
287         my $geometry    = "${TileWidth}x${TileHeight}+$x_top_left+$y_top_left";             #  geometry of a tile: width, height & x,y offsets
288         my $best_tile;                                                                      #  name of best matching tile
289
290         print "Turtle #$turtle: findBestTile - " if $Verbose;                               #  report progress if requested
291         $best_tile  = &bestTileMatch (                                                      #  match mean image-area colors to a tile:
292                             &meanColor (                                                    #   compute mean red color:
293                                 $mosaic->GetPixels (                                        #    get normalized red intensities of all pixels
294                                     map         => 'r',
295                                     geometry    => $geometry,
296                                     normalize   => 'true'
297                                 )
298                             ),
299                             &meanColor (                                                    #   compute mean green color:
300                                 $mosaic->GetPixels (                                        #    get normalized green intensities of all pixels
301                                     map         => 'g',
302                                     geometry    => $geometry,
303                                     normalize   => 'true'
304                                 )
305                             ),
306                             &meanColor (                                                    #   compute mean blue color:
307                                 $mosaic->GetPixels (                                        #    get normalized blue intensities of all pixels
308                                     map         => 'b',
309                                     geometry    => $geometry,
310                                     normalize   => 'true'
311                                 )
312                             )
313                       );
314         lock( @Bale );                                                                      #  lock bale array for read
315         lock( @Tracks );                                                                    #  lock tracks array for write
316         $Tracks[$Bale[$turtle]{X}][$Bale[$turtle]{Y}] = $best_tile;                         #  record name of best matching tile
317         lock( @No_Matches );                                                                #  lock no-of-matches array for write
318         ++$No_Matches[$turtle];                                                             #  update match count
319     }                                                                                       # end sub findBestTile
320
321     #            Usage : &noBoundaryAhead();
322     #          Purpose : Returns true if the next grid location ahead is beyond an operational boundary. Otherwise, returns false.
323     #        Arguments : None.
324     #        Externals : $turtle
325     # Shared Externals : @Bale, @Zones
326     #             Subs : None.
327     #          Remarks : None.
328     #          History : v1.0.0 - July 18, 2008 - Original release.
329
330     sub noBoundaryAhead {                                                                   # begin sub
331         lock( @Bale );                                                                      #  lock bale array for read
332         lock( @Zones );                                                                     #  lock zone array for read
333         if   ( $Bale[$turtle]{HEADING} eq 'S' ) { return !( $Bale[$turtle]{Y} + 1 > $Zones[$turtle]{BOTTOM} ) }
334         elsif( $Bale[$turtle]{HEADING} eq 'N' ) { return !( $Bale[$turtle]{Y} - 1 < $Zones[$turtle]{TOP}    ) }
335         elsif( $Bale[$turtle]{HEADING} eq 'E' ) { return !( $Bale[$turtle]{X} + 1 > $Zones[$turtle]{RIGHT}  ) }
336         elsif( $Bale[$turtle]{HEADING} eq 'W' ) { return !( $Bale[$turtle]{X} - 1 < $Zones[$turtle]{LEFT}   ) }
337     }                                                                                       # end sub noBoundaryAhead
338
339     #            Usage : &noTracksAhead( $TURTLE );
340     #          Purpose : Returns true if the next grid location ahead has not been visited. Otherwise, returns false.
341     #        Arguments : None.
342     #        Externals : $turtle
343     # Shared Externals : @Bale, @Tracks
344     #             Subs : None.
345     #          Remarks : None.
346     #          History : v1.0.0 - July 18, 2008 - Original release.
347
348     sub noTracksAhead {                                                                     # begin sub
349         lock( @Bale );                                                                      #  lock bale array for read
350         lock( @Tracks );                                                                    #  lock tracks array for read
351         if   ( $Bale[$turtle]{HEADING} eq 'S' ) { return !$Tracks[$Bale[$turtle]{X}    ][$Bale[$turtle]{Y} + 1] }
352         elsif( $Bale[$turtle]{HEADING} eq 'N' ) { return !$Tracks[$Bale[$turtle]{X}    ][$Bale[$turtle]{Y} - 1] }
353         elsif( $Bale[$turtle]{HEADING} eq 'E' ) { return !$Tracks[$Bale[$turtle]{X} + 1][$Bale[$turtle]{Y}    ] }
354         elsif( $Bale[$turtle]{HEADING} eq 'W' ) { return !$Tracks[$Bale[$turtle]{X} - 1][$Bale[$turtle]{Y}    ] }
355     }                                                                                       # end sub noTracksAhead
356
357     #            Usage : &stepForward();
358     #          Purpose : Repositions a turtle to an adjacent grid location in accordance with its heading.
359     #        Arguments : None.
360     #        Externals : $turtle, $Verbose
361     # Shared Externals : @Bale, @No_Consecutive_Turns, @Tracks
362     #             Subs : None.
363     #          Remarks : None.
364     #          History : v1.0.0 - July 18, 2008 - Original release.
365
366     sub stepForward {                                                                       # begin sub
367         lock( @Bale );                                                                      #  lock bale array for read-write
368         if   ( $Bale[$turtle]{HEADING} eq 'S' ) { ++$Bale[$turtle]{Y} }
369         elsif( $Bale[$turtle]{HEADING} eq 'N' ) { --$Bale[$turtle]{Y} }
370         elsif( $Bale[$turtle]{HEADING} eq 'E' ) { ++$Bale[$turtle]{X} }
371         elsif( $Bale[$turtle]{HEADING} eq 'W' ) { --$Bale[$turtle]{X} }
372
373         lock( @No_Consecutive_Turns );                                                      #  lock no-of-turns array for write
374         $No_Consecutive_Turns[$turtle]                  = 0;                                #  reset turtle's turn count
375         lock( @Tracks );                                                                    #  lock tracks array for write
376         $Tracks[$Bale[$turtle]{X}][$Bale[$turtle]{Y}]   = 1;                                #  update bale's accessed locations
377         print "Turtle #$turtle: stepForward - $Bale[$turtle]{X}, $Bale[$turtle]{Y}\n"       #  report new location if requested
378          if $Verbose;
379     }                                                                                       # end sub stepForward
380
381     #            Usage : &turnLeft( );
382     #          Purpose : Changes a turtle's heading in accordance with a left turn.
383     #        Arguments : None.
384     #        Externals : $turtle, $Verbose
385     # Shared Externals : @Bale, @No_Consecutive_Turns
386     #             Subs : None.
387     #          Remarks : None.
388     #          History : v1.0.0 - July 18, 2008 - Original release.
389
390     sub turnLeft {                                                                          # begin sub
391         lock( @Bale );                                                                      #  lock bale array for write
392         $Bale[$turtle]{HEADING} =~ tr/NSEW/WENS/;                                           #  change heading to the left
393         lock( @No_Consecutive_Turns );                                                      #  lock no-of-turns array for write
394         ++$No_Consecutive_Turns[$turtle];                                                   #  update turtle's turn count
395         print "Turtle #$turtle: turnLeft - $Bale[$turtle]{HEADING}\n" if $Verbose;          #  report new heading if requested
396     }                                                                                       # end sub turnLeft
397 }                                                                                           #end sub launchTurtle
398 #===== Copyright 2008, Webpraxis Consulting Ltd. - ALL RIGHTS RESERVED - Email: webpraxis@gmail.com ============================
399 # end of img2mosaic_threaded.pl
  

© 2024 Webpraxis Consulting Ltd. – ALL RIGHTS RESERVED.

Valid HTML 4.01 Transitional Valid CSS!