The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Tk::Animation;

use vars qw($VERSION);
$VERSION = '4.008'; # $Id: //depot/Tkutf8/Tk/Animation.pm#8 $

use Tk::Photo;
use base  qw(Tk::Photo);

Construct Tk::Widget 'Animation';

sub MainWindow
{
 return shift->{'_MainWIndow_'};
}

sub add_frame
{
 my $obj = shift;
 $obj->{'_frames_'} = [] unless exists $obj->{'_frames_'};
 push(@{$obj->{'_frames_'}},@_);
}

sub new
{
 my ($class,$widget,%args) = @_;
 my $obj = $class->SUPER::new($widget,%args);
 $obj->{'_MainWIndow_'} = $widget->MainWindow;
 if ($args{'-format'} eq 'gif')
  {
   my @images;
   local $@;
   while (1)
    {
     my $index = @images;
     $args{'-format'} = "gif -index $index";
     my $img;
     eval {local $SIG{'__DIE__'};  $img = $class->SUPER::new($widget,%args) };
     last if $@;
     push(@images,$img);
    }
   if (@images > 1)
    {
     $obj->add_frame(@images);
     $obj->{'_frame_index_'}  = 0;
    }
  }
 $obj->set_image( 0 );
 $obj->_get_gif_info;
 return $obj;
}

sub fast_forward {

    my( $self, $delta) = @_;

    $self->{_delta_} = $delta;
    if( not exists $self->{_playing_} ) {
	my $playing = exists $self->{'_NextId_'};
	$self->{_playing_} = $playing;
	$self->resume_animation if not $playing;
    } else {
	my $playing = delete $self->{_playing_};
	$self->pause_animation if not $playing;
    }

} # end fast_forward

*fast_reverse = \&fast_forward;

sub frame_count {
    my $frames = shift->{'_frames_'};
    return -1 unless $frames;
    return @$frames;
}

sub set_disposal_method {
    my( $self, $blank ) = @_;
    $blank = 1 if not defined $blank;
    $self->{_blank_} = $blank;
    $blank;
}

sub set_image
{
 my ($obj,$index)  = @_;
 my $frames = $obj->{'_frames_'};
 return unless $frames && @$frames;
 $index = 0 unless $index < @$frames;
 $obj->blank if $obj->{_blank_};  # helps some make others worse
 $obj->copy($frames->[$index]);
 $obj->{'_frame_index_'} = $index;
}

sub next_image
{
 my ($obj, $delta)  = @_;
 $obj->_next_image($delta);
}

sub _next_image
{
 my ($obj, $delta, $in_animation)  = @_;
 $delta = $obj->{_delta_} unless $delta;
 my $frames = $obj->{'_frames_'};
 return unless $frames && @$frames;
 my $next_index = (($obj->{'_frame_index_'} || 0) + $delta);
 if ($next_index > @$frames && $in_animation && $obj->{'_loop_'} ne 'forever')
  {
   return 0; # signal to stop animation
  }
 $next_index %= @$frames;
 $obj->set_image($next_index);
 1;
}

sub prev_image { shift->next_image( -1 ) }

sub next_image_in_animation
{
 my ($obj, $delta) = @_;
 my $continue = $obj->_next_image($delta, 1);
 if (!$continue && $self->{'_NextId_'})
  {
   $obj->pause_animation;
  }
}

sub pause_animation { 
    my $self = shift;
    my $id = delete $self->{'_NextId_'};
    Tk::catch { $id->cancel } if $id;
}

sub resume_animation {
    my( $self, $period ) = @_;
    if( not defined $self->{'_period_'} ) {
	$self->{'_period_'} = defined( $period ) ? $period : 100;
    }
    $period = $self->{'_period_'};
    my $w = $self->MainWindow;
    $self->{'_NextId_'} = $w->repeat( $period => [ $self => 'next_image_in_animation' ] );
}

sub start_animation
{
 my ($obj,$period) = @_;
 my $frames = $obj->{'_frames_'};
 return unless $frames && @$frames;
 my $w = $obj->MainWindow;
 $obj->stop_animation;
 $obj->{'_period_'} = $period if $period;
 $obj->{'_NextId_'} = $w->repeat($obj->{'_period_'},[$obj,'next_image_in_animation']);
}

sub stop_animation
{
 my ($obj) = @_;
 my $id = delete $obj->{'_NextId_'};
 Tk::catch { $id->cancel } if $id;
 $obj->set_image(0);
}

sub _get_gif_info
{
 my ($obj) = @_;
 my $info;
 if (defined(my $file = $obj->cget(-file)) && eval { require Image::Info; 1; })
  {
   $info = Image::Info::image_info($file);
  }
 elsif (defined(my $data = $obj->cget(-data)))
  {
   if ($data =~ m{^GIF8} && eval { require Image::Info; 1; })
    {
     $info = Image::Info::image_info(\$data);
    }
   elsif (eval { require Image::Info; require MIME::Base64; 1; })
    {
     $data = MIME::Base64::decode_base64($data);
     $info = Image::Info::image_info(\$data);
    }
  }
 if ($info)
  {
   $obj->{'_blank_'} = $info->{DisposalMethod} == 2 || $info->{DisposalMethod} == 3;
   $obj->{'_period_'} = $info->{Delay}*1000 if defined $info->{Delay};
   $obj->{'_loop_'} = $info->{GIF_Loop};
  }
 $obj->{'_blank_'} = 0 if !defined $obj->{'_blank_'};
 $obj->{'_period_'} = 100 if !defined $obj->{'_period_'};
 $obj->{'_loop_'} = 'forever' if !defined $obj->{'_loop_'};
 $obj->{'_delta_'} = 1;
}

1;

__END__

#
# This almost works for changing the animation on the fly
# but does not resize things correctly
#

sub gif_sequence
{
 my ($obj,%args) = @_;
 my $widget = $obj->MainWindow;
 my @images;
 local $@;
 while (1)
  {
   my $index = @images;
   $args{'-format'} = "gif -index $index";
   my $img;
   eval
    {local $SIG{'__DIE__'};
     my $img = $widget->Photo(%args);
     push(@images,$img);
    };
   last if $@;
  }
 if (@images)
  {
   delete $obj->{'_frames_'};
   $obj->add_frame(@images);
   $obj->configure(-width => 0, -height => 0);
   $obj->set_frame(0);
  }
}