File Coverage

blib/lib/Image/Math/Constrain.pm
Criterion Covered Total %
statement 53 58 91.3
branch 35 54 64.8
condition 9 15 60.0
subroutine 13 13 100.0
pod 5 5 100.0
total 115 145 79.3


line stmt bran cond sub pod time code
1             package Image::Math::Constrain;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Image::Math::Constrain - Scaling math used in image size constraining (such
8             as thumbnails)
9              
10             =head1 SYNOPSIS
11              
12             use Image::Math::Constrain;
13            
14             # Create the math object
15             my $math = Image::Math::Constrain->new(64, 48);
16            
17             # Get the scaling values for an arbitrary image
18             my $Image = My::Image->load("myimage.jpg");
19             my $scaling = $math->constrain($Image->width, $Image->height);
20             die "Don't need to scale" if $scaling->{scale} == 1;
21            
22             # Returns the three values as a list when called in array contect
23             my ($width, $height, $scale) = $math->constrain(800, 600);
24              
25             # There are lots of different ways to specify the constrain
26            
27             # Constrain based on width only
28             $math = Image::Math::Constrain->new(100, 0);
29            
30             # Constrain based on height only
31             $math = Image::Math::Constrain->new(0, 100);
32              
33             # Or you can provide the two values by ARRAY ref
34             $math = Image::Math::Constrain->new( [ 64, 48 ] );
35            
36             # Constrain height and width by the same value
37             $math = Image::Math::Constrain->new(100);
38            
39             # Various string forms to do the same thing
40             $math = Image::Math::Constrain->new('constrain(800x600)');
41             $math = Image::Math::Constrain->new('300x200');
42             $math = Image::Math::Constrain->new('300w200h');
43             $math = Image::Math::Constrain->new('100w');
44             $math = Image::Math::Constrain->new('100h');
45            
46             # Serialises back to 'constrain(800x600)'.
47             # You can use this to store the object if you wish.
48             my $string = $math->as_string;
49              
50             =head1 DESCRIPTION
51              
52             There are a number of different modules and systems that constrain image
53             sizes, such as thumbnailing. Every one of these independantly implement
54             the same logic. That is, given a width and/or height constraint, they
55             check to see if the image is bigger than the constraint, and if so scale
56             the image down proportionally so that it fits withint the constraints.
57              
58             Of course, they all do it slightly differnetly, and some do it better
59             than others.
60              
61             C has been created specifically to implement
62             this logic once, and implement it properly. Any module or script that
63             does image size constraining or thumbnailing should probably be using
64             this for its math.
65              
66             =head1 METHODS
67              
68             =cut
69              
70 2     2   34646 use 5.005;
  2         8  
  2         77  
71 2     2   11 use strict;
  2         3  
  2         122  
72             use overload 'bool' => sub () { 1 },
73 2     2   1953 '""' => 'as_string';
  2         1304  
  2         14  
74              
75 2     2   177 use vars qw{$VERSION};
  2         4  
  2         115  
76             BEGIN {
77 2     2   2169 $VERSION = '1.02';
78             }
79              
80              
81              
82              
83              
84             #####################################################################
85             # Constructor
86              
87             =pod
88              
89             =head2 new $width, $height
90              
91             -head2 new [ $width, $height ]
92              
93             =head1 new $width_and_height
94              
95             =head2 new $string
96              
97             The C constructor takes the dimentions to which you wish to
98             constrain and creates a new math object.
99              
100             You can feed a number of different height/width pairs to this object, and
101             it will returns the scaling you will need to do to shrink the image down
102             to the constraints, and the final width and height of the image after
103             scaling, at least one of which should match the constraint.
104              
105             A value of zero is used to indicate that a dimension should not be
106             constrained. Thus, C<-Enew(400, 0)> would indicate to constrain the
107             width to 400 pixels, but to ignore the height (only changing it to keep
108             the image proportional).
109              
110             The constraint dimensions can be provided in a number of different
111             formats. See the Synopsis for a quick list of these. To stay
112             compatible with automated constraint generators, you B provide
113             constrains as zero width and zero height, and the math object will not
114             attempt to do any scaling, always returning the input width/height,
115             and a scaling value of 1.
116              
117             Once created, the object is fully Storable and re-usable and does not
118             store any state information from a single calculation run.
119              
120             Returns a new Image::Math::Constrain object, or C if the
121             constraints have been defined wrongly.
122              
123             =cut
124              
125             sub new {
126 15 50   15 1 4224 my $class = ref $_[0] ? ref shift : shift;
127              
128             # Expand a single argument
129 15 100       45 if ( @_ == 1 ) {
130 6 50       16 my $value = defined $_[0] ? shift : return undef;
131 6 100 66     32 if ( ref $value eq 'ARRAY' and @$value == 2 ) {
132 1         8 return $class->new(@$value);
133             }
134 5 50       13 return undef if ref $value;
135 5         15 $value =~ s/\s//g;
136             # constrain(800x600)
137 5 100       21 return $class->new("$1", "$2") if $value =~ /^constrain\((\d+)x(\d+)\)$/;
138             # 800x600
139 4 100       29 return $class->new("$1", "$2") if $value =~ /^(\d+)x(\d+)$/;
140             # 800w600h
141 2 100       16 return $class->new("$1", "$2") if $value =~ /^(\d+)w(\d+)h$/;
142             # 800w (width only)
143 1 50       13 return $class->new("$1", 0) if $value =~ /^(\d+)w$/;
144             # 800h (height only)
145 0 0       0 return $class->new(0, "$1") if $value =~ /^(\d+)h$/;
146             # 800 (meaning 800x800)
147 0 0       0 if ( $class->_non_neg_int($value) ) {
148 0         0 return $class->new($value, $value);
149             }
150 0         0 return undef;
151             }
152              
153             # The two argument form
154 9 50       25 return undef unless @_ == 2;
155 9         29 my $self = bless {}, $class;
156 9 50       32 $self->{width} = $class->_non_neg_int($_[0]) ? shift : return undef;
157 9 50       30 $self->{height} = $class->_non_neg_int($_[0]) ? shift : return undef;
158 9         29 $self;
159             }
160              
161             =pod
162              
163             =head2 width
164              
165             The C method gets the width constraint for the object.
166              
167             Returns a positive integer, or zero if there is no width constraint.
168              
169             =cut
170              
171 8     8 1 9218 sub width { $_[0]->{width} }
172              
173             =pod
174              
175             =head2 height
176              
177             The C method gets the height constrain for the object.
178              
179             Returns a positive integer, or zero if there is no height constraint.
180              
181             =cut
182              
183 8     8 1 39 sub height { $_[0]->{height} }
184              
185             =pod
186              
187             =head2 as_string
188              
189             The C method returns the constrain rule as a string in the
190             format 'constrain(123x123)'. This string form is also supported by the
191             constructor and so it provides a good way to serialise the constrain
192             rule, should you ever need to do so.
193              
194             As this value is not localisable, it should never really be shown to the
195             user directly, unless you are sure you will never add i18n to your app.
196              
197             =cut
198              
199             sub as_string {
200 2     2 1 910 "constrain($_[0]->{width}x$_[0]->{height})";
201             }
202              
203             =pod
204              
205             =head2 constrain $width, $height
206              
207             The C method takes the height and width of an image and
208             applies the constrain math to them to get the final width, height
209             and the scaling value needed in order to get the your image from
210             it's current size to the final size.
211              
212             The resulting size will be in proportion to the original (it will have
213             the same aspect ratio) and will never be larger than the original.
214              
215             When called in array context, returns the new dimensions and scaling value
216             as a list, as in the following.
217              
218             my ($width, $height, $scale) = $math->constrain(800, 600);
219              
220             When called in scalar context, it returns a reference to a hash containing
221             the keys 'width', 'height', and 'scale'.
222              
223             my $hash = $math->constrain(800, 600);
224            
225             print "New Width : $hash->{width}\n";
226             print "New Height : $hash->{height}\n";
227             print "Scaling By : $hash->{scalar}\n";
228              
229             Having been created correctly, the object will only return an error if the
230             width and height arguments are not correct (are not positive integers).
231              
232             In list context, returns a null list, so all three values will be C.
233              
234             In scalar context, just returns C.
235              
236             =cut
237              
238             sub constrain {
239 3     3 1 1700 my $self = shift;
240 3 50       10 my $width = $self->_pos_int($_[0]) ? shift : return;
241 3 50       8 my $height = $self->_pos_int($_[0]) ? shift : return;
242 3 50 33     11 unless ( $self->{width} or $self->{height} ) {
243 0         0 return $self->_ret_val(wantarray, $width, $height, 1);
244             }
245              
246             # Determine the prefered scaling in both dimensions
247 3 100 66     20 my $w_scale = ($self->{width} and $self->{width} < $width)
248             ? ($self->{width} / $width) : 1;
249 3 100 66     13 my $h_scale = ($self->{height} and $self->{height} < $height)
250             ? ($self->{height} / $height) : 1;
251              
252             # Do we need to scale?
253 3 100 66     15 if ( $w_scale == 1 and $h_scale == 1 ) {
254 1         4 return $self->_ret_val(wantarray, $width, $height, 1);
255             }
256              
257             # Use the smaller scaling value to scale the dimentions
258 2 50       4 my $scale = $w_scale < $h_scale ? $w_scale : $h_scale;
259 2         4 $width *= $scale;
260 2         2 $height *= $scale;
261              
262 2         6 $self->_ret_val(wantarray, $width, $height, $scale);
263             }
264              
265              
266              
267              
268              
269             #####################################################################
270             # Support Methods
271              
272             # Validate a non-negative integer
273             sub _non_neg_int {
274 18 50   18   42 my $value = defined $_[1] ? $_[1] : return '';
275 18 50       39 return '' if ref $value;
276 18 100       47 return 1 if $value eq '0';
277 15         427 !! $value =~ /^[1-9]\d*$/;
278             }
279              
280             # Validate a positive integer
281             sub _pos_int {
282 6 50   6   10 my $value = defined $_[1] ? $_[1] : return '';
283 6 50       12 return '' if ref $value;
284 6         25 !! $value =~ /^[1-9]\d*$/;
285             }
286              
287             # Return as either a list or HASH reference
288             sub _ret_val {
289 3     3   3 my $self = shift;
290 3 100       15 shift(@_) ? @_ # wantarray
291             : { width => shift, height => shift, scale => shift };
292             }
293              
294             1;
295              
296             =pod
297              
298             =head1 TO DO
299              
300             - Write more special-case unit tests
301              
302             =head1 SUPPORT
303              
304             Bugs should always be submitted via the CPAN bug tracker
305              
306             L
307              
308             For other issues, contact the maintainer
309              
310             =head1 AUTHORS
311              
312             Adam Kennedy Eadamk@cpan.orgE
313              
314             Thank you to Phase N (L) for permitting
315             the open sourcing and release of this distribution.
316              
317             =head1 COPYRIGHT
318              
319             Copyright 2004 - 2008 Adam Kennedy.
320              
321             This program is free software; you can redistribute
322             it and/or modify it under the same terms as Perl itself.
323              
324             The full text of the license can be found in the
325             LICENSE file included with this module.
326              
327             =cut