File Coverage

blib/lib/GD/Tiler.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


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