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.
perl img2mosaic_threaded.pl Mona_Lisa.jpg legoChart.txt 5 6
Note that the "Verbose" parameter can be omitted or set to "0" to suppress the computational results for each tile. If set, say "1", then details of thread execution will be displayed. For example:
A turtle's number precedes the name of a subroutine that is executing. For "findBestTile", the three numbers that follow are the average values of the rgb intensities (in that order) for the image area being analyzed. This in turn is followed by the name of the best matching tile. Subroutine "stepForward" reports the turtle's new x and y grid coordinates whereas "turnLeft" prints the new heading after the turn.
BTW, the tile dimension of 5 x 6 pixels was chosen for no other reason than the width to height ratio of an interlocked unit LEGO™ brick is 5 to 6 (see http://www.owlnet.rice.edu/~elec201/Book/legos.html).
Mona_Lisa_5x6_px_tiles_threaded.gif
BTW, contrary to img2mosaic.pl, generating the assembly instructions has been omitted because the focus here is on threading.'1-White' => 'F2 F3 F2',
will result in@{ $TileRGB{ '1-White' } } = (242,243,242) (used for finding the best matching tile) and
$TileColor{ '1-White' } = '#F2F3F2' (used for drawing the tile).
With "@Bale" declared as a shared array, it is not permitted to push an anonymous hash onto the array as was done for img2photomosaic_slides.pl. As stated in the documentation, "only simple values or references to shared variables are allowed." So to construct the array of hashes, I opted to declare each hash as a shared variable. References to theses hashes are then be pushed onto the array to complete the set up. The alternative method would involve adding a leaf node to each element of the array and then declaring each key-value pairs. The latter technique is rather too tedious in this case.
The sequential nature of the code processing in
img2photomosaic_slides.pl did not
warrant guarding against this problem. In a threaded environment, the issue of lock starvation
(see
http://www.onjava.com/pub/a/onjava/2004/10/20/threads2.html?page=4 in the context of Java)
for instance causes the CPU cycles to be unevenly allocated and thus favor on thread over another. Consequently
some turtles appear to be on steroids! If allowed to run around the whole image area, such turtles quickly
catch up with their slower mates. And when this occurs in a corner (as illustrated on the left), this can
lead to a collision as two turtles can both get the go ahead to move into the same grid square. Such a collision
therefore causes one of the turtles to be boxed in and come to a premature halt. In addition, it puts the onus
on the remaining turtle to cover additional grid areas.
All things being equal for the remaining threads, the expected performance benefit severely degrades in such a scenario. Hence, collision avoidance plays an important role. A thread semaphore could be introduced to act as a traffic cop, allowing only one turtle to move at any given moment. However, this counter-measure would increase the processing time. It seems far simpler and computationally more efficient to confine the turtles to non-overlapping areas of the image in which they can run as fast as possible.
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
Full resolution (745 x 1,158 pixels). |
Threaded version
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.
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
© 2012 Webpraxis Consulting Ltd. – ALL RIGHTS RESERVED.