• home
  • forum
  • my
  • kt
  • download
  • Creating An Index Image

    Author: 2007-08-10 10:46:47 From:

    I have a digital camera that I use quite a bit to take pictures at conferences, family events, and vacations. It's convenient to have a single image that has thumbnails of all of the images that I take, similar to the index images that you sometimes get when you have 35mm film developed. In a previous article, we learned how to create a single thumbnail image. We will leverage that example to create an index image. An index image contains a group of thumbnails for an image set. For this example, an image set will be the images that exist in a given directory. For this article, I've pilfered a set of pictures that I took at Seaworld in Miami, Florida. You can see the results here.

    One of the nuisances of the GD module has been its lack of support for the GIF image format. Fortunately, the most recent version of the Perl GD library has been patched to support GIF again. Thank you Lincoln Stein (author of the Perl GD library)! When I first developed this example though, support was not available. Fortunately, all of my images are in JPEG format, so this wasn't an issue, but there are certainly many GIF images out there.

    Searching For Images In A Directory

    The first thing we need to do is to get our list of images that we want to include in the index image. We can do this by opening a directory handle with the opendir() function and filtering for .JPG files with the readdir, grep, and map.

    opendir(DIR,$dir) || die "Cannot open directory $dir: $!\n";
    my @images = grep -f, grep /\.jpg$/i, map "$dir/$_", readdir DIR;
    closedir(DIR);
    

    The readdir function returns an array containing the list of files in the directory in the given directory handle (DIR). This list will not only include files that are JPEG images, but it also returns subdirectories. We can filter the results that are returned before the list is assigned to our @images array by applying a number of filters, separated by commas, with the grep command.

    grep -f filters the list with the -f switch, which will only return files that are indeed files (not directories). grep /\.jpg$/i filters for all files that end with a .jpg extension. This gives us a complete listing of all JPEG files in the directory. However, we need to take one additional step.

    Because we need to open each JPEG file in the directory, we should map the name of the directory onto the file names so that the program knows where to fetch the files from. This is done with map "$dir/$_", which appends the directory name specified in the $dir variable, followed by a slash, on the each file name.

    Setting Borders, Spacing, And Thumbnail Sizes

    The next thing to think about concerns size and spacing of the borders and thumbnails. We need to set a maximum width and height that each thumbnail must fit into. We don't actually want to simply resize the image to the thumbnail specifications, rather we want to resize the image to fit within the dimensions while also maintaining the image proportions. I decided that the thumbnails should fit within a 100 x 100 pixel size.

    Another thing to think about is the number of thumbnails you'd like per row. This largely depends on your personal preference and the size of the thumbnail dimensions that you select. I've decided that five images across for each row works out nicely for my monitor.

    We also need to decide how many pixels we want between thumbnails and at the borders. I've decided on 20 pixels between images in a row and 40 pixels between rows. We need more space in between rows so that we can notate each image with its filename. We also need to leave a space at the top of the image so that we can add a title.

    I've created a number of variables for the settings I just described:

    $imagesPerLine ¡ª the number of thumbnails to display in each row.
    $titleBorder ¡ª the number of pixels to leave at the top of the image for a title.
    $vBorder ¡ª the number of pixels between thumbnails in a row.
    $hBorder ¡ª the number of pixels between rows
    $maxThumbnailW ¡ª the maximum width of a thumbnail
    $maxThumbnailH ¡ª the maximum height of a thumbnail

    Calculating The Index Image Size

    Now we need to calculate the size of the index image given the list of JPEG images that we filtered out previously. The first thing we can do is figure out how many rows we'll have by dividing the number of images in the @images array by the number of images to display on each row contained in the $imagesPerLine variable.

    my $rows = ceil(scalar(@images) / $imagesPerLine)

    Now we can calculate the width of the index images by multiplying the maximum number of images per row by the maximum thumbnail width and adding to that the number of pixels that will be used for spaces in between the images and at the edge of the index image. We can calculate that spacing by multiplying the horizontal border by the number of images per line, minus 1, plus two more borders for the edge of the index image.

    my $indexWidth = ($maxThumbnailW * $imagesPerLine) + ($hBorder * ($imagesPerLine - 1 + 2));

    Likewise, we can determine the height of the index image by multiplying the number of rows by the maximum thumbnail height, and adding to that the spacing that will be drawn between rows, adding to that the space at the top of the index image that will be used for the title.

    my $indexHeight = ($maxThumbnailH * $rows) + ($vBorder * ($rows - 1 + 2) + $titleBorder);

    Now that we have the size of the index image based on the number of images that we have and the spacing that we've chosen, we can create an empty index image object that we'll be dropping the thumbnails into.

    my $indexImage = new GD::Image($indexWidth,$indexHeight);

    Next, we need to allocate our background color. I'm going to use white. The first color that is allocated in an image will be set as the background color. The RGB value for white is 255,255,255.

    $indexImage- >colorAllocate(255,255,255);

    We also need to allocate black, which will be used to write the index title.

    my $black = $indexImage- >colorAllocate(0,0,0);

    Now we can actually write the index title. The title text is contained in the $titleBorder variable. We can use the string() function to write the string. I've chosen to use the largest built-in font that GD offers and to draw the title at the top left-hand side of the image with padding on the left equal to the size of a horizontal border and padding at the top equal to half the total space that was reserved for the title.

    $indexImage- >string(gdGiantFont,$hBorder,($titleBorder/2),$title,$black) ;

    Creating And Placing The Thumbnails

    Now we're ready to actually create and drop the thumbnail images into the index image. We'll need a foreach loop that cycles through each image in our @images array. But first, we need to establish the place where we will start dropping thumbnail images.

    my $nextX = $hBorder;
    my $nextY = $vBorder + $titleBorder;

    The beginning X coordinate is equal to the horizontal border (20 pixels). The starting Y position is equal to the vertical border (40 pixels) plus the titleBorder (50 pixels).

    The main guts of the application is contained in the foreach loop below. The &CreateThumbnailImage function is basically the same code that we developed in a previous article. Give a maximum thumbnail width and height, and an image filename, it will return an instance of a GD object containing the thumbnail.

    Once we have the thumbnail image, we have to place it in the new index image, using the X and Y coordinates contained in $nextX and $nextY. The copy() command is used to actually integrate the thumbnail into the index image and give an X and Y coordinate. The $fileName variable contains the name of the image, which is written above the thumbnail image with the string() command.

    foreach my $image (@images) {
        my $thisX = $nextX;
    	my $thisY = $nextY;
    	my $fileName = $image;
    	$fileName =~ s/$dir\///;
    
        my $thumbnail = &createThumbnail($maxThumbnailW,$maxThumbnailH,$image);
    
        $indexImage->copy($thumbnail,$thisX,$thisY,0,0,$thumbnail->getBounds);
    
        $indexImage->string(gdSmallFont,$thisX,($thisY - 15),$fileName,$black);
    
        if ($nextX >= ($indexWidth - $hBorder - $maxThumbnailW)) {
    	    $nextX = $hBorder;
    		$nextY += ($vBorder + $maxThumbnailH);
    	} else {
    	    $nextX += ($hBorder + $maxThumbnailW);
    	}
    }
    

    Now that the thumbnail has been dropped into place, we need to calculate the X and Y coordinate for the next thumnail image. If the current X coordinate is greater than or equal to the width of the index image minus the border and maximum width of a thumbnail image, that means that we can't draw another image in the row and must set the next X and Y coordinate in a new row. If the current X coordinate is not greater than or equal to this calculation, then we know that there is space for another thumbnail in the row and we calculate that new X coordinate by adding the maximum thumbnail width and border to the current X value. Then we loop to the next thumbnail image until we've drawn all of the images.

    We've drawn all of the thumbnails in the index image, so now it's time to save it to a file. We do this by creating a new file handle and printing the results of the jpeg method to the file. And we're done.

    open(IMAGE,">index.jpg")
        || die "Cannot open index.jpg for write: $!\n";
    print IMAGE $indexImage->jpeg(100);
    

    Example 1

    Now that we've worked through building the application in a piecemeal form, let's take a look at the whole script. Cut and paste the source code below into an editor. Make sure you have the GD Perl module installed correctly. The script must be run from the command line. It requires two parameters. The first is the path to the directory where the JPEG images exist that you want to place in an index image. The second parameter is the title that will be placed at the top of the index image.

    ex. index.pl images/ 'Seaworld 2000 - Miami, Florida'

    use GD;
    use strict;
    use POSIX;
    
    die "Syntax: index.pl <directory> <title>\n\n"
      unless @ARGV == 2;
    
    chomp(my $dir = $ARGV[0]);
    chomp(my $title = $ARGV[1]);
    
    opendir(DIR,$dir) || die "Cannot open directory $dir: $!\n";
    my @images = grep -f, grep /\.jpg$/i, map "$dir/$_", readdir DIR;
    closedir(DIR);
    
    my $imagesPerLine = 5;
    my $rows = ceil(scalar(@images) / $imagesPerLine);
    
    my $titleBorder = 50;
    
    my $vBorder = 40;
    my $hBorder = 20;
    
    my $maxThumbnailW = 100;
    my $maxThumbnailH = 100;
    
    my $indexWidth = ($maxThumbnailW * $imagesPerLine) + ($hBorder *
     ($imagesPerLine - 1 + 2));
    my $indexHeight = ($maxThumbnailH * $rows) + ($vBorder * ($rows - 1 + 2)
     + $titleBorder);
    
    my $indexImage = new GD::Image($indexWidth,$indexHeight);
    $indexImage->colorAllocate(255,255,255);
    my $black = $indexImage->colorAllocate(0,0,0);
    
    $indexImage->string(gdGiantFont,$hBorder,($titleBorder/2),$title,$black);
    
    my $nextX = $hBorder;
    my $nextY = $vBorder + $titleBorder;
    
    foreach my $image (@images) {
      my $thisX = $nextX;
      my $thisY = $nextY;
      my $fileName = $image;
      $fileName =~ s/$dir\///;
    
      my $thumbnail = &createThumbnail($maxThumbnailW,$maxThumbnailH,$image);
    
      $indexImage->copy($thumbnail,$thisX,$thisY,0,0,$thumbnail->getBounds);
    
      $indexImage->string(gdSmallFont,$thisX,($thisY - 15),$fileName,$black);
    
      if ($nextX >= ($indexWidth - $hBorder - $maxThumbnailW)) {
    	  $nextX = $hBorder;
    	  $nextY += ($vBorder + $maxThumbnailH);
      } else {
    	  $nextX += ($hBorder + $maxThumbnailW);
      }
    }
    
    open(IMAGE,">index.jpg")
      || die "Cannot open index.jpg for write: $!\n";
    print IMAGE $indexImage->jpeg(100);
    
    sub createThumbnail {
      my ($maxwidth,$maxheight,$file) = @_;
      my $srcimage = GD::Image->newFromJpeg($file);
      my ($srcW,$srcH) = $srcimage->getBounds();
      my $wdiff = $srcW - $maxwidth;
      my $hdiff = $srcH - $maxheight;
      my $newH; my $newW; my $aspect;
    
      if ($wdiff > $hdiff) {
        $newW = $srcW - $wdiff;
        $aspect = ($newW/$srcW);
        $newH = int($srcH * $aspect);
      } else {
        $newH = $srcH - $hdiff;
    	$aspect = ($newH/$srcH);
    	$newW = int($srcW * $aspect);
      }
    
      my $newimage = new GD::Image($newW,$newH);
      $newimage->copyResized($srcimage,0,0,0,0,$newW,$newH,$srcW,$srcH);
      return $newimage; }
    

    [The colored lines above are each one line. They have been split for formatting purposes.]

    Conclusion

    So in conclusion, we have used the GD library to create an index image that consists of thumbnails of images in a given directory. If you are working with many different images, you could modify this script to automate the process and even link the thumbnails to the full images. This might be helpful for providing images for conferences, events, and meetings. I know that I've been able to use the script quite a bit to organize my family pictures.

    discuss this topic to forum

    relation tutorial

    No relevant information

    New

    Hot