#!/usr/bin/perl # Filename: thumb # Author: David Ljung Madison # See License: http://MarginalHacks.com/License # Requires: convert # Description: Makes scaled/cropped thumbnails from image files use strict; ################################################## # Setup the variables ################################################## my $PROGNAME = $0; $PROGNAME =~ s|.*/||; my $CONVERT = "convert"; my $IDENTIFY = "identify"; # Undef if you don't have identify my $THUMB_DIR; my $THUMB_POST; my $THUMB_TYPE = "gif"; # Size of the thumbnails my $TN_X = 100; my $TN_Y = 100; # Crop the thumbnails? my $CROP = 0; my $FORCE = 0; ################################################## # Usage ################################################## sub usage { my $msg; foreach $msg (@_) { print "ERROR: $msg\n"; } print "\n"; print "Usage:\t$PROGNAME [-d] [-geom x] [-crop] [-dir ] \n"; print "\tMakes thumbnails from images\n"; print "\n"; print "\t-geom\tSize of thumbnail [default ${TN_X}x${TN_Y}]\n"; print "\t-crop\tCrop the image to fit thumbnail size.\n"; print "\t\tElse aspect will be maintained\n"; print "\t-dir\tThumbnail directory\n"; print "\t-post\tThumbnail postfix\n"; print "\t-f\tForce overwrite of existing thumbnails\n"; print "\t\tElse existing thumbnails are only written when the image changes\n"; print "\t-type\tType of thumbnails (gif, jpg, tiff,...)\n"; print "\t-d\tSet debug mode\n"; print "\n"; exit -1; } sub set_size { my ($size) = @_; return ($TN_X,$TN_Y) = ($1,$2) if ($size =~ /^(\d+)x(\d+)$/); usage("Can't understand geometry [$size]"); } sub parse_args { my @images; my $arg; while ($#ARGV>=0) { $arg=shift(@ARGV); if ($arg =~ /^-h$/) { usage(); } if ($arg =~ /^-d$/) { $MAIN::DEBUG = 1; next; } if ($arg =~ /^-crop$/) { $CROP = 1; next; } if ($arg =~ /^-f(orce)?$/) { $FORCE = 1; next; } if ($arg =~ /^-dir$/) { $THUMB_DIR = shift(@ARGV); next; } if ($arg =~ /^-post$/) { $THUMB_POST = shift(@ARGV); next; } if ($arg =~ /^-type$/) { $THUMB_TYPE = shift(@ARGV); next; } if ($arg =~ /^-g(eom(etry)?)?(=(.+))?$/) { set_size($4 ? $4 : shift(@ARGV)); next; } if ($arg =~ /^-/) { usage("Unknown option: $arg"); } usage("Can't find image $arg") unless (-f $arg); push(@images,$arg); } usage("You need to specify an image") unless (@images); (@images); } ################################################## # Code ################################################## sub thumb_name { my ($img) = @_; # Remove postfix $img =~ s/\.[^\.\/]+$//; my $post = $THUMB_POST || ".tn.$THUMB_TYPE"; return $img.$post unless ($THUMB_DIR); my $dir = $THUMB_DIR; ($dir,$img) = ("$`/$THUMB_DIR",$1) if ($img =~ m|/([^/]*)$|); (-d $dir) || mkdir($dir,0755) || die("[$PROGNAME] Couldn't make directory [$dir]\n"); return "$dir/$img$post"; } sub get_size { my ($img) = @_; my ($qimg) = "\Q$img\E"; return (0,0) unless (-f $img); # Try to use identify if we have it if ($IDENTIFY) { print STDERR "get_size() run: $IDENTIFY -ping $img\n" if ($MAIN::DEBUG); open(SIZE,"$IDENTIFY -ping $qimg 2>&1 |") || die("[$PROGNAME] Couldn't run identify! [$IDENTIFY]\n"); while() { print STDERR "get_size(): $_" if ($MAIN::DEBUG); if(/\s(\d+)x(\d+)(\s|\+)/) { close(SIZE); return ($1,$2); } } die("[$PROGNAME] Can't get [$img] size from 'identify -ping' output\n"); } # Kludgy way to get size, but works with all images that convert reads print STDERR "get_size() run: $CONVERT -verbose $img /dev/null\n" if ($MAIN::DEBUG); open(SIZE,"$CONVERT -verbose $qimg /dev/null 2>&1 |") || die("[$PROGNAME] Couldn't run convert! [$CONVERT]\n"); while() { print STDERR "get_size(): $_" if ($MAIN::DEBUG); if(/\s(\d+)x(\d+)(\s|\+)/) { close(SIZE); return ($1,$2); } } die("[$PROGNAME] Can't get [$img] size from 'convert -verbose' output\n"); } sub scale { my ($img,$x,$y,$new) = @_; my ($qimg) = "\Q$img\E"; my ($qnew) = "\Q$new\E"; print STDERR "scale() run: $CONVERT -verbose $img -sample ${x}x${y} $new\n" if ($MAIN::DEBUG); open(SIZE,"$CONVERT -verbose $qimg -sample ${x}x${y} $qnew 2>&1 |") || die("[$PROGNAME] Couldn't run convert! [$CONVERT]\n"); while() { print STDERR "scale(): $_" if ($MAIN::DEBUG); if(/=>(\d+)x(\d+)\s/) { close(SIZE); return ($1,$2); } } close(SIZE); # Sometimes convert doesn't give us the new size information #print STDERR "[$PROGNAME] Error scaling $img\n"; get_size($new); } sub crop { my ($img,$x,$y,$off_x,$off_y,$new) = @_; my ($qimg) = "\Q$img\E"; my ($qnew) = "\Q$new\E"; print STDERR "crop() run: $CONVERT $img -crop ${x}x${y}+${off_x}+${off_y} $new\n" if ($MAIN::DEBUG); system("$CONVERT $qimg -crop ${x}x${y}+${off_x}+${off_y} $qnew"); return unless ($?); print STDERR "[$PROGNAME] Error cropping $img\n"; } ################################################## # Main code ################################################## sub main { foreach my $img ( parse_args() ) { print STDERR "\nIMAGE: $img\n" if ($MAIN::DEBUG); my ($thumb) = thumb_name($img); if (-f $thumb && !$FORCE && -M $thumb < -M $img) { print "$thumb\n"; next; } my ($x,$y) = get_size($img); # Which way do we need to shrink? convert will scale down w/ aspect # as much as is needed to *fit* inside the geometry we give it # Hack: Assume the image is larger than a thumbnail my ($scale_x,$scale_y) = ($TN_X,$TN_Y); if ($CROP) { if ( $x/$TN_X < $y/$TN_Y ) { # Make vertical bigger so that we don't scale horizontal past $TN_X $scale_y = $y; } else { $scale_x = $x; } } ($x,$y) = scale($img,$scale_x,$scale_y,$thumb); next unless $x; if ($CROP) { # Now crop the other dimension my ($off_x,$off_y) = (0,0); if ( $x > $TN_X ) { $off_x = int(($x-$TN_X)/2); } if ($y > $TN_Y) { $off_y = int(($y-$TN_Y)/2); } crop($thumb,$TN_X,$TN_Y,$off_x,$off_y,$thumb) unless ($x==$TN_X && $y==$TN_Y); } print "$thumb\n"; } } main();