PDA

View Full Version : [Source] FindColor(Tolerance) using ScanLines



Freddy1990
02-26-2007, 10:21 PM
Well, I notice many people do (no offence) a very newbish thing...
They use GetPixel for colorfinding functions...
Of course it works and it's good to learn it, but at a certain point you should move on to better techniques because GetPixel is incredibly slow.
It has to make an api call for every pixel.

I've quickly put together a function which uses scanlines.
A small explanation...
Colorfunctions that use scanlines get a complete bitmap of the client canvas and then get line by line rows of pixels to find a color in.

I've tried to comment it so it's clear how everything works and easier to use to make your own functions.
This is not the best technique, but it's good, fast and reliable.

function FindColor(var x, y: Integer; Color, xs, ys, xe, ye, Tolerance: Integer; Window: Hwnd): Boolean;
var
Bmp: TBitmap; // Bitmap of the client
tmpDC: HDC; // Device context of the client's window handle
Size: TRect; // Rect(angle) of the client's window
cx, cy: Integer; // For-loop vars
Line: PRGB32Array; // The scanline
begin
Result := False; // In case the color isn't found => Result = False
x := -1; // In case the color isn't found => x = -1
y := -1; // In case the color isn't found => y = -1
Bmp := TBitmap.Create; // We create our bitmap instance
tmpDC := GetWindowDC(Window); // We get the device context of the client's window handle
GetWindowRect(Window, Size); // We get the rect(angle) of the client's window
Bmp.Width := Size.Right - Size.Left; // We set the width of our bitmap
Bmp.Height := Size.Bottom - Size.Top; // We set the height of our bitmap
BitBlt(Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, tmpDC, 0, 0, SRCCOPY);
// ^^^ We copy the client's canvas onto the bitmap
Bmp.PixelFormat := pf32bit; // We set the bitmap to 32bits
DeleteDC(tmpDC); // We delete the device context to avoid memory leakage
for cy := ys to ye do // Loop for the rows of pixels (y)
begin
if cy >= Bmp.Height then Break; // Break the loop if you reach the end of the bitmap
Line := Bmp.ScanLine[cy]; // We retrieve the scanline (line of pixels) from the bitmap for the current y
for cx := xs to xe do // Loop for the colums of pixels (x)
begin
if cx >= Bmp.Width then Break; // Break the loop if you reach the end of the bitmap
if (SimilarColors(RGB(Line[cx].R, Line[cx].G, Line[cx].B), Color, Tolerance)) then
// ^^^ We convert the RGB values of the current pixel to a color and compare it with the tolerance to the entered color
begin // If the color is similar (or for tol 0 the same) then...
Result := True; // Result of the function
x := cx; // Returned x-value
y := cy; // Returned y-value
Line := nil; // Free the scanline to avoid memory leaks
Bmp.Free; // Free the bitmap to avoid memory leaks
Exit; // Exit the function
end;
end;
end;
Line := nil; // Free the scanline to avoid memory leaks
Bmp.Free; // Free the bitmap to avoid memory leaks
end;

For this function to work you will need 2 things, the following type declarations:
type
TRGB32 = packed record
B, G, R, A: Byte;
end;
TRGB32Array = packed array[0..MaxInt div SizeOf(TRGB32) - 1] of TRGB32;
PRGB32Array = ^TRGB32Array;

and the following 2 functions to compare the colors (which can also be found on my site (http://stuart.existhost.com/freddy/productions/ or http://freddy.impsoft.net)
function ColorRGB(SColor, i: Integer): Extended;
var
Color: TColor;
begin
Color := ColorToRGB(SColor);
Result := 0;
case i of
1: result := GetRValue(Color);
2: result := GetGValue(Color);
3: result := GetBValue(Color);
end;
end;

function SimilarColors(Color1, Color2, Tolerance: Integer): Boolean;
begin
Result := False;
if (Abs(Round(ColorRGB(Color1, 1)) - Round(ColorRGB(Color2, 1))) <= Tolerance) then
if (Abs(Round(ColorRGB(Color1, 2)) - Round(ColorRGB(Color2, 2))) <= Tolerance) then
if (Abs(Round(ColorRGB(Color1, 3)) - Round(ColorRGB(Color2, 3))) <= Tolerance) then
Result := True;
end;

I hope you can learn something from this, have fun ;)

EDIT:
(And for those who might want to use this for scar... You need to use GetClientWindowHandle in scar to return the selected client window if you didn't know already)

tarajunky
02-26-2007, 10:34 PM
Is this the same thing that is already implemented in SCAR? A lot of those functions look very familiar. :)

Freddy1990
02-26-2007, 10:44 PM
Meh, the techniques i and scar use are different from this and different from eachother, but... it comes down to the fact that they're fast, this does in like 50-200ms what it'd do with getpixel in 2 seconds or something, getting the bitmap is actually what slows it down the most

ruler
02-27-2007, 10:44 AM
it takes 50-200ms to get the clients screen into a array of intergers?

Sumilion
02-27-2007, 11:29 AM
Amazing, i can learn a lot from you. Nice site too btw, made it all yourself ?

Freddy1990
02-27-2007, 11:47 AM
it takes 50-200ms to get the clients screen into a array of intergers?

It can go faster, it just depends on ur cpu speed... if you got a slow one then the function will be slower, there's no way around that


Amazing, i can learn a lot from you. Nice site too btw, made it all yourself ?

Free template ;)

ruler
02-27-2007, 11:57 AM
The runescape entire client 702x502 not a entire desktop.
Takes 50-200ms? to make into a array of intergers?

by the look of your code, keep in mind i dont know how to run it but.

iBot takes 15ms to make a short[][][] array.
and 6ms to do a search with tolerance with no result over entire client screen.

So estimate is this taking about 80 percent:
if (SimilarColors(RGB(Line[cx].R, Line[cx].G, Line[cx].B), Color, Tolerance)).

If one can show me a list of function types and operations avaiable i can optimze it.

Sumilion
02-27-2007, 12:17 PM
Free template ;)

No offence, but i thought so :p, otherwise my respect would've grown even more and we wouldnt want that.

Freddy1990
02-27-2007, 03:14 PM
The runescape entire client 702x502 not a entire desktop.
Takes 50-200ms? to make into a array of intergers?

There are pc's who can run this in under 10ms, as i said, it all depends on cpu speed.

And well, you could shorten down those 2 functions for the toelrance comparison to this i guess:
function SimilarColors(Color1, Color2, Tolerance: Integer): Boolean;
var
C1, C2: TColor;
begin
Result := False;
C1 := ColorToRGB(Color1);
C2 := ColorToRGB(Color2);
if (Abs(Round(GetRValue(C1) - GetRValue(C2))) <= Tolerance) then
if (Abs(Round(GetGValue(C1) - GetGValue(C2))) <= Tolerance) then
if (Abs(Round(GetBValue(C1) - GetBValue(C2))) <= Tolerance) then
Result := True;
end;

Makes it a bit faster...
My pc does this in an average 23.44 ms (Thats for an entire scan without result of the rs window btw)

ruler
02-27-2007, 11:05 PM
remove every method call, make it one method.

Freddy1990
02-28-2007, 06:51 AM
That doesn't help...

ruler
02-28-2007, 12:31 PM
use bit shifts not methods.

ruler
02-28-2007, 04:43 PM
/**
*
* @param color
* @param type
* COLOR_TYPE_AWT or COLOR_TYPE_SCAR
*/
public RGB(int color, int type) {
if (type == COLOR_TYPE_AWT) {
r = (short) ((color >> 16) & 0xff);
g = (short) ((color >> 8) & 0xff);
b = (short) ((color) & 0xff);
} else if (type == COLOR_TYPE_SCAR) {
r = (short) ((color) & 0xff);
g = (short) ((color >> 8) & 0xff);
b = (short) ((color >> 16) & 0xff);
}
}

Freddy1990
02-28-2007, 06:50 PM
Here you go, I haven't tested it, but it should work... happy?
function SimilarColors(Color1, Color2, Tolerance: Integer): Boolean;
begin
Result := False;
if (Abs((Color1 and $ff) - (Color2 and $ff)) <= Tolerance) then
if (Abs(((Color1 and $ff00) shr 8) - ((Color2 and $ff00) shr 8)) <= Tolerance) then
if (Abs(((Color1 and $ff0000) shr 16) - ((Color2 and $ff0000) shr 16)) <= Tolerance) then
Result := True;
end;

ruler
03-01-2007, 07:42 AM
:cool: please speed test :)

Wizzup?
03-01-2007, 09:31 AM
Here you go, I haven't tested it, but it should work... happy?
function SimilarColors(Color1, Color2, Tolerance: Integer): Boolean;
begin
Result := False;
if (Abs((Color1 and $ff) - (Color2 and $ff)) <= Tolerance) then
if (Abs(((Color1 and $ff00) shr 8) - ((Color2 and $ff00) shr 8)) <= Tolerance) then
if (Abs(((Color1 and $ff0000) shr 16) - ((Color2 and $ff0000) shr 16)) <= Tolerance) then
Result := True;
end;

Is the Result := False; nessecairy? (And would that matter to speed?)

Freddy1990
03-01-2007, 03:12 PM
Is the Result := False; nessecairy? (And would that matter to speed?)
Kinda, delphi sometimes has this weird annoying habbit...
But anyway, you could as well write it like this:
function SimilarColors(Color1, Color2, Tolerance: Integer): Boolean;
begin
Result := ((Abs((Color1 and $ff) - (Color2 and $ff)) <= Tolerance) and
(Abs(((Color1 and $ff00) shr 8) - ((Color2 and $ff00) shr 8)) <= Tolerance) and
(Abs(((Color1 and $ff0000) shr 16) - ((Color2 and $ff0000) shr 16)) <= Tolerance));
end;


:cool: please speed test :)
*says some un-understandble stuff*
average 11ms

Well, as i said..., i didn't write the original function so i can't help that it sux :)

Starblaster100
03-04-2007, 12:56 AM
Is the Result := False; nessecairy? (And would that matter to speed?)

In delphi you must define everything



Result := False;
I := 0;

etc.

Otherwise you get a load of hints saying:



Variable 'i' might not have been initialized.


With such a big project like SCAR etc. if this message came up for every procedure, you would have to constantly weed through all the hints just to find the error messages. Because of this, putting Result := False, i := 0; etc. becomes second nature

You will see more of this in the dpr files in the repos.

Hope that helps.