root/branches/release-41/lib/MT/Image.pm @ 2649

Revision 2649, 15.5 kB (checked in by fumiakiy, 17 months ago)

L10 Japanese.

  • Property svn:keywords set to Author Date Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::Image;
8
9use strict;
10use MT;
11use base qw( MT::ErrorHandler );
12
13sub new {
14    my $class = shift;
15    $class .= "::" . MT->config->ImageDriver;
16    my $image = bless {}, $class;
17    $image->load_driver
18        or return $class->error( $image->errstr );
19    if (@_) {
20        $image->init(@_)
21            or return $class->error( $image->errstr );
22    }
23    $image;
24}
25
26sub get_dimensions {
27    my $image = shift;
28    my %param = @_;
29    my($w, $h) = ($image->{width}, $image->{height});
30    if (my $pct = $param{Scale}) {
31        ($w, $h) = (int($w * $pct / 100), int($h * $pct / 100));
32    } else {
33        if ($param{Width} && $param{Height}) {
34            ($w, $h) = ($param{Width}, $param{Height});
35        } else {
36            my $x = $param{Width} || $w;
37            my $y = $param{Height} || $h;
38            my $w_pct = $x / $w;
39            my $h_pct = $y / $h;
40            my $pct = $param{Width} ? $w_pct : $h_pct;
41            ($w, $h) = (int($w * $pct), int($h * $pct));
42        }
43    }
44    ($w, $h);
45}
46
47sub inscribe_square {
48    my $class = shift;
49    my %params = @_;
50    my ($w, $h) = @params{qw( Width Height )};
51
52    my ($dim, $x, $y);
53
54    if ($w > $h) {
55        $dim = $h;
56        $x = int(($w - $dim) / 2);
57        $y = 0;
58    }
59    else {
60        $dim = $w;
61        $x = 0;
62        $y = int(($h - $dim) / 2);
63    }
64
65    return ( Size => $dim, X => $x, Y => $y ); 
66}
67
68sub make_square {
69    my $image = shift;
70    my %square = $image->inscribe_square(
71        Width  => $image->{width},
72        Height => $image->{height},
73    );
74    $image->crop(%square);
75}
76
77sub check_upload {
78    my $class = shift;
79    my %params = @_;
80
81    my $fh = $params{Fh};
82
83    ## Use Image::Size to check if the uploaded file is an image, and if so,
84    ## record additional image info (width, height). We first rewind the
85    ## filehandle $fh, then pass it in to imgsize.
86    seek $fh, 0, 0;
87    eval { require Image::Size; };
88    return $class->error(
89        MT->translate(
90                "Perl module Image::Size is required to determine "
91              . "width and height of uploaded images."
92        )
93    ) if $@;
94    my ( $w, $h, $id ) = Image::Size::imgsize($fh);
95
96    my $write_file = sub {
97        $params{Fmgr}->put( $fh, $params{Local}, 'upload' );
98    };
99
100    ## Check file size?
101    my $file_size;
102    if ($params{Max}) {
103        ## Seek to the end of the handle to find the size.
104        seek $fh, 0, 2;  # wind to end
105        $file_size = tell $fh;
106        seek $fh, 0, 0;
107    }
108
109    ## If the image exceeds the dimension limit, resize it before writing.
110    if (my $max_dim = $params{MaxDim}) {
111        if (defined($w) && defined($h) && ($w > $max_dim || $h > $max_dim)) {
112            my $uploaded_data = eval { local $/; <$fh> };
113            my $img = $class->new( Data => $uploaded_data )
114                or return $class->error($class->errstr);
115
116            if ($params{Square}) {
117                (undef, $w, $h) = $img->make_square()
118                    or return $class->error($img->errstr);
119            }
120            (my($resized_data), $w, $h) = $img->scale(
121                (($w > $h) ? 'Width' : 'Height') => $max_dim )
122                    or return $class->error($img->errstr);
123
124            $write_file = sub {
125                $params{Fmgr}->put_data( $resized_data, $params{Local}, 'upload' )
126            };
127            $file_size = length $resized_data;
128        }
129    }
130
131    if (my $max_size = $params{Max}) {
132        if ($max_size < $file_size) {
133            return $class->error(MT->translate( "File size exceeds maximum allowed: [_1] > [_2]",
134                    $file_size, $max_size ) );
135        }
136    }
137
138    ($w, $h, $id, $write_file);
139}
140
141package MT::Image::ImageMagick;
142@MT::Image::ImageMagick::ISA = qw( MT::Image );
143
144sub load_driver {
145    my $image = shift;
146    eval { require Image::Magick };
147    if (my $err = $@) {
148        return $image->error(MT->translate("Can't load Image::Magick: [_1]", $err));
149    }
150    1;
151}
152
153sub init {
154    my $image = shift;
155    my %param = @_;
156    my %arg = ();
157    if (my $type = $param{Type}) {
158        %arg = (magick => lc($type));
159    } elsif (my $file = $param{Filename}) {
160        (my $ext = $file) =~ s/.*\.//;
161        %arg = (magick => lc($ext));
162    }
163    my $magick = $image->{magick} = Image::Magick->new(%arg);
164    if (my $file = $param{Filename}) {
165        my $x = $magick->Read($file);
166        return $image->error(MT->translate(
167            "Reading file '[_1]' failed: [_2]", $file, $x)) if $x;
168        ($image->{width}, $image->{height}) = $magick->Get('width', 'height');
169    } elsif ($param{Data}) {
170        my $x = $magick->BlobToImage($param{Data});
171        return $image->error(MT->translate(
172            "Reading image failed: [_1]", $x)) if $x;
173        ($image->{width}, $image->{height}) = $magick->Get('width', 'height');
174    }
175    $image;
176}
177
178sub scale {
179    my $image = shift;
180    my($w, $h) = $image->get_dimensions(@_);
181    my $magick = $image->{magick};
182    my $err = $magick->can('Resize') ?
183              $magick->Resize(width => $w, height => $h) :
184              $magick->Scale(width => $w, height => $h);
185    return $image->error(MT->translate(
186        "Scaling to [_1]x[_2] failed: [_3]", $w, $h, $err)) if $err;
187    $magick->Profile("*") if $magick->can('Profile');
188    ($image->{width}, $image->{height}) = ($w, $h);
189    wantarray ? ($magick->ImageToBlob, $w, $h) : $magick->ImageToBlob;
190}
191
192sub crop {
193    my $image = shift;
194    my %param = @_;
195    my ($size, $x, $y) = @param{qw( Size X Y )};
196    my $magick = $image->{magick};
197
198    my $err = $magick->Crop(width => $size, height => $size, x => $x, y => $y);
199    return $image->error(MT->translate(
200        "Cropping a [_1]x[_1] square at [_2],[_3] failed: [_4]", $size, $x,
201        $y, $err)) if $err;
202
203    ## Remove page offsets from the original image, per this thread:
204    ## http://studio.imagemagick.org/pipermail/magick-users/2003-September/010803.html
205    $magick->Set( page => '+0+0' );
206
207    ($image->{width}, $image->{height}) = ($size, $size);
208    wantarray ? ($magick->ImageToBlob, $size, $size) : $magick->ImageToBlob;
209}
210
211sub convert {
212    my $image = shift;
213    my %param = @_;
214    my $type = $param{Type};
215
216    my $magick = $image->{magick};
217    my $err = $magick->Set( magick => uc $type );
218    return $image->error(MT->translate(
219            "Converting image to [_1] failed: [_2]", $type, $err)) if $err;
220
221    $magick->ImageToBlob;
222}
223
224package MT::Image::NetPBM;
225@MT::Image::NetPBM::ISA = qw( MT::Image );
226
227sub load_driver {
228    my $image = shift;
229    eval { require IPC::Run };
230    if (my $err = $@) {
231        return $image->error(MT->translate("Can't load IPC::Run: [_1]", $err));
232    }
233    my $pbm = $image->_find_pbm or return;
234    1;
235}
236
237sub init {
238    my $image = shift;
239    my %param = @_;
240    if (my $file = $param{Filename}) {
241        $image->{file} = $file;
242        if (!defined $param{Type}) {
243            (my $ext = $file) =~ s/.*\.//;
244            $param{Type} = uc $ext;
245        }
246    } elsif (my $blob = $param{Data}) {
247        $image->{data} = $blob;
248    }
249    my %Types = (jpg => 'jpeg', jpeg => 'jpeg', gif => 'gif', 'png' => 'png');
250    my $type = $image->{type} = $Types{ lc $param{Type} };
251    if (!$type) {
252        return $image->error(MT->translate("Unsupported image file type: [_1]", $type));
253    }
254    my($out, $err);
255    my $pbm = $image->_find_pbm or return;
256    my @in = ("$pbm${type}topnm", ($image->{file} ? $image->{file} : ()));
257    my @out = ("${pbm}pnmfile", '-allimages');
258    IPC::Run::run(\@in, '<', ($image->{file} ? \undef : \$image->{data}), '|',
259        \@out, \$out, \$err)
260        or return $image->error(MT->translate(
261            "Reading image failed: [_1]", $err));
262    ($image->{width}, $image->{height}) = $out =~ /(\d+)\s+by\s+(\d+)/;
263    $image;
264}
265
266sub scale {
267    my $image = shift;
268    my($w, $h) = $image->get_dimensions(@_);
269    my $type = $image->{type};
270    my($out, $err);
271    my $pbm = $image->_find_pbm or return;
272    my @in = ("$pbm${type}topnm", ($image->{file} ? $image->{file} : ()));
273    my @scale = ("${pbm}pnmscale", '-width', $w, '-height', $h);
274    my @out;
275    for my $try (qw( ppm pnm )) {
276        my $prog = "${pbm}${try}to$type";
277        @out = ($prog), last if -x $prog;
278    }
279    my(@quant);
280    if ($type eq 'gif') {
281        push @quant, ([ "${pbm}ppmquant", 256 ], '|');
282    }
283    IPC::Run::run(\@in, '<', ($image->{file} ? \undef : \$image->{data}), '|',
284        \@scale, '|',
285        @quant,
286        \@out, \$out, \$err)
287        or return $image->error(MT->translate(
288            "Scaling to [_1]x[_2] failed: [_3]", $w, $h, $err));
289    ($image->{width}, $image->{height}) = ($w, $h);
290    wantarray ? ($out, $w, $h) : $out;
291}
292
293sub crop {
294    my $image = shift;
295    my %param = @_;
296    my ($size, $x, $y) = @param{qw( Size X Y )};
297   
298    my($w, $h) = $image->get_dimensions(@_);
299    my $type = $image->{type};
300    my($out, $err);
301    my $pbm = $image->_find_pbm or return;
302    my @in = ("$pbm${type}topnm", ($image->{file} ? $image->{file} : ()));
303
304    my @crop = ("${pbm}pnmcut", $x, $y, $size, $size);
305    my @out;
306    for my $try (qw( ppm pnm )) {
307        my $prog = "${pbm}${try}to$type";
308        @out = ($prog), last if -x $prog;
309    }
310    my(@quant);
311    if ($type eq 'gif') {
312        push @quant, ([ "${pbm}ppmquant", 256 ], '|');
313    }
314    IPC::Run::run(\@in, '<', ($image->{file} ? \undef : \$image->{data}), '|',
315        \@crop, '|',
316        @quant,
317        \@out, \$out, \$err)
318        or return $image->error(MT->translate(
319            "Cropping to [_1]x[_1] failed: [_2]", $size, $err));
320    ($image->{width}, $image->{height}) = ($w, $h);
321    wantarray ? ($out, $w, $h) : $out;
322}
323
324sub convert {
325    my $image = shift;
326    my %param = @_;
327
328    my $type = $image->{type};
329    my $outtype = lc $param{Type};
330
331    my($out, $err);
332    my $pbm = $image->_find_pbm or return;
333    my @in = ("$pbm${type}topnm", ($image->{file} ? $image->{file} : ()));
334
335    my @out;
336    for my $try (qw( ppm pnm )) {
337        my $prog = "${pbm}${try}to$outtype";
338        @out = ($prog), last if -x $prog;
339    }
340    my(@quant);
341    if ($type eq 'gif') {
342        push @quant, ([ "${pbm}ppmquant", 256 ], '|');
343    }
344    IPC::Run::run(\@in, '<', ($image->{file} ? \undef : \$image->{data}), '|',
345        @quant,
346        \@out, \$out, \$err)
347        or return $image->error(MT->translate(
348            "Converting to [_1] failed: [_2]", $type, $err));
349    $out;
350}
351
352sub _find_pbm {
353    my $image = shift;
354    return $image->{__pbm_path} if $image->{__pbm_path};
355    my @NetPBM = qw( /usr/local/netpbm/bin /usr/local/bin /usr/bin );
356    my $pbm;
357    for my $path (MT->config->NetPBMPath, @NetPBM) {
358        next unless $path;
359        $path .= '/' unless $path =~ m!/$!;
360        $pbm = $path, last if -x "${path}pnmscale";
361    }
362    return $image->error(MT->translate(
363        "You do not have a valid path to the NetPBM tools on your machine."))
364        unless $pbm;
365    $image->{__pbm_path} = $pbm;
366}
367
368package MT::Image::GD;
369@MT::Image::GD::ISA = qw( MT::Image );
370
371sub load_driver {
372    my $image = shift;
373    eval { require GD };
374    if (my $err = $@) {
375        return $image->error(MT->translate("Can't load GD: [_1]", $err));
376    }
377    1;
378}
379
380sub init {
381    my $image = shift;
382    my %param = @_;
383
384    if ((!defined $param{Type}) && (my $file = $param{Filename})) {
385    (my $ext = $file) =~ s/.*\.//;
386    $param{Type} = lc $ext;
387    }
388    my %Types = (jpg => 'jpeg', jpeg => 'jpeg', gif => 'gif', 'png' => 'png');
389    $image->{type} = $Types{ lc $param{Type} }
390        or return $image->error(MT->translate("Unsupported image file type: [_1]", $param{Type}));
391
392    if (my $file = $param{Filename}) {
393    $image->{gd} = GD::Image->new($file)
394        or return $image->error(MT->translate("Reading file '[_1]' failed: [_2]", $file, $@));
395    } elsif (my $blob = $param{Data}) {
396    $image->{gd} = GD::Image->new($blob)
397        or return $image->error(MT->translate("Reading image failed: [_1]", $@));
398    }
399    ($image->{width}, $image->{height}) = $image->{gd}->getBounds();
400    $image;
401}
402
403sub blob {
404    my $image = shift;
405    my $type = $image->{type};
406    $image->{gd}->$type;
407}
408
409sub scale {
410    my $image = shift;
411    my($w, $h) = $image->get_dimensions(@_);
412    my $src = $image->{gd};
413    my $gd = GD::Image->new($w, $h);
414    $gd->copyResampled($src, 0, 0, 0, 0, $w, $h, $image->{width}, $image->{height});
415    ($image->{gd}, $image->{width}, $image->{height}) = ($gd, $w, $h);
416    wantarray ? ($image->blob, $w, $h) : $image->blob;
417}
418
419sub crop {
420    my $image = shift;
421    my %param = @_;
422    my ($size, $x, $y) = @param{qw( Size X Y )};
423    my $src = $image->{gd};
424    my $gd = GD::Image->new($size, $size);
425    $gd->copy($src, 0, 0, $x, $y, $size, $size);
426    ($image->{gd}, $image->{width}, $image->{height}) = ($gd, $size, $size);
427    wantarray ? ($image->blob, $size, $size) : $image->blob;
428}
429
430sub convert {
431    my $image = shift;
432    my %param = @_;
433    $image->{type} = lc $param{Type};
434    $image->blob;
435}
436
4371;
438__END__
439
440=head1 NAME
441
442MT::Image - Movable Type image manipulation routines
443
444=head1 SYNOPSIS
445
446    use MT::Image;
447    my $img = MT::Image->new( Filename => '/path/to/image.jpg' );
448    my($blob, $w, $h) = $img->scale( Width => 100 );
449
450    open FH, ">thumb.jpg" or die $!;
451    binmode FH;
452    print FH $blob;
453    close FH;
454
455=head1 DESCRIPTION
456
457I<MT::Image> contains image manipulation routines using either the
458I<NetPBM> tools, the I<ImageMagick> and I<Image::Magick> Perl module,
459or the I<GD> and I<GD> Perl module.
460The backend framework used (NetPBM, ImageMagick, GD) depends on the value of
461the I<ImageDriver> setting in the F<mt.cfg> file (or, correspondingly, set
462on an instance of the I<MT::ConfigMgr> class).
463
464Currently all this is used for is to create thumbnails from uploaded images.
465
466=head1 USAGE
467
468=head2 MT::Image->new(%arg)
469
470Constructs a new I<MT::Image> object. Returns the new object on success; on
471error, returns C<undef>, and the error message is in C<MT::Image-E<gt>errstr>.
472
473I<%arg> can contain:
474
475=over 4
476
477=item * Filename
478
479The path to an image to load.
480
481=item * Data
482
483The actual contents of an image, already loaded from a file, a database,
484etc.
485
486=item * Type
487
488The image format of the data in I<Data>. This should be either I<JPG> or
489I<GIF>.
490
491=back
492
493=head2 $img->scale(%arg)
494
495Creates a thumbnail from the image represented by I<$img>; on success, returns
496a list containing the binary contents of the thumbnail image, the width of the
497scaled image, and the height of the scaled image. On error, returns C<undef>,
498and the error message is in C<$img-E<gt>errstr>.
499
500I<%arg> can contain:
501
502=over 4
503
504=item * Width
505
506=item * Height
507
508The width and height of the final image, respectively. If you provide only one
509of these arguments, the other dimension will be scaled appropriately. If you
510provide neither, the image will be scaled to C<100%> of the original (that is,
511the same size). If you provide both, the image will likely look rather
512distorted.
513
514=item * Scale
515
516To be used instead of I<Width> and I<Height>; the value should be a percentage
517(ie C<100> to return the original image without resizing) by which both the
518width and height will be scaled equally.
519
520=back
521
522=head2 $img->get_dimensions(%arg)
523
524This utility method returns a width and height value pair after applying
525the given arguments. Valid arguments are the same as the L<scale> method.
526If 'Width' is given, a proportionate height will be calculated. If a
527'Height' is given, the width will be calculated. If 'Scale' is given
528the height and width will be calculated based on that scale (a value
529between 1 to 100).
530
531=head1 AUTHOR & COPYRIGHT
532
533Please see the I<MT> manpage for author, copyright, and license information.
534
535=cut
Note: See TracBrowser for help on using the browser.