Pretty much all of the tile animations I've created so far have involved synchronous displacements. That is, all the tiles start and end their transits simultaneously, regardless of the distance travelled. In this article, I explored asynchronous timing of the tile animations.
The special effect chosen is morphing between various images. In the simplest case, one morphs a whole image into the next, as in
To convey the parameters of the animation, an XML file is used. To explain the required tags, let's examine the data file demo_morphs.xml ( [Download demo_morphs.xml] [MD5 checksum] ). It governs the creation of the latter animated GIF image:
XML file "demo_morphs.xml" | Remarks |
---|---|
<animation> | start of XML declaration |
<output>demo_morphs.gif</output> | name of the output file |
<frames>5</frames> | the number of intermediate, interpolated frames for each morphing sequence |
<delay>15</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 |
<tile> | start of tile specifications |
<width>10</width> | in pixels |
<height>10</height> | in pixels |
</tile> | end of tile specifications |
<imageDims> | all constituent images to be scaled initially to the following pixel dimensions |
<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_morph.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_morphs.pl demo_morphs.xml
Here we are requesting that the XML file "demo_morphs.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 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_morphs.pl: Creates an animated GIF that morphs image tiles asynchronously. 012 #=============================================================================================================================== 013 # Usage : perl anim_morphs.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/morphs/anim_morphs.shtml for details. 019 # History : v1.0.0 - October 30, 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 srand( time() ^ ($$ + ($$ << 15)) ); #seed the random number generator 027 028 my $XmlFile = shift || die FATAL, 'No XML file specified'; #get path of XML data file 029 my $Anim = XMLin( $XmlFile, ForceArray => [qw(image)] ); #read the XML data file 030 my $TileWidth = $$Anim{tile}{width}; #parameterize pixel width of a tile 031 my $TileHeight = $$Anim{tile}{height}; #parameterize pixel height of a tile 032 my $NoImages = @{ $$Anim{image} }; #get the number of source images 033 my @Spinners = ( '-', '\\', '|', '/' ); #define symbols for spinner 034 035 my $NoTileCols = POSIX::ceil( $$Anim{imageDims}{width} / $TileWidth ); #compute integral number of tile columns 036 my $NoTileRows = POSIX::ceil( $$Anim{imageDims}{height} / $TileHeight ); #compute integral number of tile rows 037 my $TileCount = $NoTileCols * $NoTileRows; #compute total number of tiles 038 039 my $CanvasWidth = $NoTileCols * $TileWidth; #set canvas width 040 my $CanvasHeight = $NoTileRows * $TileHeight; #set canvas height 041 print colored ['bold green'], "XML data read\n\n"; #report end of initialization 042 #------------------------------------------------------------------------------------------------------------------------------- 043 # 1) LOAD AND SCALE THE IMAGES: 044 print 'Reading & scaling images... '; #report start of image processing 045 my $Images = Image::Magick->new( magick => 'JPG' ); #instantiate an object for the images 046 047 die FATAL, "At least 2 images required\n" if $NoImages < 2; #check for at least 2 images 048 for( 0..$NoImages-1 ) { #repeat for each source image 049 my $file = $$Anim{image}[$_]; # parameterize the file name 050 die FATAL, "Cannot locate image file '$file'\n" unless -e $file; # check image existence 051 $Images->Read( $file ); # read image file 052 } #until all images processed 053 $Images->Quantize( colors => 256, colorspace => 'RGB' ); #ensure uniform color space 054 $Images->Scale( geometry => "${CanvasWidth}x${CanvasHeight}!" ); #scale images to canvas dimensions 055 print colored ['bold green'], "Done\n\n"; #report end of image processing 056 #------------------------------------------------------------------------------------------------------------------------------- 057 # 2) CREATE TILE IMAGES AND RECORD THEIR LOCATIONS: 058 print 'Creating tile images... '; #report start of tile processing 059 my ( $CursorX, $CursorY ) = Cursor(); #record cursor position 060 061 my $Tiles = Image::Magick->new( magick => 'GIF' ); #instantiate an image object for the tiles 062 my @Tile_X; #array for tile top-left x-coordinates 063 my @Tile_Y; #array for tile top-left y-coordinates 064 { #start naked block as firewall 065 my $tile = Image::Magick->new( magick => 'GIF' ); # instantiate an image object for a tile 066 067 for my $imgIdx ( 0..$NoImages-1 ) { # repeat for each source image 068 for ( my $y_topLeft = 0; # for each tile row: start at top and work down 069 $y_topLeft <= $CanvasHeight - $TileHeight; 070 $y_topLeft += $TileHeight 071 ) { 072 for ( my $x_topLeft = 0; # for each tile column: work left to right 073 $x_topLeft <= $CanvasWidth - $TileWidth; 074 $x_topLeft += $TileWidth 075 ) { 076 my $geometry = "${TileWidth}x${TileHeight}+$x_topLeft+$y_topLeft"; # define tile geometry 077 $tile = $Images->[$imgIdx]->Clone(); # init tile with image 078 $tile->Crop( geometry => $geometry ); # crop tile area 079 $tile->Set( page => '0x0+0+0' ); # shrink canvas 080 push @$Tiles, $tile; # store the tile image 081 push @Tile_X, $x_topLeft; # store the tile coords 082 push @Tile_Y, $y_topLeft; 083 print locate( $CursorY, $CursorX ), clline, # report tile processing 084 colored ['bold yellow'], '(', $Spinners[$#$Tiles % 4], ')'; 085 } # until all tile columns processed 086 } # until all tile rows processed 087 } # until all images processed 088 undef $tile; # destroy the tile image object 089 } #end naked block 090 print locate( $CursorY, $CursorX ), clline, #report end of tile processing 091 colored ['bold green'], scalar @Tile_X, " tiles\n\n"; 092 #------------------------------------------------------------------------------------------------------------------------------- 093 # 3) CREATE MORPHED IMAGES FOR EACH TILE: 094 print 'Morphing each tile... '; #report start of morphing process 095 ( $CursorX, $CursorY ) = Cursor(); #record cursor position 096 097 my @Countdown; #countdowns for start of morphing display 098 my @Morphs; #morphed images for each tile 099 { #start naked block as firewall 100 my $tiles2morph = Image::Magick->new( magick => 'GIF' ); # instantiate an image object for tile pairs 101 102 for my $imgIdx ( 0..$NoImages-1 ) { # repeat for each source image 103 for my $tileIdx ( $imgIdx*$TileCount..($imgIdx+1)*$TileCount - 1 ){ # repeat for each of the image's tiles 104 @$tiles2morph = (); # clear tile pairs 105 push @$tiles2morph, $Tiles->[$tileIdx], # set current tile as start image 106 $Tiles->[($tileIdx+$TileCount)%($NoImages*$TileCount)]; # and corresponding tile of next image as end image 107 $Morphs[$tileIdx] = $tiles2morph->Morph( frames => $$Anim{frames} ); # generate sequence of morphed images 108 $Countdown[$tileIdx] = int rand( int $TileCount / 10 ); # set random countdown 109 print locate( $CursorY, $CursorX ), clline, # report morphing process 110 colored ['bold yellow'], '(', $Spinners[$#Morphs % 4], ')'; 111 } # until all image's tiles processed 112 } # until all images processed 113 undef $tiles2morph; # destroy the image object for tile pairs 114 } #end naked block 115 undef $Tiles; #destroy the image object for the tiles 116 print locate( $CursorY, $CursorX ), clline, #report end of morphing process 117 colored ['bold green'], scalar @Morphs, " morph sequences\n\n"; 118 #------------------------------------------------------------------------------------------------------------------------------- 119 # 4) CREATE ANIMATION FRAMES: 120 print 'Creating animation frames... '; #report start of frame processing 121 ( $CursorX, $CursorY ) = Cursor(); #record cursor position 122 123 my $Canvas = $Images->[0]; #init canvas with zeroth image 124 my $Frames = Image::Magick->new( magick => 'GIF' ); #instantiate an image object for animation frames 125 my $FrameNo = 0; #init no of animation frames 126 my $NoSequencesDone; #number of morph-sequence displays ended per image 127 undef $Images; #destroy the source image object 128 { #start naked block as firewall 129 my $morphedTile; # morphed tile to be displayed 130 for my $imgIdx ( 0..$NoImages-1 ) { # repeat for each source image 131 $NoSequencesDone = 0; # reset ended-sequence count for new image 132 until( $NoSequencesDone == $TileCount ) { # repeat 133 $NoSequencesDone = 0; # reset ended-sequence count for current image 134 for my $tileIdx ($imgIdx*$TileCount..($imgIdx+1)*$TileCount -1 ){ # repeat for each of the image's tiles 135 next if --$Countdown[$tileIdx] > 0; # decrement countdown & skip if greater than zero 136 $Canvas->Composite # add any morphed tile image to canvas 137 ( image => $morphedTile, 138 compose => 'Over', 139 geometry => "${TileWidth}x${TileHeight}+$Tile_X[$tileIdx]+$Tile_Y[$tileIdx]" 140 ) 141 if $morphedTile = shift @{$Morphs[$tileIdx]}; 142 ++$NoSequencesDone unless $Morphs[$tileIdx]->[0]; # increment sequence counter if no morphed images left 143 } # until all tiles processed 144 push @$Frames, $Canvas->Clone(); # add canvas to animation frames 145 print locate( $CursorY, $CursorX ), clline, # report frame processing 146 colored ['bold yellow'], '(', $Spinners[++$FrameNo % 4], ')'; 147 } # until all morph sequences displayed 148 } # until all images processed 149 undef $morphedTile; # destroy morphed tile object 150 } #end naked block 151 print locate( $CursorY, $CursorX ), clline, #report end of frame processing 152 colored ['bold green'], scalar @$Frames, " frames\n\n"; 153 undef $Canvas; #destroy the canvas object 154 undef @Countdown; #destroy the array for the countdowns 155 undef @Morphs; #destroy the image object for the morph sequences 156 undef @Tile_X; #destroy the arrays for the tile coordinates 157 undef @Tile_Y; 158 #------------------------------------------------------------------------------------------------------------------------------- 159 # 5) CREATE ANIMATED GIF IMAGE: 160 print 'Creating animated GIF image... '; #report start of animation processing 161 $Frames->Write #output the animation 162 ( delay => $$Anim{delay}, 163 loop => $$Anim{loops}, 164 dispose => 'background', 165 filename => $$Anim{output} 166 ); 167 print colored ['bold green'], $$Anim{output}, "\n"; #report end of animation processing 168 exit; 169 #===== Copyright 2009, Webpraxis Consulting Ltd. - ALL RIGHTS RESERVED - Email: webpraxis@gmail.com ============================ 170 # end of anim_morphs.pl
© 2024 Webpraxis Consulting Ltd. – ALL RIGHTS RESERVED.