Greetings fellow scripters. Warning, TLDR
The past few weeks I've been working on a library of object finding functions. I am presenting the current work here for community review and feedback. All input welcome!
This library is not 100% debugged and completed but it it's getting there. I thought it would be smart to get some external opinions and review. The bigger it gets the harder it is to change major design issues. I haven't tested the choose functions yet (and didn't write the chooseWithin function).
I started working on this for a few reasons.
- Most of the functions that accept more than one color do not associate the CTS setting with the color. They take multiple colors but only one CTS spec. This reduces their value since you pretty much always have different hue/saturation per color.
- I wanted to work with 'inclusive' searching (where all of the given colors must be found in a certain distance from each other)
- I had some confusion on how and when to use different splitting techniques like tpa2atpa and split/cluster and wanted to learn more
- The available functions required me to write a lot of the same code repeatedly. I'm a lazy programmer. I would rather see the complexity moved to the data structures and have the code be simplified.
What I present here is a data structure that fully defines all of the colors for an object (inlcuding their CTS settings). It allows you to attach a filter specification per color. You can also assign a filter specification to the object as a whole so you are able to choose different settings for the colors and the objects. Or leave the color information raw (no filter) and only filter the final result (object).
Objects can contain a specification for inclusive logic. This is where all of the defined colors must be found within certain distances from each other. I was inispired on this topic by @Runaway @Runaway; in this post. There are a number of other related posts on the topic that I found (a b c). [Why don't mention tags work for me? Argh.]
I'm going to present some examples first, then describe the code a little afterward. Because examples are fun and the rest is boring!
Examples:
Here is the test image used for the examples. It has 8 colors (dark&light blue, dark&light green, dark&light purple, a rusty red, and orange).
Example 1
The first example just finds the blue objects in the image.
Simba Code:
program example1;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter: TObjFilterSpec;
atpa: T2DPointArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
myObj.init();
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
atpa := myObj.findColorsATPA(searchBox);
end.
Output for example 1: It returns an ATPA but it only has one row since we only used one color.
Example 2:
Same thing but with all the colors in the object. Colors are using CTS 0, 1, and 2.
Simba Code:
program example2;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter: TObjFilterSpec;
atpa: T2DPointArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
myObj.init();
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
myObj.addColor(objColor(3642899, {tol} 38, colorsetting(1), colFilter)); // greens cts1
myObj.addColor(objColor(9181579, {tol} 15, {hue} 0.01, {sat} 2.09, colFilter)); // purples cts2
myObj.addColor(objColor(1376392, colFilter)); // rust cts0 pure
myObj.addColor(objColor(2588671, colFilter)); // orange cts0 pure
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
atpa := myObj.findColorsATPA(searchBox);
end.
Output for example 2: Note in this output each color got its own TPA in the ATPA returned
Example 3:
Find all the colors, break the output into TBoxes for us. We have to add an object level filter to do boxing.
Simba Code:
program example3;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter, objFilter: TObjFilterSpec;
tba: TBoxArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
objFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
myObj.init(objFilter);
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
myObj.addColor(objColor(3642899, {tol} 38, colorsetting(1), colFilter)); // greens cts1
myObj.addColor(objColor(9181579, {tol} 15, {hue} 0.01, {sat} 2.09, colFilter)); // purples cts2
myObj.addColor(objColor(1376392, colFilter)); // rust cts0 pure
myObj.addColor(objColor(2588671, colFilter)); // orange cts0 pure
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
tba := myObj.findBoxes(searchBox);
end.
Output for example 3
Example 4:
Same as example 3 but returns centerpoint instead of boxes.
Simba Code:
program example4;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter, objFilter: TObjFilterSpec;
tpa: TPointArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
objFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
myObj.init(objFilter);
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
myObj.addColor(objColor(3642899, {tol} 38, colorsetting(1), colFilter)); // greens cts1
myObj.addColor(objColor(9181579, {tol} 15, {hue} 0.01, {sat} 2.09, colFilter)); // purples cts2
myObj.addColor(objColor(1376392, colFilter)); // rust cts0 pure
myObj.addColor(objColor(2588671, colFilter)); // orange cts0 pure
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
tpa := myObj.findCenters(searchBox);
end.
Output for example 4
Example 5:
Now we get fancy. Add an inclusive spec and find the boxes where ALL of the colors are found within distance 35 of each other:
Simba Code:
program example5;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter, objFilter: TObjFilterSpec;
tba: TBoxArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
objFilter.init(TObjFilterStyle.CLUSTER, {dist} 35, {min} 80, {max} maxInt);
myObj.init(objFilter);
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
myObj.addColor(objColor(3642899, {tol} 38, colorsetting(1), colFilter)); // greens cts1
myObj.addColor(objColor(9181579, {tol} 15, {hue} 0.01, {sat} 2.09, colFilter)); // purples cts2
myObj.addColor(objColor(1376392, colFilter)); // rust cts0 pure
myObj.addColor(objColor(2588671, colFilter)); // orange cts0 pure
myObj.setInclusiveSpec(objInclusiveSpec(TObjInclusiveAlg.EXACT,
{minDist} 2, {maxDist} 35));
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
tba := myObj.findBoxes(searchBox);
end.
Output for example 5
Example 6:
Define two objects. One is the blue and purple colors. The second object is has the rust and orange colors. It also has an inclusive spec demanding that both rust and orange are found near each other. Then we find the rust/orange object
within the blue/purple object.
Simba Code:
program example6
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
blueObj, rustObj: TObjFind;
colFilter, objFilter: TObjFilterSpec;
searchBox : TBox;
clW, clH: integer;
tba: TBoxArray;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {minCount} 10, {maxCount} maxInt);
objFilter.init(TObjFilterStyle.CLUSTER, {dist} 10, {min} 10, {max} maxInt);
blueObj.init(objFilter);
rustObj.init(objFilter);
blueObj.addColor(objColor(13387839, colFilter)); // blue
blueObj.addColor(objColor(10766755, colFilter)); // purple
rustObj.addColor(objColor(1376392, colFilter)); // red/brown
rustObj.addColor(objColor(2588671, colFilter)); // orange
rustObj.setInclusiveSpec(objInclusiveSpec(TObjInclusiveAlg.EXACT,
{minDist} 2, {maxDist} 35));
getclientdimensions(clW, clH);
searchBox := intToBox(0, 0, clW-1, clH-1);
tba := rustObj.findWithinBoxes(blueObj, searchBox);
end.
Output for example 6
Code Discussion
Filter Specifiers
The filter object data is stored in a record. There are functions provided to initialize or create filter objects. There is also a default (null) specification defined.
Simba Code:
type TObjFilterSpec = record {INTERNAL}
const _NULL_FILTER_SPEC {A default/blank TObjFilterSpec}
Filters define how a color or object gets split into subclusters. Each filter has a 'style' indicating which of the splitting functions to use during processing (TPointArray.toATAPA(), .split(), .cluster()). There is an enum defining those.
Simba Code:
type TObjFilterStyle = (TOATPA, CLUSTER, SPLIT, NONE);
They can hold width and height variables or a dist variable. This allows the internal functions to use either of those variations. Filters also have a low/high pixel count indicating how big these clusters should be in order to be retained.
Here are the TObjFilterSpec type functions to initialize filters.
Simba Code:
procedure TObjFilterSpec.init();
procedure TObjFilterSpec.init(style: TObjFilterStyle; w: integer;
h: integer; countLow: integer; countHigh: integer); overload;
procedure TObjFilterSpec.init(style: TObjFilterStyle; distance: integer;
countLow: integer; countHigh: integer); overload;
And some functions that create TObjFilterSpec objects on the fly:
Simba Code:
function objFilterSpec(): TObjFilterSpec;
function objFilterSpec(style: TObjFilterStyle; w: integer;
h: integer; countLow: integer; countHigh: integer): TObjFilterSpec; overload;
function objFilterSpec(style: TObjFilterStyle; distance: integer;
countLow: integer; countHigh: integer): TObjFilterSpec; overload;
Inclusive Specifiers
An inclusive spec can be attached to the main object. It is also stored in a record with functions to initialize/create it. There is also a default/null instance defined for cases where no inclusive processing will be required.
Simba Code:
type TObjInclusiveSpec = record {INTERNAL}
const _NULL_INCLUSIVE_SPEC {A default/blank TObjInclusiveSpec}
The inclusive spec defines the algorithm to be used and a minimum/maximum distance between the colors for them to be considered matches. The algorithm is in an enum and the rest is loaded in the TObjInclusiveSpec.init function.
Simba Code:
type TObjInclusiveAlg = (EXACT, RUNAWAY);
procedure TObjInclusiveSpec.init();
procedure TObjInclusiveSpec.init(alg: TObjInclusiveAlg; min: integer;
max: integer); overload;
These functions will create a TObjInclusiveSpec on the fly:
Simba Code:
function objinclusiveSpec(): TObjInclusiveSpec;
function objinclusiveSpec(alg: TObjInclusiveAlg; min: integer;
max: integer): TObjInclusiveSpec; overload;
Colors
Colors also have their own record, object functions, and functions to create them. The main object holds an array of colors.
Each color in the array can have any CTS setting and a unique filter specifier, allowing you to define processing with precision.
Simba Code:
type TObjColor = record {INTERNAL}
type TObjColorArray = array of TObjColor;
TObjColor Initializers:
Simba Code:
{CTS0}
procedure TObjColor.init(col: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC);
{CTS0 w/ tolerance}
procedure TObjColor.init(col: integer; tol: integer;
spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload;
{CTS2}
procedure TObjColor.init(col: integer; tol: integer; hue: extended;
saturation: extended; spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload;
{CTS3}
procedure TObjColor.init(col: integer; tol: integer; sensitivity: extended;
spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload;
{any CTS}
procedure TObjColor.init(col: integer; tol: integer; cs: TcolorSettings;
spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload;
Matching creator functions:
Simba Code:
function objColor(col: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor;
function objColor(col: integer; tol: integer;
spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload;
function objColor(col: integer; tol: integer; hue: extended;
saturation: extended; spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload;
function objColor(col: integer; tol: integer; sensitivity: extended;
spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload;
function objColor(col: integer; tol: integer; cs: TcolorSettings;
spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload;
The main object
Like the rest, the TObjFind type is a record holding the data and a number of functions to initialize them.
This type also has the "real' processing functions that find the object data for you.
Simba Code:
TObjFind = record {INTERNAL}
Creation / query functions:
Simba Code:
procedure TObjFind.init(colArr: TObjColorArray; spec: TObjFilterSpec = _NULL_FILTER_SPEC;
inclSpec: TobjInclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = '');
procedure TObjFind.init(spec: TObjFilterSpec = _NULL_FILTER_SPEC;
inclSpec: TobjInclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = ''); overload;
procedure TObjFind.init(col: TObjColor; spec: TObjFilterSpec = _NULL_FILTER_SPEC;
inclSpec: TobjInclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = ''); overload;
procedure TObjFind.addColor(col: TObjColor);
procedure TObjFind.setColors(colArr: TObjColorArray);
function TObjFind.getColors(): TObjColorArray;
procedure TObjFind.setFilterSpec(spec: TObjFilterSpec);
function TObjFind.getFilterSpec(): TObjFilterSpec;
procedure TObjFind.setInclusiveSpec(iSpec: TObjInclusiveSpec);
function TObjFind.getInclusiveSpec(): TObjInclusiveSpec;
function TObjFind.getLastPoint(): TPoint;
function TObjFind.getLastBox(): TBox;
Processing functions:
Simba Code:
function TObjFind.findColorsATPA(searchBox: TBox = [-1,-1,-1,-1];
sortFrom: TPoint = [-1,-1]): T2DPointArray;
function TObjFind.findColors(searchBox: TBox = [-1,-1,-1,-1];
sortFrom: TPoint = [-1,-1]): TPointArray;
function TObjFind.findBoxes(searchBox: TBox = [-1,-1,-1,-1];
sortFrom: TPoint = [-1,-1]): TBoxArray;
function TObjFind.findCenters(searchBox: TBox = [-1,-1,-1,-1];
sortFrom: TPoint = [-1,-1]): TPointArray;
function TObjFind.choose(searchBox: TBox; sortFrom: TPoint; matchUptext: TStringArray;
excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray;
byBox: boolean = false; pointVariance: integer = 5): boolean;
function TObjFind.choose(searchBox: TBox; matchUptext: TStringArray;
excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray;
byBox: boolean = false; pointVariance:integer = 5): boolean; overload;
function TObjFind.findWithinATPA(enclosingObj: TObjFind;
searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): T2DPointArray;
function TObjFind.findWithinColors(enclosingObj: TObjFind;
searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TPointArray;
function TObjFind.findWithinBoxes(enclosingObj: TObjFind;
searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TBoxArray;
function TObjFind.findWithinCenters(enclosingObj: TObjFind;
searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TPointArray;
function TObjFind.chooseWithin(enclosingObj: TObjFind; searchBox: TBox;
sortFrom: TPoint; matchUptext: TStringArray; excludeUptext: TStringArray;
mouseStyle: integer; choices: TStringArray; byBox: boolean = false;
pointVariance: integer = 5): boolean;
Credits:
Runaway's algorithm used with permission and credited in the code.
Code link:
You probably want to remove the include libupdater.simba at the top and the initializer at the end so it won't try to use my autoupdater. The attachment below has that done already.
http://pastebin.com/raw.php?i=pGwsdnSt