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
© 2024 Webpraxis Consulting Ltd. – ALL RIGHTS RESERVED.