File Coverage

blib/lib/Tempest.pm
Criterion Covered Total %
statement 62 149 41.6
branch 5 34 14.7
condition 5 15 33.3
subroutine 16 35 45.7
pod 5 21 23.8
total 93 254 36.6


line stmt bran cond sub pod time code
1             package Tempest;
2              
3 6     6   100179 use strict;
  6         9  
  6         181  
4 6     6   20 use warnings;
  6         4  
  6         153  
5 6     6   2493 use version;
  6         9378  
  6         26  
6              
7 6     6   379 use Carp;
  6         9  
  6         391  
8 6     6   23 use File::Basename;
  6         8  
  6         505  
9              
10             =head1 NAME
11              
12             Tempest - Flexible temperature-map/heat-map generator
13              
14             =head1 DESCRIPTION
15              
16             Tempest is implemented natively in multiple programming languages, including
17             Perl 5. This implementation is "pure" Perl, meaning that there is no
18             C or XS code to configure or compile. Installation entails the steps for any
19             modern CPAN module:
20              
21             perl Makefile.PL
22             make
23             make test
24             make install
25              
26             =head1 VERSION
27              
28             Version 2009.10.31_1 (beta release)
29              
30             Tempest API Version 2009.07.15
31              
32             =cut
33              
34             our $VERSION = qv('2009.10.31_1'); # using CPAN alpha versioning to denote non-stable release
35             our $API_VERSION = qv('2009.07.15');
36              
37             =head1 SYNOPSIS
38              
39             This module exposes the Tempest API through class instantiation:
40              
41             use Tempest;
42            
43             # Create new instance
44             $heatmap = new Tempest(
45             'input_file' => 'screenshot.png',
46             'output_file' => 'heatmap.png',
47             'coordinates' => [ [0,10], [2,14], [2,14] ],
48             ));
49            
50             # Configure as needed
51             $heatmap->set_image_lib( Tempest::LIB_GD );
52            
53             # Generate and write heatmap image
54             $heatmap->render();
55              
56             =head1 CONSTANTS
57              
58             These constants can be assigned to the C property to specify use
59             of a given image library for all image manipulations.
60              
61             =head3 C
62              
63             For forcing use of L support.
64              
65             =head3 C
66              
67             For forcing use of L support.
68              
69             =head3 C
70              
71             For forcing use of L support.
72              
73             =cut
74              
75 6     6   25 use constant LIB_MAGICK => 'Image::Magick';
  6         7  
  6         457  
76 6     6   21 use constant LIB_GMAGICK => 'Graphics::Magick';
  6         8  
  6         235  
77 6     6   20 use constant LIB_GD => 'GD';
  6         7  
  6         6299  
78              
79             =head1 PROPERTIES
80              
81             =head2 Required Properties
82              
83             =head3 C
84              
85             The generated heatmap will share the same dimensions as this image,
86             and - if indicated - will be overlaid onto this image with a given
87             opacity.
88              
89             =cut
90              
91             my %input_file;
92              
93             =head3 C
94              
95             The generated heatmap will be written to this path, replacing any
96             existing file without warning.
97              
98             =cut
99              
100             my %output_file;
101              
102             =head3 C
103              
104             The contained x,y coordinates will mark the center of all plotted data
105             points in the heatmap. Coordinates can - and in many cases are
106             expected to - be repeated.
107              
108             =cut
109              
110             my %coordinates;
111              
112             =head2 Optional Properties
113              
114             =head3 C
115              
116             This image, expected to be greyscale, is used to plot data points for
117             each of the given coordinates. Defaults to a bundled image, if none
118             is provided.
119              
120             =cut
121              
122             my %plot_file;
123              
124             =head3 C
125              
126             This image, expected to be a true color vertical gradient, is used as a
127             color lookup table and is applied to the generated heatmap. Defaults
128             to a bundled image, if none is provided.
129              
130             =cut
131              
132             my %color_file;
133              
134             =head3 C
135              
136             If true, the heatmap is overlaid onto the input image with a given
137             opacity before being written to the filesystem. Defaults to B.
138              
139             =cut
140              
141             my %overlay;
142              
143             =head3 C
144              
145             Indicates with what percentage of opaqueness to overlay the heatmap
146             onto the input image. If 0, the heatmap will not be visible; if 100,
147             the input image will not be visible. Defaults to b<50>.
148              
149             =cut
150              
151             my %opacity;
152              
153             =head3 C
154              
155             Indicates which supported image manipulation library should be used
156             for rendering operations. Defaults to the first available from the
157             following:
158              
159             =cut
160              
161             my %image_lib;
162              
163             my @_required = ('input_file', 'output_file', 'coordinates');
164             my @_optional = ('plot_file', 'color_file', 'overlay', 'opacity', 'image_lib');
165              
166             =head1 METHODS
167              
168             =head2 C
169              
170             Class constructor, accepts a hash of named arguments corresponding to
171             the class' own getter and setter methods.
172              
173             $heatmap = new Tempest(
174             'input_file' => 'screenshot.png',
175             'output_file' => 'heatmap.png',
176             'coordinates' => [ [0,10], [2,14], [2,14] ],
177             );
178              
179             =cut
180              
181             sub new {
182 1     1 1 79 my $class = shift;
183            
184 1 50       5 croak('Bad parameter list, should be a hash') if @_ % 2;
185 1         4 my %params = @_;
186            
187             # inside-out object model
188 1         3 my $self = bless \(my $dummy), $class;
189            
190             # set defaults
191 1         31 $plot_file{$self} = dirname(__FILE__) . '/Tempest/data/plot.png';
192 1         34 $color_file{$self} = dirname(__FILE__) . '/Tempest/data/clut.png';
193 1         4 $overlay{$self} = 1;
194 1         3 $opacity{$self} = 50;
195 1         6 $image_lib{$self} = $self->_calc_image_lib();
196            
197             # for all required parameters..
198 0         0 for my $param_name (@_required) {
199             # ..ensure they were provided
200 0 0       0 if(!exists $params{$param_name}) {
201 0         0 croak("Missing required parameter '$param_name'");
202             }
203            
204             # ..and call each of their setters
205 0         0 eval('$self->set_' . $param_name . '($params{$param_name})');
206             }
207            
208             # for all optional parameters..
209 0         0 for my $param_name (@_optional) {
210             # ..if they were provided..
211 0 0       0 if(exists $params{$param_name}) {
212             # ..call their setters
213 0         0 eval('$self->set_' . $param_name . '($params{$param_name})');
214             }
215             }
216            
217 0         0 return $self;
218             }
219              
220             =head2 C
221              
222             Initiates processing of provided arguments, and writes a heatmap image
223             to the filesystem. Returns B on success.
224              
225             die('Rendering failed') if ! $heatmap->render();
226              
227             =cut
228              
229             sub render {
230 0     0 1 0 my $self = shift;
231            
232 0         0 my $lib_name = ucfirst(lc($image_lib{$self}));
233 0         0 $lib_name =~ s/\W//g;
234            
235 0         0 my $result = eval('require Tempest::'.$lib_name.'; return Tempest::'.$lib_name.'::render($self);');
236 0 0       0 croak($@) if $@;
237 0         0 return $result;
238             }
239              
240             =head2 C
241              
242             Returns the version number of the current release.
243              
244             die('Outdated') if $heatmap->version() lt '2009.06.15';
245              
246             =cut
247              
248             sub version {
249 0     0 1 0 return $VERSION;
250             }
251              
252             =head2 C
253              
254             Returns the version number of the currently supported Tempest API.
255              
256             die('API is outdated') if $heatmap->api_version() lt '2009.06.15';
257              
258             =cut
259              
260             sub api_version {
261 0     0 1 0 return $API_VERSION;
262             }
263              
264             =head2 Setters
265              
266             Each setter method assigns a new value to its respective property.
267             The setters also return the current class instance, so they can be 'chained'.
268              
269             For example, if we wanted to change the C used for image processing,
270             and immediately render the resulting heatmap:
271              
272             # render heatmap with Image::Magick support
273             $heatmap ->set_image_lib( Tempest::LIB_MAGICK ) ->render();
274              
275             =head2 Getters
276              
277             Each getter method returns the current value of its respective property.
278              
279             For example, if we wanted to retrieve the C to be rendered and
280             immediately output them with the L module:
281              
282             use Data::Dumper;
283             print Dumper( $heatmap->get_coordinates() );
284              
285             =cut
286              
287             sub set_input_file {
288 0     0 0 0 my $self = shift;
289 0         0 my $input_file = shift;
290            
291 0 0       0 if(-r $input_file) {
292 0         0 $input_file{$self} = $input_file;
293             }
294             else {
295 0         0 croak("Image '$input_file' is not readable");
296             }
297            
298 0         0 return $self;
299             }
300              
301             sub get_input_file {
302 0     0 0 0 my $self = shift;
303 0         0 return $input_file{$self};
304             }
305              
306              
307             sub set_output_file {
308 0     0 0 0 my $self = shift;
309 0         0 my $output_file = shift;
310            
311 0 0 0     0 if((! -e $output_file) || -w $output_file) {
312 0         0 $output_file{$self} = $output_file;
313             }
314             else {
315 0         0 croak("Image '$output_file' is not writable");
316             }
317            
318 0         0 return $self;
319             }
320              
321             sub get_output_file {
322 0     0 0 0 my $self = shift;
323 0         0 return $output_file{$self};
324             }
325              
326              
327             sub set_coordinates {
328 0     0 0 0 my $self = shift;
329 0         0 my $coordinates = shift;
330            
331             # verify an array of 2-element arrays
332 0 0       0 if(ref($coordinates) ne 'ARRAY') {
333 0         0 croak('Bad coordinates: not an array reference');
334             }
335            
336 0         0 for my $pair (@{$coordinates}) {
  0         0  
337 0 0 0     0 if(ref($pair) ne 'ARRAY' || scalar(@{$pair}) != 2) {
  0         0  
338 0         0 croak('Bad coordinate pair: ' . join(',', @{$pair}));
  0         0  
339             }
340             }
341            
342 0         0 $coordinates{$self} = $coordinates;
343 0         0 return $self;
344             }
345              
346             sub get_coordinates {
347 0     0 0 0 my $self = shift;
348 0         0 return $coordinates{$self};
349             }
350              
351              
352             sub set_plot_file {
353 0     0 0 0 my $self = shift;
354 0         0 my $plot_file = shift;
355            
356 0 0       0 if(-r $plot_file) {
357 0         0 $plot_file{$self} = $plot_file;
358             }
359             else {
360 0         0 croak("Image '$plot_file' is not readable");
361             }
362            
363 0         0 return $self;
364             }
365              
366             sub get_plot_file {
367 0     0 0 0 my $self = shift;
368 0         0 return $plot_file{$self};
369             }
370              
371              
372             sub set_color_file {
373 0     0 0 0 my $self = shift;
374 0         0 my $color_file = shift;
375            
376 0 0       0 if(-r $color_file) {
377 0         0 $color_file{$self} = $color_file;
378             }
379             else {
380 0         0 croak("Image '$color_file' is not readable");
381             }
382            
383 0         0 return $self;
384             }
385              
386             sub get_color_file {
387 0     0 0 0 my $self = shift;
388 0         0 return $color_file{$self};
389             }
390              
391              
392             sub set_overlay {
393 0     0 0 0 my $self = shift;
394 0         0 my $overlay = shift;
395            
396 0 0       0 $overlay{$self} = $overlay ? 1 : 0;
397 0         0 return $self;
398             }
399              
400             sub get_overlay {
401 0     0 0 0 my $self = shift;
402 0         0 return $overlay{$self};
403             }
404              
405              
406             sub set_opacity {
407 0     0 0 0 my $self = shift;
408 0         0 my $opacity = shift;
409            
410 0 0 0     0 if($opacity >=0 && $opacity <= 100) {
411 0         0 $opacity{$self} = $opacity;
412             }
413             else {
414 0         0 croak("'$opacity' is not a valid percentage (integer from 0 to 100)");
415             }
416            
417 0         0 return $self;
418             }
419              
420             sub get_opacity {
421 0     0 0 0 my $self = shift;
422 0         0 return $opacity{$self};
423             }
424              
425              
426             sub set_image_lib {
427 0     0 0 0 my $self = shift;
428 0         0 my $image_lib = shift;
429            
430 0 0       0 if($self->has_image_lib($image_lib)) {
431 0         0 $image_lib{$self} = $image_lib;
432             }
433             else {
434 0         0 croak("Image library '$image_lib' could not be found");
435             }
436            
437 0         0 return $self;
438             }
439              
440             sub get_image_lib {
441 0     0 0 0 my $self = shift;
442 0         0 return $image_lib{$self};
443             }
444              
445             =head2 C
446              
447             Returns true value if the given image library is available.
448              
449             die('GD is unavailable') if ! $heatmap->has_image_lib(Tempest::LIB_GD);
450              
451             =cut
452              
453             sub has_image_lib {
454 3     3 1 6 my $self = shift;
455 3         2 my $image_lib = shift;
456            
457             # work as instance method or static method
458 3 50       9 if(ref($self) ne 'Tempest') {
459 3         3 $image_lib = $self;
460 3         9 undef $self;
461             }
462            
463 3 50 100     23 if($image_lib eq LIB_MAGICK || $image_lib eq LIB_GMAGICK || $image_lib eq LIB_GD) {
      66        
464 3     1   196 eval("no warnings 'all'; require $image_lib;");
  1     1   8  
  1     1   2  
  1         49  
  1         4  
  1         21  
  1         31  
  1         4  
  1         1  
  1         22  
465 3 50       11 if(!$@) {
466 0         0 return 1;
467             }
468             else {
469 3         11 return 0;
470             }
471             }
472             else {
473 0         0 croak("Image library '$image_lib' is not supported");
474             }
475             }
476              
477             # Determine optimal supported (and available) image library to use
478             # not intended to be public, so no need to document it
479             sub _calc_image_lib {
480 1     1   4 for my $image_lib (LIB_MAGICK, LIB_GMAGICK, LIB_GD) {
481 3 50       7 if(Tempest::has_image_lib($image_lib)) {
482 0         0 return $image_lib;
483             }
484             }
485            
486 1         196 croak('No supported image library could be found');
487             }
488              
489             =head2 C
490              
491             Class destructor, destroys the class instance. Normally not invoked directly.
492              
493             # free up resources
494             $heatmap->DESTROY();
495              
496             =cut
497              
498             sub DESTROY {
499 1     1   32 my $self = shift;
500            
501 1         3 for my $param_name (@_required, @_optional) {
502             {
503 6     6   38 no strict 'refs';
  6         9  
  6         387  
  8         4  
504 8         7 delete ${$param_name}{$self};
  8         26  
505             };
506             }
507             }
508              
509             =head1 COPYRIGHT & LICENSE
510              
511             Copyright 2009 Evan Kaufman, all rights reserved.
512              
513             This program is released under the MIT license.
514              
515             L
516              
517             =head1 ADDITIONAL LINKS
518              
519             =over
520              
521             =item Tempest on Google Code:
522              
523             L
524              
525             =back
526              
527             =cut
528              
529             1;