Making thumbnails with a restriction of file size

Top Japanese page




Overview

This is a sample to make a shrink image with limiting file size of the image. In case to display an image on a mobile browser, byte size of the image file may be limited like 25KB or so. Of course, the quality of a smaller generated image file gets worse. Also refer to Making thumbnails.

Flow

  1. Make an object from original image file
  2. Get the image size from the object
  3. Generate a new object of the image after changing the size
  4. Copy the original image to the new image through the objects
  5. Write out the image into a file
  6. Get the file size of the generated image file
  7. Re-generate a new image file if the generated image file's file size is bigger than specified value
  8. Calculate a new file size(geometry) based on file byte size
  9. Re-generate a new image based on re-calculated image size(geometry)

A sample code

 use GD;
 
 my ($body,$path,$ext) = fileparse("$imagefile",'\.\w+');
 
 my $im;
 $im =~ /\.jpe?g$/i and $im = GD::Image->newFromJpeg($imagefile);
 $im =~ /\.gif$/i   and $im = GD::Image->newFromGif($imagefile);
 $im =~ /\.png$/i   and $im = GD::Image->newFromPng($imagefile);
 
 my ($width, $height) = $im->getBounds();
 
 my $new_width;
 my $new_height;
 if ($width > $target_width or $height > $target_height){
        my $width_shrink  = $target_width  / $width;
        my $height_shrink = $target_height / $height;
        my $shrink_ratio;
        if ($width_shrink < $height_shrink){
            $shrink_ratio = $width_shrink;
        } else {
            $shrink_ratio = $height_shrink;
        }
        $new_width  = int($width  * $shrink_ratio);
        $new_height = int($height * $shrink_ratio);
 } else {
        $new_width  = $width;
        $new_height = $height;
 }
 
 my $thumbsbody = "${body}_${new_width}x${new_height}";
 my $thumbimage = "$thumbsbody.$ext";
 &genimage($im,$new_width,$new_height,$width,$height,$thumbimage);
 
 my $byte = -s "$thumbimage";
 if ($byte > $size_limit){
        my $size_ratio = sqrt($size_limit/$byte);
        my $new_width2 = int($new_width * $size_ratio);
        my $new_height2 = int($new_height * $size_ratio);
        $thumbsbody = "${body}_${new_width2}x${new_height2}";
        $thumbimage = "$thumbsbody.$ext";
        &genimage($im,$new_width2,$new_height2,$width,$height,$thumbimage);
 }
 
 undef $im;
 
 print "<img src=\"$thumbimage\" width=\"$new_width\" height=\"$new_height\">";
 
 sub genimage {
    my ($im,$new_width,$new_height,$width,$height,$thumbimage) = @_;
    my $target_im = new GD::Image($new_width,$new_height,1);
    $target_im->copyResized($im,0,0,0,0,$new_width,$new_height,
                            $width,$height);
    
    unless (open(IMAGE, "> $thumbimage")){
        &error ("Failed to generate a shrunk file");
    }
    binmode(IMAGE);
    if ($thumbimage =~ /\.jpe?g$/i){
        print IMAGE $target_im->jpeg(85);
    } elsif ($thumbimage =~ /\.gif$/i) {
        print IMAGE $target_im->gif();
    } elsif ($thumbimage =~ /\.png$/i) {
        print IMAGE $target_im->png();
    }
    close(IMAGE);
    undef $target_im;
 }

Description of the code

 use GD;

Load GD.


 my ($body,$path,$ext) = fileparse("$imagefile",'\.\w+');

Split image file name to body name, path name and extention.

 my $im;
 $im =~ /\.jpe?g$/i and $im = GD::Image->newFromJpeg($imagefile,1);
 $im =~ /\.gif$/i   and $im = GD::Image->newFromGif($imagefile);
 $im =~ /\.png$/i   and $im = GD::Image->newFromPng($imagefile);

$image contains an image file. Based on the extention, select a constracter method which load an image file and generate an object.

Old versions of GD do not support newFromGif. And if the extention and actual image format is different, the GD reports an error. If the image file's size is 0, the GD also reports an error. These errors are fatal and script itself stops working. The error cause could be found from the server log file and must be fix the cause.

 $im = GD::Image->newFromJpeg($image,1);

The second argument of the newFromJpeg is set when Truecolor is selected. According to the document, Truecolor is automatically selected when an object is created from a file. However, generated files seem different than not specifing this argument. Therefore, in this example use it.

 my ($width, $height) = $im->getBounds();

Get the image size by getBound method. The widht is put into $width, the heigh is put into $height.

 my $new_width;
 my $new_height;
 if ($width > $target_width or $height > $target_height){
        my $width_shrink  = $target_width  / $width;
        my $height_shrink = $target_height / $height;
        my $shrink_ratio;
        if ($width_shrink < $height_shrink){
            $shrink_ratio = $width_shrink;
        } else {
            $shrink_ratio = $height_shrink;
        }
        $new_width  = int($width  * $shrink_ratio);
        $new_height = int($height * $shrink_ratio);
 } else {
        $new_width  = $width;
        $new_height = $height;
 }

Write out the object into a file. To write out the same format as the original file, this example uses the extention to identify the format. It is not necessary to be the same format as the original one. If you want to convert the format, you can do it now. The written file is a shrunk thumbnail.

 my $thumbsbody = "${body}_${new_width}x${new_height}";
 my $thumbimage = "$thumbsbody.$ext";

Create a new file name to be generated. To distinguish with the original file name, it forms as <filename>_<width>x<height>.<ext>.

 &genimage($im,$new_width,$new_height,$width,$height,$thumbimage);

Generate a new image file by genimage. This function will be explained later. At this point, the image file is generated as the file name $thumbimage. However, the file size could be exceeded than specified limitation.

 my $byte = -s "$thumbimage";

Get the file size of the $thumbimage file. $byte contains the file size.

 if ($byte > $size_limit){

If the byte size is more than specified size, proceed next.

        my $size_ratio = sqrt($size_limit/$byte);

Calculate a ratio of the exceeded file size.

        my $new_width2 = int($new_width * $size_ratio);
        my $new_height2 = int($new_height * $size_ratio);

Based on the ratio calculated above, re-calculate width and height. Idea here is to generate smaller geometry of the image file and make a corresponding file size. Since it is not direct file size calculation, there could be some error. Even though small image, HTML can display the image in any size using the IMG tag.

        $thumbsbody = "${body}_${new_width2}x${new_height2}";
        $thumbimage = "$thumbsbody.$ext";

Make a file name to be generated. In order to avoid overwrite to the original file name, use a different name. This process is not necessary if you want to overwrite to the original file.

        &genimage($im,$new_width2,$new_height2,$width,$height,$thumbimage);
 }

Write out the generate image into the file.

 undef $im;

Discard the object. This process usually does not necessary. However, in some environment, garbage correctoin is failing and temporary files may remain on the server. So for safety reason, just do this.

 print "<img src=\"$thumbimage\" width=\"$new_width\" height=\"$new_height\">";

Display the generated image file. The displaying size is first spcified size. Secondaly generated file is displayed with that size. It is flexible due to HTML.

 sub genimage {

Explain the genimage function.

    my ($im,$new_width,$new_height,$width,$height,$thumbimage) = @_;

Get arguments.

    my $target_im = new GD::Image($new_width,$new_height,1);

Generate a new object with the target width and height. The third arugument also indicates to choose Truecolor.

    $target_im->copyResized($im,0,0,0,0,$new_width,$new_height,
                            $width,$height);

Copy the original image object into the new size-modified image object using copyResized. For reference, copyResizse could take the following parameters.

 $im->copyResized($sourceImage,$dstX,$dstY,
                     $srcX,$srcY,$destW,$destH,$srcW,$srcH)
 
 $sourceImage : Original image object
 $dstX        : Target image's start X
 $dstY        : Target image's start Y
 $srcX        : Original image's start X
 $srcY        : Original image's start Y
 $destW       : Target image width
 $destH       : Target image heigh
 $srcW        : Original image width
 $srcH        : Original image height
    unless (open(IMAGE, "> $thumbimage")){
        &error ("Failed to generate a shrunk file.");
    }
    binmode(IMAGE);
    if ($thumbimage =~ /\.jpe?g$/i){
        print IMAGE $target_im->jpeg(85);
    } elsif ($thumbimage =~ /\.gif$/i) {
        print IMAGE $target_im->gif();
    } elsif ($thumbimage =~ /\.png$/i) {
        print IMAGE $target_im->png();
    }
    close(IMAGE);

Write out the object into a file. To write out the same format as the original file, this example uses the extention to identify the format. It is not necessary to be the same format as the original one. If you want to convert the format, you can do it now. The written file is a shrunk thumbnail.

  $target_im->jpeg(85);

The argument of the jpeg method means Quality. It takes a number between 0 and 100. Bigger number gets better quality. If it is omitted, jpeg method is supposed to choose the best quality. However, from my experience, it did not work that well. Since 100 becomes too big file size, a number 80 though 90 should be good enough.

    undef $target_im;
 }

Discard the object. This process usually does not necessary. However, in some environment, garbage correctoin is failing and temporary files may remain on the server. So for safety reason, just do this.