File Coverage

blib/lib/Imager/Filter/Autocrop.pm
Criterion Covered Total %
statement 86 94 91.4
branch 32 54 59.2
condition 29 56 51.7
subroutine 10 10 100.0
pod 0 1 0.0
total 157 215 73.0


line stmt bran cond sub pod time code
1             package Imager::Filter::Autocrop;
2              
3 2     2   56009 use 5.006;
  2         6  
4 2     2   8 use strict;
  2         2  
  2         30  
5 2     2   5 use warnings;
  2         6  
  2         38  
6 2     2   757 use Imager;
  2         28886  
  2         9  
7 2     2   75 use Imager::Color;
  2         2  
  2         1423  
8              
9             =head1 NAME
10              
11             Imager::Filter::Autocrop - Automatic crop filter for Imager.
12              
13             =head1 VERSION
14              
15             Version 1.23
16              
17             =head1 SYNOPSIS
18              
19             use Imager;
20             use Imager::Filter::Autocrop;
21              
22             my $img = Imager->new();
23             $img->read(file => 'image.jpg');
24             $img->filter(type => 'autocrop') or die $img->errstr;
25              
26             =head1 DESCRIPTION
27              
28             This module extends C functionality with the autocrop filter, similar to ImageMagick and GraphicsMagick "trim". It does
29             have a few additional features as well, such as support for the border or detection-only mode. The distribution also includes
30             a command-line script autocrop.pl.
31              
32             Note: If the image looks blank (whether genuinely or because of the 'fuzz' parameter), or if there is nothing to crop,
33             then the filter call will return false and the 'errstr' method will return appropriate message.
34              
35             =head1 NAME OVERRIDE
36              
37             You can change the name under which the filter is registered, by specifying it in 'use' directive. For example, to change the
38             name to 'trim' from the default 'autocrop':
39              
40             use Imager;
41             use Imager::Filter::Autocrop 'trim';
42              
43             $img->filter(type => 'trim') or die $img->errstr;
44              
45             =head1 PARAMETERS
46              
47             =over 12
48              
49             =item C
50              
51             By default the color of the top left pixel is used for cropping. You can explicitly specify one though, by either
52             providing the '#RRGGBB' value or passing an object of C class or its subclass.
53              
54             # The following two calls are identical.
55             $img->filter(type => 'autocrop', color => "#FFFFFF");
56             $img->filter(type => 'autocrop', color => Imager::Color->new(255, 255, 255));
57              
58             =item C
59              
60             You can specify the deviation for the color value (for all RGB channels or the main channel if it is a greyscale image),
61             by using the 'fuzz' parameter. All image colors within the range would be treated as matching candidates for cropping.
62            
63             $img->filter(type => 'autocrop', fuzz => 20);
64              
65             =item C
66              
67             You can specify the border around the image for cropping. If the cropping area with the border is identical to the original
68             image height and width, then no actual cropping will be done.
69              
70             $img->filter(type => 'autocrop', border => 10);
71              
72             =item C
73              
74             Finally, you can just detect the cropping area by passing a hash reference as a 'detect' parameter. On success, your hash
75             will be set with left, right, top and bottom keys and appropriate values. Please note that if the 'border' parameter is used,
76             the detected area values will be adjusted appropriately.
77              
78             my %points = ();
79             $img->filter(type => 'autocrop', detect => \%points);
80              
81             =back
82              
83             =cut
84              
85             our $VERSION = '1.23';
86              
87             sub autocrop {
88 2     2 0 74010 my (%params) = @_;
89 2         6 my ($img, $fuzz, $border, $color, $detect) = @params{qw};
90 2   33     12 $color||=$params{'colour'}; # Yes, we support British version too.
91             # Check if colour is given, otherwise read the corner pixel.
92 2 50       5 if ($color) {
93 0 0       0 $color = Imager::Color->new($color) unless UNIVERSAL::isa($color, 'Imager::Color');
94             } else {
95 2         8 $color = $img->getpixel(x => 0, y => 0);
96             }
97 2 50       54 die "AUTOCROP_ERROR_COLOR: Color is not set correctly\n" unless defined $color;
98 2         10 my ($r, $g, $b) = $color->rgba;
99 2         9 my @range = ([ $r - $fuzz, $r + $fuzz ], [ $g - $fuzz, $g + $fuzz ], [ $b - $fuzz, $b + $fuzz ]);
100 2         14 my %original = (left => 0, right => $img->getwidth, top => 0, bottom => $img->getheight);
101 2         41 my $crop = _scan($img, \@range, \%original);
102 2         2 my $bordered = 0;
103 2         10 for (keys %original) {
104 8 100       13 if ($original{$_}) {
105 4         6 $crop->{$_}+=$border;
106 4 50       8 if ($crop->{$_} >= $original{$_}) {
107 0         0 $crop->{$_} = $original{$_};
108 0         0 $bordered++;
109             }
110             } else {
111 4         6 $crop->{$_}-=$border;
112 4 50       9 if ($crop->{$_} <= $original{$_}) {
113 0         0 $crop->{$_} = $original{$_};
114 0         0 $bordered++;
115             }
116             }
117             }
118 2 50       4 die "AUTOCROP_ERROR_NOCROP: Nothing to crop\n" if ($bordered == 4);
119 2 50 33     11 if ($detect and ref $detect eq 'HASH') {
120 2         2 %{$detect} = %{$crop};
  2         45  
  2         5  
121             } else {
122 0 0       0 my $rv = $img->crop(%{$crop}) or die $img->errstr;
  0         0  
123 0         0 $img->{IMG} = $rv->{IMG};
124             }
125             }
126              
127             sub _scan {
128 2     2   2 my ($image, $range, $original) = @_;
129 2         4 my ($line, $top, $bottom, $left, $right) = (0, 0, 0, undef, undef);
130 2         3 my ($bpoint, $rpoint) = ($original->{bottom} - 1, $original->{right} - 1);
131 2         5 my ($outline, $pos, $rpos, @colors, @scanned);
132 2 50       5 my $channels = $image->getchannels < 3 ? 0 : 2;
133 2         27 for ($line = $bpoint; $line >= 0; $line--) {
134 38         42 ($outline, $pos, $rpos) = _outline($image, $line, $range, $channels, $rpoint);
135 38 100       86 last if $outline;
136             }
137 2 50       5 die "AUTOCROP_ERROR_BLANK: Image looks blank\n" unless $outline;
138 2         2 $bottom = $line;
139 2 50       5 ($left, $right) = ($pos, $rpos) if $outline;
140 2         4 for ($line = 0; $line < $bottom; $line++) {
141             # NB - don't use left/right boundaries here
142 32         37 ($outline, $pos, $rpos) = _outline($image, $line, $range, $channels, $rpoint);
143 32 100       71 last if $outline;
144             }
145 2         2 $top = $line;
146 2 50       4 if ($outline) {
147 2 50 33     8 $left = $pos if (!defined $left or $pos < $left);
148 2 50 33     10 $right = $rpos if (!defined $right or $rpos > $right);
149             }
150 2 50 33     15 unless (defined $left and defined $right and $left == 0 and $right == $rpoint) {
      33        
      33        
151 2         5 for ($line = $top + 1; $line < $bottom; $line++) {
152 30         38 ($outline, $pos, $rpos) = _outline($image, $line, $range, $channels, $rpoint, $left, $right);
153 30 100       67 if ($outline) {
154 4 50 33     11 $left = $pos if (!defined $left or $pos < $left);
155 4 50 33     18 $right = $rpos if (!defined $right or $rpos > $right);
156 4 50 33     29 last if (defined $left and defined $right and $left == 0 and $right == $rpoint);
      33        
      33        
157             }
158             }
159             }
160 2 50       5 $right++ if defined $right;
161 2 50       4 $bottom++ if defined $bottom;
162 2         14 return { top => $top, bottom => $bottom, left => $left, right => $right };
163             }
164              
165             sub _outline {
166 100     100   78 my ($image, $line, $range, $channels, $rpos, $left, $right) = @_;
167 100         202 my @colors = unpack "C*", $image->getscanline(y => $line);
168 100         4174 my ($outline, $routline, $pos) = (0, 0, 0);
169 100         71 my @color;
170 100         158 while (@colors) {
171 6438         6539 @color = splice @colors, 0, 4;
172 6438         6006 $outline = _out_of_range(\@color, $range, $channels);
173 6438 100 100     13690 last if ($outline or (defined $left and $pos >= $left));
      66        
174 6404         7760 $pos++;
175             }
176 100         133 while (@colors) {
177 1156         1176 @color = splice @colors, -4, 4;
178 1156         1100 $routline = _out_of_range(\@color, $range, $channels);
179 1156 100 100     3263 last if ($routline or (defined $right and $rpos <= $right));
      66        
180 1122         1397 $rpos--;
181             }
182 100   100     294 return ($outline||$routline, $pos, $rpos);
183             }
184              
185             sub _out_of_range {
186 7594     7594   5506 my ($scanned, $range, $channels) = @_;
187 7594         6840 for (0 .. $channels) {
188 22754 100 66     61223 if ($scanned->[$_] < $range->[$_]->[0] or $scanned->[$_] > $range->[$_]->[1]) {
189 14         21 return 1;
190             }
191             }
192 7580         5980 return 0;
193             }
194              
195             sub import {
196 2     2   16 my ($self, $type) = @_;
197 2   100     16 Imager->register_filter(
198             type => $type||'autocrop',
199             callsub => \&autocrop,
200             callseq => [ 'image' ],
201             defaults => {
202             fuzz => 0,
203             border => 0,
204             });
205             }
206              
207             =head1 SEE ALSO
208              
209             L
210              
211             =head1 AUTHOR
212              
213             Alexander Yezhov, C<< >>
214             Domain Knowledge Ltd.
215             L
216              
217             =head1 BUGS
218              
219             Please report any bugs or feature requests to C, or through
220             the web interface at L. I will be notified, and then you'll
221             automatically be notified of progress on your bug as I make changes.
222              
223              
224              
225             =head1 SUPPORT
226              
227             You can find documentation for this module with the perldoc command.
228              
229             perldoc Imager::Filter::Autocrop
230              
231              
232             You can also look for information at:
233              
234             =over 4
235              
236             =item * RT: CPAN's request tracker (report bugs here)
237              
238             L
239              
240             =item * AnnoCPAN: Annotated CPAN documentation
241              
242             L
243              
244             =item * CPAN Ratings
245              
246             L
247              
248             =item * Search CPAN
249              
250             L
251              
252             =back
253              
254              
255             =head1 LICENSE AND COPYRIGHT
256              
257             Copyright 2016 Alexander Yezhov.
258              
259             This program is free software; you can redistribute it and/or modify it
260             under the terms of the Artistic License (2.0). You may obtain a
261             copy of the full license at:
262              
263             L
264              
265             Any use, modification, and distribution of the Standard or Modified
266             Versions is governed by this Artistic License. By using, modifying or
267             distributing the Package, you accept this license. Do not use, modify,
268             or distribute the Package, if you do not accept this license.
269              
270             If your Modified Version has been derived from a Modified Version made
271             by someone other than you, you are nevertheless required to ensure that
272             your Modified Version complies with the requirements of this license.
273              
274             This license does not grant you the right to use any trademark, service
275             mark, tradename, or logo of the Copyright Holder.
276              
277             This license includes the non-exclusive, worldwide, free-of-charge
278             patent license to make, have made, use, offer to sell, sell, import and
279             otherwise transfer the Package with respect to any patent claims
280             licensable by the Copyright Holder that are necessarily infringed by the
281             Package. If you institute patent litigation (including a cross-claim or
282             counterclaim) against any party alleging that the Package constitutes
283             direct or contributory patent infringement, then this Artistic License
284             to you shall terminate on the date that such litigation is filed.
285              
286             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
287             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
288             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
289             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
290             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
291             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
292             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
293             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
294              
295              
296             =cut
297              
298             1;