The 2002 Perl Advent Calendar
[about] | [archives] | [contact] | [home]

On the 5th day of Advent my True Language brought to me..
Image::Size

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] }

  • Image::Info, an alternative module for doing the same thing
  • HTML::TokeParser::Simple
  • Image::Magick