Referring back to the Perl script anim_flipcard.pl, I discussed how to either flip or twirl a 3D "card" displaying multiple images. In this article, I discuss a simple variant where the images are hinged at their top side. The resulting illusion mimics the images being peeled a pad, as shown in the following animation:
For those unfamiliar with the principles governing the illusion and the methods used to create it, the article regarding anim_flipcard.pl should be consulted. The only new concept here is that the background of each animation frame is no longer transparent. Rather, it displays in a cyclic manner the image underlying the one being distorted. Thus the first image peeled off will also appear as the last background in the first loop of the animation.
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_pad.xml
( [Download demo_pad.xml]
[MD5 checksum] )
that governs the creation of the above animated GIF image:
| XML file "demo_pad.xml" | Remarks |
|---|---|
| <animation> | start of XML declaration |
| <output>demo_pad.gif</output> | name of the output file |
| <frames>16</frames> | the number of intermediate, interpolated frames for each quarter turn |
| <delay>10</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 |
| <imageDims> | all constituent images to be scaled to the following pixel dimensions. |
| <width>100</width> | in pixels |
| <height>150</height> | in pixels |
| <stretch>5</stretch> | in pixels. Horizontal distance along the axis of "rotation" of the theoretical corner end points from an image's vertical side. |
| </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_pad.pl, displayed below, is the resulting code. 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 anim_pad.pl demo_pad.xml
Here we are requesting that the XML file "demo_pad.xml" be processed. A screen shot at the end of processing is:
If you have any questions regarding the code or my explanations, please do not hesitate in contacting me.
001 use strict;
002 use warnings;
003 use Image::Magick;
004 use Term::ANSIScreen qw/:color :cursor :screen/;
005 use Win32::Console::ANSI qw/ Cursor /;
006 use XML::Simple;
007 $XML::Simple::PREFERRED_PARSER = 'XML::Parser';
008 use constant FATAL => colored ['white on red'], "\aFATAL ERROR: ";
009 #===== Copyright 2009, Webpraxis Consulting Ltd. - ALL RIGHTS RESERVED - Email: webpraxis@gmail.com ============================
010 # anim_pad.pl: Creates an animated GIF that peels "3D" sheets off a pad.
011 #===============================================================================================================================
012 # Usage : perl anim_pad.pl XmlFile
013 # Arguments : XmlFile = path of XML file for the animation specifications.
014 # Input Files : See arguments.
015 # Output Files : The animated GIF specified in the XML data file.
016 # Temporary Files : None.
017 # Remarks : See http://www.webpraxis.ab.ca/flips/anim_pad.shtml for details.
018 # History : v1.0.0 - October 1, 2009 - Original release.
019 #===============================================================================================================================
020 # 0) INITIALIZE:
021 $| = 1; #set STDOUT buffer to auto-flush
022 cls(); #clear screen
023 print colored ['black on white'], "$0\n\n\n", #display program name
024 colored ['reset'], 'Initializing... '; #report start of initialization
025
026 my $XmlFile = shift || die FATAL, 'No XML file specified'; #get path of XML data file
027 my $Anim = XMLin( $XmlFile, ForceArray => [qw(image)] ); #read the XML data file
028 my $ImgWidth = $$Anim{imageDims}{width}; #parameterize the width of the images
029 my $ImgHeight = $$Anim{imageDims}{height}; #parameterize the height of the images
030 my $ImgStretch = $$Anim{imageDims}{stretch}; #parameterize the image distortion
031 my $NoImages = @{ $$Anim{image} }; #get the number of source images
032 my @Spinners = ( '-', '\\', '|', '/' ); #define symbols for spinner
033
034 my $CanvasWidth = $ImgWidth + 2 * $ImgStretch; # set canvas width
035 my $CanvasHeight = $ImgHeight; # set frame canvas height
036 print colored ['bold green'], "XML data read\n\n"; #report end of initialization
037 #-------------------------------------------------------------------------------------------------------------------------------
038 # 1) LOAD AND SCALE THE IMAGES:
039 print 'Reading & scaling images... '; #report start of image processing
040 my $Images = Image::Magick->new( magick => 'JPG' ); #instantiate an object for the images
041
042 die FATAL, "Even number of images required\n" if $NoImages % 2; #check for even number of images
043 for( 0..$NoImages-1 ) { #repeat for each source image
044 my $file = $$Anim{image}[$_]; # parameterize the file name
045 die FATAL, "Cannot locate image file '$file'\n" unless -e $file; # check image existence
046 $Images->Read( $file ); # read image file
047 } #until all images processed
048 $Images->Quantize( colors => 256, colorspace => 'RGB' ); #ensure uniform color space
049 $Images->Scale( geometry => "${ImgWidth}x${ImgHeight}!" ); #scale images to stated dimensions
050 print colored ['bold green'], "Done\n\n"; #report end of image processing
051 #-------------------------------------------------------------------------------------------------------------------------------
052 # 2) CREATE ANIMATION FRAMES:
053 print 'Creating animation frames... '; #report start of frame processing
054 my($CursorX,$CursorY) = Cursor(); #record cursor position
055
056 my $Canvas = Image::Magick->new( magick => 'GIF' ); #instantiate an image object for the frame canvas
057 my $Parallelogram = Image::Magick->new( magick => 'GIF' ); #instantiate an image object for the distorted image
058 my $Frames = Image::Magick->new( magick => 'GIF' ); #instantiate an image object for animation frames
059 my $FrameNo = 0; #init no of animation frames
060 my $Interpolate = sub { my ( $lambda, $start, $end ) = @_; #anonymous sub for interpolating x,y-coordinates
061 int( $lambda * $end + ( 1. - $lambda ) * $start );
062 };
063 my $DeltaLambda = 1. / ( $$Anim{frames} + 1. ); #set "rotation" step-size
064
065 my $ImgX_topLeft = $ImgStretch; #top-left corner coords for 2D image
066 my $ImgY_topLeft = 0;
067 my $ImgX_bottomRight = $CanvasWidth - $ImgStretch - 1; #bottom-right corner coords for 2D image
068 my $ImgY_bottomRight = $CanvasHeight - 1;
069
070 my $x_left; #apex coords for distorted image
071 my $x_right;
072 my $y_bottom;
073
074 for my $imgIdx ( 0..$NoImages - 1 ) { #repeat for each pairing of images
075 #QUARTER RIGHT-HAND-RULE ROTATION OF IMAGE HINGED AT THE TOP:
076 for( my $lambda = 0.; $lambda < 1. - $DeltaLambda/2.; $lambda += $DeltaLambda ) { # repeat for each rotation step
077 $x_left = &$Interpolate( $lambda, $ImgX_topLeft, 0 );
078 $x_right = &$Interpolate( $lambda, $ImgX_bottomRight, $CanvasWidth - 1 );
079 $y_bottom = &$Interpolate( $lambda, $ImgY_bottomRight, 0 );
080 &drawFrame( $imgIdx ); # draw the animation frame
081 } # until all rotation steps processed
082 } #until all images processed
083 print locate( $CursorY, $CursorX ), clline, #report end of frame processing
084 colored ['bold green'], $FrameNo, " frames\n\n";
085 undef $Canvas; #destroy the canvas object
086 undef $Parallelogram; #destroy the distorted-image object
087 undef $Images; #destroy the object for source images
088 #-------------------------------------------------------------------------------------------------------------------------------
089 # 3) CREATE ANIMATED GIF IMAGE:
090 print 'Creating animated GIF image... '; #report start of animation processing
091 $Frames->Write #output the animation
092 ( delay => $$Anim{delay},
093 loop => $$Anim{loops},
094 dispose => 'background',
095 filename => $$Anim{output}
096 );
097 print colored ['bold green'], $$Anim{output}, "\n"; #report end of animation processing
098 exit;
099 #===== SUBROUTINES =============================================================================================================
100 # Usage : &drawFrame( $IMGINDEX );
101 # Purpose : Draw an animation frame with the specified image index
102 # Arguments : $IMGINDEX = index of the ImageMagick object $Images.
103 # Subs : None.
104 # Remarks : None.
105 # History : v1.0.0 - October 1, 2009 - Original release.
106
107 sub drawFrame { #begin sub
108 my $imgIdx = shift; # parameterize the argument
109
110 @$Parallelogram = (); # clear the distorted-iamge canvas
111 $Parallelogram->Set( size => "${CanvasWidth}x${CanvasHeight}" ); # set canvas size
112 $Parallelogram->ReadImage( 'xc:transparent' ); # set canvas background to transparent
113 $Parallelogram->Composite # init canvas with designated image
114 ( image => $Images->[$imgIdx],
115 compose => 'Over',
116 geometry => "${ImgWidth}x${ImgHeight}+$ImgStretch+0"
117 );
118 $Parallelogram->Distort # match image corners to perspective apexes:
119 ( points => [ $ImgX_topLeft, $ImgY_topLeft, $ImgX_topLeft, $ImgY_topLeft, # top left
120 $ImgX_topLeft, $ImgY_bottomRight, $x_left, $y_bottom, # bottom left
121 $ImgX_bottomRight, $ImgY_topLeft, $ImgX_bottomRight, $ImgY_topLeft, # top right
122 $ImgX_bottomRight, $ImgY_bottomRight, $x_right, $y_bottom # bottom right
123 ],
124 type => 'Bilinear',
125 'virtual-pixel' => 'transparent',
126 'best-fit' => 1,
127 );
128
129 @$Canvas = (); # clear the frame canvas
130 $Canvas->Set( size => "${CanvasWidth}x${CanvasHeight}" ); # set canvas size
131 $Canvas->ReadImage( 'xc:transparent' ); # set canvas background to transparent
132 $Canvas->Composite # init canvas with next image as background
133 ( image => $Images->[( $imgIdx + 1 ) % $NoImages],
134 compose => 'Over',
135 geometry => "${ImgWidth}x${ImgHeight}+$ImgStretch+0"
136 );
137 $Canvas->Composite # add parallelogram as foreground
138 ( image => $Parallelogram,
139 compose => 'Over',
140 geometry => "${CanvasWidth}x${CanvasHeight}+0+0"
141 );
142 push @$Frames, @$Canvas; # add frame canvas to image sequence
143 print locate( $CursorY, $CursorX ), clline, # report frame processing
144 colored ['bold yellow'], '(', $Spinners[++$FrameNo % 4], ')';
145 } #end sub drawFlipFrame
146 #===== Copyright 2009, Webpraxis Consulting Ltd. - ALL RIGHTS RESERVED - Email: webpraxis@gmail.com ============================
147 # end of anim_pad.pl
© 2012 Webpraxis Consulting Ltd. – ALL RIGHTS RESERVED.