package Animation::Animation;
use feature "say";
use Image::Magick;
use Data::Dumper;
use Math::Trig;
use Tie::Cfg;
use File::Spec::Functions;
sub new
{
my $class = shift;
my $self = bless {arguments => shift}, $class;
$self->setConfig();
$self->setFrames();
$self;
}
sub setConfig
{
my $self = shift;
my $path = $FindBin::Bin . "/config";
tie my %global_config, "Tie::Cfg", READ=>$path, SEP=>"=";
$self->{"global_config"} = \%global_config;
if ($self->getOption("config"))
{
tie my %local_config, "Tie::Cfg", READ=>$self->getOption("config"), SEP=>"=";
$self->{"local_config"} = \%local_config;
}
}
sub setFrames
{
my ($self, $frames) = @_;
$self->{"frames"} = $frames;
}
sub getOption
{
my ($self, $name, $section) = @_;
my $arguments = $self->{"arguments"};
my $local_config = $self->{"local_config"};
my $global_config = $self->{"global_config"};
my $value;
if ($section)
{
$compound_name = "$section-$name";
if (exists $arguments->{$compound_name})
{
$value = $arguments->{$compound_name};
}
elsif ($local_config && exists $local_config->{$section}{$name})
{
$value = $local_config->{$section}{$name};
}
elsif (exists $global_config->{$section}{$name})
{
$value = $global_config->{$section}{$name};
}
}
else
{
if (exists $arguments->{$name})
{
$value = $arguments->{$name};
}
elsif ($local_config && exists $local_config->{$name})
{
$value = $local_config->{$name};
}
elsif (exists $global_config->{$name})
{
$value = $global_config->{$name};
}
}
$value;
}
sub buildPoints
{
my ($self, $shape, $radius, $cx, $cy, $rotation, $cross_angle, $cross_minor_radius_size) = @_;
my @points;
for ($shape)
{
if (/^star$/)
{
@points = $self->buildStarPoints($radius, $cx, $cy, $rotation);
}
elsif (/^triangle$/)
{
@points = $self->buildPolygonPoints(3, $radius, $cx, $cy, $rotation);
}
elsif (/^circle$/)
{
@points = $self->buildCirclePoints($radius, $cx, $cy);
}
elsif (/^square$/)
{
@points = $self->buildPolygonPoints(4, $radius, $cx, $cy, $rotation);
}
elsif (/^cross$/)
{
@points = $self->buildCrossPoints($radius, $cross_angle, $cx, $cy, $rotation,
$cross_minor_radius_size);
}
}
@points;
}
sub buildStarPoints
{
my ($self, $radius, $cx, $cy, $rotation) = @_;
my $count = 10;
my $step = deg2rad(360 / $count);
my @points;
my ($len, $angle, $x, $y);
for (0..$count - 1)
{
$len = $radius;
if ($_ % 2)
{
$len /= 2;
}
$angle = $rotation + $step * $_;
push @points, [$self->getPointOnCircle($len, $angle, $cx, $cy)];
}
@points;
}
sub buildPolygonPoints
{
my ($self, $sides, $radius, $cx, $cy, $rotation) = @_;
my $step = deg2rad(360 / $sides);
my @points;
for (0..$sides - 1)
{
$angle = $rotation + $step * $_;
push @points, [$self->getPointOnCircle($radius, $angle, $cx, $cy)];
}
@points;
}
sub buildCirclePoints
{
my ($self, $radius, $cx, $cy) = @_;
[$cx, $cy], [$cx, $cy + $radius];
}
sub buildCrossPoints
{
my ($self, $radius, $angle, $cx, $cy, $rotation, $minor_radius_size) = @_;
my @points;
my $step = deg2rad(90);
my ($x, $y, $offset, $minor_radius);
for (0..3)
{
$offset = $step * $_;
push @points, [$self->getPointOnCircle($radius, $rotation + $offset, $cx, $cy)];
push @points, [$self->getPointOnCircle($radius, $rotation + $offset + $angle, $cx, $cy)];
$minor_radius = sqrt((2 * (sin($angle * $minor_radius_size) * $radius)) ** 2);
push @points, [$self->getPointOnCircle($minor_radius,
$rotation + $offset + $angle + ($step - $angle) / 2,
$cx, $cy)];
}
@points;
}
sub getPointOnCircle
{
my ($self, $radius, $angle, $cx, $cy) = @_;
my $x = $self->round(($radius * sin($angle)) + $cx);
my $y = $self->round(($radius * cos($angle)) + $cy);
$x, $y;
}
sub round
{
int(@_[1] + .5);
}
sub getPrimitive
{
@_[1] eq "circle" ? @_[1] : "polygon";
}
sub buildPointsString
{
my $points = @_[1];
my $str = "";
my @point;
for (0..$#{$points})
{
@point = @{${$points}[$_]};
$str .= $point[0] . "," . $point[1] . " ";
}
$str;
}
sub getColor
{
my ($self, $gradient, $index, $resolution) = @_;
int(($index % $resolution + 1) * ($#{$gradient} + 1) / $resolution) - 1;
}
sub wrapIndex
{
@_[1] % (@_[2] + 1);
}
sub readGradient
{
my ($self, $name) = @_;
my $gradient = Image::Magick->new;
$gradient->read($self->findGradientFile($name));
my @colors;
for (0..$gradient->Get("width") - 1)
{
push @colors, $self->buildColorName($gradient->GetPixel(x=>$_, 'y'=>0, map=>'str',
normalize=>"False"));
}
@colors;
}
sub findGradientFile
{
my ($self, $name) = @_;
my $section = "gradients";
my $root = $FindBin::Bin . "/" . $self->getOption("root", "gradients");
say "root: " . $root;
for (glob $root . "*")
{
if (/$name\..*$/)
{
return $_;
}
}
}
sub buildColorName
{
"rgb(" . (@_[1] >> 8) . ", " . (@_[2] >> 8) . ", " . (@_[3] >> 8) . ")";
}
sub write
{
my $self = shift;
my $destination = $self->getDestination();
my $frames = $self->{"frames"};
if (!$self->getOption("split"))
{
$frames->Write($destination);
}
else
{
mkdir $destination;
my $delay;
for ($ii = 0; $frames->[$ii]; $ii++)
{
$delay = $frames->[$ii]->Get("delay");
$frames->[$ii]->Write("$destination/$ii-$delay.png");
}
}
say "wrote: $destination";
}
sub getDestination
{
my $self = shift;
my $root = $self->getOption("destination", "file");
my $pad_length = $self->getOption("name-pad-length", "file");
my $destination;
if ($self->getOption("out"))
{
$destination = $self->getOption("out");
}
elsif (!$self->getOption("split"))
{
$destination = $self->buildPathPrefix($self->findSlot("$root/*.gif"));
$destination .= ".gif";
}
else
{
$destination = $self->buildPathPrefix($self->findSlot("$root/*/"));
}
$destination;
}
sub findSlot
{
my $self = shift;
my @existing = glob shift;
for (1..@existing)
{
$existing[$_ - 1] =~ /([0-9]+)/;
if ($_ < int($1))
{
return $_;
}
}
@existing + 1;
}
sub buildPathPrefix
{
my $self = shift;
my $pad_length = $self->getOption("name_pad_length", "file");
$self->getOption("destination", "file") . sprintf("%0${pad_length}d", shift);
}
1;