SRL Archive - SRL Suggestions

FindObjColorCluster


Jahooma

2009-10-26 19:02:15

Hello, I think I've written a very useful function for finding objects. Basically, you only need to pick a few colors on the object and my function will return an array of points on the screen where those colors are clustered together.

For example, I took this function and used it in a script I'm making to open the bank using three pre-picked colors on the banker. It works perfectly (as far as I know). For most objects that have more than one color, finding them will be as simple as color picking 2-4 colors that make up a unique cluster.

Here's a description of the arguments:
//Searches the rectangle x1,y1 to x2,y2 for the colors. w and h give width and height of the rect used to find nearby colors
//tol is an array of the tolerence on each color (where the first tol integer corresponds to the first color in the color array, etc.)
//xColorOffset is an array that offsets all the points of the corresponding color horizontally, and yColorOffset something similarly vertically
//For example, in order to find the smelting furnace using the two colors [5204965, 3223859], I will need to shift the points found with the first orange color (the heat coming off) so that they are clustered closer to the other gray color of the actual furnace.
//so for xColorOffset I might put [-15, 0] meaning the first color is offset by -15 horizontally.

And here's the actual code with comments (warning, it's a bit complicated at times):

function FindObjColorCluster(colors:TIntegerArray; xColorOffset, yColorOffset:TIntegerArray; x1, y1, x2, y2, w, h:Integer; tol:TIntegerArray):TPointArray;
var
i, j, k:Integer;
numColors, numColorsLessOne, clusterIndex:Integer;
colorsPoints, colorClusters:T2DPointArray;
colorIndex:TIntegerArray;
clusterMidPoints:TPointArray;
closeColorFound, doneFindingClusters:Boolean;
begin
if(not LoggedIn) then Exit;
numColors := Length(colors);
if(numColors <= 0) or (Length(tol) <> numColors) then
Exit;
numColorsLessOne := numColors - 1;
SetLength(colorsPoints, numColors);
for i := 0 to numColorsLessOne do // for each color...
begin
FindColorsTolerance(colorsPoints[i], colors[i], x1, y1, x2, y2, tol[i]);
RAaSTPAEx(colorsPoints[i], w, h);
end;
SetLength(colorIndex, numColors);
SetLength(colorClusters, Length(colorsPoints[0]));
for i := 0 to numColorsLessOne do
colorIndex[i] := High(colorsPoints[i]);
for i := 0 to High(colorClusters) do
SetLength(colorClusters[i], numColors);
clusterIndex := 0;

//add offsets
if(Length(xColorOffset) > 0) then
for i := 0 to numColorsLessOne do
for j := 0 to colorIndex[i] do
IncEx(colorsPoints[i][j].x, xColorOffset[i]);
if(Length(yColorOffset) > 0) then
for i := 0 to numColorsLessOne do
for j := 0 to colorIndex[i] do
IncEx(colorsPoints[i][j].y, yColorOffset[i]);
if(numColors = 1) then
begin
Result := colorsPoints[0];
Exit;
end;
//at this point we've got an ATPA with the points organized by color
//next, find common clusters where one point from each color is inside one rectangle
for i := 0 to colorIndex[0] do //for each point of the first color
begin
colorClusters[clusterIndex][0] := colorsPoints[0][i];
for j := 1 to numColorsLessOne do //for each color after the first
begin
closeColorFound := False;
for k := 0 to colorIndex[j] do //for each point in each color
begin
if(PointIsNearby(colorsPoints[0][i], colorsPoints[j][k], w, h)) then //if first color point is close to this color point, add to colorCluster
begin
closeColorFound := True;
colorClusters[clusterIndex][j] := colorsPoints[j][k];
TSwap(colorsPoints[j][k], colorsPoints[j][colorIndex[j]]); //these lines stop this point from being compared again to
Dec(colorIndex[j]); //another of the first points, saving some time
if(colorIndex[j] < 0) then
doneFindingClusters := True;
break;
end;
end;
if(not closeColorFound) then
break;
if(j = numColorsLessOne) then //if this is last color, that means we have found a color cluster!
Inc(clusterIndex);
end;
if(doneFindingClusters) then
break;
end;
//almost done... now we have an ATPA with clusters of points and each cluster containing one point of each color,
//so, get the middle point of each of these clusters, and return them in a TPA
SetLength(clusterMidPoints, clusterIndex);
for i := 0 to clusterIndex - 1 do
MiddleTPAEx(colorClusters[i], clusterMidPoints[i].x, clusterMidPoints[i].y);
Result := clusterMidPoints;
end;

And this is a small function I used in the above code:
function PointIsNearby(p1, p2:TPoint; w, h:Integer):Boolean; //is p2 within the rectangle given by w, h and centered on p1
begin
Result := (p2.x > p1.x - w/2) and (p2.x < p1.x + w/2) and (p2.y > p1.y - h/2) and (p2.y < p1.y + h/2);
end;
I'm surprised that there isn't a function like PointIsNearby. Maybe there is and I just didn't see it. Oh well.

Anyway, in case you're interested, here's the function I wrote to open the bank:
function MyOpenBank:Boolean;
var
i:Integer;
thePoints:TPointArray;
bankColors:TIntegerArray;
begin
bankColors := [16642813, 7303028, 939362];
thePoints := FindObjColorCluster(bankColors, [], [], MSX1, MSY1, MSX2, MSY2, 20, 20, [9, 9, 9]);
if(Length(thePoints) > 0) then
begin
for i := 0 to Length(thePoints) - 1 do
if(thePoints[i].y < thePoints[0].y) then
TSwap(thePoints[0],thePoints[i]);
if(MenuSelect(thePoints[0].x, thePoints[0].y , 'nk Ba')) then
Result := True;
end;
end;
The part with the for loop and swapping is just to get the banker which is highest up (lowest y value). And the function MenuSelect is another function I wrote, but I don't think it really needs an explanation.

And here's a slightly faster version of FindObjColorCluster which is the same except it returns the first TPoint it finds instead of going on to find every point and returning them in an array. This one also has no offsets and one tol value which it uses for every color, so it's much simpler to use.
function FindObjColorCluster2(colors:TIntegerArray; x1, y1, x2, y2, w, h, tol:Integer):TPoint;
var
numColors, numColorsLessOne, i, j, k, l:Integer;
colorsPoints:T2DPointArray;
colorCluster:TPointArray;
colorIndex:TIntegerArray;
closeColorFound, doneFindingCluster:Boolean;
thePoint:TPoint;
begin
if(not LoggedIn) then Exit;
numColors := Length(colors);
if(numColors <= 0) then
Exit;
numColorsLessOne := numColors - 1;
SetLength(colorsPoints, numColors);
SetLength(colorCluster, numColors);
for i := 0 to numColorsLessOne do
begin
FindColorsTolerance(colorsPoints[i], colors[i], x1, y1, x2, y2, tol);
RAaSTPAEx(colorsPoints[i], w, h);
end;
if(numColors = 1) then
begin
Result := colorsPoints[0][0];
Exit;
end;
SetLength(colorIndex, numColors);
for i := 0 to numColorsLessOne do
colorIndex[i] := High(colorsPoints[i]);
for j := 0 to colorIndex[0] do
begin
colorCluster[0] := colorsPoints[0][j];
for k := 1 to numColorsLessOne do
begin
closeColorFound := False;
for l := 0 to colorIndex[k] do
begin
if(PointIsNearby(colorsPoints[0][j], colorsPoints[k][l], w, h)) then
begin
closeColorFound := True;
colorCluster[k] := colorsPoints[k][l];
TSwap(colorsPoints[k][l], colorsPoints[k][colorIndex[k]]);
Dec(colorIndex[k]);
if(colorIndex[k] < 0) then
doneFindingCluster := True;
break;
end;
end;
if(not closeColorFound) then
break;
if(k = numColorsLessOne) then
begin
MiddleTPAEx(colorCluster, thePoint.x, thePoint.y);
Result := thePoint;
Exit;
end;
end;
if(doneFindingCluster) then
break;
end;
end;
Knowing how you do things here, you might want to change it to return a boolean, and add an argument for a TPoint to output the point found. (just let me know, and I'll change it).

Finally, I've attached a file with the functions so you can test them.

Hopefully you'll find my function as useful as I did. And thanks for the wonderful library to work with; I don't know what I'd do without RAaSTPAEx. All I can say is I'm glad I joined this community.

Jahooma

Edit: updated code a little with suggestions from Narcle.
I would like to say that the code is fairly unclear in how it does its stuff, but that's because I've optimized it quite a bit. For example the use of the variable colorIndex in the code is quite difficult to explain and even harder to just figure out by looking at it. A short explanation is that it holds the high value that the for loops should search up to for each color. When points are put into the colorCluster variable, they are excluded from begin matched again with other points to make other clusters, so they are swapped to the back of the array, and the colorIndex[k] is decreased by 1.

Narcle

2009-10-26 19:21:42

There is an easier way to do this. I don't mean to out ball you just showing.

I used the ClusterColorsATPA in one of my fighters, the other function I made to show how it can be changed to Middle TPA.

function ClusterColorsATPA(Colors: TIntegerArray; w, h: integer): T2DPointArray;
var
H, i, cts: integer;
TPA: TPointArray;
begin
cts := GetColorToleranceSpeed;
ColorToleranceSpeed(1);
H := High(Colors);
SetArrayLength(Result, H + 1);
for i := 0 to H do
FindColorsSpiralTolerance(MSCX, MSCY, Result[i], Colors[i], MSX1, MSY1, MSX2, MSY2, 4);
ColorToleranceSpeed(cts);
try
TPA := MergeATPA(Result);
ClearSameIntegersAndTPA(Colors, TPA);
except
Exit;
end;
if GetArrayLength(TPA) < 1 then
Exit;
Result := TPAtoATPAEx(TPA, w, h);
SortATPAFrom(Result, Point(MSCX, MSCY));
end;


Function MiddleCluster(ATPA: T2DPointArray): TPointArray;
var
i: integer;
begin
SetArrayLength(Result, High(ATPA));
for i := 0 to High(ATPA) do
Result[i] := MiddleTPA(ATPA[i]);
end;

Jahooma

2009-10-26 20:39:27

Hi Narcle,

Somehow I knew someone must have thought of it before me. At the very least, I have learned quite a bit on this adventure, so it hasn't all been for nothing.

Anyway, I've been testing your code, and it hasn't quite been doing what I expect. I have to admit, I'm not really sure what functions like ClearSameIntegersAndTPA and FindColorsSpiralTolerance do exactly. It's probably something I'm doing wrong, but then again, maybe my function does something a little bit different than yours. Mine finds a point (or points) which is near each and every color in the array of colors. My best guess at what yours does is that it finds a point where the most pixels are clustered of the colors given, while not requiring one of each color. Is that an accurate description of what yours does?
It doesn't seem that your function does what mine does because it merges all the points found from all the colors together into one array. Maybe there is still hope.

Jahooma

Narcle

2009-10-26 21:18:30

Oh I knew they did different things I'm just showing a similar function as your own. It has the same basis and has been used before my own.

From: Jahooma
Mine finds a point (or points) which is near each and every color in the array of colors.


As I go through it I get what your doing a bit more. Your output requires that a point from each color has to be in the box. (I'm guessing box size is w/h?) The only thing I'm worried about is what is the base for determining the box? The first color, second color, all of them together? You could get many different results depending what base you use to determine if the TPoint is in the correct box found by the search.

You could even do a distance requirement between the different color points.
If Distance(Point1, Point2) < 20 then
TPA := Point1;

However even then you would need a base point to determine it.


From looking at it it could be shortened. Some pointers to help it process faster:

GetArrayLength() is the same as Length() [Doesn't speed it up just shorter to write]

To speed it up you can use High() instead of Length()
Length = High - Low (something like that)

The only time it matters is if the array doesn't start at 0 and in your loops you start at 0 as is. If the arrays didn't start at 0 (which they rarely do) you would have to start the loop as:
For i := Low(array) to High(array) do
etc.
But for the arrays you define you can easily change it to
for i := 0 to High(array) do
Intead of
for i := 0 to ArrayLength-1 do

Its only a slight increase but every millisecond counts.

Jahooma

2009-10-26 22:05:30

Oh good. I'm glad mine does something different.

Thanks for taking the time to look at my code. You are correct that w, h defines the box which each color must be present in. Right now the base, or the center of the boxes, comes from the points of the first color. It used at the beginning to RAaSTPAEx it and then later to determine if one point of each color is within the box. I could conceivably separate these two and have two widths and heights for arguments… hmm I'll think about that.
I'll tidy up my code based on your suggestions.

Once this function is straightened out do you think it could possibly be worthy of being committed? Although my opinion is of course biased because I wrote it, I do think it's one of the most useful functions there is for finding objects.

Narcle

2009-10-27 09:24:45

Idk if it would be commited. Something like this would fit in Wiz plugin but it has find colors in it etc. SRL is kinda crowded atm and not many people use clusters.

MylesMadness

2009-10-27 16:24:12

Is this like NeXus' heat clusters?

Simtoon

2009-10-27 16:29:10

Can you put them in SCAR tags?

Nava2

2009-10-27 16:46:37

I followed most of this function, albeit its useful, I think its more or less redundant to functions such as FindObjTPA.

Hmm, tough choice.

I am impressed though.

Jahooma

2009-10-27 19:18:54

I'm going to do my best to convince you that my function is useful and should be committed. Here goes.

First of all, it's easy to use. I mentioned already how easy it was to find the banker, but in the same script I also used it to find the furnace (it was a smelting script). All that is required is picking a few colors and that's all.

It's thorough and dependable. Simply put, if that particular combination of colors are clustered on screen, it will find them. There are no ifs ands or buts. In addition, I would bet my versions of opening banks and furnaces rival even the carefully put together versions currently in SRL. It works so well that in my script I get to choose which banker I want to open my account with.

It's powerful, with many possible applications. You can use it to find nearly anything in any script, and it's not limited to objects in runescape that have "uptext" when you mouse over them.

It doesn't require using/moving the mouse at all. In other words, it's totally silent and undetectable. Also, no mouse moving = no waiting for the mouse to move = faster script.

In my opinion, FindObjColorCluster is simply superior to most of the other FindObj functions. I urge you to download my test file and actually try it out (it seems no one has done so yet). By all means, compare it to the other functions both in terms of it's effectiveness and use of computer resources. What is your honest opinion of which is better?

Now I know that I'm relatively unknown on these forums, but nonetheless, I hope you will take me and my function just as seriously as you would a respected member. You won't hurt my feelings if you do decide against committing it, but I expect well reasoned arguments of why it doesn't make the cut.

Cheers,

Jahooma

Nava2

2009-10-27 21:14:09

Okay, here are my responses, you were kind of rude, so I will do the same. Sorry…

From: Jahooma
First of all, it's easy to use. I mentioned already how easy it was to find the banker, but in the same script I also used it to find the furnace (it was a smelting script). All that is required is picking a few colors and that's all.

for i := 0 to High(Colors) do
FindColorsTolerance(ATPA[i], Colors[i], x1, y1, x2, y2, Tols[i]);
ATPA := SplitTPA(MergeATPA(ATPA), 4);
for i := 0 to High(ATPA) do
if (High(ATPA[i]) > 500) then
begin;
Result[resInd] := ATPA[i];
Inc(resInd);
end;
SetLength(Result, resInd);
Does almost the SAME thing.

From: Jahooma

It's thorough and dependable. Simply put, if that particular combination of colors are clustered on screen, it will find them. There are no ifs ands or buts. In addition, I would bet my versions of opening banks and furnaces rival even the carefully put together versions currently in SRL. It works so well that in my script I get to choose which banker I want to open my account with.


The latter will to thanks to SortATPABySize and SortATPAFromFirstPoint.

From: Jahooma

It's powerful, with many possible applications. You can use it to find nearly anything in any script, and it's not limited to objects in runescape that have "uptext" when you mouse over them.


see the latter..

From: Jahooma

It doesn't require using/moving the mouse at all. In other words, it's totally silent and undetectable. Also, no mouse moving = no waiting for the mouse to move = faster script.

same thing..

From: Jahooma

In my opinion, FindObjColorCluster is simply superior to most of the other FindObj functions. I urge you to download my test file and actually try it out (it seems no one has done so yet). By all means, compare it to the other functions both in terms of it's effectiveness and use of computer resources. What is your honest opinion of which is better?

…

From: Jahooma

Now I know that I'm relatively unknown on these forums, but nonetheless, I hope you will take me and my function just as seriously as you would a respected member. You won't hurt my feelings if you do decide against committing it, but I expect well reasoned arguments of why it doesn't make the cut.


I am actually insulted. We treat everyone with equal eyes. Yes, seniority is enough to pull a bit, but as a group everything in SRL is run through a democratic system.

If others disagree with me, then so be it. But I do believe that this is redundant and can be done fairly easily.

~n2

Also, I wouldn't mind some benchmarks.

Narcle

2009-10-27 21:49:03

From: Nava2
But I do believe that this is redundant and can be done fairly easily.


I agree. Most make their own finding procedures depending on the situation and can be more effective. If someone needs cluster color finding they usually make their own (like myself).

I've made hundreds of functions and only a couple get into the include. Unless I did a rewrite (worldswitcher).

Jahooma

2009-10-27 23:03:07

I'm sorry, it wasn't at all my intention to be rude. I wasn't trying to accuse you of being unfair. No, I thought you had actually brought up a good point that I needed to answer - that my function is too similar to the other functions that already exist.

I realize that my previous post sounds somewhat arrogant with a matter-of-fact attitude. This was not my intention. Like I would do in a debate, I was simply listing the reasons why it should be committed. Please don't take offense to anything after the words "Here goes." As to the last paragraph, that's simply another argument asking to be carefully considered.

However, while searching for functions similar to mine, I did find one called FindObjTPAMulti which I couldn't help but notice was immediately committed with no questions. This is is probably what made me write that last paragraph, but I certainly didn't mean it to come off as harsh as it did. Now I regret writing it, especially since I can see that those here have put a lot of thought into my function and value equality highly. I'll try to be more careful in my future posts, but again, I'm sorry.

I think I'm going to have to respectfully disagree with you, though. The code that you wrote isn't the same because (based on my very limited knowledge of what the functions do) it doesn't require that all the colors be within the same rectangle. I guess I'm saying that what makes my function different and useful is that requirement. 3/4 of my function's code is just about matching up the points of different colors to make clusters with one point of each color. This, I think, is what's missing from the SRL library, and I don't think it's something that, as Narcle said, should be rewritten depending on the situation because for one it's complicated to do, and also it's not something that can really be done better when you know the specifics. It's a fairly general use algorithm that doesn't depend on whether you're searching for cows or furnaces.

Thus I still think it would be a helpful addition to the library, but I'm completely willing to accept whatever decision you all ultimately make.

Man, the tone of this thread has gotten way too anxiously unhappy with too much tension (largely my fault). Maybe a few smiley's will ease the situation and signal the start of a friendly debate between fellow SRL forum members who just enjoy scripting!

:) :) :) :)

(Is it getting late or what?)

Jahooma

Nava2

2009-10-27 23:18:22

@Jahooma, well said.

Idk, still don't think so.

noidea

2009-10-27 23:37:11

Can you post debug pics of the result of the function? Like DebugTPA() etc?

Jahooma

2009-10-28 18:33:03

Can you post debug pics of the result of the function? Like DebugTPA() etc?

Yeah sure. I've never done this before but here's what I got, a before and after shot.
The first picture attached is of all the colors that are found. (specifically, [16642813, 7303028, 939362] with a tol of 9 on each). The second picture shows that the script found 2 points, each one directly on top of the bankers with the white shirts.

I'm not sure how helpful that is, but I don't know what other pics I could take.

Jahooma

MylesMadness

2009-10-28 21:04:52

If the code looked prettier and maybe some benchmarks I wouldn't mind it

noidea

2009-10-28 23:31:31

Those results are pretty good (the debug images). Post some benchmarks (Speed to find those bankers, using timefrommark etc..) :)

Jahooma

2009-10-29 18:18:49

From: noidea
Those results are pretty good (the debug images). Post some benchmarks (Speed to find those bankers, using timefrommark etc..) :)

Thanks for that explanation. Okay time for some benchmarks.

I didn't really know which functions to compare mine to, so I just used FindObjTPA with one of the bank colors, FindObjTPAMulti, and of course FindObjColorCluster. I wasn't completely sure what I should have put for cts and minCount for the first two functions, so hopefully that doesn't throw things off too much.
Here's the code I used:
procedure MeasureTime;
var
t, t2, x, y:Integer;
a:TPointArray;
begin
Wait(1000);
MarkTime(t);
FindObjTPA(x, y, 16642813, 9, -1, 20, 20, 1, ['Ban','ank'])
t2 := TimeFromMark(t);
Writeln('FindObjTPA took ' + IntToStr(t2) + ' miliseconds');
Writeln(' and returned the point (' + IntToStr(x) + ', ' + IntToStr(y) + ')');

Wait(1000);
MarkTime(t);
FindObjTPAMulti(X, Y,[16642813, 7303028, 939362],9, -1, 20, 20, 1, ['Ban','ank']);
t2 := TimeFromMark(t);
Writeln('FindObjTPAMulti took ' + IntToStr(t2) + ' miliseconds');
Writeln(' and returned the point (' + IntToStr(x) + ', ' + IntToStr(y) + ')');

Wait(1000);
MarkTime(t);
a := FindObjColorCluster([16642813, 7303028, 939362],[],[], MSX1, MSY1, MSX2, MSY2, 20, 20, [9, 9, 9]);
MMouse(a[0].x, a[0].y, 0,0);
t2 := TimeFromMark(t);
Writeln('FindObjColorCluster took ' + IntToStr(t2) + ' miliseconds, and returned the array of points:');
PrintTPA(a);
end;
The other functions automatically move the mouse to the x,y point they found, so I made mine do the same. But, the main problem was that depending on how far away the mouse was from the point found, the time the function took changed dramatically. If the mouse had to move from the other side of the screen, the function's time would sometimes double. The solution I found was to let it run once to position the mouse in the right spot, and then run it again immediately using the keyboard, and without moving the mouse.

Here's how they fared in the first bank test in lumbridge:
FindObjTPA took 78 miliseconds
and returned the point (283, 103)
FindObjTPAMulti took 124 miliseconds
and returned the point (281, 105)
FindObjColorCluster took 47 miliseconds, and returned the array of points:
[(281,99)]
See attached pic.

In falador bank, the script actually threw me an error with something about the uptext(?). So, I couldn't get a time for the first two functions.
But here's what it got for mine:
FindObjColorCluster took 63 miliseconds, and returned the array of points:
[(337,79), (303,87), (266,93), (230,102), (192,108), (156,114)]

I verified it: the six points outputted were correctly over each one of the six bankers. See pic attached.

Overall, it looks like my function is faster. If you question my results or want to change the cts or minCount or something, feel free to try it out with the code I've posted.

MylesMadness

2009-10-29 18:30:49

Nice, even with the mmouse it took 63 ms?

Jahooma

2009-10-29 19:03:04

Nice, even with the mmouse it took 63 ms?

Yeah, except the mouse was already positioned from the previous run in the correct spot, so the 65 ms really is with no mouse movement (as are all the times)

Jahooma

2009-11-07 15:06:39

It's been more than a week. Is anyone going to step up and say that it should be committed? With the evidence I have posted above, I think it's fair to say that the burden is now on the rest of you to prove that it shouldn't be committed, because from what I can tell so far, FindObjColorCluster is both faster and better at finding objects than any other function currently in the library. Who's with me?

Nava2

2009-11-08 12:58:37

From: Jahooma
It's been more than a week. Is anyone going to step up and say that it should be committed? With the evidence I have posted above, I think it's fair to say that the burden is now on the rest of you to prove that it shouldn't be committed, because from what I can tell so far, FindObjColorCluster is both faster and better at finding objects than any other function currently in the library. Who's with me?


I don't see why it shouldn't be committed..