Image::BoxFind - scan for rectangles inside of images
use Image::BoxFind; my $bf = Image::BoxFind->new({ image_file = "/tmp/menu.jpeg" }); my $count = $bf->count_rectangles; # NOT YET IMPLEMENTED my ($image_width, $image_height) = $bf->dimensions(); my $bg_color = $bf->background_color(); TODO demo the other routines: "scan"s and so on.
Image::BoxFind is an OOP module to pick out rectangular shapes from inside of an image.
The theory is that scanning for rectangles inside of images is useful for automating tests of graphical user interfaces.
Note: the "rectangles" of interest here are aligned with the x and y axes.
For convenience, a rectangle is represented here as a list of the four corners, where each corner is a point (i.e. a list of the two x and y values).
The corners are listed in the following order:
1 2 --------------- | | | | | | --------------- 4 3
So, a "list of rectangles" is an aref of arefs of arefs.
To verify that one of these figures truly is a rectangle, we first check that the corners are (roughly) lined up horizontally and vertically:
y1 == y2 x2 == x3 y3 == y4 x1 == x4
(and if the corners were found with the "follow*" routines the edges should be simple straight horizontals and verticals).
By a "point", we mean two x and y values that specify a pixel in the image. An array reference.
Where possible, we work with color values in the native Image::Magick format: a comma-seperated string of decimals:
rr,gg,bb,t
(The fourth entry is 'alpha' or 'transparency' or something like that: often it's just zero).
This code maintains a "cursor" location inside of the image: cursor_x and cursor_y, though most routines accept explicit coordinates as arguments.
The "cursor" points to a single pixel, but we often focus on a larger area near the cursor, a rectangular region called the spot. This is a rectangle as defined by two settable parameters "spotsize_x" and "spotsize_y" (though the spot may be truncated when near an image boundary: spot_bounds_truncated).
For the sake of simplicity: the cursor is the upper-left hand corner of the spot.
There are multiple threshold settings for determining whether a difference in color is significance. Different values are needed for different ways of measuring color difference (luminence, color distance, etc.).
Spatial "fuzziness": when two things are almost on top of each other, we'll consider them to be in the same place. This parameter controls the cut-off for significance in spatial difference.
The pixpat (short for "pixel pattern") summarizes the matrix of colors in the spot by averaging them in the direction of travel to get an array of colors. So, the number of colors in the pixpat array is the width of the spot in the transverse direction. The family of "follow_*" methods typically look for changes in the "pixpat" in an attempt at tracking the edge of a rectangle.
Rather than work with a fixed width and height spotsize, some methods here can use a "forward_horizon" and "transverse_horizon" to get an assymetric spotsize who's orientation flips depending on the direction the cursor is being scanned.
Creates a new Image::BoxFind object, taking a hashref as an argument, with named fields identical to the names of the object attributes. Either the attribute image_file or imh is required.
Inputs:
An optional hashref, with named fields identical to the names of the object attributes. The attributes, in order of likely utility:
The name of the file you intend to scan. This is required, unless an Image::Magick image object is passed in instead (see imh attribute)
The "image handle": an Image::Magick image object either created internally using the image_file attribute, or passed in as an argument. (( This is stupid. Require the fucking filename! ))
Cutoff for significance in color luminance differences. Defaults to 3500.
Cutoff for significance in color distance differences. Defaults to 10.
Threshold of ignorable change in the "pixel pattern". Used by has_pixpat_changed_past_threshold
With this flag on and a smaller pixpat_threshold, *sudden* changes should be detected but a gradual drift in the appearence of a border (e.g. a shading effect) might be ignored.
The various 'follow_*' methods that look for changes in the "pixpat" by
can compare either (1) the current one to the original one at the start of the follow operation or (2) (the default) with "ignore_subtle_pixpat_change" set to a true value, then instead it will compare the current pixpat to the immediately previous one. Since a threshold of ignorable change is allowed (see "pixpat_threshold" above), this allows for a shifting standard with a smaller defined threshold.
Default: on.
Minimum difference between min and max color distance in the colors of a pixpat before it becomes at all plausible the pixpat represents an edge. Used by looks_like_edge.
A direction code: 'x_plus', 'x_minus', 'y_plus', or 'y_minus'
Size of the spotsize in the direction of travel of the cursor. (Only used by some methods). Overrides spotsize_x and spotsize_y settings.
Size of the spotsize in the transverse direction. (Only used by some methods). Overrides spotsize_x and spotsize_y settings.
Width of the "spot", the region examined at the cursor. INTERNAL USE ONLY. ("horizon" values are swapped in by "set_direction").
Height of the "spot", the region examined at the cursor. INTERNAL USE ONLY. ("horizon" values are swapped in by "set_direction").
A spatial fuzziness parameter. Default: 4.
The routine center_on_edge looks beyond the boundaries of the spot in an attempt at repositioning the spot with any nearby edge moved toward it's center. The refocus_factor is the factor applied to the spot dimension, to determine how widely the center_on_edge method (and similar methods) will range in looking for the edge. Default: 4 ((TODO still? Make smaller?))
Much like the various other thresholds: used by center_on_edge to skip recentering if there isn't very much color variation going on nearby. Default: 10
Some operations here back off slightly from an edge (to stay away from blurred corners and so on). This is a standard step size for this purpose. Default: 3.
Note: there's no association between step_back and the following two "step_*".
Horizontal step size for routines that raster across the entire image, ala roughly_raster_for_rectangular_regions.
Vertical step size for routines that raster across the entire image, ala roughly_raster_for_rectangular_regions.
When you keep running into the wall despite your best efforts to stop short, you can deploy "beware" throughout your code to gain an extra margin of safety. I.e. this is a total hack.
Defaults to 10, unless I change it.
Empirically determined it needs to be at least 8 (for *some* settings, e.g. spotsize of 5?) or "roughly_raster" doesn't.
X-coordinate of an internally used "cursor", pointing at a pixel of the image.
Y-coordinate of an internally used "cursor", pointing at a pixel of the image.
Image_Height of the image (i.e. the maximum "y" value plus one)
Image_Width of the image (i.e. the maximum "x" value plus one)
The background color of the image, as determined by the background_color method.
Minimum allowed width for a rectangle. Default 12.
Minimum allowed height for a rectangle. Default 12.
The name of the method used internally to determine if color has changed significantly. May be one of:
The name of a method used internally to find a rectangle somewhere in the image. Used by routines such as roughly_raster_for_rectangular_regions. The value may be one of:
The name of a method used internally to find some rectangles (plural) somewhere in the image. A variant of the above rectangle_finder that holds open the possibility of finding more than one rectangle at once.
Used by routines such as smarter_sweep_for_squarish_shapes, but not it's predecessor: smart_sweep_for_squarish_shapes
The value may be one of:
A further generalization of the notion behind the above color_diff. The name of the method to be used to detect a change. EXPERIMENTAL.
Used by detected_change, which is not yet in use (and probably never will be). TODO
This may be used by the above change_detector, a way to keep the data that change_detector will compare the current state to. Note: may be anything, a scalar value or a ref to any data structure.
Color of annotations made to images. Typically 'red' or 'green'.
Method that initializes object attributes and then locks them down to prevent accidental creation of new ones.
Any class that inherits from this one should have an init of it's own that calls this init. Otherwise, it's an internally used routine that is not of much interest to client coders.
Internally used routine. Initialize a new Image::Magick object using the file specified by the "image_file" field.
Applies the Image::Magick "Edge" image filter to the current image, using the value of the edge_detect attribute as a "radius" argument.
Saves the current image under a modified name using the given string as a "suffix" for a new file name, and placing the new file in a sub-directory named "output".
Routines that get information about the entire image.
Determine (and stash) width and height of the image.
Returns array of width and height values.
Determines the most common color in an image, and returns it in the native Image::Magick form, a comma-seperated string of decimals:
rr,gg,bb,t
Utility routines to do geometric calculations
Returns the coordinates of the upper-left and lower-right corners of the spot at the given x, y location, or at the spot at the cursor if the location is not specified.
Note: The spot is prevented from extending past the image boundaries, erroring out if asked to do so.
Returns the coordinates of the upper-left and lower-right corners of the spot at the given x, y location, or at the spot at the cursor if the location is not specified.
Note: The spot is prevented from extending past the image boundaries, it is silently truncated to keep it from doing so.
Example usage:
my ($x1, $y1, $x2, $y2) = $self->spot_bounds_truncated( $x0, $y0 );
Calculate the effective x and y boundaries to be used by a routines that move the "spot" though the entire image.
Example:
my ($x_bound, $y_bound) = $self->main_bounding;
Given four points, tries to determine if they (roughly) define the corners of a rectangle, and if the rectangle is of a significant size.
The object attribute "fuzziness" is used to determine how close two locations need to be to be considered the same: the differences in the x and y components both need to be less than the fuzziness value.
The object attributes "minimum_width" and "minimum_height" determine how wide and tall a rectangle is required to be.
Given four points, tries to determine if they (roughly) define the corners of a rectangle.
This version makes no effort to throw away small fry. Rectangles of any size qualify.
Utility routines to do color calculations
Given a list of the RR GG BB values for a color, calculates the luminence using the weighting factors defined in the object: weights. Note: luminence is an easily calculated value that approximates the subjective impression of brightness.
Example:
$l = luminence( $rr, $gg, $bb );
Compares the two given colors, and returns true if they're significantly different, and false otherwise.
This is a wrapper method that makes it eaisier to swap in different ways of calculating color differences. It defaults to has_changed_distance.
Compares the given two colors, and returns true if they're luminence is significantly different, and false otherwise.
Example usage: if( $self->has_changed_luminence( $color, $ref_color); ) { print "Color has changed significantly\n"; last; }
A "color" is the color string (as used by Image::Magick).
As written, this routine checks if the difference in luminence exceeds the luminance_threshold. It returns the value of the luminance difference, or 0 (to indicate "false"), so this can be thought of as a "luminence difference" routine, which rounds down to 0 if below the luminance_threshold.
Note: as written, this ignores any changes in "alpha".
Compares the given two colors, and returns true if they're color distance is significantly different, and false otherwise.
Uses the color_distance_threshold settting to determine significance.
Note: as written, this ignores any changes in "alpha".
Compares the given two colors, and returns true if they're color distance is significantly (?) different, and false otherwise.
This is an older, poorly implemented version of a method intended to use color distance. (Note: in perl '**' is expotentiation, not '^'). It remains slightly possible that it does something useful (for the wrong reasons).
Still uses the color_distance_threshold settting to determine significance.
Note: as written, this ignores any changes in "alpha".
A probe examines conditions in a particular location.
Returns the most common color of all the pixels in the spot. Note: this need not be the "majority", just the "plurality".
Determine the average color of the spot at the given x,y location, or at the current spot by default.
Given a pixpat, this looks through it to find the location of an "edge" in the image. Determines the offset of the edge inside the pixpat by finding location of the maximum delta colordistance value, and returns both offset and "max_delta_colordist".
Example usage:
my ($offset, $max_delta_colordist) = $bf->analyze_pixpat_for_edge( $pixpat );
Note: this uses an empirically determined technique for picking the edge out of the field of colors: it finds the place where the change of the color-distance is maximized (loosely speaking, this is a maximum of the rate of change of color).
This appears to be slightly better than the more conventional approaches of looking for a steep color gradient (the maximum color-distance), or of looking for inflection points (zero-crossings in the rate of change of the color distance, taking it as an approximation of the second-derivative).
Adjusts the location of the cursor to center the spot on any nearby edge.
Optionally takes a pair of x, y values, otherwise it uses the object cursor. Returns the new point, after setting the object cursor to the adjusted location.
Looks beyond the spot by temporarily expanding the size of the spot in the transverse direction, by multiplying the spotsize by the refocus_factor object attribute.
Also relies on the object-attribute: delta_colordist_threshold (a lower threshold would mean greater sensitivity to changes).
Uses analyze_pixpat_for_edge to do the work of picking out an edge from inside of a pixpat.
A scan sweeps in a particular direction looking for some feature (e.g. some sort of change, e.g. a change in the average color of the spot, as in the "*_spotcolor" methods).
Scans vertically, starting at the given x, y location, defaulting to the the cursor location -- until a significant change in the average color is detected.
Sets the cursor at new location.
Detects a change in the average color larger than the "threshold" setting.
Scans horizontally -- starting at the cursor location -- until a significant change in the average color is detected.
Sets the cursor at new location.
Detects a change in the average color larger than the "threshold" setting.
Scans vertically -- starting at the cursor location -- until a significant change in the average color is detected.
Sets the cursor at new location.
Detects a change in the average color larger than the "threshold" setting.
Scans horizontally backwards (toward the left edge), starting at the cursor location, until a significant change in the average color is detected.
Sets the cursor at new location.
Detects a change in the average color larger than the "threshold" setting.
Takes a location as a pair of x/y values as arguments. If omitted uses the cursor_x and cursor_y object data.
Sweeps downward looking for something that looks like an edge, as defined by looks_like_edge, with determine_pixpat_forward.
Returns the y value if it finds an edge, or undef it it doesn't see one before the image boundary.
Scan downward and attempt to find a rectangle. Looks for changes of the average spot color.
Begins searching at the given x, y location (but defaults to the cursor location).
If found, returns a data structure containing the four points at (or near) the corners of the rectangle.
Otherwise, returns undef.
Example:
if ( my $corners = $self->look_down_boxfind( $x, $y) ) { push @raw_rectangles, $corners; }
Note: the edge of the image is never taken as the edge of a "rectangle", (we're interested in GUI applications, where the fashion is to have boxes and buttons offset from the window borders).
Scan the image for rectangles.
Returns a list of rectangles (see concepts section above).
Scan the image for rectangles.
Returns a list of rectangles (see concepts section above).
Like roughly_raster_for_rectangular_regions, but it works from the lower-right hand corner, stepping backwards through the image; which is presumably this is better for using the "rectangle_finder": boxfind_upward_via_pixpat.
Scan the image for rectangles.
Returns an array reference, a list of rectangles (see concepts section above).
Like roughly_raster_for_rectangular_regions, but smarter.
Can not find nested rectangles, however.
Given a rectangle, looks at the region below it to find any rectangles down there (it calls itself recursively to find rectangles below rectangles).
Returns list of rectangles found.
Note: first time, can be given a dummy "degenerate" rectangle zero pixels high, to trick it into scanning the whole image from the top.
Example usage:
my $new_rects = $self->peer_under_rect_for_rects( $given_rect );
Uniquifies a list of sorted rectangles: by scanning through the list, comparing all pairs of them.
We use the is_dupe_box method to determine if they're roughly identical, and if so we keep only one of them.
Note: Instead of just choosing one member of a duplicate pair, it might be better to take a geometric average of the corners of the near duplicates. TODO
If the list is empty, is should also return an empty list without error, but if it looks like something else has been passed in, this should 'confess' (i.e. die with a stack trace).
Given a list of rectangles, sorts them in the obvious numeric order.
If the list is empty, is should also return an empty list without error, but if it looks like something else has been passed in, this should 'confess' (i.e. die with a stack trace).
Returns true if the two given rectangles are (roughly) duplicates of each other (i.e. their corners coincide with a delta-x and delta-y smaller than the "fuzziness" parameter, an object attribute).
Note: instead of looking at delta-x and delta-y, it might be more rigorous to calculate the distance between the two points (using the geometric_distance method)... but I suspect thinking in terms of x any y errors maps more closely to the way this data is determined, so I'm sticking with this simpler calculation.
Compares lists of rectangles, returns true if they're effectively identical (within the limits of "fuzziness", as in is_dupe_box.
Compares lists of rectangles, returns true if they're effectively identical (within the limits of "fuzziness", as in is_dupe_box. If they're not identical, it croaks, returning a message explaining at what point they diverge.
Compares two lists of rectangles, and reports on the place where they diverge. Returns an empty string if they match each other, within the limits of "fuzziness", as in is_dupe_box.
A rough check to see if we have a ref to an array of arrays of arrays.
Note: can yield false positives.
A rough check to see if we have a ref to an array of arrays, as opposed to an array of arrays of arrays.
Note: can yield false positives.
Given two points, calculates the distance between them.
As written, restricted to 2D (x,y) points.
Given a list of rectangles, returns the number of rectangles.
TODO: not yet in use. But the goal is a count_rectangles command, remember? This is a (trivial) piece of the problem.
A "follow" method moves forward and looks to one or both sides, trying to maintain some condition as it proceeds (e.g. a pixel pattern indicating an edge).
Given a direction code: 'x_plus', 'x_minus', 'y_plus', or 'y_minus' Moves in that direction as far as it can go while maintaining the same pattern of pixels throughout a certain width (the spotsize in the direction transverse, for now).
Returns the point where it stops (x, y).
Sets the cursor to that location.
The 'follow_pixpat_*' methods below move in the direction indicated by their suffixes:
'x_plus', 'x_minus', 'y_plus', or 'y_minus'
Each of them makes use of the "spot", averaging the color of the rows of pixels in the direction of travel, but preserving differences in the transverse direction. They look for a change in this array of average colors (using the has_pixpat_changed method), and then stop, setting the cursor and returning the x and y values of the point (as an aref).
If no change is detected the extreme limit at the edge of the image is returned.
(( TODO review that design decision -- the main idea is that it saves me from propagating an undef return and handling it in a special way, or alternately from trapping an error. ))
Starting at the given (x, y) location (defaulting to the cursor) moves in the "x_plus" direction until a change in the pixpat is detected.
Example usage:
my $end_point = $bf->follow_pixpat_x_plus( $x0, $y0 );
Given two "pixpats", the current and the previous one, this returns true if there's a difference between the two.
If the second argument is omitted, this will instead use the "previous_state" value from the object data.
Ex. usage:
if ($self->has_pixpat_changed( $current, $previous ) ){ my $self->set_cursor_y( $y ); return ($y, $x); }
Note that this is a comparison sensitive to the slightest change (no "fuzziness" or "threshold" concept applies here).
Calculate the difference in "color distance" from two Image::Magick color strings.
Ex. usage:
my $color_distance = $self->color_distance( $color1, $color2 );
Note, any difference in the fourth parameter (alpha) is ignored.
If the second color is skipped, the distance to the origin is returned.
See "pixpat" in concepts.
Averages colors in the spot, in stripes aligned across the direction of travel: this determines a "pixel pattern" that can be used to detect an edge. This is used to look ahead for an edge, in the "forward" direction.
Ex. usage:
$self->set_direction('x_plus'); my @colors = $self->determine_pixpat_forward( $x, $y );
(Remember, "set_direction" has a side-effect: it sets the spotsize in the forward direction to the "forward_horizon", and the spotsize in the transverse direction to the "transverse_horizon".)
This uses a "spot" (see concepts) that is silently truncated to fit the image boundaries.
See "pixpat" in concepts.
Averages colors in the spot, in stripes aligned with the direction of travel, to determine a "pixel pattern" that can be used to follow an edge. (This looks sideways for an edge, hence the name "transverse".)
Ex. usage:
$self->set_direction('x_plus'); my @colors = $self->determine_pixpat_transverse( $x, $y );
(Remember, "set_direction" has a side-effect: it sets the spotsize in the forward direction to the "forward_horizon", and the spotsize in the transverse direction to the "transverse_horizon".)
This uses a "spot" (see concepts) that is silently truncated to fit the image boundaries.
Given an array of color strings, returns the color string of the average.
Ex. my $ave = $self->average_array_of_colors( \@colors );
Given a pixpat, returns true if there's enough variation in color (as measured by color distance) over the pattern so that it's plausible it represents an "edge" of a GUI element.
Uses the object setting edge_contrast_cutoff to determine if there's enough difference bettween min and max color distance.
Looks at the change in color distance between adjacent colors in the "pixpat" array.
A very general routine to compare a saved state to the current state, using a method of detecting change defined in the object data.
Ex. usage (TODO)
$self->set_change_detector( "null_change_detector" ); for my $x (0 .. $xmin) { for my $y (0 .. $ymin) { ($mark_x, $mark_y) = $self->follow_y_plus( $x, $y); $self->set_cursor_x( $x ); $self->set_cursor_y( $y ); } } $self->set_previous_state( $current_state );
Scan downward and attempt to find a rectangle. Begins from the current cursor location, or from a given location if the x/y values have been supplied.
If found, returns a data structure containing the four points at (or near) the corners of the rectangle.
Otherwise, returns undef.
Example:
$self->set_forward_horizon( 3 ); $self->set_transverse_horizon( 4); if ( my $corners = $self->boxfind_downward_via_pixpat( $x, $y) ) { push @raw_rectangles, $corners; }
Note: this routine tries to use the "follow_*" family of methods, which scan using "pixel_patterns". As written, these routines might actually treat the edge of the image as the edge of a rectangle.
This is much like boxfind_downward_via_pixpat, except that instead of scanning down for a change in spotcolor, it scans downward for something that looks like it might be an edge pixpat (uses scan_for_edgey_pixpat).
Begins from the current cursor location, or from a given location if the x/y values have been supplied.
If found, returns a data structure containing the four points at (or near) the corners of the rectangle.
Otherwise, returns undef.
Example:
$self->set_forward_horizon( 3 ); $self->set_transverse_horizon( 4); if ( my $corners = $self->boxfind_downward_purely_via_pixpat( $x, $y) ) { push @raw_rectangles, $corners; }
Note: this routine tries to use the "follow_*" family of methods, which scan using "pixel patterns". As written, these routines might actually treat the edge of the image as the edge of a rectangle.
Scan upward and attempt to find a rectangle. Begins from the current cursor location, or from a given location if the x/y values have been supplied.
If found, returns a data structure containing the four points at (or near) the corners of the rectangle.
Otherwise, returns undef.
Example:
$self->set_forward_horizon( 3 ); $self->set_transverse_horizon( 4); if ( my $corners = $self->boxfind_upward_via_pixpat( $x, $y) ) { push @raw_rectangles, $corners; }
Note: this routine tries to use the "follow_*" family of methods, which scan using "pixel_patterns" instead of simple average spot color differences.
As written, these routines might actually treat the edge of the image as the edge of a rectangle.
This is much like boxfind_downward_via_pixpat, except that instead of scanning down for a change in spotcolor, it scans downward for something that looks like it might be an edge pixpat (uses scan_for_edgey_pixpat), and further, it uses calls to "center_on_edge" to try to improve it's precision in edge following.
Scan downward and attempt to find a rectangle. Begins from the current cursor location, or from a given location if the x/y values have been supplied.
If found, returns a data structure containing the four points at (or near) the corners of the rectangle.
Otherwise, returns undef.
Example:
$self->set_forward_horizon( 3 ); $self->set_transverse_horizon( 4); if ( my $corners = $self->boxfind_downward_recenter( $x, $y) ) { push @raw_rectangles, $corners; }
Note: this routine tries to use the "follow_*" family of methods, which scan using "pixel_patterns" instead of simple average spot color differences.
As written, these routines might actually treat the edge of the image as the edge of a rectangle.
Looks at both the current location, and also looks some indefinite distance below, to attempt to find a rectangle.
This is like boxfind_downward_recenter above, EXCEPT: since this may return more than one rect, it must use a "list of rects" data structure for it's return.
Common code factored out from the above boxfind_* methods.
Given an x and y value, presumes that that location is right on the top edge of a rectangle. It traces the sides of the rectangle (internally using the "follow_pixpat_*" routines and "center_on_edge") and returns a rect data structure or undef if one is not found.
Takes a list of rectangles and pretty-prints them to STDOUT.
Takes a list of rectangles and pretty-prints them to a string.
Given a single rectangle, pretty-prints it to STDOUT.
Given a single rectangle, returns a pretty-printed string.
Dumps a report of the "adjustable parameters" in the object data.
Applies the Image::Magick "Edge" image filter to the current image, using the value of the edge_detect attribute as a "radius" argument.
Applies the Image::Magick "Edge" image filter to the current image, using the value of the edge_detect attribute as a "radius" argument.
Methods that might not be that useful, but are reasonably well tested.
Scans vertically, until a change in color is detected.
Sets the cursor at new location.
Detects a single-pixel change of any quantity.
Scans horizontally, until a change in color is detected.
Sets the cursor at new location.
Detects a single-pixel change of any quantity.
Setter for object attribute "direction".
Side-effect: sets the spotsize in the forward direction to the "forward_horizon", and the spotsize in the transverse direction to the "transverse_horizon".
The naming convention used:
setters begin with "set_", I<but> getters have *no* prefix.
This is on the principle that the most commonly used case deserves the simplest syntax
(Note: in general mutators are now deprecated, see Conway "Best Practices").
These accessors exist for all of the object attributes (documented above) irrespective of whether they're expected to be externally useful. Note: no leading underscores have been used to indicate "internal" use.
A wrapper around the getter for direction, that returns only the first portion of it, the axis: "x" or "y".
A wrapper around the getter for direction, that returns only the second portion of it, the sign "+" or "-".
Note: the internal codes 'plus' and 'minus' are converted to the mathematical symbols.
Representing a rectangle as four points may seem excessive: mathematically, this is twice as much information as is necessary (e.g. postgresql's "box" geometric datatype uses only two diagonally opposite points, alternately a rectangle could be represented by a single point plus width and height).
Using all four corners is a convenience suited to the way this code tries to identify rectangles: crawling along the edges from corner-to-corner, only checking later to make sure they line up with each other. Also, the redundant specification allows a certain amount of spatial fuzziness: the alignment need not be perfect in order for us to call it "rectangular". To reduce our rectangles to postgresql's box-type, we would need to throw away some information (possibly by taking averages of each of the x and y coordinates).
A rectangle example:
$rect = [ [ 20, 66 ], [ 20, 99 ], [ 130, 99 ], [ 130, 66 ] ];
(( TODO add an example of a list of rectangles? ))
http://obsidianrook.com/Perl/image-boxfind
Joseph Brenner, <doom@kzsu.stanford.edu>, 28 Sep 2007
Copyright (C) 2007 by Joseph Brenner
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.2 or, at your option, any later version of Perl 5 you may have available.
None reported... yet.