Image::Size does what any good module really should do - one thing, and one thing well, and that thing in this example is finding the size of an image.
How many times have you created a webpage, linked to an image, and simply have forgotten to include the width="" and height="" attributes in the img tag? Web browsers use these tags to determine how the image should be shown, and although by default they will show it at it's normal size (which is almost certainly what you want) they can't work this out this until they've loaded the image. That means that your page can't display properly - if at all - until all the images have loaded.
With Image::Size you can quickly and easily find out the size of an image, and with the help of a little scripting you can add the image sizes to your webpage.
There are many good reasons for using the Image::Size over other Perl graphics libraries such as Image::Magick and GD. Firstly, and foremostly, it's easier to install. It doesn't require any external libraries. Secondly, when it examines an image, Image::Size is only looking for the image size, often meaning all it has to do is read the first few bytes of the image, whereas a 'proper' image library like GD would try and be more general purpose and load the entire image - a much less efficient approach.
Thirdly, and most importantly, Image::Size caches the size of each image it loads for you. This means that if you use one script to run over all your pages then each image only has to be loaded once, which will make the whole process a lot quicker.
Using image size is simplicity itself. The easiest way to use it is simply to pass it a filename of an image and it will return the height and width
use Image::Size;
# get the image size, and print it out my ($width, $height) = imgsize("image.jpg"); print "The image is $width by $height";
Of course, image size is able to use an open file handle
use Image::Size; use IO::File;
# open the file in binary mode my $filehandle = IO::File->new("image.jpg") or die "coundn't open 'image.jpg': $!"; binmode $filehandle;
# get the image size, and print it out my ($width, $height) = imgsize($filehandle); print "The image is $width by $height";
Or even a image that's already been read into memory;
use Image::Size; use IO::File;
# open a file in binary mode my $filehandle = IO::File->new("image.jpg") or die "coundn't open 'image.jpg': $!"; binmode $filehandle;
# read in the entire file into $image_data my $image_data; { local $/; # set it so <> reads all the file at once $image_data = <$filehandle>; # read in the file }
# get the image size and print it out my ($width, $height) = imgsize($image_data); print "The image is $width by $height";
This is all very useful, but nine times out of ten the real reason
you'll want to use Image::Size is because you want to fill in the
width and height attributes of a HTML <IMG>
tag. Luckly
the writers of Image::Size provided a couple of convenience
methods that allow us to really easily create parts of HTML.
The first of these convenience methods is able to automatically produce the string containing both attributes. It's not exported by default from the Image::Size module, so to access it you need to pass the 'html_imgsize' parameter to the use call.
# load the module, and import 'html_imgsize' use Image::Size qw(html_imgsize);
# load the cross platform file operations module # (which will allow us to use the 'catfile' function) use File::Spec::Functions;
sub img_tag { # get the name of the image passed in my $img_name = shift;
# return a tag with the image name width and height return qq{<img src="/imgs/$img_name" } . html_imgsize(catfile(DOCROOT, "imgs", $img_name)) . qq{>} }
Where DOCROOT
is where the imgs
directory is kept on your local
hard drive. An example call later on that script to generate an image
tag might look like:
print "<p>And this is a picture of us inside</p>" print img_tag("us_inside.jpg");
This is fine technique if we're creating the HTML 'by hand'. But what if we're using the CGI module to automatically output the html for us. The normal way to create an image ref with the CGI module is:
use CGI qw(:html);
print img(-src => "imgs/$imgname", -width => 40, -height => 20,);
Now of course if we had function that returned a list containing -width => 40, -height=>20
then we could substitute it in for the
subject parameters themselves. Luckly, this is exactly what the
attr_imgsize
function does:
use CGI qw(:html); use Image::Size qw(attr_imgsize);
print img(-src => "img/$imgname", attr_imgsize(catfile(DOCROOT,"img",$imgname)));
Note again that this function is not automatically exported by Image::Size and how we have to import it manually within the use statement.
Now we've seen all the ways we can use Image::Size lets end
with a bigger example. This following script takes existing files,
parses them with one of the HTML::Parser modules (in this case
HTML::TokeParser::Simple) and change the img
tags as we go to
have the correct dimensions.
#!/usr/bin/perl
# turn on perl's safety features use strict; use warnings;
# load the modules we need use HTML::TokeParser::Simple; use Image::Size; use IO::AtomicFile;
# for each html page passed on the command line foreach my $htmlfile (@ARGV) { # create a parser to decode that file my $parser = HTML::TokeParser::Simple->new($htmlfile);
# open temp file that will be copied to the original # file once we're done. my $fh = IO::AtomicFile->new($htmlfile,">") or die "Can't open '$htmlfile.TMP': $!";
# process each bit of the file while (my $token = $parser->get_token()) { # is it an img tag? if ($token->is_start_tag('img')) { # get the attributes (the src="" etc) my $attr = $token->return_attr;
# change the width and height ($attr->{width}, $attr->{height}) = imgsize(url2file($attr->{src}));
# now recreate the tag print {$fh} "<img"; print {$fh} map { qq{ $_="$attr->{$_}"} } keys %$attr; print {$fh} ">"; } else { # something else? Print it as is without changes print {$fh} $token->as_is; } } }
# A function that converts urls into file paths. # (since I'm on unix and all my image locations are relative, # this just returns what it was originally passed.) sub url2file { return $_[0] }