Wizzup?
2011-06-14 17:05:01
So to provide proper CTS 3 support (note: _not_ the same as SCAR's CTS 3), I'm currently rewriting most of the finding functions to implement the colour comparison in a more appropriate way. I've succeeded in this for the simple FindColor*Tolerance* functions; but the more complex functions are a different issue.
Taking FindBitmapToleranceIn as example. The actual comparison is done the in ColorSame function, which is supposed to be inline. (So we don't call it every time, the compiler compiles the function into the loop, for performance reasons)
However, assuming that it is inlined; we can pass a lot of variables because they won't have to be ``passed'' anyway; the function is just inlined. Currently, we pass variables for *each* of the CTS'es: R,G,B and H,S,L as well as HueMod and SatMod. Obviously I could just add L_, a_, b_ (_ because some are already taken), but I think a different approach is required. There's a fat chance the functions aren't actually inlined and I think Nielsie95 told me the FPC inline implementation wasn't always as fast as you'd expect it to be.
This is how it is currently in FindColorsTolerance, note that it doesn't use ColorSame (after I cleaned it up, concept remains the same):
Note that for each cts, we have a subprocedure which also includes the entire ``loop''. This is not a *real* problem when the loops are small, even though it's not a perfect example of code reuse - but it'll get messy when we use it for those *big* loops, I think.
At the moment I can't think of another method that will supposedly be faster than the approach taken in FindColorsTolerance, but it will *add* a lot of code. And if you find a bug in one of those loops, you have to fix it for all the CTS subprocedures in the procedure, not optimal. Plus, if we add an extra CTS we have to add a subprocedure with the loop for the procedure for _each_ procedure.
Taking FindBitmapToleranceIn as example. The actual comparison is done the in ColorSame function, which is supposed to be inline. (So we don't call it every time, the compiler compiles the function into the loop, for performance reasons)
function TMFinder.FindBitmapToleranceIn(bitmap: TMufasaBitmap; out x, y: Integer; xs,
ys, xe, ye: Integer; tolerance: Integer): Boolean;
var
MainRowdata : TPRGB32Array;
BmpRowData : TPRGB32Array;
PtrData : TRetData;
BmpW,BmpH : integer;
xBmp,yBmp : integer;
tmpY : integer;
dX, dY, xx, yy: Integer;
CCTS : integer;
H,S,L,HMod,SMod : extended;
SkipCoords : T2DBoolArray;
label NotFoundBmp;
{ Don't know if the compiler has any speed-troubles with goto jumping in nested for loops. }
begin
Result := false;
// checks for valid xs,ys,xe,ye? (may involve GetDimensions)
DefaultOperations(xs,ys,xe,ye);
// calculate delta x and y
dX := xe - xs;
dY := ye - ys;
PtrData := TClient(Client).IOManager.ReturnData(xs, ys, dX + 1, dY + 1);
//Caculate the row ptrs
MainRowdata:= CalculateRowPtrs(PtrData,dy+1);
BmpRowData:= CalculateRowPtrs(bitmap);
//Get the 'fixed' bmp size
BmpW := bitmap.Width - 1;
BmpH := bitmap.Height - 1;
//Heck our bitmap cannot be outside the search area
dX := dX - bmpW;
dY := dY - bmpH;
//Compiler hints
HMod := 0;SMod := 0;H := 0.0;S := 0.0; L := 0.0;
CCTS := Self.CTS;
//Get the "skip coords".
CalculateBitmapSkipCoords(Bitmap,SkipCoords);
for yy := 0 to dY do
for xx := 0 to dX do
begin;
for yBmp:= 0 to BmpH do
begin;
tmpY := yBmp + yy;
for xBmp := 0 to BmpW do
if not SkipCoords[yBmp][xBmp] then
if not ColorSame(CCTS,tolerance,
BmpRowData[yBmp][xBmp].R,BmpRowData[yBmp][xBmp].G,BmpRowData[yBmp][xBmp].B,
MainRowdata[tmpY][xBmp + xx].R,MainRowdata[tmpY][xBmp + xx].G,MainRowdata[tmpY][xBmp + xx].B,
H,S,L,HMod,SMod) then
goto NotFoundBmp;
end;
//We did find the Bmp, otherwise we would be at the part below
TClient(Client).IOManager.FreeReturnData;
x := xx + xs;
y := yy + ys;
result := true;
exit;
NotFoundBmp:
end;
TClient(Client).IOManager.FreeReturnData;
end;
However, assuming that it is inlined; we can pass a lot of variables because they won't have to be ``passed'' anyway; the function is just inlined. Currently, we pass variables for *each* of the CTS'es: R,G,B and H,S,L as well as HueMod and SatMod. Obviously I could just add L_, a_, b_ (_ because some are already taken), but I think a different approach is required. There's a fat chance the functions aren't actually inlined and I think Nielsie95 told me the FPC inline implementation wasn't always as fast as you'd expect it to be.
This is how it is currently in FindColorsTolerance, note that it doesn't use ColorSame (after I cleaned it up, concept remains the same):
function TMFinder.FindColorsTolerance(out Points: TPointArray; Color, xs, ys,
xe, ye, Tol: Integer): Boolean;
var
PtrData: TRetData;
Ptr: PRGB32;
PtrInc,C: Integer;
dX, dY, clR, clG, clB: Integer;
procedure cts0;
var xx, yy: integer;
begin
for yy := ys to ye do
begin
for xx := xs to xe do
begin
if ((abs(clB-Ptr^.B) <= Tol) and (abs(clG-Ptr^.G) <= Tol) and (Abs(clR-Ptr^.R) <= Tol)) then
begin;
ClientTPA[c].x := xx;
ClientTPA[c].y := yy;
inc(c);
end;
inc(Ptr);
end;
Inc(Ptr, PtrInc);
end;
end;
procedure cts1;
var xx, yy: integer;
begin
for yy := ys to ye do
begin
for xx := xs to xe do
begin
if (Sqrt(sqr(clR-Ptr^.R) + sqr(clG - Ptr^.G) + sqr(clB - Ptr^.B)) <= Tol) then
begin;
ClientTPA[c].x := xx;
ClientTPA[c].y := yy;
inc(c);
end;
inc(ptr);
end;
Inc(Ptr, PtrInc);
end;
end;
procedure cts2;
var xx, yy: integer;
H1, S1, L1, H2, S2, L2, hueXTol, satXTol: Extended;
begin
ColorToHSL(color,H1,S1,L1);
HueXTol := hueMod * Tol;
SatXTol := satMod * Tol;
for yy := ys to ye do
begin
for xx := xs to xe do
begin
RGBToHSL(Ptr^.R,Ptr^.G,Ptr^.B,H2,S2,L2);
if ((abs(H1 - H2) <= HueXTol) and (abs(S1 - S2) <= SatXTol) and (abs(L1 - L2) <= Tol)) then
begin;
ClientTPA[c].x := xx;
ClientTPA[c].y := yy;
Inc(c);
end;
Inc(Ptr)
end;
Inc(Ptr, PtrInc);
end;
end;
begin
Result := false;
DefaultOperations(xs,ys,xe,ye);
dX := xe - xs;
dY := ye - ys;
//next, convert the color to r,g,b
ColorToRGB(Color, clR, clG, clB);
PtrData := TClient(Client).IOManager.ReturnData(xs, ys, dX + 1, dY + 1);
// Do we want to "cache" these vars?
// We will, for now. Easier to type.
Ptr := PtrData.Ptr;
PtrInc := PtrData.IncPtrWith;
c := 0;
case CTS of
0: cts0();
1: cts1();
2: cts2();
end;
SetLength(Points, C);
Move(ClientTPA[0], Points[0], C * SizeOf(TPoint));
Result := C > 0;
TClient(Client).IOManager.FreeReturnData;
end;Note that for each cts, we have a subprocedure which also includes the entire ``loop''. This is not a *real* problem when the loops are small, even though it's not a perfect example of code reuse - but it'll get messy when we use it for those *big* loops, I think.
At the moment I can't think of another method that will supposedly be faster than the approach taken in FindColorsTolerance, but it will *add* a lot of code. And if you find a bug in one of those loops, you have to fix it for all the CTS subprocedures in the procedure, not optimal. Plus, if we add an extra CTS we have to add a subprocedure with the loop for the procedure for _each_ procedure.