#! /Users/jj/bin/perl5.10.0 # photofit.pl - crops or expands image size to fit standard paper formats # # Copyright (C) 2007 Jon Allen # # This software is licensed under the terms of the Artistic # License version 2.0. # # For full license details, please read the file 'artistic-2_0.txt' # included with this distribution, or see # http://www.perlfoundation.org/legal/licenses/artistic-2_0.html # Packaging command: # ~/perl/perl-5.10.0/bin/pp -o photofit.exe photofit.pl -M Paper::Specs::photo -M Paper::Specs::photo::7x5 -M Image/ExifTool/Writer.pl -M Image/ExifTool/WriteExif.pl -M Image/ExifTool/WriteXMP.pl -M Image/ExifTool/WriteCanonRaw.pl -M Image/ExifTool/WritePostScript.pl -M Image/ExifTool/WritePDF.pl -M Image/ExifTool/WriteIPTC.pl --l ./libjpeg.62.dylib --l libtiff.3.dylib #--Load required modules and activate Perl's safety features--------------- use strict; use warnings; use File::Spec::Functions; use FindBin; use Getopt::ArgvFile; use Getopt::Long; use Image::ExifTool; use Image::Size; use Imager; use List::Util qw/max min/; use Paper::Specs 0.10 units=>'in',strict=>1; use Switch 'Perl6'; our $VERSION = '0.03'; #--Define available options------------------------------------------------ my %specifiers = ( 'paper-size' => '=s', 'crop' => '!', 'border' => '=s', ); #--Look for a default settings file---------------------------------------- #--Parse command line options---------------------------------------------- my %options; GetOptions( \%options, optionspec(%specifiers), 'version' => sub { print "This is photofit.pl, version $VERSION\n"; exit; }, ) or die("[Error] Could not parse options\n"); #--Check correct options have been supplied-------------------------------- my $usage = 'Usage: photofit.pl --paper-size= [options] file1 file2 ... fileX'; die "$usage\n" unless (@ARGV >= 1); die "$usage\n" unless ($options{paper_size}); #--Check paper size is valid----------------------------------------------- my $form; eval { local $SIG{__WARN__} = sub{}; $form = (Paper::Specs->find(code=>$options{paper_size}))[0]; }; if ($form) { $options{paper_width} = $form->sheet_width; $options{paper_height} = $form->sheet_height; } else { die "Unknown paper size: $options{paper_size}\n"; } #--Process images---------------------------------------------------------- foreach my $image_file (@ARGV) { print "Processing $image_file\n"; process($image_file,%options); } exit; #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- # process #-------------------------------------------------------------------------- # Purpose: Process an image file # # Usage: process($filename,%options); #-------------------------------------------------------------------------- sub process { my $source_filename = shift; my %options = @_; #--Set target filename--------------------------------------------------- my $target_filename = $source_filename; $target_filename =~ s/(\..*?)$/_$options{paper_size}$1/; #--Get dimensions of source image---------------------------------------- my ($source_width,$source_height,$error) = imgsize($source_filename); unless ($source_width && $source_height) { die "Cannot open source image $ARGV[0]: $error\n"; } #--Calculate target dimensions------------------------------------------- my $paper_longside = max($options{paper_width},$options{paper_height}); my $paper_shortside = min($options{paper_width},$options{paper_height}); my $paper_aspect = $paper_longside / $paper_shortside; print "Paper: $paper_longside x $paper_shortside ($paper_aspect)\n"; my $image_longside = max($source_width,$source_height); my $image_shortside = min($source_width,$source_height); my $image_aspect = $image_longside / $image_shortside; print "Image: $image_longside x $image_shortside ($image_aspect)\n"; #--Load and resize source image------------------------------------------ my $target_image; my ($target_width,$target_height); my ($target_longside,$target_shortside); if ($image_aspect == $paper_aspect) { $target_width = $source_width; $target_height = $source_height; $target_image = Imager->new->read(file=>$source_filename); $target_longside = $image_longside; $target_shortside = $image_shortside; } else { $target_longside = $image_longside; $target_shortside = $image_shortside; my ($frame_longside,$frame_shortside) = (0,0); if ($paper_aspect > $image_aspect) { $target_longside = $image_shortside * $paper_aspect; $frame_longside = ($target_longside - $image_longside) / 2; } else { $target_shortside = $image_longside / $paper_aspect; $frame_shortside = ($target_shortside - $image_shortside) / 2; } my ($border_width,$border_height) = ($source_width < $source_height) ? ($frame_shortside,$frame_longside) : ($frame_longside,$frame_shortside); $target_image = Imager->new->read(file=>$source_filename); $target_image = solid_border($target_image,'white',$border_width,$border_height); } #--Add border------------------------------------------------------------ if ($options{border}) { # Note this section is not finished if ($options{border} =~ /^(\d+(?:\.\d+)?)(in|pt|cm|mm)$/i) { my ($border_value,$border_unit) = ($1,lc $2); my $border_inches; given ($border_unit) { when 'in' { $border_inches = $border_value; } when 'pt' { $border_inches = $border_value / 72; } when 'cm' { $border_inches = $border_value / 2.54; } when 'mm' { $border_inches = $border_value / 25.4; } } my $dpi = $target_shortside / ($paper_shortside - 2 * $border_inches); my $border_shortside = $border_inches * $dpi; my $border_longside = $border_shortside * $paper_aspect; my ($border_width,$border_height) = ($source_width < $source_height) ? ($border_shortside,$border_longside) : ($border_longside,$border_shortside); $target_image = solid_border($target_image,'white',$border_width,$border_height); } else { die "Invalid border size '$options{border}', must specify unit (mm,cm,in,pt - e.g. 10mm, 0.5in)\n"; } } #--Write modified image to file------------------------------------------ $target_image->write(file=>$target_filename,jpegquality=>100); #--Copy EXIF tags from source image-------------------------------------- copy_exif(source=>$source_filename, destination=>$target_filename); } #-------------------------------------------------------------------------- # copy_exif #-------------------------------------------------------------------------- # Purpose: Copy EXIF tags from one file to another (the Imager module # removes EXIF data, so we have to put it back so that the new # file has the correct ICC colour profile etc. # # Usage: copy_exif(source=>'src.jpg', destination=>'dest.jpg'); #-------------------------------------------------------------------------- sub copy_exif { my %args = @_; my $exif = Image::ExifTool->new(); $exif->SetNewValuesFromFile($args{source}); # need to change image size tags $exif->WriteInfo($args{destination}); } #-------------------------------------------------------------------------- # optionspec #-------------------------------------------------------------------------- # Purpose: Convert option specifiers to Getopt::Long format # # Usage: my @options = optionspec( 'string' => '=s', # 'integer' => '=i', # 'switch' => '' ); #-------------------------------------------------------------------------- sub optionspec { my %option_specs = @_; my @getopt_list; while (my ($option_name,$spec) = each %option_specs) { (my $variable_name = $option_name) =~ tr/-/_/; (my $nospace_name = $option_name) =~ s/-//g; my $getopt_name = ($variable_name ne $option_name) ? "$variable_name|$option_name|$nospace_name" : $option_name; push @getopt_list,"$getopt_name$spec"; } return @getopt_list; } #-------------------------------------------------------------------------- # solid_border #-------------------------------------------------------------------------- # Purpose: Adds a border to an Imager object # # Usage: #-------------------------------------------------------------------------- sub solid_border { my ($source, $color, $border_width, $border_height) = @_; my $out = Imager->new(xsize => $source->getwidth() + 2 * $border_width, ysize => $source->getheight() + 2 * $border_height, bits => $source->bits, channels => $source->getchannels); # we can do it the lazy way for a solid border - just fill the whole image $out->box(filled => 1, color=>$color) or die "Invalid color '$color':", $out->errstr, "\n"; $out->paste(left => $border_width, top => $border_height, img => $source); return $out; } #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- __END__ =head1 NAME photofit.pl - crops or adds borders to an image to fit standard paper formats =head1 SYNOPSYS photofit.pl [options] --paper-size =head1 DESCRIPTION Adds white borders to F so that it becomes the same aspect ratio as I, then saves the resulting image as F. This image can then be printed without any extra cropping being applied by the printer. =head1 DEPENDENCIES Requires the following non-core Perl modules: =over =item Imager =item Image::Size =item Paper::Specs =back =head1 AUTHOR Written by Jon Allen See L for more information