File Coverage

blib/lib/Image/Match.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             # $Id: Match.pm,v 1.4 2008/09/02 10:34:44 dk Exp $
2             package Image::Match;
3              
4 3     3   64221 use strict;
  3         11  
  3         120  
5 3     3   15 use warnings;
  3         6  
  3         84  
6 3     3   5533 use Prima::noX11;
  0            
  0            
7             use Prima;
8             require Exporter;
9              
10             our $VERSION = '1.02';
11             our $Y_GROWS_UPWARDS = 0;
12             our $DEBUG = 0;
13              
14             sub match
15             {
16             my ( $image, $subimage, %options) = @_;
17              
18             local $Y_GROWS_UPWARDS = $Y_GROWS_UPWARDS;
19             mode($options{mode}) if exists $options{mode};
20              
21             $options{overlap} ||= 'some';
22             die "Bad overlap mode '$options{overlap}': must be one of: none, some, all\n"
23             unless $options{overlap} =~ /^(some|all|none)$/;
24             my $overlap_all = $options{overlap} eq 'all';
25              
26             my $G = $image-> data;
27             my $W = $image-> width;
28             my $H = $image-> height;
29             my $w = $subimage-> width;
30             my $h = $subimage-> height;
31             my $bpp = ($image-> type & im::BPP) / 8;
32             print STDERR
33             "match image ($w x $h x ",
34             $subimage-> type & im::BPP, ") in ",
35             "($W x $H x ",
36             $image-> type & im::BPP, ") ",
37             "length ", length($G), "\n"
38             if $DEBUG;
39              
40             # Requirements: need same bpp and same colormap.
41             # Also, 1 and 4 bit images aren't supported, autoconvert
42             if ( $bpp <= 1) {
43             my $cm1 = join(',', $image-> colormap);
44             my $cm2 = join(',', $subimage-> colormap);
45             if ( $cm1 eq $cm2) {
46             # good, palettes are equal. now, are types equal?
47             if ( $subimage-> type != $image-> type) {
48             $subimage-> type( $image-> type);
49             printf STDERR ("subimage converted to type=%x\n", $image->type) if $DEBUG;
50             }
51             } else {
52             # force convert to 24bits
53             $image-> type(24);
54             $subimage-> type(24);
55             print STDERR "both images converted to 24 bpp\n" if $DEBUG;
56             }
57             }
58              
59             my $I = $subimage-> data;
60             my $gw = int(( $W * ( $image-> type & im::BPP) + 31) / 32) * 4;
61             my $iw = int(( $w * ( $subimage-> type & im::BPP) + 31) / 32) * 4;
62             my $ibw = $w * $bpp;
63             my $dw = $gw - $ibw;
64             print "global=$gw, local=$iw, max=$ibw diff=$dw\n" if $DEBUG;
65            
66             my $rx = join( ".{$dw}", map { quotemeta substr( $I, $_ * $iw, $ibw) }
67             (0 .. $subimage-> height - 1));
68             my ( $x, $y);
69             my @ret;
70              
71             pos($G) = 0;
72             study $G;
73             while ( 1) {
74             if ( $DEBUG) {
75             my $ap = pos($G);
76             my $ax = $ap % $gw / $bpp;
77             my $ay = int(($ap - ($ax + $w) * $bpp) / $gw);
78             $ay = $H - $ay - 1 if $Y_GROWS_UPWARDS;
79             print STDERR
80             "begin match at $ap = $ax $ay, ",
81             length($G) - $ap, " bytes left\n";
82             }
83              
84             # match
85             unless ( $G =~ m/\G.*?$rx/gcs) {
86             print STDERR "-> negative\n" if $DEBUG;
87             return unless $options{multiple};
88             last;
89             }
90             my $p = pos($G);
91             $x = ($p - $w * $bpp) % $gw / $bpp;
92             $y = int(($p - ( $x + $w) * $bpp) / $gw) + 1;
93             $y = $y - $h;
94             $y = $H - $h - $y unless $Y_GROWS_UPWARDS;
95             print STDERR "-> positive at $p = $x $y\n" if $DEBUG;
96              
97             if ( $x + $w > $W) {
98             print STDERR "-> scanline wrap, skipping\n" if $DEBUG;
99             next;
100             }
101             pos($G) -= ($h - 1) * $gw;
102             pos($G) -= $ibw - $bpp if $overlap_all;
103              
104             # store results
105             push @ret, $x, $y;
106             return $x, $y unless $options{multiple};
107             }
108              
109             # filter output
110             if ( $options{overlap} eq 'none') {
111             my @r;
112             my @ranges; # for each scanline store list of occupied pixels as x1-x2 ranges
113             print STDERR "removing overlapped rectangles\n" if $DEBUG;
114             RECT: for ( my $i = 0; $i < @ret; $i+=2) {
115             my ( $x1, $y1) = @ret[$i, $i+1];
116             my ( $x2, $y2) = ( $x1 + $w, $y1 + $h);
117             print STDERR "checking ($x1,$y1)-($x2,$y2)\n" if $DEBUG;
118             for ( my $y = $y1; $y < $y2; $y++) {
119             $ranges[$y] ||= [];
120             for my $xranges ( @{ $ranges[$y] }) {
121             next if
122             $x1 >= $xranges->[1] or
123             $x2 < $xranges->[0];
124             print STDERR "-> overlaps, skipping\n" if $DEBUG;
125             next RECT;
126             }
127              
128             # does not overlap, register
129             push @{ $ranges[$y] }, [ $x1, $x2 ];
130             }
131             push @r, $x1, $y1;
132             }
133             @ret = @r;
134             }
135              
136             print STDERR "return: [@ret]\n" if $DEBUG;
137             return @ret;
138             }
139              
140             sub screenshot
141             {
142             shift if defined($_[0]) and ( ref($_[0]) or ($_[0] =~ /Image/) );
143              
144             unless ( $::application) {
145             my $error = Prima::XOpenDisplay();
146             die $error if defined $error;
147             require Prima::Application;
148             import Prima::Application;
149             }
150              
151             my ( $x, $y, $w, $h) = @_;
152             my @as = $::application-> size;
153              
154             $x ||= 0;
155             $y ||= 0;
156             $w = $as[0] unless defined $w;
157             $h = $as[1] unless defined $h;
158              
159             $y = $as[1] - $h - $y unless $Y_GROWS_UPWARDS;
160              
161             return $::application-> get_image( $x, $y, $w, $h);
162             }
163              
164             sub mode
165             {
166             shift if defined($_[0]) and ( ref($_[0]) or ($_[0] =~ /Image/) );
167             return $Y_GROWS_UPWARDS ? 'geom' : 'screen' unless @_;
168             die "bad Image::Match::mode: must be 'geom' or 'screen'\n"
169             unless $_[0] =~ /^(geom|screen)$/;
170             $Y_GROWS_UPWARDS = $_[0] eq 'geom';
171             }
172              
173             *Prima::Image::match = \&match;
174             *Prima::Image::screenshot = \&screenshot;
175              
176             1;
177              
178             =pod
179              
180             =head1 NAME
181              
182             Image::Match - locate an image inside another
183              
184             =head1 DESCRIPTION
185              
186             The module searches for occurencies of an image inside of a larger image.
187              
188             The interesting stuff here is the image finding itself - it is done by a regex!
189             For all practical reasons, regexes operate on strings of bytes, and images can
190             be easily treated as such. For example, one needs to locate an image 2x2 in a
191             larger 7x7 image. The constructed regex should contain the first scanline of
192             the smaller image, 2 bytes, verbatim, then match 7 - 2 = 5 of any byte found,
193             and finally the second scanline, 2 bytes again. Of course there are some
194             quirks, but these explained in the API section.
195              
196             The original idea was implemented in L and L, but
197             this module extracts the pure matching logic, unburdened from wrappers that
198             were needed back then for matters at hand.
199              
200             =head1 SYNOPSIS
201              
202             use strict;
203             use Image::Match;
204              
205             # make screenshot
206             my $big = Image::Match-> screenshot;
207             # extract 70x70 image
208             my $small = $big-> extract( 230, $big-> height - 70 - 230, 70, 70);
209             # save
210             $small-> save('1.png');
211             # load
212             $small = Prima::Image-> load('1.png') or die "Can't load: $@";
213             # find again
214             my ( $x, $y) = $big-> match( $small);
215             print defined($x) ? "found at $x:$y\n" : "not found\n";
216              
217             =head1 API
218              
219             =over
220              
221             =item match $IMAGE, $SUBIMAGE, %OPTIONS
222              
223             Locates a $SUBIMAGE in $IMAGE, returns one or many matches, depending on
224             C<$OPTIONS{multiple}>. If single match is requested, stops on the first match,
225             and returns a single pair of (X,Y) coordinates. If C<$OPTIONS{multiple}> is 1,
226             returns array of (X,Y) pairs. In both modes, returns empty list if nothing was
227             found.
228              
229             C<$OPTIONS{mode}> overrides global C.
230              
231             C<$OPTIONS{overlap}> can be set to one of three values: I, I,
232             I, to determine how the overlapping matches are reported when
233             C<$OPTIONS{multiple}> is set. I will never report overlapping rectanges,
234             and I report all possible occurencies of C<$SUBIMAGE> in C<$IMAGE>.
235             I is similar to I, but is a bit faster, and will not report
236             overlapping rectangles that begin on the same scanline. I is also the
237             default overlapping mode.
238              
239             =item screenshot [ $X = 0, $Y = 0, $W = screen width, $H = screen height ]
240              
241             Returns a new C object with a screen shot, taken at
242             given coordinates.
243              
244             =item mode $MODE = 'screen'
245              
246             The module uses L for imaging storage and manipulations. Note that Prima
247             uses coordinate system where Y axis grows upwards. This module however can use
248             both geometrical (Y grows upwards, C) and screen-based (Y grows
249             downwards, C) modes. The latter mode is the default.
250              
251             =back
252              
253             =head1 NOTES
254              
255             On unix, C by default will start X11. The module changes that behavior,
256             so X11 connection is not needed. If your code though needs X11 connection,
257             change that by adding
258              
259             use Prima;
260              
261             before invoking
262              
263             use Image::Match
264              
265             See L for more information.
266              
267             If you need to use other image backends than Prima, that can be done too.
268             There is L that brings together Prima and ImageMagick,
269             and there is L, that does the same for PDL. GD, Imglib2, and
270             Imager, those we can't deal much with, except for saving to and loading from png
271             files.
272              
273             =head1 SEE ALSO
274              
275             L, L, L
276              
277             =head1 LICENSE AND COPYRIGHT
278              
279             This library is free software; you can redistribute it and/or modify it
280             under the same terms as Perl itself.
281              
282             =head1 AUTHOR
283              
284             Dmitry Karasik, Edmitry@karasik.eu.orgE.
285              
286             =cut