line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Catalyst::View::Thumbnail; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
19851
|
use warnings; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
31
|
|
4
|
1
|
|
|
1
|
|
4
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
22
|
|
5
|
1
|
|
|
1
|
|
662
|
use parent 'Catalyst::View'; |
|
1
|
|
|
|
|
339
|
|
|
1
|
|
|
|
|
5
|
|
6
|
1
|
|
|
1
|
|
635688
|
use Image::Info qw/image_info/; |
|
1
|
|
|
|
|
1614
|
|
|
1
|
|
|
|
|
68
|
|
7
|
1
|
|
|
1
|
|
1405
|
use Imager; |
|
1
|
|
|
|
|
36185
|
|
|
1
|
|
|
|
|
8
|
|
8
|
1
|
|
|
1
|
|
60
|
use List::Util qw/min max/; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
714
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 NAME |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
Catalyst::View::Thumbnail - Catalyst view to resize images for thumbnails |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=cut |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION = '0.03'; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
sub process { |
19
|
0
|
|
|
0
|
1
|
|
my ($self, $c) = @_; |
20
|
|
|
|
|
|
|
|
21
|
0
|
|
|
|
|
|
my $image = $self->render_image($c); |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# render_image() will return an Imager object on success, |
24
|
|
|
|
|
|
|
# or an error message on failure. |
25
|
|
|
|
|
|
|
|
26
|
0
|
0
|
|
|
|
|
if (UNIVERSAL::isa($image, 'Imager')) { |
27
|
0
|
|
0
|
|
|
|
my $mime_type = $c->stash->{image_type} || image_info(\$c->stash->{image})->{file_media_type}; |
28
|
0
|
|
|
|
|
|
(my $file_type = $mime_type) =~ s!^image/!!; |
29
|
|
|
|
|
|
|
|
30
|
0
|
|
|
|
|
|
my $thumbnail; |
31
|
0
|
|
|
|
|
|
$image->write( |
32
|
|
|
|
|
|
|
data => \$thumbnail, |
33
|
|
|
|
|
|
|
type => $file_type, |
34
|
|
|
|
|
|
|
); |
35
|
|
|
|
|
|
|
|
36
|
0
|
|
|
|
|
|
$c->response->content_type($mime_type); |
37
|
0
|
|
|
|
|
|
$c->response->body($thumbnail); |
38
|
|
|
|
|
|
|
} else { |
39
|
0
|
|
|
|
|
|
my $error = qq/Couldn't render image: $image/; |
40
|
0
|
|
|
|
|
|
$c->log->error($error); |
41
|
0
|
|
|
|
|
|
$c->error($error); |
42
|
0
|
|
|
|
|
|
return 0; |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub render_image { |
48
|
0
|
|
|
0
|
0
|
|
my ($self, $c) = @_; |
49
|
|
|
|
|
|
|
|
50
|
0
|
0
|
|
|
|
|
return "Image data missing from stash" unless $c->stash->{image}; |
51
|
|
|
|
|
|
|
|
52
|
0
|
|
|
|
|
|
my $image = Imager->new(); |
53
|
0
|
0
|
|
|
|
|
$image->read(data => $c->stash->{image}) or return $image->errstr; |
54
|
|
|
|
|
|
|
|
55
|
0
|
0
|
|
|
|
|
if ($c->stash->{zoom}) { |
56
|
|
|
|
|
|
|
$image = $image->crop( |
57
|
|
|
|
|
|
|
width => $image->getwidth * ($c->stash->{zoom} / 100), |
58
|
0
|
0
|
|
|
|
|
height => $image->getheight * ($c->stash->{zoom} / 100), |
59
|
|
|
|
|
|
|
) or return $image->errstr; |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
0
|
0
|
0
|
|
|
|
if ($c->stash->{x} or $c->stash->{y}) { |
63
|
0
|
|
|
|
|
|
$c->log->debug('Creating thumbnail image'); |
64
|
0
|
|
|
|
|
|
my $source_aspect = $image->getwidth / $image->getheight; |
65
|
0
|
|
|
|
|
|
$c->log->debug('Source width: ' . $image->getwidth . ' height: ' . $image->getheight . ' aspect ratio: ' . $source_aspect); |
66
|
0
|
|
0
|
|
|
|
$c->stash->{x} ||= $c->stash->{y} * $source_aspect; |
67
|
0
|
|
0
|
|
|
|
$c->stash->{y} ||= $c->stash->{x} / $source_aspect; |
68
|
|
|
|
|
|
|
|
69
|
0
|
|
|
|
|
|
$c->log->debug('Target width: ' . $c->stash->{x} . ' height: ' . $c->stash->{y}); |
70
|
|
|
|
|
|
|
|
71
|
0
|
0
|
|
|
|
|
unless ($c->stash->{scaling} eq 'fit') { |
72
|
0
|
|
|
|
|
|
my $thumbnail_aspect = $c->stash->{x} / $c->stash->{y}; |
73
|
0
|
|
|
|
|
|
$c->log->debug('Thumbnail aspect ratio: ' . $thumbnail_aspect); |
74
|
|
|
|
|
|
|
|
75
|
0
|
0
|
|
|
|
|
if ($source_aspect > $thumbnail_aspect) { |
76
|
0
|
|
|
|
|
|
$c->log->debug('Cropping image to fit aspect ratio of thumbnail'); |
77
|
0
|
|
|
|
|
|
$c->log->debug('Source aspect > thumbnail aspect'); |
78
|
0
|
|
|
|
|
|
$c->log->debug('Cropping to width: '.$image->getheight * $thumbnail_aspect.' x height: '.$image->getheight); |
79
|
0
|
0
|
|
|
|
|
$image = $image->crop( |
80
|
|
|
|
|
|
|
width => $image->getheight * $thumbnail_aspect, |
81
|
|
|
|
|
|
|
height => $image->getheight, |
82
|
|
|
|
|
|
|
) or return $image->errstr; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
0
|
0
|
|
|
|
|
if ($source_aspect < $thumbnail_aspect) { |
86
|
0
|
|
|
|
|
|
$c->log->debug('Cropping image to fit aspect ratio of thumbnail'); |
87
|
0
|
|
|
|
|
|
$c->log->debug('Source aspect < thumbnail aspect'); |
88
|
0
|
|
|
|
|
|
$c->log->debug('Cropping to width: '.$image->getwidth.' x height: '.$image->getwidth / $thumbnail_aspect); |
89
|
0
|
0
|
|
|
|
|
$image = $image->crop( |
90
|
|
|
|
|
|
|
width => $image->getwidth, |
91
|
|
|
|
|
|
|
height => $image->getwidth / $thumbnail_aspect, |
92
|
|
|
|
|
|
|
) or return $image->errstr; |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
0
|
|
|
|
|
|
$c->log->debug('Scaling image to thumbnail'); |
97
|
|
|
|
|
|
|
$image = $image->scale( |
98
|
|
|
|
|
|
|
xpixels => $c->stash->{x}, |
99
|
|
|
|
|
|
|
ypixels => $c->stash->{y}, |
100
|
0
|
0
|
|
|
|
|
type => 'min', |
101
|
|
|
|
|
|
|
qtype => 'mixing', |
102
|
|
|
|
|
|
|
) or return $image->errstr; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
0
|
|
|
|
|
|
return $image; |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=head1 SYNOPSIS |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Create a thumbnail view: |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
script/myapp_create view Thumbnail Thumbnail |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Then in your controller: |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
sub thumbnail :Local :Args(1) { |
117
|
|
|
|
|
|
|
my ($self, $c, $image_id) = @_; |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
my $image_obj = $c->model('MyApp::Images')->find({id=>$image_id}) |
120
|
|
|
|
|
|
|
or $c->detach('/default'); |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
$c->stash->{x} = 100; # Create a 100px square thumbnail |
123
|
|
|
|
|
|
|
$c->stash->{y} = 100; |
124
|
|
|
|
|
|
|
$c->stash->{image} = $image_obj->data; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
$c->forward('View::Thumbnail'); |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head1 DESCRIPTION |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Catalyst::View::Thumbnail resizes images to produce thumbnails, with options to set the desired x or y |
132
|
|
|
|
|
|
|
dimensions (or both), and specify a zoom level and scaling type. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=head2 Options |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
The view is controlled by setting the following values in the stash: |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=over |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=item image |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
Contains the raw data for the full-size source image. |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
This is a mandatory option. |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=item x |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
The width (in pixels) of the thumbnail. |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
This is optional, but at least one of the C<x> or C<y> parameters must be set. |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=item y |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
The height (in pixels) of the thumbnail. |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
This is optional, but at least one of the C<x> or C<y> parameters must be set. |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=item zoom |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Zoom level, expressed as a number between 1 and 100. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
If the C<zoom> option is given, the thumbnail will be 'zoomed-in' by the |
163
|
|
|
|
|
|
|
appropriate amount, e.g. a zoom level of 80 will create a thumbnail using the |
164
|
|
|
|
|
|
|
middle 80% of the source image. |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
This parameter is optional, if omitted then a zoom level of 100 will be used, |
167
|
|
|
|
|
|
|
i.e. create thumbnails using 100% of the source image. |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=item scaling |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
Scaling type, can be either 'fit' or 'fill'. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
If both the C<x> and C<y> parameters are set, the aspect ratio (x/y) of the |
174
|
|
|
|
|
|
|
thumbnail image may not match the aspect ratio of the source image. |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
To prevent the thumbnail from looking 'stretched', there is a choice of two |
177
|
|
|
|
|
|
|
scaling options: |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=over |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=item fit |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
Fits the thumbnail within the specified C<x> and C<y> dimensions, preserving |
184
|
|
|
|
|
|
|
all of the source image. |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Note that by using this scaling method, the generated thumbnails may be smaller |
187
|
|
|
|
|
|
|
than the the specified C<x> and C<y> dimensions. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=item fill |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
Fills the thumbnail to the exact C<x> and C<y> dimensions as specified, cropping |
192
|
|
|
|
|
|
|
the source image as necessary. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=back |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
This parameter is optional, and will default to 'fill' if omitted. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=item image_type |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Mime type for the output image. This is normally the same as the input image. |
201
|
|
|
|
|
|
|
If you set this the Imager library will produce an image of that format. This |
202
|
|
|
|
|
|
|
is useful when you want to convert something like a tiff to a jpeg. Note |
203
|
|
|
|
|
|
|
that the conversions can be strange so this may not be a good idea for all images. |
204
|
|
|
|
|
|
|
See the C<Imager> documentation for more details. |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=back |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head2 Image formats |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
The generated thumbnails will always be produced in the same format (PNG, JPG, etc) |
211
|
|
|
|
|
|
|
as the source image. |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
Catalyst::View::Thumbnail uses the L<Imager> module to crop and resize images, |
214
|
|
|
|
|
|
|
so will accept any image format supported by I<Imager>. |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
Please see the L<Imager> documentation for more details and installation notes. |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=head1 SEE ALSO |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
Catalyst::View::Thumbnail tutorial (with examples): L<http://perl.jonallen.info/writing/articles/creating-thumbnails-with-catalyst> |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=head1 AUTHOR |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
Jon Allen (JJ), C<< <jj@jonallen.info> >> |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=head1 BUGS |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
Please report any bugs or feature requests to C<bug-catalyst-view-thumbnail at rt.cpan.org>, or through |
229
|
|
|
|
|
|
|
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Catalyst-View-Thumbnail>. I will be notified, and then you'll |
230
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=head1 SUPPORT |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
Commercial support, customisation, and training for this module is available |
235
|
|
|
|
|
|
|
from Penny's Arcade Limited - contact L<info@pennysarcade.co.uk> for details. |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
You can also look for information at: |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=over 4 |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Catalyst-View-Thumbnail> |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
L<http://annocpan.org/dist/Catalyst-View-Thumbnail> |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=item * CPAN Ratings |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
L<http://cpanratings.perl.org/d/Catalyst-View-Thumbnail> |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=item * Search CPAN |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
L<http://search.cpan.org/dist/Catalyst-View-Thumbnail/> |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=back |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
Copyright (C) 2009 Jon Allen (JJ). |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
This module is free software; you |
264
|
|
|
|
|
|
|
can redistribute it and/or modify it under the same terms |
265
|
|
|
|
|
|
|
as Perl 5.10.0. For more details, see the full text of the |
266
|
|
|
|
|
|
|
licenses in the directory LICENSES. |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
This module is distributed in the hope that it will be |
269
|
|
|
|
|
|
|
useful, but it is provided "as is" and without any express |
270
|
|
|
|
|
|
|
or implied warranties. |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=cut |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
1; # End of Catalyst::View::Thumbnail |