#! /usr/bin/perl ## ## The cuttlefish Visualization Tool. ## Copyright (C) 2006 The Regents of the University of California. ## ## This program is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License version 2 as published ## by the Free Software Foundation. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## written by Bradley Huffaker ## ## documention by Joshua Polterock ## Bradley Huffaker ## Marina Fomenkova ## ## use Getopt::Std; use FindBin qw($Bin); use lib "$Bin/../lib/cuttlefish"; use Control; use Canvas; use GD; use Map; use Histogram; use Math::Trig; use Util; use POSIX; my $gifsicle_found; CheckConvert(); use strict; my ($executable) = ($0 =~ /([^\/]+)$/); my $usage = "usage:$0 [-h] (many) [-o outputfile] config-file\n"; sub Help() { print STDERR $usage; print<= 0) || defined $opts{h}) { print STDERR $usage; exit -1; } if (defined $opts{h}) { Help(); exit(0); } my $control = new Control(); $control->ReadConfig(@ARGV); if (defined $opts{o}) { $control->{"output"} = $opts{o}; } if (defined $opts{d}) { $control->{"delay"} = $opts{d}; } if (defined $opts{f}) { $control->{"frame-rate"} = $opts{f}; } if (defined $opts{w}) { $control->setValue("working-dir",$opts{w}); } my $working_dir = $control->{"working-dir"}; unless (-d $working_dir) { mkdir ($working_dir,0744) || die("Unable to make directory \"$working_dir\":$!"); } my $name_font = gdTinyFont; if (defined $opts{b}) { my $map = new Map(); $map->setValueGlobal("bar_height", $opts{b}); } if (defined $opts{B}) { my $map = new Map(); $map->setValueGlobal("bar_width", $opts{B}); } if (defined $opts{v}) { $control->SetValue("verbose", 1); } my $start_frame = $opts{s}; my $end_frame = $opts{e}; if (defined $opts{S}) { $start_frame = $end_frame = $opts{S}; } foreach my $val ($start_frame, $end_frame) { if (defined $val && !($val =~ /^\d+$/)) { die($usage, "\ntframe (s N) must have an integer operator \"$opts{s}\""); } } my ($control_min, $control_max) = ($control->{"value-min"},$control->{"value-max"}); my ($value_min, $value_max) = ($control_min, $control_max); my @frames = BuildFrames($control); if ((defined $control_min && $value_min != $control_min) || (defined $control_max && $value_max != $control_max)) { die("Min and Max Values defined in config $control_min,$control_max,", " did not contain all values $value_min,$value_max"); } my @pngs = DrawImages($control, @frames); my @gifs = MergeImages($control, \@pngs, \@frames); unless (defined $opts{c}) { CleanUp($control, $working_dir, @pngs, @gifs); } if (defined $control->{"verbose"}) { print "output:",$control->{output},"\n"; } ################################################################## # Building the Frames ################################################################## sub BuildFrames { my ($control) = @_; my @times = sort {$a<=>$b} keys %{$control->{frames}}; my $frame_rate = $control->{"frame-rate"}; if ($#times < 0) { print STDERR "no frames found, exiting\n"; exit(-1); } if ($#times < 1 && $frame_rate > 1) { print STDERR "one frame found, ignoring frame_rate:$frame_rate\n"; $frame_rate= 1; } my @maps = @{$control->{maps}}; my @frames; sub AddExtreaFrames { my ($time_from, $time_to, $time_last, $time, $sum_last, $sum) = @_; my $time_diff = $time - $time_last; my $sum_diff = $sum - $sum_last; my $num_frames = $frame_rate-1; foreach my $i (1..$num_frames) { my $t = $time_diff*($i/$frame_rate) + $time_last; my $s = $sum_diff*($i/$frame_rate) + $sum_last; foreach my $map (@maps) { $map->addFrame($time_from, $t, $time_to); } push @frames, { "sum" => $s, "time" => $t, }; } } my ($time_last, $sum_last); foreach my $index (0..$#times) { my $time = $times[$index]; my ($sum) = BuildBasePoints($control, $time); if (defined $time_last && $frame_rate > 1) { AddExtreaFrames($time_last, $time, $time_last, $time, $sum_last, $sum); } push @frames, { "sum" => $sum, "time" => $time, "master" => 1 }; $time_last = $time; $sum_last = $sum; } if ($frame_rate > 1) { my ($total); foreach my $index (1..$#times) { $total += $times[$index] - $times[$index-1]; } my $time_size = $total/$#times; my $frame = $frames[$#frames]; my $sum = $frame->{"sum"}; my $time = $frame->{"time"}; AddExtreaFrames($time, $time, $time, $time+$time_size, $sum, $sum); } # Add Delay and Size information if ($#frames == 0) { $frames[0]{"size"} = 1; $frames[0]{"delay"} = 1; } else { my $time_last; my ($min, $total); foreach my $index (1..$#frames) { my $size = $frames[$index]{"time"} - $frames[$index-1]{"time"}; $frames[$index-1]{"size"} = $size; if (!defined $min || $min > $size) { $min = $size; } $total += $size; } my $average_size = $total/$#frames; $frames[$#frames]{"size"} = $average_size; foreach my $index (0..$#frames) { $frames[$index]{"delay"} = $frames[$index]{"size"}/$average_size; } } my $num_length = log(@frames)/log(10); foreach my $index (0..$#frames) { my $num = $index; while (length($num) < $num_length) { $num = "0".$num; } my $working_dir = $control->{"working-dir"}; my $file = "$working_dir/cuttlefish.$$".".$num".".png"; $frames[$index]{"file"} = $file; } return @frames; } sub BuildBasePoints { my ($control, $time) = @_; my @maps = @{$control->{maps}}; my $sum = 0; foreach my $key (keys %{$control->{frames}{$time}}) { my $value = $control->{frames}{$time}{$key}; my $long = $control->{nodelist}{$key}{long}; my $lat = $control->{nodelist}{$key}{lat}; my $name = $control->{nodelist}{$key}{name}; my $point_used; foreach my $map (@maps) { if ($map->addDataPoint($time, $long, $lat, $value, $name)) { $point_used = 1; } } if (defined $point_used) { $sum += $value; } } return ($sum); } ################################################################## # Draw Images ################################################################## sub DrawImages { my ($control, @frames) = @_; my $verbose = $control->{verbose}; my ($w, $h) = $control->getWidthHieght(); my $work_canvas = new Canvas($w,$h); my @maps = @{$control->{maps}}; my @objects = @{$control->{objects}}; my ($value_min, $value_max); foreach my $map (@maps) { ($value_min, $value_max) = $map->getMinMax($value_min, $value_max); } foreach my $object (@objects) { if ($object->{"type"} eq "Legend") { $object->setValueMinMax($value_min,$value_max); } elsif ($object->{"type"} eq "Histogram") { $object->setFrames(@frames); } } my @images; my $start = 0; my $end = $#frames; if (defined $start_frame) { unless ($start_frame <= $#frames) { die("start frame:$start_frame is greater " ." then the number of frames:$#frames\n"); } $start = $start_frame; } if (defined $end_frame) { unless ($end_frame <= $#frames) { die("end frame:$end_frame is greater " ." then the number of frames:$#frames\n"); } $end = $end_frame; } foreach my $index ($start..$end) { my $frame = $frames[$index]; my $sum = $frame->{"sum"}; my $time = $frame->{"time"}; my $file = $frame->{"file"}; if (defined $verbose || defined $start_frame || defined $end_frame) { print "DrawImages: ",$index+1-$start," of ",$end-$start+1, ," time:$time file:$file\n"; } SetBlack($work_canvas); foreach my $object (@objects) { my $type = $object->{"type"}; if ($type eq "Map") { $object->draw($time, $work_canvas, $value_min, $value_max); } elsif ($type eq "Image") { $object->draw($work_canvas); } elsif ($type eq "Histogram" || $type eq "Legend") { $object->draw($work_canvas, $index); } } $work_canvas->printPng($file); $images[$index] = $file; } return @images; } =cut sub FindWidthHeight { my ($control) = @_; my @maps = @{$control->{maps}}; my @graphs = @{$control->{graphs}}; my ($width,$height) = (5,5); foreach my $object (@maps, @graphs) { my $w = $object->{x} + $object->{width}; my $h = $object->{y} + $object->{height}; if ($w > $width) { $width = $w; } if ($h > $height) { $height = $h; } } $control->{global}->setValue("width",$width); $control->{global}->setValue("height",$height); return ($width, $height); } =cut sub SetBlack { my ($canvas) = @_; my ($w,$h) = $canvas->getBounds(); my $black = $canvas->getColor(0,0,0); $canvas->{IMAGE}->filledRectangle(0,0,$w,$h,$black); } ################################################################## # Merge Images to form final output ################################################################## sub MergeImages { my ($control, $pngs, $frames) = @_; my $verbose = $control->{"verbose"}; my @pngs = @$pngs; my @gifs; my @delay_file; my $delay_master = $control->GetValue("delay"); my $start = 0; my $end = $#frames; if (defined $start_frame) { $start = $start_frame; } if (defined $end_frame) { $end = $end_frame; } foreach my $index ($start..$end) { my $delay = POSIX::floor($delay_master*$frames[$index]{"delay"}); if ($index == $#pngs) { $delay = $delay_master*8; } my $png = $pngs[$index]; if (defined $gifsicle_found) { my $gif = $png; $gif =~ s/png$/gif/; push @gifs, $gif; push @delay_file, "-d $delay $gif"; if (defined $verbose) { print "$index convert $png $gif\n"; } system("convert $png $gif"); } else { push @delay_file, "-delay $delay $png"; } } my $output = $control->GetValue("output"); unless ($output =~ /[gG][iI][fF]/) { $output .= ".gif"; } my @commands; if (defined $gifsicle_found) { @commands = ( "gifsicle", "--colors", 256, "-lforever", "-O2", "-o", $output, @delay_file); } else { @commands = ( "convert", "-format", "gif", @delay_file, $output); } my $command = join(" ",@commands); if (defined $verbose) { print $command,"\n"; } system($command); return @gifs; } ################################################################## # Cleans up the images and temp directory ################################################################## sub CleanUp { my ($control, $dir, @files) = @_; my $verbose = $control->{"verbose"}; if (defined $verbose) { print "Removing Temporary files\n"; } my $start = 0; my $end = $#files; if (defined $start_frame) { $start = $start_frame; } if (defined $end_frame) { $end = $end_frame; } foreach my $index ($start..$end) { my $file = $files[$index]; unless (unlink $file) { print STDERR "Failed to removed temporary file $file:$!\n"; } } unless (rmdir $dir) { print STDERR "Failed to removed temporary directory $dir:$!\n"; } } ################################################################## # Checks to make sure convert is in path ################################################################## sub CheckConvert { unless (`convert -help` =~ /ImageMagick/) { die("$0 requires convert from ImageMagick"); } unless (`gifsicle -h` =~ /GIF/) { print STDERR "Failed to find gifsicle in your path, falling back on convert\n"; } else { $gifsicle_found = 1; } }