2009-05-29

image resizing in perl

perl is really versatile, and can do a lot of things. moreover, you can do it in a lot of different ways. the difficult part being sometimes to choose the way you want to do it...

so i need to resize an image within perl. this is for games::risk, a perl implementation of the famous board game (btw, try it and tell me if you like it!). i want the background image to fit the window size on resize events. of course, i want it to be fast - but i also want something maintainable.

looking on cpan, i found 3 modules: gd, image::magick and image::imlib2. using gd to resize a picture is a nightmare, therefore i ditched it for image::resize which is a convenient wrapper around gd.

here are the needed incantations to resize:
  • with imlib2

  • my $old = Image::Imlib2->load($src);
    my $new = $old->create_scaled_image($w, $h);

  • with image::magick - note that the operation is done inplace, so one needs to clone the image first to compare the same things.

  • my $old = Image::Magick->new;
    $old->Read($src);
    my $new = $old->Clone;
    $new->Scale(width=>$w, height=>$h);

  • with image::resize

  • my $old = Image::Resize->new($src);
    my $new = $old->resize($w, $h);

so i benchmarked them and found that on average, image::imlib2 is a few times faster than image::magick, which is itself around the same speed or a bit faster than image::resize.

however, image::imlib2 has a drawback: the only way to get back the new image is to save it to a file, where one can get the new image as a scalar directly:
  • image::resize with $img->jpeg
  • image::magick with $img->ImageToBlob


therefore, one must also take this into account in the benchmarking! so here's the latest version of the bench:

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

use Benchmark qw{ :all };
use Image::Size;

use Image::Resize;
use Image::Imlib2;
use Image::Magick;

my $src = "src.jpg";
my $dst = "dst.jpg";

my ($w, $h) = imgsize($src);
my @sizes = (
[10,10], [100,100], [1000,1000],
[640,400], [840,600], [1024,768],
[$w,$h], [$w/2,$h/2], [$w/4,$h/4],
[$h,$w], [$h/2,$w/2], [$h/4,$w/4],
);

my $imlib2 = Image::Imlib2->load($src);
my $resize = Image::Resize->new($src);
my $magick = Image::Magick->new; $magick->Read($src);

foreach my $s ( @sizes ) {
my ($width, $height) = @$s;
say "-> ${width}x${height}";
local $/;

cmpthese( -3, {
imlib2 => sub {
my $img = $imlib2->create_scaled_image($width, $height);
$img->save($dst);
open my $fh, '<', $dst;
return <$fh>;
},
magick => sub {
my $img = $magick->Clone;
$img->Scale(width=>$width,height=>$height);
return $img->ImageToBlob;
},
resize => sub {
my $img = $resize->resize($width,$height);
return $img->jpeg; # $img->png
},
} );
say '';
}


and i was quite astonished to see that image::imlib2 is still 2 or 3 times faster that image::magick or image::resize!

so, imlib2 may not the best api around, but sure it's fast...

7 comments:

  1. How does resulting images quality compares, though? ;)

    ReplyDelete
  2. There's also Imager, which has been great for most my image-needs.

    ReplyDelete
  3. @cedric: according to imagemagick display, they all have the same 75% quality which is a sane defaut.

    @lars: unfortunately, imager suffers from the same api uncompleteness, so i'm forced to save to a temp file:

    my $img = $imager->scale(xpixels=>$width, ypixels=>$height);
    $img->write(file=>$dst);
    open my $fh, '<', $dst;
    return <$fh>;

    which is really too big a disadvantage for imager to succeed. typical comparison:

    Rate imager resize magick imlib2
    imager 24.4/s -- -73% -80% -92%
    resize 91.7/s 276% -- -23% -70%
    magick 119/s 388% 30% -- -61%
    imlib2 303/s 1143% 231% 155% --

    ReplyDelete
  4. Um, using Imager, why can't you just write the image data to a scalar rather than a file?

    my $imager = Imager->new;
    $imager->read( file => $src );
    my $data;
    $imager->scale( xpixels => $width, ypixels => $height )->write( type=> 'png', data=> \$data );
    return $data;

    ReplyDelete
  5. What dou you mean by 75% quality?
    I'm referring to the fact that many down/upscaling algorithms exists... with varying qualities ;)

    ReplyDelete
  6. @steve: oh, sorry, i'm not familiar at all with Imager. so, using what you suggest (640x400):

    Rate resize imager magick imlib2
    resize 12.5/s -- -20% -45% -72%
    imager 15.6/s 25% -- -31% -64%
    magick 22.7/s 81% 45% -- -48%
    imlib2 44.0/s 252% 181% 94% --

    that's better, but still not on par with imlib2.


    @cedric: 75% as percentage of original quality, which i guess means 25% loss. and i don't know which algorithms are used. i don't know that much about images, i just know i needed to resize one for a game background... :-)

    ReplyDelete
  7. You can set the image qualities by:


    GD / Image::Resize
    $img->jpeg(100)

    Imlib2
    $img->set_quality(100)

    Imager
    $img->write(data => \$data, type => 'jpeg', jpegquality => 100)

    PerlMagick
    $img->Set(quality=>100);


    I am a big fan of 'imlib2' because of it's speed, but it is not practical for any website developer for 2 reasons:

    1.) no file handle input
    2.) no ability to save data to variable (for printing to screen or furthor manipulation) like in your script

    ReplyDelete