File Coverage

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


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