File Coverage

blib/lib/Imager/Filter/Autocrop.pm
Criterion Covered Total %
statement 83 91 91.2
branch 30 50 60.0
condition 23 38 60.5
subroutine 10 10 100.0
pod 0 1 0.0
total 146 190 76.8


line stmt bran cond sub pod time code
1             package Imager::Filter::Autocrop;
2              
3 2     2   72669 use 5.006;
  2         4  
4 2     2   8 use strict;
  2         2  
  2         30  
5 2     2   6 use warnings;
  2         4  
  2         41  
6 2     2   765 use Imager;
  2         29250  
  2         9  
7 2     2   75 use Imager::Color;
  2         2  
  2         1408  
8              
9             =head1 NAME
10              
11             Imager::Filter::Autocrop - Automatic crop filter for Imager.
12              
13             =head1 VERSION
14              
15             Version 1.22
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.22';
86              
87             sub autocrop {
88 2     2 0 112539 my (%params) = @_;
89 2         8 my ($img, $fuzz, $border, $color, $detect) = @params{qw};
90 2   33     14 $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         10 $color = $img->getpixel(x => 0, y => 0);
96             }
97 2 50       57 die "AUTOCROP_ERROR_COLOR: Color is not set correctly\n" unless defined $color;
98 2         12 my ($r, $g, $b) = $color->rgba;
99 2         11 my @range = ([ $r - $fuzz, $r + $fuzz ], [ $g - $fuzz, $g + $fuzz ], [ $b - $fuzz, $b + $fuzz ]);
100 2         14 my $crop = _scan($img, \@range);
101 2         9 my %original = (left => 0, right => $img->getwidth, top => 0, bottom => $img->getheight);
102 2         42 my $bordered = 0;
103 2         9 for (keys %original) {
104 8 100       15 if ($original{$_}) {
105 4         4 $crop->{$_}+=$border;
106 4 50       9 if ($crop->{$_} >= $original{$_}) {
107 0         0 $crop->{$_} = $original{$_};
108 0         0 $bordered++;
109             }
110             } else {
111 4         4 $crop->{$_}-=$border;
112 4 50       7 if ($crop->{$_} <= $original{$_}) {
113 0         0 $crop->{$_} = $original{$_};
114 0         0 $bordered++;
115             }
116             }
117             }
118 2 50       7 die "AUTOCROP_ERROR_NOCROP: Nothing to crop\n" if ($bordered == 4);
119 2 50 33     18 if ($detect and ref $detect eq 'HASH') {
120 2         4 %{$detect} = %{$crop};
  2         44  
  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   3 my ($image, $range) = @_;
129 2         7 my ($line, $top, $bottom, $left, $right, $lines) = (0, 0, 0, undef, undef, $image->getheight);
130 2         21 my ($outline, $pos, $rpos, @colors, @scanned);
131 2 50       6 my $channels = $image->getchannels < 3 ? 0 : 2;
132 2         32 for ($line = $lines - 1; $line >= 0; $line--) {
133 38         44 ($outline, $pos, $rpos) = _outline($image, $line, $range, $channels);
134 38 100       96 last if $outline;
135             }
136 2 50       5 die "AUTOCROP_ERROR_BLANK: Image looks blank\n" unless $outline;
137 2         3 $bottom = $line;
138 2 50       6 ($left, $right) = ($pos, $rpos) if $outline;
139 2         7 for ($line = 0; $line < $bottom; $line++) {
140             # NB - don't use left/right boundaries here
141 32         40 ($outline, $pos, $rpos) = _outline($image, $line, $range, $channels);
142 32 100       82 last if $outline;
143             }
144 2         5 $top = $line;
145 2 50       5 if ($outline) {
146 2 50 33     11 $left = $pos if (!defined $left or $pos < $left);
147 2 50 33     12 $right = $rpos if (!defined $right or $rpos > $right);
148             }
149 2         7 for ($line = $top + 1; $line < $bottom; $line++) {
150 30         40 ($outline, $pos, $rpos) = _outline($image, $line, $range, $channels, $left, $right);
151 30 100       113 if ($outline) {
152 4 50 33     14 $left = $pos if (!defined $left or $pos < $left);
153 4 50 33     20 $right = $rpos if (!defined $right or $rpos > $right);
154             }
155             }
156 2 50       5 $right++ if defined $right;
157 2 50       4 $bottom++ if defined $bottom;
158 2         16 return { top => $top, bottom => $bottom, left => $left, right => $right };
159             }
160              
161             sub _outline {
162 100     100   101 my ($image, $line, $range, $channels, $left, $right) = @_;
163 100         290 my @colors = unpack "C*", $image->getscanline(y => $line);
164 100         4918 my ($outline, $routline, $pos, $rpos) = (0, 0, 0, @colors/4 - 1);
165 100         79 my @color;
166 100         157 while (@colors) {
167 6438         6969 @color = splice @colors, 0, 4;
168 6438         6798 $outline = _out_of_range(\@color, $range, $channels);
169 6438 100 100     14312 last if ($outline or (defined $left and $pos >= $left));
      66        
170 6404         7976 $pos++;
171             }
172 100         166 while (@colors) {
173 1156         1274 @color = splice @colors, -4, 4;
174 1156         1174 $routline = _out_of_range(\@color, $range, $channels);
175 1156 100 100     3469 last if ($routline or (defined $right and $rpos <= $right));
      66        
176 1122         1483 $rpos--;
177             }
178 100   100     326 return ($outline||$routline, $pos, $rpos);
179             }
180              
181             sub _out_of_range {
182 7594     7594   5467 my ($scanned, $range, $channels) = @_;
183 7594         7331 for (0 .. $channels) {
184 22754 100 66     63038 if ($scanned->[$_] < $range->[$_]->[0] or $scanned->[$_] > $range->[$_]->[1]) {
185 14         22 return 1;
186             }
187             }
188 7580         7141 return 0;
189             }
190              
191             sub import {
192 2     2   16 my ($self, $type) = @_;
193 2   100     17 Imager->register_filter(
194             type => $type||'autocrop',
195             callsub => \&autocrop,
196             callseq => [ 'image' ],
197             defaults => {
198             fuzz => 0,
199             border => 0,
200             });
201             }
202              
203             =head1 SEE ALSO
204              
205             L
206              
207             =head1 AUTHOR
208              
209             Alexander Yezhov, C<< >>
210             Domain Knowledge Ltd.
211             L
212              
213             =head1 BUGS
214              
215             Please report any bugs or feature requests to C, or through
216             the web interface at L. I will be notified, and then you'll
217             automatically be notified of progress on your bug as I make changes.
218              
219              
220              
221             =head1 SUPPORT
222              
223             You can find documentation for this module with the perldoc command.
224              
225             perldoc Imager::Filter::Autocrop
226              
227              
228             You can also look for information at:
229              
230             =over 4
231              
232             =item * RT: CPAN's request tracker (report bugs here)
233              
234             L
235              
236             =item * AnnoCPAN: Annotated CPAN documentation
237              
238             L
239              
240             =item * CPAN Ratings
241              
242             L
243              
244             =item * Search CPAN
245              
246             L
247              
248             =back
249              
250              
251             =head1 LICENSE AND COPYRIGHT
252              
253             Copyright 2016 Alexander Yezhov.
254              
255             This program is free software; you can redistribute it and/or modify it
256             under the terms of the Artistic License (2.0). You may obtain a
257             copy of the full license at:
258              
259             L
260              
261             Any use, modification, and distribution of the Standard or Modified
262             Versions is governed by this Artistic License. By using, modifying or
263             distributing the Package, you accept this license. Do not use, modify,
264             or distribute the Package, if you do not accept this license.
265              
266             If your Modified Version has been derived from a Modified Version made
267             by someone other than you, you are nevertheless required to ensure that
268             your Modified Version complies with the requirements of this license.
269              
270             This license does not grant you the right to use any trademark, service
271             mark, tradename, or logo of the Copyright Holder.
272              
273             This license includes the non-exclusive, worldwide, free-of-charge
274             patent license to make, have made, use, offer to sell, sell, import and
275             otherwise transfer the Package with respect to any patent claims
276             licensable by the Copyright Holder that are necessarily infringed by the
277             Package. If you institute patent litigation (including a cross-claim or
278             counterclaim) against any party alleging that the Package constitutes
279             direct or contributory patent infringement, then this Artistic License
280             to you shall terminate on the date that such litigation is filed.
281              
282             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
283             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
284             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
285             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
286             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
287             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
288             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
289             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
290              
291              
292             =cut
293              
294             1;