File Coverage

blib/lib/Catalyst/View/Thumbnail/Simple.pm
Criterion Covered Total %
statement 15 78 19.2
branch 0 24 0.0
condition 0 23 0.0
subroutine 5 7 71.4
pod 0 2 0.0
total 20 134 14.9


line stmt bran cond sub pod time code
1             package Catalyst::View::Thumbnail::Simple;
2              
3 1     1   26177 use warnings;
  1         3  
  1         31  
4 1     1   5 use strict;
  1         2  
  1         28  
5 1     1   5461 use Imager;
  1         100619  
  1         9  
6 1     1   1148 use Image::Info qw/image_info dim/;
  1         10391  
  1         107  
7 1     1   12 use base 'Catalyst::View';
  1         3  
  1         1605  
8              
9             our $VERSION = 0.0010;
10              
11             sub process {
12 0     0 0   my ($self, $c) = @_;
13              
14 0           my $stash = $c->stash;
15 0           my $res = $c->res;
16              
17             # check for image data
18 0 0         unless (exists $stash->{image}) {
19 0           $c->error(q{Image data missing from stash, please set 'image' key in} .
20             q{ stash to a scalar ref containing raw image data});
21 0           return $c->res->status(404);
22             }
23              
24             # derive mime type for response
25 0           my $image_info = image_info $stash->{image};
26 0           my $mime_type = $image_info->{file_media_type};
27              
28 0           my ($w, $h) = dim($image_info);
29 0           my $longest = (sort { $a < $b } ($h, $w))[0];
  0            
30 0           my $size = $stash->{image_size};
31 0           my $square = $stash->{square};
32              
33             # get type that imager can accept like 'png'
34 0           my $imager_type = $mime_type;
35 0           $imager_type =~ s|^image/||;
36            
37 0           my $read_type = $imager_type;
38 0   0       my $write_type = $stash->{image_type} || $read_type;
39              
40             # find out if image needs to be processed at all
41 0           my $should_thumbnail = 0;
42            
43             # size is smaller than longest side
44 0 0 0       $should_thumbnail = 1
45             if ($size && ($size < $longest));
46            
47             # need to crop image
48 0 0 0       $should_thumbnail = 1
49             if ($square && ($w != $h));
50              
51             # write type is different than read type
52 0 0         $should_thumbnail = 1
53             if ($write_type ne $imager_type);
54              
55             # force param
56 0 0         $should_thumbnail = 1
57             if $stash->{force_read};
58              
59              
60 0           my $image = undef;
61            
62 0 0         if ($should_thumbnail) {
63             # generate thumbnail
64 0           $image = $self->generate_thumbnail($c, $read_type);
65             } else {
66             # just return it as is
67 0           $stash->{image_data} = $stash->{image};
68 0           $res->content_type($mime_type);
69 0           return $res->body(
70 0           ${ $c->stash->{image} }
71             );
72             }
73              
74              
75 0 0         if (ref $image) {
76             # image got scaled or read in at least
77              
78             # stash the imager object
79 0           delete $stash->{image};
80 0           $stash->{image} = $image;
81            
82             # quality of output
83 0   0       my $quality = $stash->{jpeg_quality} || 100;
84              
85             # write image data to a scalar
86 0           my $thumbnail;
87 0           $image->write(
88             data => \$thumbnail,
89             type => $write_type,
90             jpegquality => $quality
91             );
92            
93             # stash raw image data
94 0           $stash->{image_data} = \$thumbnail;
95            
96             # return image in response
97 0           $res->content_type('image/' . $write_type);
98 0           $res->body($thumbnail);
99             } else {
100             # error out
101 0           $c->error("Couldn't render image: $image");
102 0           return 0;
103             }
104              
105             }
106              
107              
108             sub generate_thumbnail {
109 0     0 0   my ($self, $c, $type) = @_;
110              
111             # check type
112 0 0         return 'Unable to derive image type from image data, your version of ' .
113             'Imager can read in the following image types: ' .
114             join(', ', Imager->read_types) unless $type;
115            
116 0           my $stash = $c->stash;
117 0   0       my $config = $c->config->{'View::Thumbnail::Simple'} || {};
118              
119             # max image size allowed, defaults to 15 mb
120 0   0       my $max_size = $stash->{max_image_size} ||
121             $config->{max_image_size} || 15_728_640;
122              
123             # read image
124 0           my $image = Imager->new;
125 0           $image->read(
126 0 0         data => ${$stash->{image}},
127             type => $type,
128             bytes => $max_size
129             ) or return 'Imager failed to read image: ' . $image->errstr;
130            
131 0           my $size = $stash->{image_size};
132 0           my $square = $stash->{square};
133 0           my $height = $image->getheight;
134 0           my $width = $image->getwidth;
135              
136 0           my ($longest, $shortest) = sort { $a < $b } ($height, $width);
  0            
137              
138 0 0 0       if ($square && ($height != $width)) {
139             # crop
140 0           $image = $image->crop(
141             width => $shortest,
142             height => $shortest
143             );
144              
145 0           $height = $width = $longest = $shortest;
146             }
147              
148            
149 0 0 0       if ($size && ($size < $longest)) {
150             # image needs to be scaled
151              
152             # pass the right parameter to Imager->scale
153 0 0         my $dimension = $width > $height ?
154             'xpixels' : 'ypixels';
155              
156             # scaling algo to use
157 0   0       my $qtype = $config->{scaling_qtype} || 'mixing';
158 0           my $new_image = $image->scale(
159             $dimension => $size,
160             qtype => $qtype
161             );
162            
163             # return scaled image
164 0           return $new_image;
165             }
166            
167             # return unchanged image
168 0           return $image;
169              
170             }
171              
172             1;
173              
174             __END__
175              
176             =pod
177              
178             =head1 NAME
179              
180             Catalyst::View::Thumbnail::Simple - Simple Catalyst view class for
181             thumbnailing images
182              
183             =head1 SYNOPSIS
184              
185             # in a view class of your application
186              
187             package MyApp::View::Thumbnailer;
188             use base 'Catalyst::View::Thumbnail::Simple;
189             1;
190              
191             # in your controller
192              
193             my $raw_image_data = $image->data;
194              
195             # scale
196             $c->stash(
197             image => \$raw_image_data,
198             image_size => 150
199             );
200             $c->forward('View::Thumbnailer');
201              
202              
203             # crop into a square and scale
204             $c->stash(
205             image => \$raw_image_data,
206             image_size => 300,
207             square => 1
208             );
209             $c->forward('View::Thumbnailer');
210              
211             # scale and transcode image to png
212             $c->stash(
213             image => \$raw_image_data,
214             image_size => 150,
215             image_type => 'png'
216             );
217              
218             =head1 DESCRIPTION
219              
220             This module is a View class for Catalyst that will simply and
221             efficiently create 'thumbnails' or scaled down versions of images
222             using arguably the most sane perl image manipulation library,
223             Imager. The behavior of this module is controlled by stash and config
224             values.
225              
226             =head2 Required stash attributes
227              
228             =over
229              
230             =item image
231              
232             A scalar reference containing raw image data.
233              
234             =back
235              
236             =head2 Optional stash attributes
237              
238             =over
239              
240             =item image_size
241              
242             An integer in pixels of the desired longest side of the image. It will
243             be scaled accordingly, maintaining it's original aspect ratio.
244              
245             =item image_type
246              
247             You can set this attribute to a string (e.g. 'png') to try to force
248             Imager to write an image of a certain file format (note that this may
249             fail). Otherwise the image type is automatically derived from the
250             source image.
251              
252             =item square
253              
254             Set this to true to cause the image to be cropped into a square. See
255             crop() in L<Imager::Transformations>. Note that this takes place
256             before the image is scaled (if image_size is available).
257              
258             =item max_image_size
259              
260             An integer in bytes of the largest size of image you want Imager to
261             read (defaults to 15 megabytes). Note that this can also be set in
262             your application's configuration like so:
263              
264             # example in YAML
265             View::Thumbnail::Simple:
266             max_image_size: 10_485_760
267              
268             =item force_read
269              
270             This module will avoid reading in image data unless necessary for
271             scaling, cropping or transcoding (if the stash values are set,
272             original image dimensions are checked as well as type). This is done
273             to avoid unnecessary re-compression and loss of quality. If you want
274             to force this module to read and write the image data regardless of
275             these variables, set this stash key to a true value.
276              
277             =back
278              
279             =head2 Optional config parameters
280              
281             =over
282              
283             =item max_image_size
284              
285             See above section about max_image_size
286              
287             =item scaling_qtype
288              
289             Pick what Imager scaling algo to use, defaults to 'mixing'. Please see
290             the documentation on 'scale()' in L<Imager::Transformations>.
291              
292             =item jpeg_quality
293              
294             An integer between 0-100 used to determine the quality of the image when writing JPEG files, defaults to 100. Please see the JPEG section of L<Imager::Files>.
295              
296             =back
297              
298             =head2 Returned stash attributes
299              
300             After generating the thumbnail from the image data, the following
301             stash values will be set:
302              
303             =over
304              
305             =item image
306              
307             The scaled Imager image object.
308              
309             =item image_data
310              
311             A scalar reference containing the raw data of the scaled image (this
312             could be useful for caching purposes).
313              
314             =back
315              
316             =head2 Imager read/write formats
317              
318             Imager requires several libraries (e.g. libpng, libjpeg) for the
319             ability to read some image file types. If you are unsure if you have
320             these dependencies installed, type the following in your command shell
321             to reveal what image types your version of Imager can currently read:
322              
323             perl -MImager -e 'print join(q{, }, Imager->read_types) . qq{\n}'
324              
325             See the L<Imager> perldocs for more information on image formats.
326              
327             =head1 COPYRIGHT & LICENSE
328              
329             Copyright (C) 2011 <aesop@cpan.org>
330              
331             This library is free software; you can redistribute it and/or modify
332             it under the same terms as Perl itself, either Perl version 5.10.1 or,
333             at your option, any later version of Perl 5 you may have available.
334              
335             =cut
336              
337             1;