File Coverage

blib/lib/Imager/Tiler.pm
Criterion Covered Total %
statement 12 102 11.7
branch 0 80 0.0
condition 0 27 0.0
subroutine 4 6 66.6
pod 1 1 100.0
total 17 216 7.8


line stmt bran cond sub pod time code
1             package Imager::Tiler;
2              
3 1     1   1572 use Imager;
  1         29382  
  1         5  
4 1     1   44 use Exporter;
  1         2  
  1         46  
5              
6             our @ISA = qw(Exporter);
7              
8             our @EXPORT = ();
9             our @EXPORT_OK = qw(tile);
10              
11 1     1   3 use strict;
  1         1  
  1         19  
12 1     1   4 use warnings;
  1         1  
  1         816  
13              
14             our $VERSION = '1.010002'; # VERSION
15              
16             =pod
17              
18             =for stopwords EdgeMargin ImagesPerRow PNG RGB TileMargin zoffix HEdgeMargin HTileMargin VEdgeMargin VTileMargin
19              
20             =head1 NAME
21              
22             Imager::Tiler - package to aggregate images into a single tiled image via Imager
23              
24             =head1 SYNOPSIS
25              
26             use Imager::Tiler qw(tile);
27             #
28             # use computed coordinates for layout, and retrieve the
29             # coordinates for later use (as imported method)
30             #
31             my ($img, @coords) = tile(
32             Images => [ 'chart1.png', 'chart2.png', 'chart3.png', 'chart4.png'],
33             Background => 'lgray',
34             Center => 1,
35             VEdgeMargin => 10,
36             HEdgeMargin => 10,
37             VTileMargin => 5,
38             HTileMargin => 5);
39             #
40             # use explicit coordinates for layout (as class method)
41             #
42             my $explimg = Imager::Tiler->tile(
43             Images => [ 'chart1.png', 'chart2.png', 'chart3.png', 'chart4.png'],
44             Background => 'lgray',
45             Width => 500,
46             Height => 500,
47             Coordinates => [
48             10, 10,
49             120, 10,
50             10, 120,
51             120, 120 ]);
52              
53             =head1 DESCRIPTION
54              
55             Creates a new tiled image from a set of input images. Various arguments
56             may be specified to position individual images, or the default
57             behaviors can be used to create an reasonable placement to fill a
58             square image.
59              
60             =head1 METHODS
61              
62             Only a single method is provided:
63              
64             =head4 $image = Imager::Tiler->tile( %args )
65              
66             =head4 ($image, @coords) = Imager::Tiler->tile( %args )
67              
68             Returns a Imager::Image object of the images specified in %args,
69             positioned according to the directives in %arg. In array context,
70             also returns the list of upper left corner coordinates of each image,
71             so e.g., an application can adjust the image map coordinate values
72             for individual images.
73              
74             Valid %args are:
75              
76             =over 4
77              
78             =item B> C<$color> I<(optional)>
79              
80             specifies a color to be used as the tiled image background. Must be a string
81             of either hexadecimal RGB values, I B<'#FFAC24'>, or a name from
82             the following list of supported colors:
83              
84             white lyellow lpurple lbrown
85             lgray yellow purple dbrown
86             gray dyellow dpurple transparent
87             dgray lgreen lorange
88             black green orange
89             lblue dgreen pink
90             blue lred dpink
91             dblue red marine
92             gold dred cyan
93              
94             Default is white.
95              
96             =item B
> C<$boolean> I<(optional)>
97              
98             If set to a "true" value, causes images to be centered within
99             their computed tile location; ignored if B is specified.
100             Default is false, which causes images to be anchored to the
101             upper left corner of their tile.
102              
103             =item B> C<\@coords> I<(optional)>
104              
105             arrayref of (X, Y) coordinates of the upper left corner of each tiled image;
106             must have an (X, Y) element for each input image. If not provided,
107             the default is a computed layout to fit images into an equal (or nearly equal)
108             number of rows and columns, in a left to right, top to bottom mapping in the
109             order specified in B. B.
110              
111             If B is specified, then B and B must also be
112             specified, and any margin values are ignored.
113              
114             =item B> C<$pixels> I<(optional)>
115              
116             outer edge margin for both top and bottom;
117             If either HEdgeMargin or VEdgeMargin, they override this value.
118              
119             =item B> C<$format> I<(optional)>
120              
121             Output image format; default is 'PNG'; valid values depend on the
122             Imager installations; see L for details.
123              
124             =item B> C<$pixels> I<(optional)>
125              
126             horizontal edge margin; space in pixels at left and right of output image;
127             default zero.
128              
129             =item B> C<$height> I<(optional)>
130              
131             total height of output image; if not specified, defaults to
132             minimum height needed to contain the images
133              
134             =item B> C<$pixels> I<(optional)>
135              
136             horizontal margin between tile images;
137             default zero.
138              
139             =item B> C<\@images> I<(required)>
140              
141             arrayref of images to be tiled; may be either Imager::Image objects,
142             or filenames; if the latter, the format is derived from
143             the file qualifier
144              
145             =item B> C<$count> I<(optional)>
146              
147             Specifies the number of images per row in the layout; ignored if
148             B is also specified. Permits an alternate layout to
149             the default approximate square layout.
150              
151             =item B> C I<(optional)>
152              
153             When set to a true value, causes tiled image to have a small
154             drop shadow behind them (10 pixels along the right and lower edges).
155             Default false.
156              
157             =item B> C<$pixels> I<(optional)>
158              
159             tile image margin, both top and bottom; if either
160             HTileMargin or VTileMargin are specified, they override this value.
161              
162             =item B> C<$pixels> I<(optional)>
163              
164             vertical edge margin; space in pixels at top and bottom of output image;
165             default zero.
166              
167             =item B> C<$pixels> I<(optional)>
168              
169             vertical margin between tile images;
170             default zero.
171              
172             =item B> C<$width> I<(optional)>
173              
174             total width of output image; if not specified, defaults to
175             minimum width needed to contain the images
176              
177             =back
178              
179             =head1 SEE ALSO
180              
181             L
182              
183             =head1 REPOSITORY
184              
185             Fork this module on GitHub:
186             L
187              
188             =head1 BUGS
189              
190             To report bugs or request features, please use
191             L
192              
193             If you can't access GitHub, you can email your request
194             to C
195              
196             =head1 MAINTAINER
197              
198             Zoffix Znet (zoffix 'at' cpan.org)
199              
200             =head1 AUTHOR, COPYRIGHT, and LICENSE
201              
202             Dean Arnold L
203              
204             Copyright(C) 2007, 2008, Dean Arnold, Presicient Corp., USA.
205              
206             Permission is granted to use, copy, modify, and redistribute this
207             software under the terms of the Academic Free License version 3.0, as specified at the
208             Open Source Initiative website L.
209              
210             =cut
211              
212             my %colors = (
213             white => [255,255,255],
214             lgray => [191,191,191],
215             gray => [127,127,127],
216             dgray => [63,63,63],
217             black => [0,0,0],
218             lblue => [0,0,255],
219             blue => [0,0,191],
220             dblue => [0,0,127],
221             gold => [255,215,0],
222             lyellow => [255,255,125],
223             yellow => [255,255,0],
224             dyellow => [127,127,0],
225             lgreen => [0,255,0],
226             green => [0,191,0],
227             dgreen => [0,127,0],
228             lred => [255,0,0],
229             red => [191,0,0],
230             dred => [127,0,0],
231             lpurple => [255,0,255],
232             purple => [191,0,191],
233             dpurple => [127,0,127],
234             lorange => [255,183,0],
235             orange => [255,127,0],
236             pink => [255,183,193],
237             dpink => [255,105,180],
238             marine => [127,127,255],
239             cyan => [0,255,255],
240             lbrown => [210,180,140],
241             dbrown => [165,42,42],
242             transparent => [1,1,1, 0]
243             );
244             #
245             # compute coordinates for tiled images
246             #
247             sub _layout {
248 0     0     my ($center, $vedge, $hedge, $vtile, $htile, $imgsperrow, $shadow, @images) = @_;
249 0           my ($rows, $cols);
250              
251 0           my $imgcnt = scalar @images;
252 0 0         if (defined($imgsperrow)) {
253 0           $cols = $imgsperrow;
254 0           $rows = int($imgcnt/$cols);
255 0 0         $rows++
256             unless (($rows * $cols) >= $imgcnt);
257             }
258             else {
259 0           $rows = $cols = int(sqrt($imgcnt));
260 0 0         unless (($rows * $cols) == $imgcnt) {
261 0           $cols++;
262 0 0         $rows++
263             unless (($rows * $cols) >= $imgcnt);
264             }
265             }
266             #
267             # compute width and height based on input images
268             #
269 0           my @rowh = ( (0) x $rows );
270 0           my @colw = ( (0) x $cols );
271 0           my @coords = ();
272 0 0         $shadow = $shadow ? 10 : 0;
273 0           foreach my $r (0..$rows-1) {
274 0           $rowh[$r] = 0;
275 0           foreach my $c (0..$cols - 1) {
276 0           my $img = ($r * $cols) + $c;
277 0 0         last unless $images[$img];
278              
279 0 0 0       my $w = $images[$img]->getwidth() + $shadow +
280             ((($r == 0) || ($r == $rows - 1)) ? (($vtile >> 1) + $vedge) : $vtile);
281 0 0 0       my $h = $images[$img]->getheight() + $shadow +
282             ((($c == 0) || ($c == $cols - 1)) ? (($htile >> 1) + $hedge) : $htile);
283              
284 0 0         $colw[$c] = $w
285             if ($colw[$c] < $w);
286 0 0         $rowh[$r] = $h
287             if ($rowh[$r] < $h);
288             }
289             }
290             #
291             # compute total image size
292             #
293 0           my ($totalw, $totalh) = ($vedge * 2, $hedge * 2);
294 0           map $totalw += $_, @colw;
295 0           map $totalh += $_, @rowh;
296             #
297             # now compute placement coords
298             #
299 0           my ($left, $top) = ($vedge, $hedge);
300 0           foreach my $r (0..$#rowh) {
301 0           foreach my $c (0..$#colw) {
302 0           my $img = ($r * $cols) + $c;
303 0 0         last unless $images[$img];
304              
305 0 0         if ($center) {
306 0           push @coords,
307             $left + (($colw[$c] - $images[$img]->getwidth() - $shadow) >> 1),
308             $top + (($rowh[$r] - $images[$img]->getheight() - $shadow) >> 1);
309             }
310             else {
311 0           push @coords, $left, $top;
312             }
313 0           $left += $colw[$c];
314             }
315              
316 0           $top += $rowh[$r];
317 0           $left = $vedge;
318             }
319 0           return ($totalw, $totalh, @coords);
320             }
321              
322             sub tile {
323 0 0   0 1   shift if ($_[0] eq 'Imager::Tiler'); # if called as a object, not class, method
324 0           my %args = @_;
325              
326 0 0 0       die 'No images specified.'
      0        
327             unless $args{Images} && ref $args{Images} &&
328             (ref $args{Images} eq 'ARRAY');
329              
330 0           foreach (@{$args{Images}}) {
  0            
331 0 0 0       next if (ref $_ && $_->isa('Imager'));
332 0           my $img = Imager->new;
333 0 0         die 'Cannot load image $_:' . $img->errstr()
334             unless $img->read(file => $_);
335 0           $_ = $img;
336             }
337              
338 0 0         $args{TileMargin} = 0
339             unless exists $args{TileMargin};
340              
341 0 0         $args{EdgeMargin} = 0
342             unless exists $args{EdgeMargin};
343              
344 0 0         $args{VEdgeMargin} = $args{EdgeMargin}
345             unless exists $args{VEdgeMargin};
346              
347 0 0         $args{HEdgeMargin} = $args{EdgeMargin}
348             unless exists $args{HEdgeMargin};
349              
350 0 0         $args{VTileMargin} = $args{TileMargin}
351             unless exists $args{VTileMargin};
352              
353 0 0         $args{HTileMargin} = $args{TileMargin}
354             unless exists $args{HTileMargin};
355              
356 0           my $background = $colors{white};
357 0 0         if (exists $args{Background}) {
358 0 0 0       die "Invalid Background $args{Background}."
359             unless exists $colors{$args{Background}} ||
360             ($args{Background}=~/^#[0-9a-fA-F]+$/);
361 0   0       $background = $colors{$args{Background}} || $args{Background};
362             }
363              
364 0 0         $args{Format} = 'png'
365             unless exists $args{Format};
366              
367 0           my $format = lc $args{Format};
368              
369 0           my ($w, $h) = ($args{Width}, $args{Height});
370              
371 0           my @coords;
372 0 0         if (exists $args{Coordinates}) {
373 0 0         die "Width not specified for explicit placement."
374             unless exists $args{Width};
375              
376 0 0         die "Height not specified for explicit placement."
377             unless exists $args{Height};
378              
379 0           @coords = @{$args{Coordinates}};
  0            
380 0           my $imgcnt = scalar @{$args{Images}};
  0            
381              
382 0 0         die "$imgcnt images require " . ($imgcnt * 2) . " coordinates, but only" . scalar @coords . " specified."
383             if ($imgcnt * 2) > scalar @coords;
384             #
385             # we'll permit more coords than images;
386             # we also permit coords to place images outside the Width/Height
387             #
388             }
389             else {
390 0           ($w, $h, @coords) = _layout(
391             $args{Center},
392             $args{VEdgeMargin},
393             $args{HEdgeMargin},
394             $args{VTileMargin},
395             $args{HTileMargin},
396             $args{ImagesPerRow},
397             $args{Shadow},
398 0           @{$args{Images}});
399              
400 0 0 0       die "Specified Width $args{Width} less than computed width of $w."
401             if (exists $args{Width}) && ($args{Width} < $w);
402              
403 0 0 0       die "Specified Height $args{Height} less than computed height of $h."
404             if (exists $args{Height}) && ($args{Height} < $h);
405             }
406             #
407             # now create and populate the image
408             # (need a way to support truecolor ?)
409             #
410 0 0         my $tiled = Imager->new(xsize => $w, ysize => $h, channels => 4)
411             or die "Unable to create image.";
412              
413 0 0         $background = ref $background
414             ? Imager::Color->new(@$background)
415             : Imager::Color->new($background);
416 0 0         die "Unable to create background color."
417             unless defined $background;
418              
419 0 0         my $shadow = $args{Shadow}
420             ? Imager::Color->new(120, 120, 120, 80)
421             : undef;
422 0 0         $tiled->box(box => [ 0,0, $w - 1, $h - 1], color => $background, filled => 1)
423             or die $tiled->errstr();
424              
425 0           my $x = 0;
426 0           foreach (@{$args{Images}}) {
  0            
427 0           $_ = $_->convert(preset => 'addalpha');
428 0           $w = $coords[$x++];
429 0           $h = $coords[$x++];
430 0 0         $tiled->box(box => [ $w + 9, $h + 9, $w + $_->getwidth() + 9, $h + $_->getheight() + 9],
431             color => $shadow, filled => 1)
432             if $shadow;
433 0 0         $tiled->rubthrough(src => $_, tx => $w, ty => $h) or die $tiled->errstr();
434             }
435             #
436             # in array context, returns the coordinates so e.g. any image maps
437             # can be adjusted to the tiled image's newl location
438             #
439 0           my $imgdata;
440 0 0         $tiled->write(data => \$imgdata, type => $format) or
441             die $tiled->errstr();
442 0 0         return wantarray ? ($imgdata, @coords) : $imgdata;
443             }
444              
445             1;