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/&/&/g; 183 s/</</g; 184 s/>/>/g; 185 s/"/"/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