1#============================================================= -*-Perl-*-
2#
3# Template::Plugin::Image
4#
5# DESCRIPTION
6#  Plugin for encapsulating information about an image.
7#
8# AUTHOR
9#   Andy Wardley <abw@wardley.org>
10#
11# COPYRIGHT
12#   This module is free software; you can redistribute it and/or
13#   modify it under the same terms as Perl itself.
14#
15#============================================================================
16
17package Template::Plugin::Image;
18
19use strict;
20use warnings;
21use base 'Template::Plugin';
22use Template::Exception;
23use File::Spec;
24
25our $VERSION = 1.21;
26our $AUTOLOAD;
27
28BEGIN {
29    if (eval { require Image::Info; }) {
30        *img_info = \&Image::Info::image_info;
31    }
32    elsif (eval { require Image::Size; }) {
33        *img_info = sub {
34            my $file = shift;
35            my @stuff = Image::Size::imgsize($file);
36            return { "width"  => $stuff[0],
37                     "height" => $stuff[1],
38                     "error"  =>
39                        # imgsize returns either a three letter file type
40                        # or an error message as third value
41                        (defined($stuff[2]) && length($stuff[2]) > 3
42                            ? $stuff[2]
43                            : undef),
44                   };
45        }
46    }
47    else {
48        die(Template::Exception->new("image",
49            "Couldn't load Image::Info or Image::Size: $@"));
50    }
51
52}
53
54#------------------------------------------------------------------------
55# new($context, $name, \%config)
56#
57# Create a new Image object.  Takes the pathname of the file as
58# the argument following the context and an optional
59# hash reference of configuration parameters.
60#------------------------------------------------------------------------
61
62sub new {
63    my $config = ref($_[-1]) eq 'HASH' ? pop(@_) : { };
64    my ($class, $context, $name) = @_;
65    my ($root, $file, $type);
66
67    # name can be a positional or named argument
68    $name = $config->{ name } unless defined $name;
69
70    return $class->throw('no image file specified')
71        unless defined $name and length $name;
72
73    # name can be specified as an absolute path or relative
74    # to a root directory
75
76    if ($root = $config->{ root }) {
77        $file = File::Spec->catfile($root, $name);
78    }
79    else {
80        $file = defined $config->{file} ? $config->{file} : $name;
81    }
82
83    # Make a note of whether we are using Image::Size or
84    # Image::Info -- at least for the test suite
85    $type = $INC{"Image/Size.pm"} ? "Image::Size" : "Image::Info";
86
87    # set a default (empty) alt attribute for tag()
88    $config->{ alt } = '' unless defined $config->{ alt };
89
90    # do we want to check to see if file exists?
91    bless {
92        %$config,
93        name => $name,
94        file => $file,
95        root => $root,
96        type => $type,
97    }, $class;
98}
99
100#------------------------------------------------------------------------
101# init()
102#
103# Calls image_info on $self->{ file }
104#------------------------------------------------------------------------
105
106sub init {
107    my $self = shift;
108    return $self if $self->{ size };
109
110    my $image = img_info($self->{ file });
111    return $self->throw($image->{ error }) if defined $image->{ error };
112
113    @$self{ keys %$image } = values %$image;
114    $self->{ size } = [ $image->{ width }, $image->{ height } ];
115
116    $self->{ modtime } = (stat $self->{ file })[10];
117
118    return $self;
119}
120
121#------------------------------------------------------------------------
122# attr()
123#
124# Return the width and height as HTML/XML attributes.
125#------------------------------------------------------------------------
126
127sub attr {
128    my $self = shift;
129    my $size = $self->size();
130    return "width=\"$size->[0]\" height=\"$size->[1]\"";
131}
132
133
134#------------------------------------------------------------------------
135# modtime()
136#
137# Return last modification time as a time_t:
138#
139#   [% date.format(image.modtime, "%Y/%m/%d") %]
140#------------------------------------------------------------------------
141
142sub modtime {
143    my $self = shift;
144    $self->init;
145    return $self->{ modtime };
146}
147
148
149#------------------------------------------------------------------------
150# tag(\%options)
151#
152# Return an XHTML img tag.
153#------------------------------------------------------------------------
154
155sub tag {
156    my $self = shift;
157    my $options = ref $_[0] eq 'HASH' ? shift : { @_ };
158
159    my $tag = '<img src="' . $self->name() . '" ' . $self->attr();
160
161    # XHTML spec says that the alt attribute is mandatory, so who
162    # are we to argue?
163
164    $options->{ alt } = $self->{ alt }
165        unless defined $options->{ alt };
166
167    if (%$options) {
168        for my $key (sort keys %$options) {
169            my $escaped = escape( $options->{$key} );
170            $tag .= qq[ $key="$escaped"];
171        }
172    }
173
174    $tag .= ' />';
175
176    return $tag;
177}
178
179sub escape {
180    my ($text) = @_;
181    for ($text) {
182        s/&/&amp;/g;
183        s/</&lt;/g;
184        s/>/&gt;/g;
185        s/"/&quot;/g;
186    }
187    $text;
188}
189
190sub throw {
191    my ($self, $error) = @_;
192    die (Template::Exception->new('Image', $error));
193}
194
195sub AUTOLOAD {
196    my $self = shift;
197   (my $a = $AUTOLOAD) =~ s/.*:://;
198
199    $self->init;
200    return $self->{ $a };
201}
202
2031;
204
205__END__
206
207=head1 NAME
208
209Template::Plugin::Image - Plugin access to image sizes
210
211=head1 SYNOPSIS
212
213    [% USE Image(filename) %]
214    [% Image.width %]
215    [% Image.height %]
216    [% Image.size.join(', ') %]
217    [% Image.attr %]
218    [% Image.tag %]
219
220=head1 DESCRIPTION
221
222This plugin provides an interface to the L<Image::Info> or L<Image::Size>
223modules for determining the size of image files.
224
225You can specify the plugin name as either 'C<Image>' or 'C<image>'.  The
226plugin object created will then have the same name.  The file name of
227the image should be specified as a positional or named argument.
228
229    [% # all these are valid, take your pick %]
230    [% USE Image('foo.gif') %]
231    [% USE image('bar.gif') %]
232    [% USE Image 'ping.gif' %]
233    [% USE image(name='baz.gif') %]
234    [% USE Image name='pong.gif' %]
235
236A C<root> parameter can be used to specify the location of the image file:
237
238    [% USE Image(root='/path/to/root', name='images/home.png') %]
239    # image path: /path/to/root/images/home.png
240    # img src: images/home.png
241
242In cases where the image path and image url do not match up, specify the
243file name directly:
244
245    [% USE Image(file='/path/to/home.png', name='/images/home.png') %]
246
247The C<alt> parameter can be used to specify an alternate name for the
248image, for use in constructing an XHTML element (see the C<tag()> method
249below).
250
251    [% USE Image('home.png', alt="Home") %]
252
253You can also provide an alternate name for an C<Image> plugin object.
254
255    [% USE img1 = image 'foo.gif' %]
256    [% USE img2 = image 'bar.gif' %]
257
258The C<name> method returns the image file name.
259
260    [% img1.name %]     # foo.gif
261
262The C<width> and C<height> methods return the width and height of the
263image, respectively.  The C<size> method returns a reference to a 2
264element list containing the width and height.
265
266    [% USE image 'foo.gif' %]
267    width: [% image.width %]
268    height: [% image.height %]
269    size: [% image.size.join(', ') %]
270
271The C<modtime> method returns the modification time of the file in question,
272suitable for use with the L<Date|Template::Plugin::Date> plugin, for example:
273
274    [% USE image 'foo.gif' %]
275    [% USE date %]
276    [% date.format(image.modtime, "%B, %e %Y") %]
277
278The C<attr> method returns the height and width as HTML/XML attributes.
279
280    [% USE image 'foo.gif' %]
281    [% image.attr %]
282
283Typical output:
284
285    width="60" height="20"
286
287The C<tag> method returns a complete XHTML tag referencing the image.
288
289    [% USE image 'foo.gif' %]
290    [% image.tag %]
291
292Typical output:
293
294    <img src="foo.gif" width="60" height="20" alt="" />
295
296You can provide any additional attributes that should be added to the
297XHTML tag.
298
299    [% USE image 'foo.gif' %]
300    [% image.tag(class="logo" alt="Logo") %]
301
302Typical output:
303
304    <img src="foo.gif" width="60" height="20" alt="Logo" class="logo" />
305
306Note that the C<alt> attribute is mandatory in a strict XHTML C<img>
307element (even if it's empty) so it is always added even if you don't
308explicitly provide a value for it.  You can do so as an argument to
309the C<tag> method, as shown in the previous example, or as an argument
310
311    [% USE image('foo.gif', alt='Logo') %]
312
313=head1 CATCHING ERRORS
314
315If the image file cannot be found then the above methods will throw an
316C<Image> error.  You can enclose calls to these methods in a
317C<TRY...CATCH> block to catch any potential errors.
318
319    [% TRY;
320         image.width;
321       CATCH;
322         error;      # print error
323       END
324    %]
325
326=head1 USING Image::Info
327
328At run time, the plugin tries to load L<Image::Info> in preference to
329L<Image::Size>. If L<Image::Info> is found, then some additional methods are
330available, in addition to C<size>, C<width>, C<height>, C<attr>, and C<tag>.
331These additional methods are named after the elements that L<Image::Info>
332retrieves from the image itself. The types of methods available depend on the
333type of image (see L<Image::Info> for more details). These additional methods
334will always include the following:
335
336=head2 file_media_type
337
338This is the MIME type that is appropriate for the given file format.
339The corresponding value is a string like: "C<image/png>" or "C<image/jpeg>".
340
341=head2 file_ext
342
343The is the suggested file name extension for a file of the given
344file format.  The value is a 3 letter, lowercase string like
345"C<png>", "C<jpg>".
346
347=head2 color_type
348
349The value is a short string describing what kind of values the pixels
350encode.  The value can be one of the following:
351
352    Gray
353    GrayA
354    RGB
355    RGBA
356    CMYK
357    YCbCr
358    CIELab
359
360These names can also be prefixed by "C<Indexed->" if the image is
361composed of indexes into a palette.  Of these, only "C<Indexed-RGB>" is
362likely to occur.
363
364(It is similar to the TIFF field PhotometricInterpretation, but this
365name was found to be too long, so we used the PNG inspired term
366instead.)
367
368=head2 resolution
369
370The value of this field normally gives the physical size of the image
371on screen or paper. When the unit specifier is missing then this field
372denotes the squareness of pixels in the image.
373
374The syntax of this field is:
375
376   <res> <unit>
377   <xres> "/" <yres> <unit>
378   <xres> "/" <yres>
379
380The C<E<lt>resE<gt>>, C<E<lt>xresE<gt>> and C<E<lt>yresE<gt>> fields are
381numbers.  The C<E<lt>unitE<gt>> is a string like C<dpi>, C<dpm> or
382C<dpcm> (denoting "dots per inch/cm/meter).
383
384=head2 SamplesPerPixel
385
386This says how many channels there are in the image.  For some image
387formats this number might be higher than the number implied from the
388C<color_type>.
389
390=head2 BitsPerSample
391
392This says how many bits are used to encode each of samples.  The value
393is a reference to an array containing numbers. The number of elements
394in the array should be the same as C<SamplesPerPixel>.
395
396=head2 Comment
397
398Textual comments found in the file.  The value is a reference to an
399array if there are multiple comments found.
400
401=head2 Interlace
402
403If the image is interlaced, then this returns the interlace type.
404
405=head2 Compression
406
407This returns the name of the compression algorithm is used.
408
409=head2 Gamma
410
411A number indicating the gamma curve of the image (e.g. 2.2)
412
413=head1 AUTHOR
414
415Andy Wardley E<lt>abw@wardley.orgE<gt> L<http://wardley.org/>
416
417=head1 COPYRIGHT
418
419Copyright (C) 1996-2007 Andy Wardley.  All Rights Reserved.
420
421This module is free software; you can redistribute it and/or
422modify it under the same terms as Perl itself.
423
424=head1 SEE ALSO
425
426L<Template::Plugin>, L<Image::Info>
427
428=cut
429
430# Local Variables:
431# mode: perl
432# perl-indent-level: 4
433# indent-tabs-mode: nil
434# End:
435#
436# vim: expandtab shiftwidth=4:
437