anim_blinds.pl
Creates an animated GIF that rotates horizontal/vertical 3D "blinds" but with multiple images.

In my previous article regarding the Perl script anim_flipcard.pl, I discussed how to either flip or twirl a 3D "card" displaying multiple images. Time now to move to the next level of complexity where the images are sliced into slats. These in turn are made to rotate, sequentially revealing the constituent images, as if mimicking a set of either horizontal or vertical 3D "blinds". Using our usual set of four reference images, the following animation is an example:

Leonardo da Vinci, Mona_Lisa, St. Anne

The coding required to pull this off is truly an extension of anim_flipcard.pl. For those unfamiliar with the principles governing the illusion and the methods used to create it, that article should be consulted first before proceeding further. For those in the know, I'll simply say that the key here is that each slat must be treated in the same manner as a source image in anim_flipcard.pl. The reason follows from the definition of ImageMagick's "Distort" command: one can only distort a whole image. Consequently, one cannot simply lay the slats flat on a canvas and mapped each one to the requisite parallelogram. Rather, for each slat, one must initialize an image object with a slat source image, distort it and then place the result at the appropriate location on the frame canvas.

As with all our previous animation scripts, an XML file conveys the animation specifications. To explain the required XML tags, let's examine the data file demo_horizontalblinds.xml ( [Download demo_horizontalblinds.xml]   [MD5 checksum] ). It governs the creation of the following animated GIF image:

Leonardo da Vinci, Mona_Lisa, St. Anne
XML file "demo_horizontalblinds.xml" Remarks
<animation> start of XML declaration
<simulation>horizontal</simulation> the type of simulation. The other valid option is vertical.
<output>demo_horizontalBlinds.gif</output> name of the output file
<frames>10</frames> the number of intermediate, interpolated frames for each quarter turn
<delay>25</delay> the number of milliseconds in delaying the image views
<loops>0</loops> the number of times to cycle the animated GIF: 0 results in infinite looping
<slats> start of specifications for all the slats
<width>25</width> in pixels. Measurement of the shorter dimension, be it for horizontal or vertical blinds
<stretch>5</stretch> in pixels. Vertical or horizontal distance along the axis of "rotation" of the theoretical corner end points from a slat-image side.
</slats> end of slat specifications
<imageDims> all constituent images to be scaled to the following pixel dimensions. They are applied to the images prior to the final dimension adjustments for an integral number of slats.
<width>100</width> in pixels
<height>150</height> in pixels
</imageDims> end of image specifications
<image>Leonardo.jpg</image> filename of the 1st image. Images are processed in the order of their declaration.
<image>Mona_Lisa.jpg</image> filename of the 2nd image
<image>webpraxis.jpg</image> filename of the 3rd image
<image>St_Anne.jpg</image> filename of the 4th image
</animation> end of XML declaration

For the processing phase, Perl is used along with ImageMagick's drawing primitives accessed through its PerlMagick interface. The Perl script anim_blinds.pl, displayed below, is the resulting code. It is released for personal, non-commercial and non-profit use only. Note in passing that, as with the aforementioned previous script, no attempt has been made to render the code more compact or increase its computational efficiency. The emphasis is strictly on clarity where repetition has been favored over abstraction.

The listing includes the line numbers in order to reference them in the following general remarks.

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


anim_blinds.pl -- [Download latest version: v1.0.0 - September 28, 2009]   [MD5 checksum]
001 use strict;
002 use warnings;
003 use POSIX qw(ceil);
004 use Image::Magick;
005 use Term::ANSIScreen qw/:color :cursor :screen/;
006 use Win32::Console::ANSI qw/ Cursor /;
007 use XML::Simple;
008     $XML::Simple::PREFERRED_PARSER  = 'XML::Parser';
009 use constant FATAL => colored ['white on red'], "\aFATAL ERROR: ";
010 #===== Copyright 2009, Webpraxis Consulting Ltd. - ALL RIGHTS RESERVED - Email: webpraxis@gmail.com ============================
011 # anim_blinds.pl: Creates an animated GIF that rotates horizontal/vertical 3D "blinds" but with multiple images.
012 #===============================================================================================================================
013 #           Usage : perl anim_blinds.pl XmlFile
014 #       Arguments : XmlFile = path of XML file for the animation specifications.
015 #     Input Files : See arguments.
016 #    Output Files : The animated GIF specified in the XML data file.
017 # Temporary Files : None.
018 #         Remarks : See http://www.webpraxis.ab.ca/flips/anim_blinds.shtml for details.
019 #         History : v1.0.0 - September 28, 2009 - Original release.
020 #===============================================================================================================================
021 # 0) INITIALIZE:
022 $| = 1;                                                                                 #set STDOUT buffer to auto-flush
023 cls();                                                                                  #clear screen
024 print colored ['black on white'], "$0\n\n\n",                                           #display program name
025       colored ['reset'], 'Initializing... ';                                            #report start of initialization
026
027 my $XmlFile     = shift || die FATAL, 'No XML file specified';                          #get path of XML data file
028 my $Anim        = XMLin( $XmlFile, ForceArray => [qw(image)] );                         #read the XML data file
029 my $ImgWidth    = $$Anim{imageDims}{width};                                             #parameterize the width of the images
030 my $ImgHeight   = $$Anim{imageDims}{height};                                            #parameterize the height of the images
031 my $SlatWidth   = $$Anim{slats}{width};                                                 #parameterize the width of a slat
032 my $SlatStretch = $$Anim{slats}{stretch};                                               #parameterize the slat distortion
033 my $Simulation  = $$Anim{simulation};                                                   #parameterize the simulation request
034 my $NoImages    = @{ $$Anim{image} };                                                   #get the number of source images
035 my @Spinners    = ( '-', '\\', '|', '/' );                                              #define symbols for spinner
036
037 my $NoSlats;                                                                            #number of slats per source image
038 my $CanvasWidth;                                                                        #frame canvas width in pixels
039 my $CanvasHeight;                                                                       #frame canvas height in pixels
040 if( $Simulation eq 'vertical' ) {                                                       #if vertical slats requested
041     $NoSlats        = POSIX::ceil( $ImgWidth / $SlatWidth );                            # compute integral number of slats
042     $ImgWidth       = $NoSlats * $SlatWidth;                                            # adjust source image width
043     $CanvasWidth    = $ImgWidth;                                                        # set frame canvas width
044     $CanvasHeight   = $ImgHeight + 2 * $SlatStretch;                                    # set frame canvas height
045 } elsif( $Simulation eq 'horizontal' ) {                                                #else if horizontal slats requested
046     $NoSlats        = POSIX::ceil( $ImgHeight / $SlatWidth );                           # compute integral number of slats
047     $ImgHeight      = $NoSlats * $SlatWidth;                                            # adjust source image height
048     $CanvasWidth    = $ImgWidth + 2 * $SlatStretch;                                     # set frame canvas width
049     $CanvasHeight   = $ImgHeight;                                                       # set frame canvas height
050 } else {                                                                                #else unknown/undefined simulation
051     die FATAL, "Invalid simulation request '$Simulation'\n";
052 }                                                                                       #end if-elsif-else
053 print colored ['bold green'], "XML data read\n\n";                                      #report end of initialization
054 #-------------------------------------------------------------------------------------------------------------------------------
055 # 1) LOAD AND SCALE THE IMAGES:
056 print 'Reading & scaling images... ';                                                   #report start of image processing
057 my $Images = Image::Magick->new( magick => 'JPG' );                                     #instantiate an object for the images
058
059 die FATAL, "Even number of images required\n" if $NoImages % 2;                         #check for even number of images
060 for( 0..$NoImages-1 ) {                                                                 #repeat for each source image
061     my $file = $$Anim{image}[$_];                                                       # parameterize the file name
062     die FATAL, "Cannot locate image file '$file'\n" unless -e $file;                    # check image existence
063     $Images->Read( $file );                                                             # read image file
064 }                                                                                       #until all images processed
065 $Images->Quantize( colors => 256, colorspace => 'RGB' );                                #ensure uniform color space
066 $Images->Scale( geometry => "${ImgWidth}x${ImgHeight}!" );                              #scale images to adjusted dimensions
067 print colored ['bold green'], "Done\n\n";                                               #report end of image processing
068 #-------------------------------------------------------------------------------------------------------------------------------
069 # 2) CREATE SLAT IMAGES:
070 print 'Creating slat images... ';                                                       #report start of slat processing
071 my ( $CursorX, $CursorY ) = Cursor();                                                   #record cursor position
072
073 my $Slats       = Image::Magick->new( magick => 'GIF' );                                #instantiate an image object for the slats
074 {                                                                                       #start naked block as firewall
075     my $slat    = Image::Magick->new( magick => 'GIF' );                                # instantiate an image object for a slat
076
077     if( $Simulation eq 'vertical' ) {                                                   # if vertical slats requested
078         for my $imgIdx ( 0..$NoImages-1 ) {                                             #  repeat for each source image
079             for (   my $x_topLeft   = 0;                                                #   for each slat: work left to right
080                     $x_topLeft      <= $ImgWidth - $SlatWidth;
081                     $x_topLeft      += $SlatWidth
082                 ) {
083                 my $geometry    = "${SlatWidth}x${ImgHeight}+$x_topLeft+0";             #    define slat geometry
084                 $slat           = $Images->[$imgIdx]->Clone();                          #    init slat with image
085                 $slat->Crop( geometry => $geometry );                                   #    crop slat area
086                 $slat->Set( page => '0x0+0+0' );                                        #    shrink canvas
087                 push @$Slats, @$slat;                                                   #    store the slat image
088                 print   locate( $CursorY, $CursorX ), clline,                           #    report slat processing
089                         colored ['bold yellow'], '(', $Spinners[$#$Slats % 4], ')';
090             }                                                                           #   until all slats created
091         }                                                                               #  until all images processed
092     } else {                                                                            # else horizontal slats requested
093         for my $imgIdx ( 0..$NoImages-1 ) {                                             #  repeat for each source image
094             for (   my $y_topLeft   = 0;                                                #   for each slat: work top to bottom
095                     $y_topLeft      <= $ImgHeight - $SlatWidth;
096                     $y_topLeft      += $SlatWidth
097                 ) {
098                 my $geometry    = "${ImgWidth}x${SlatWidth}+0+$y_topLeft";              #    define slat geometry
099                 $slat           = $Images->[$imgIdx]->Clone();                          #    init slat with image
100                 $slat->Crop( geometry => $geometry );                                   #    crop slat area
101                 $slat->Set( page => '0x0+0+0' );                                        #    shrink canvas
102                 push @$Slats, @$slat;                                                   #    store the slat image
103                 print   locate( $CursorY, $CursorX ), clline,                           #    report slat processing
104                         colored ['bold yellow'], '(', $Spinners[$#$Slats % 4], ')';
105             }                                                                           #   until all slats created
106         }                                                                               #  until all images processed
107     }                                                                                   # end if-else
108     undef $slat;                                                                        # destroy the slat image object
109 }                                                                                       #end naked block
110 print   locate( $CursorY, $CursorX ), clline,                                           #report end of slat processing
111         colored ['bold green'], scalar @$Slats, " slats\n\n";
112 undef $Images;                                                                          #destroy the source image object
113 #-------------------------------------------------------------------------------------------------------------------------------
114 # 3) CREATE ANIMATION FRAMES:
115 print 'Creating animation frames... ';                                                  #report start of frame processing
116 ( $CursorX, $CursorY )  = Cursor();                                                     #record cursor position
117
118 my $Canvas              = Image::Magick->new( magick => 'GIF' );                        #instantiate an image object for the frame canvas
119 my $Frames              = Image::Magick->new( magick => 'GIF' );                        #instantiate an image object for animation frames
120 my $FrameNo             = 0;                                                            #init no of animation frames
121 my $Interpolate         = sub   {   my ( $lambda, $start, $end ) = @_;                  #anonymous sub for interpolating x,y-coordinates
122                                     int( $lambda * $end + ( 1. - $lambda ) * $start );
123                                 };
124 my $DeltaLambda         = 1. / ( $$Anim{frames} + 1. );                                 #set turn step-size
125
126 my $SlatX_topLeft;                                                                      #top-left corner coords for 2D slat
127 my $SlatY_topLeft;
128 my $SlatX_bottomRight;                                                                  #bottom-right corner coords for 2D slat
129 my $SlatY_bottomRight;
130 my $SlatX_mid;                                                                          #mid coord along x-axis
131 my $SlatY_mid;                                                                          #mid coord along y-axis
132 if( $Simulation eq 'vertical' ) {                                                       #if vertical slats requested
133     $SlatX_mid          = ( $SlatWidth - 1 ) / 2;                                       # set mid coord along x-axis
134     $SlatX_topLeft      = 0;                                                            # set top-left corner coords
135     $SlatY_topLeft      = $SlatStretch;
136     $SlatX_bottomRight  = $SlatWidth - 1;                                               # set bottom-right corner coords
137     $SlatY_bottomRight  = $CanvasHeight - $SlatStretch - 1;
138 } else {                                                                                #else horizontal slats requested
139     $SlatY_mid          = ( $SlatWidth - 1 ) / 2;                                       # set mid coord along y-axis
140     $SlatX_topLeft      = $SlatStretch;                                                 # set top-left corner coords
141     $SlatY_topLeft      = 0;
142     $SlatX_bottomRight  = $CanvasWidth  - $SlatStretch - 1;                             # set bottom-right corner coords
143     $SlatY_bottomRight  = $SlatWidth;
144 }                                                                                       #end if-else
145
146 my $x_left;                                                                             #perspective apex coords for vertical slats
147 my $x_right;
148 my $y_topLeft;
149 my $y_bottomLeft;
150 my $y_topRight;
151 my $y_bottomRight;
152
153 my $y_top;                                                                              #perspective apex coords for horizontal slats
154 my $y_bottom;
155 my $x_topLeft;
156 my $x_bottomLeft;
157 my $x_topRight;
158 my $x_bottomRight;
159
160 for my $imgIdx ( 0..$NoImages - 1 ) {                                                   #repeat for each pairing of images
161     #QUARTER RIGHT-HAND-RULE TURN OF RECTO SLATS:
162     for( my $lambda = 0.; $lambda < 1. - $DeltaLambda/2.; $lambda += $DeltaLambda ) {   # repeat for each turn step
163         if( $Simulation eq 'vertical' ) {                                               #   interpolate apex coords
164             $x_left         = &$Interpolate( $lambda,   $SlatX_topLeft,     $SlatX_mid                          );
165             $x_right        = &$Interpolate( $lambda,   $SlatX_bottomRight, $SlatX_mid                          );
166             $y_topLeft      = &$Interpolate( $lambda,   $SlatY_topLeft,     0                                   );
167             $y_bottomLeft   = &$Interpolate( $lambda,   $SlatY_bottomRight, $SlatY_bottomRight  + $SlatStretch  );
168             $y_topRight     = &$Interpolate( $lambda,   $SlatY_topLeft,     $SlatY_topLeft      + $SlatStretch  );
169             $y_bottomRight  = &$Interpolate( $lambda,   $SlatY_bottomRight, $SlatY_bottomRight  - $SlatStretch  );
170             &drawVerticalSlats( $imgIdx );                                              #   draw all the image's slats
171         } else {                                                                        #   interpolate apex coords
172             $y_top          = &$Interpolate( $lambda,   $SlatY_topLeft,     $SlatY_mid                          );
173             $y_bottom       = &$Interpolate( $lambda,   $SlatY_bottomRight, $SlatY_mid                          );
174             $x_topLeft      = &$Interpolate( $lambda,   $SlatX_topLeft,     $SlatX_topLeft      + $SlatStretch  );
175             $x_bottomLeft   = &$Interpolate( $lambda,   $SlatX_topLeft,     0                                   );
176             $x_topRight     = &$Interpolate( $lambda,   $SlatX_bottomRight, $SlatX_bottomRight  - $SlatStretch  );
177             $x_bottomRight  = &$Interpolate( $lambda,   $SlatX_bottomRight, $SlatX_bottomRight  + $SlatStretch  );
178             &drawHorizontalSlats( $imgIdx );                                            #   draw all the image's slats
179         }                                                                               #  end if-else
180     }                                                                                   # until all turn steps processed
181     #QUARTER RIGHT-HAND-RULE TURN OF VERSO SLATS:
182     for(my $lambda=$DeltaLambda; $lambda < 1.-$DeltaLambda/2.; $lambda+=$DeltaLambda) { # repeat for each turn step
183         if( $Simulation eq 'vertical' ) {                                               #   interpolate apex coords
184             $x_left         = &$Interpolate( $lambda,   $SlatX_mid,                         $SlatX_topLeft      );
185             $x_right        = &$Interpolate( $lambda,   $SlatX_mid,                         $SlatX_bottomRight  );
186             $y_topLeft      = &$Interpolate( $lambda,   $SlatY_topLeft      + $SlatStretch, $SlatY_topLeft      );
187             $y_bottomLeft   = &$Interpolate( $lambda,   $SlatY_bottomRight  - $SlatStretch, $SlatY_bottomRight  );
188             $y_topRight     = &$Interpolate( $lambda,   0,                                  $SlatY_topLeft      );
189             $y_bottomRight  = &$Interpolate( $lambda,   $SlatY_bottomRight  + $SlatStretch, $SlatY_bottomRight  );
190             &drawVerticalSlats( ( $imgIdx + 1 ) % $NoImages );                          #   draw all the image's slats
191         } else {                                                                        #   interpolate apex coords
192             $y_top          = &$Interpolate( $lambda,   $SlatY_mid,                         $SlatY_topLeft      );
193             $y_bottom       = &$Interpolate( $lambda,   $SlatY_mid,                         $SlatY_bottomRight  );
194             $x_topLeft      = &$Interpolate( $lambda,   0,                                  $SlatX_topLeft      );
195             $x_bottomLeft   = &$Interpolate( $lambda,   $SlatX_topLeft      + $SlatStretch, $SlatX_topLeft      );
196             $x_topRight     = &$Interpolate( $lambda,   $SlatX_bottomRight  + $SlatStretch, $SlatX_bottomRight  );
197             $x_bottomRight  = &$Interpolate( $lambda,   $SlatX_bottomRight  - $SlatStretch, $SlatX_bottomRight  );
198             &drawHorizontalSlats( ( $imgIdx + 1 ) % $NoImages );                        #   draw all the image's slats
199         }                                                                               #  end if-else
200     }                                                                                   # until all turn steps processed
201 }                                                                                       #until all images processed
202 print   locate( $CursorY, $CursorX ), clline,                                           #report end of frame processing
203         colored ['bold green'], $FrameNo, " frames\n\n";
204 undef $Canvas;                                                                          #destroy the canvas object
205 undef $Slats;                                                                           #destroy the slats object
206 #-------------------------------------------------------------------------------------------------------------------------------
207 # 4) CREATE ANIMATED GIF IMAGE:
208 print 'Creating animated GIF image... ';                                                #report start of animation processing
209 $Frames->Write                                                                          #output the animation
210             (   delay       => $$Anim{delay},
211                 loop        => $$Anim{loops},
212                 dispose     => 'background',
213                 filename    => $$Anim{output}
214             );
215 print colored ['bold green'], $$Anim{output}, "\n";                                     #report end of animation processing
216 exit;
217 #===== SUBROUTINES =============================================================================================================
218 #     Usage : &drawHorizontalSlats( $IMGINDEX );
219 #   Purpose : Draw an animation frame with horizontal slats for the specified image index
220 # Arguments : $IMGINDEX = image index as was defined for the ImageMagick object $Images.
221 #      Subs : None.
222 #   Remarks : None.
223 #   History : v1.0.0 - September 28, 2009 - Original release.
224
225 sub drawHorizontalSlats {                                                               #begin sub
226     my $imgIdx  = shift;                                                                # parameterize the argument
227     my $slat    = Image::Magick->new( magick => 'GIF' );                                # instantiate an image object for the slat canvas
228
229     @$Canvas    = ();                                                                   # clear the frame canvas
230     $Canvas->Set( size => "${CanvasWidth}x${CanvasHeight}" );                           # set frame canvas size
231     $Canvas->ReadImage( 'xc:transparent' );                                             # set frame canvas background to transparent
232
233     for my $slatIdx ( $imgIdx*$NoSlats..($imgIdx+1)*$NoSlats - 1 ) {                    # repeat for each of the image's slats
234         @$slat  = ();                                                                   #  clear the slat canvas
235         $slat->Set( size => "${CanvasWidth}x${SlatWidth}" );                            #  set slat canvas size
236         $slat->ReadImage( 'xc:transparent' );                                           #  set slat canvas background to transparent
237         $slat->Composite                                                                #  init slat canvas with designated slat image
238                     (   image           => $Slats->[$slatIdx],
239                         compose         => 'Over',
240                         geometry        => "${ImgWidth}x${SlatWidth}+$SlatStretch+0"
241                     );
242         $slat->Distort                                                                  #  match slat corners to perspective apexes:
243                 (   points          =>  [   $SlatX_topLeft,     $SlatY_topLeft,     $x_topLeft,     $y_top,    #  top left
244                                             $SlatX_topLeft,     $SlatY_bottomRight, $x_bottomLeft,  $y_bottom, #  bottom left
245                                             $SlatX_bottomRight, $SlatY_topLeft,     $x_topRight,    $y_top,    #  top right
246                                             $SlatX_bottomRight, $SlatY_bottomRight, $x_bottomRight, $y_bottom  #  bottom right
247                                         ],
248                         type            => 'Bilinear',
249                         'virtual-pixel' => 'transparent',
250                         'best-fit'      => 1,
251                     );
252         $Canvas->Composite                                                              #  add slat to frame canvas
253                     (   image           => $slat,
254                         compose         => 'Over',
255                         x               => 0,
256                         y               => ( $slatIdx % $NoSlats ) * $SlatWidth
257                     );
258     }                                                                                   # until all image's slats processed
259     push @$Frames, @$Canvas;                                                            # add frame canvas to animation sequence
260     print   locate( $CursorY, $CursorX ), clline,                                       # report frame processing
261             colored ['bold yellow'], '(', $Spinners[++$FrameNo % 4], ')';
262 }                                                                                       #end sub drawHorizontalSlats
263 #-------------------------------------------------------------------------------------------------------------------------------
264 #     Usage : &drawVerticalSlats( $IMGINDEX );
265 #   Purpose : Draw an animation frame with vertical slats for the specified image index
266 # Arguments : $IMGINDEX = image index as was defined for the ImageMagick object $Images.
267 #      Subs : None.
268 #   Remarks : None.
269 #   History : v1.0.0 - September 28, 2009 - Original release.
270
271 sub drawVerticalSlats {                                                                 #begin sub
272     my $imgIdx  = shift;                                                                # parameterize the argument
273     my $slat    = Image::Magick->new( magick => 'GIF' );                                # instantiate an image object for the slat canvas
274
275     @$Canvas    = ();                                                                   # clear the frame canvas
276     $Canvas->Set( size => "${CanvasWidth}x${CanvasHeight}" );                           # set frame canvas size
277     $Canvas->ReadImage( 'xc:transparent' );                                             # set frame canvas background to transparent
278
279     for my $slatIdx ( $imgIdx*$NoSlats..($imgIdx+1)*$NoSlats - 1 ) {                    # repeat for each of the image's slats
280         @$slat  = ();                                                                   #  clear the slat canvas
281         $slat->Set( size => "${SlatWidth}x${CanvasHeight}" );                           #  set slat canvas size
282         $slat->ReadImage( 'xc:transparent' );                                           #  set slat canvas background to transparent
283         $slat->Composite                                                                #  init slat canvas with designated slat image
284                     (   image           => $Slats->[$slatIdx],
285                         compose         => 'Over',
286                         geometry        => "${SlatWidth}x${ImgHeight}+0+$SlatStretch"
287                     );
288         $slat->Distort                                                                  #  match slat corners to perspective apexes:
289                     (   points          =>  [   $SlatX_topLeft,     $SlatY_topLeft,     $x_left,    $y_topLeft,    #  top left
290                                                 $SlatX_topLeft,     $SlatY_bottomRight, $x_left,    $y_bottomLeft, #  bottom left
291                                                 $SlatX_bottomRight, $SlatY_topLeft,     $x_right,   $y_topRight,   #  top right
292                                                 $SlatX_bottomRight, $SlatY_bottomRight, $x_right,   $y_bottomRight #  bottom right
293                                             ],
294                         type            => 'Bilinear',
295                         'virtual-pixel' => 'transparent',
296                         'best-fit'      => 1,
297                     );
298         $Canvas->Composite                                                              #  add slat to frame canvas
299                     (   image           => $slat,
300                         compose         => 'Over',
301                         x               => ( $slatIdx % $NoSlats ) * $SlatWidth,
302                         y               => 0
303                     );
304     }                                                                                   # until all image's slats processed
305     push @$Frames, @$Canvas;                                                            # add frame canvas to animation sequence
306     print   locate( $CursorY, $CursorX ), clline,                                       # report frame processing
307             colored ['bold yellow'], '(', $Spinners[++$FrameNo % 4], ')';
308 }                                                                                       #end sub drawVerticalSlats
309 #===== Copyright 2009, Webpraxis Consulting Ltd. - ALL RIGHTS RESERVED - Email: webpraxis@gmail.com ============================
310 # end of anim_blinds.pl
			

© 2024 Webpraxis Consulting Ltd. – ALL RIGHTS RESERVED.