The master tutorial on CTS 0 1 2 and 3
Making your life more colorfull!
Did you always wanted to know the differences between the color tolerance speed methods provided by simba? Then this is your change! These functions will make you be able to write better, faster and more reliable functions to navigate your bot through the world of runescape. Need some extra motivation to read this tutorial? Just check these amazing testimonials and let them convince you to check it out.
Originally Posted by
Martin Luther King
I never understood how people thought that white and black people where different. But thanks to this tutorial I now understand that you need quite some tolerance to see them as equals.
Originally Posted by
Picasso
Before I read this tutorial my paintings where boring and mostly black and white. But thanks to Bart de Boer I now know how to make interesting combinations with colors.
Originally Posted by
Saruman of Many Colours
When I changed from a white wizard to a more colorful one I always thought people would still be able to relate me with certain colors, now I see that even with the slightest tolerance people can confuse me with both a tree and a tomato.
Table of contents
- A short introduction
- How do the CTS methods work?
- CTS no
- CTS_0
- CTS_1
- CTS_2
- CTS_3
- How does the code behind the methods work?
- CTS no
- CTS_0
- CTS_1
- CTS_2
- CTS_3
- Finally
A short introduction
I created this tutorial to expend my knowledge on the color tolerance speed methods and to pass my knowledge onto others. If you got questions or suggestion you can post them in thread. I will not respond on personal messages, cause for every single person asking there are a lot with the same question. So I hope you have good read.
In this tutorial I will explain the color tolerance speed(CTS) methods. You might ask yourself what are these methods and why would it be interesting to know how they work. CTS is the method that simba uses to compare two colors with each other. These functions return either true if the colors are equal or within the tolerance or it will return false if the tolerance is too low and the differences between the colors are too high.
The default CTS used in simba is CTS_1, this is a simple CTS method that is slower then CTS_No and CTS_0 but faster as CTS_2 and CTS_3. The best CTS method to use depends on the situation. Sometimes CTS_0 will give more accurate results as CTS_1 and vice versa. By understanding the pros and cons of each CTS method you will be able to write better and more efficient bots. Note that this knowledge also applies to other color searching goals including "real life" object recognition.
What do the CTS methods do?
In this section of this tutorial I will explain what every method does and what the differences are between them. I recommend you to read this if you want to expand your knowledge on this area. If you still got questions about how the CTS methods work please post your questions in this thread so I can expand that explanation.
CTS_No
The first one is CTS method I will discuss is a short one. This method is the most basic way to check if to colors are equal. It basically checks if the red, green and blue are equal, this method leaves no room for tolerance. It isn't possible to use this method color find functions that use tolerance. But luckily there are a non-tolerance alternatives like FindColor and CountColor to replace them.
Note that although this is a color tolerance speed method it doesn't support tolerance.
You might think this is enough to find objects in runescape and write a color based bot. But sadly it isn't. Jagex implemented a system in runescape which add a bit of difference to each color every time the object is loaded. This makes it a lot harder and this is also the reason why this color tolerance speed method isn't used a lot. I believe items in the inventory have a black outline color that is 'static', so that one doesn't at all, in almost all other cases it's better to use a color tolerance speed modifier that supports a tolerance modifier.
CTS_0
This is the fastest way in simba to compare colors while still using tolerance. But it's big con is the way it uses the tolerance parameter. So it's not that accurate in most cases. Instead of just checking if the R, G and B values of color 1 and color 2 are the same, this one calculates the differences between them and then it checks if those differences between them is within the tolerance field. To explain this I stole the following image from wikipedia:
Copyright: wikipedia.com
This cube shows how RGB works, black is the corner in the back you can't see and white is in the corner removed from this cube. I assume you understand the principle of RGB(Red, Green and Blue). With CTS_0 you pick your first color out of this cube, draw a smaller cube around it (with a width, height and length of twice your tolerance), and checks if the second color is within that smaller cube you created around your first color. If it is in that smaller cube, than it will find the colors similar. The cons of this method that the distance between the corners and the middle of the cube are bigger than at other points of the cube.
Example:
Color 1 = R200 G200 B200
Color 2 = R180 G200 B200
Color 3 = R185 G185 B185
Tolerance is 15
Color 1 and Color 2 aren't similar. Red 200 - Red 180 > Tol 15
Color 1 and Color 3 are similar. Red 200 - Red 185 <= Tol 15, Blue 200 - Blue 185 <= Tol 15 and Green 200 - Green 185 <= Tol 15.
This method is fast, really fast. You can easily compare the complete screen with a not noticeable delay. Once you get a bit used to thinking in the red, green and blue color values you will notice that this one does it job quite well. This is useful in any application where speed is more important than the quality of the search. As you see in the example above, a difference in only one color can easily get a point to be excluded from your search. Cause of that reason this is not the default way to navigate through the world of runescape.
CTS_1
This is the default method that simba uses to find colors with tolerance. It's not so fast as CTS_0, but will give in return better results in most cases. Beginners don't need to use other CTS methods. If you look back at the previous image, you again pick color 1 in it, then you draw a sphere around that point with a radius of the tolerance. If the second color is within the sphere then this method will return positive. One of the pros of this method is that a sphere doesn't got corners like a cube, so this one will give more accurate results in most cases.
Example:
Color 1 = R200 G200 B200
Color 2 = R180 G200 B200
Color 3 = R185 G185 B185
Tolerance is 20 and 20 * 20 = 400
Color 1 and Color 2 are similar. 20 * 20 + 0 * 0 + 0 * 0 = 400 and 400 <= 400
Color 1 and Color 3 aren't similar. 15 * 15 + 15 * 15 + 15 * 15 = 675 > 400
This one is fast and reliable. Not as fast as CTS 0 but that barely noticeable. There are few applications where you will notice the speed difference. You will probably never will unless you got a lot of colors you want to compare. But in return of that small speed loss it gives you a way to compare colors with your tolerance which lies closer to the way a human eye would compare them. It searches for a difference through out all the color parameters, making this a better method to use than CTS 0 (few exceptions). If you're satisfied with the color finding with the default method then it's a safe place to stay.
CTS_2
The big difference between the CTS 2 method and the ones before, is that this method doesn't use the RGB color model. Instead the HSL color model is just. This might be a bit hard to understand at the beginning, so I once again stole a picture for you guys:
image courtesy J Kyrnin
As you can see, the 3D representation of this color model is a cylinder. The sat parameter is how much the color leans towards a certain color. For example; a very red color got a high saturation and a light pink one got a low saturation. The hue parameter is which color it leans to, note that it's percentage of a angle so the range is [-infinity ,infinity]. Lightness is last parameter, basically how dark or light the color is.
Many programs like photoshop got RGB to HSL converters, if you don't have any, don't worry, just follow this link. I suggest that if you are new to this concept of H S L, play with that converter to get used to it. Note that on that converter L and S are both percentages, so between the 1 and the 0, but H is an angle, between the 0 and the 359.
If you keep the saturation on 100% and the lightness on 50% you will notice that will only get primary colors. Like basic red, green, yellow, purple and green. Note that the hue will only change that aspect of the color. The color will stay have the same shade lightness cause L will remain at 50%. If you lower the saturation the color will slowly become grayer and grayer. When the saturation reaches 0% the color will be between white and black, changing the hue will have no effect on the color.
CTS 2 requires two additional modifiers, hue and sat. These modifiers are multiplied with your tolerance to get the range for hue and sat. So in practical use this will mean that you can specify the importance of the hue and saturation in your color comparison. If you put satMod on 0, than it will only look for hue differences.
For example:
Color 1
H = 0.5
S = 0.8
L = 0.7
Color 2
H = 0.45
S = 0.3
L = 0.65
Tolerances
tol = 30
hue mod = 0.20
sat mod = 0.90
hue mod = 0.20 * 0.30 -> 0.06
sat mod = 0.90 * 0.30 -> 0.27
Hue difference = 0.5 - 0.45 -> 0.05
Sat difference = 0.90 - 0.65 -> 0.25
l difference = 0.7 - 0.65 -> 0.05
Hue = 0.05 < 0.06
Sat = 0.25 < 0.27
l = 0.05 < 0.30
These 2 colors are similar!
CTS_3
Explained in a post for now. Will add it when I'm done with it. This link.
How does the CTS code work?
In this section of the tutorial I will talk about the code that simba uses to compare the code. I recommend this section if you want to go deeper as the basics. It's not necessary to read if you want to build a reliable bot. This is where I got all of my information from.
CTS_No
The source is:
Simba Code:
function ColorSame_ctsNo(ctsInfo: Pointer; C2: PRGB32): boolean;
var
C1: TCTSNoInfo;
begin
C1 := PCTSNoInfo(ctsInfo)^;
Result := (C1.B = C2^.B)
and (C1.G = C2^.G)
and (C1.R = C2^.R);
end;
This is the first CTS method I will explain, because of that I will als go a bit in the code that is used in all CTS methods. I hope you enjoy and learn .
As you might notice this function requires two variables, a pointer and a color (PRGB32 is just a color). I assume that not all readers of this tutorial are aware about what a pointer is cause pascal script doesn't support pointers, so I will start with some information about pointers.
A pointer is, as simple as it may sound, nothing more than a memory adress. Usually at that adress there is some memory space reserved or used by a certain variable. With a pointer you can access the information on that memory adress with the following code: yourPointer^ . This is used in every CTS function to get the first color and some extra parameters. The memory space is reserved by Create_CTSInfo_helper. In this case the following part of that function:
Simba Code:
Result := AllocMem(SizeOf(TCTSNoInfo));
ColorToRGB(Color, PCTSNoInfo(Result)^.R, PCTSNoInfo(Result)^.G,
PCTSNoInfo(Result)^.B);
As you may notice, the color stored at the pointer is simply a RGB color.
CTS_0
Luckily this function isn't that hard to explain. Here you can see the source of the function.
Simba Code:
function ColorSame_cts0(ctsInfo: Pointer; C2: PRGB32): boolean;
var
C1: TCTS0Info;
begin
C1 := PCTS0Info(ctsInfo)^;
Result := (Abs(C1.B - C2^.B) <= C1.Tol)
and (Abs(C1.G - C2^.G) <= C1.Tol)
and (Abs(C1.R - C2^.R) <= C1.Tol);
end;
As you might notice C1 is not just a color but also hold the tolerance. I am not sure why they went this way cause it's a bit confusing. It basically gets the differences between color 1 and color 2 there R, G and B values and it will return false if any of the three exceed the tolerance.
Simba Code:
Result := AllocMem(SizeOf(TCTS0Info));
ColorToRGB(Color, PCTS0Info(Result)^.R, PCTS0Info(Result)^.G,
PCTS0Info(Result)^.B);
PCTS0Info(Result)^.Tol := Tol;
And there is the source where the tolerance is set. That is the only difference with CTS_no.
CTS_1
This function is the same as the previous one, except that this one adds some magic from Pythagoras.
Simba Code:
function ColorSame_cts1(ctsInfo: Pointer; C2: PRGB32): boolean;
var
C1: TCTS1Info;
r,g,b: integer;
begin
C1 := PCTS1Info(ctsInfo)^;
b := C1.B - C2^.B;
g := C1.G - C2^.G;
r := C1.R - C2^.R;
Result := (b*b + g*g + r*r) <= C1.Tol;
end;
It's basically B dif^2 * G dif^2 * R dif^2 <= Tol^2. but the tolerance is squared in the Create_CTSInfo_helper function. Also note that a computer is 100 times faster with squares then with square roots, so if possible functions should try to avoid them.
Simba Code:
Result := AllocMem(SizeOf(PCTS1Info));
ColorToRGB(Color, PCTS1Info(Result)^.R, PCTS1Info(Result)^.G,
PCTS1Info(Result)^.B);
PCTS1Info(Result)^.Tol := Tol * Tol;
And here you can see tolerance being squared.
CTS_2
This is were shit gets real! Well... It isn't that hard to understand, but it doesn't use the RGB model. If you're used to RGB this might take some time to understand. I do not fully understand this yet myself, so if anyone could help me with this one:
Simba Code:
function ColorSame_cts2(ctsInfo: Pointer; C2: PRGB32): boolean;
var
r,g ,b: extended;
CMin, CMax,D : extended;
h,s,l : extended;
i: TCTS2Info;
begin
i := PCTS2Info(ctsInfo)^;
The basic part of every CTS method. Getting the required information and init the vars.
Simba Code:
R := Percentage[C2^.r];
G := Percentage[C2^.g];
B := Percentage[C2^.b];
Percentage is actually an array with a length of 255. So this works the same as dividing them by 255. R, G and B now contains the red, green and blue percentage between the complete absence of a certain color to the maximum amount of it.
Simba Code:
CMin := R;
CMax := R;
if G < Cmin then CMin := G;
if B < Cmin then CMin := B;
if G > Cmax then CMax := G;
if B > Cmax then CMax := B;
l := 0.5 * (Cmax + Cmin);
Sets CMin and CMax to the highest and lowest RGB percentage respectively. Then sets l to the average between CMin and CHigh. Note that one of the colors(unless they are all the same) will be dropped when its neither the highest or the lowest and will not be used to check the sat. It will be used again to calculate the hue.
Simba Code:
//The L-value is already calculated, lets see if the current point meats the requirements!
if abs(l*100 - i.L) > i.Tol then
exit(false);
if Cmax = Cmin then
begin
//S and H are both zero, lets check if it mathces the tol
if (i.H <= (i.hueMod)) and
(i.S <= (i.satMod)) then
exit(true)
else
exit(false);
end;
It will check if the difference between the l var (lightness in the picture above), if its higher then the tolerance than these colors are too different and there is no need to continue the check.
After that it will check if CMax and CMin are equal, the color is a greyscale without color. So H and S of color 2 are both zero. If the H and S of color 1 are lower or equal to the amount they may differ from color 2(hueMod and satMod) then it isn't necessary to calculate the H and S from color 2 and this method will return positive. Or else it means that color 1 got to much sat or hue, it well then exit with negative.
Note that the hue, sat and l of color 1, mostly referred to in this code as i. Are calculated before this method. Also not that hue mod and sat mod are multiplied by your tolerance var.
Simba Code:
RGBToHSL(R, G, B, i.H, i.S,
i.L);
i.hueMod := Tol * hueMod;
i.satMod := Tol * satMod;
Simba Code:
D := Cmax - Cmin;
if l < 0.5 then
s := D / (Cmax + Cmin)
else
s := D / (2 - Cmax - Cmin);
// We've Calculated the S, check match
if abs(S*100 - i.S) > i.satMod then
exit(false);
Calculates the saturation and check if it's in the range of satMod. If not, Exit!
Simba Code:
if R = Cmax then
h := (G - B) / D
else
if G = Cmax then
h := 2 + (B - R) / D
else
h := 4 + (R - G) / D;
h := h / 6;
if h < 0 then
h := h + 1;
//Finally lets test H2
h := h * 100;
if h > i.H then
Result := min(h - i.H, abs(h - (i.H + 100) )) <= i.hueMod
else
Result := min(i.H - h, abs(i.H - (h + 100) )) <= i.hueMod;
end;
Finally calculate the hue and returns either true or false if its in the range of the hueMod.
Finally
The complete source cab found here: (finder.pas)https://github.com/MerlijnWajer/Simb...ore/finder.pas
Simba Code:
function ColorSame_ctsNo(ctsInfo: Pointer; C2: PRGB32): boolean;
var
C1: TCTSNoInfo;
begin
C1 := PCTSNoInfo(ctsInfo)^;
Result := (C1.B = C2^.B)
and (C1.G = C2^.G)
and (C1.R = C2^.R);
end;
function ColorSame_cts0(ctsInfo: Pointer; C2: PRGB32): boolean;
var
C1: TCTS0Info;
begin
C1 := PCTS0Info(ctsInfo)^;
Result := (Abs(C1.B - C2^.B) <= C1.Tol)
and (Abs(C1.G - C2^.G) <= C1.Tol)
and (Abs(C1.R - C2^.R) <= C1.Tol);
end;
function ColorSame_cts1(ctsInfo: Pointer; C2: PRGB32): boolean;
var
C1: TCTS1Info;
r,g,b: integer;
begin
C1 := PCTS1Info(ctsInfo)^;
b := C1.B - C2^.B;
g := C1.G - C2^.G;
r := C1.R - C2^.R;
Result := (b*b + g*g + r*r) <= C1.Tol;
end;
function ColorSame_cts2(ctsInfo: Pointer; C2: PRGB32): boolean;
var
r,g ,b: extended;
CMin, CMax,D : extended;
h,s,l : extended;
i: TCTS2Info;
begin
i := PCTS2Info(ctsInfo)^;
R := Percentage[C2^.r];
G := Percentage[C2^.g];
B := Percentage[C2^.b];
CMin := R;
CMax := R;
if G < Cmin then CMin := G;
if B < Cmin then CMin := B;
if G > Cmax then CMax := G;
if B > Cmax then CMax := B;
l := 0.5 * (Cmax + Cmin);
//The L-value is already calculated, lets see if the current point meats the requirements!
if abs(l*100 - i.L) > i.Tol then
exit(false);
if Cmax = Cmin then
begin
//S and H are both zero, lets check if it mathces the tol
if (i.H <= (i.hueMod)) and
(i.S <= (i.satMod)) then
exit(true)
else
exit(false);
end;
D := Cmax - Cmin;
if l < 0.5 then
s := D / (Cmax + Cmin)
else
s := D / (2 - Cmax - Cmin);
// We've Calculated the S, check match
if abs(S*100 - i.S) > i.satMod then
exit(false);
if R = Cmax then
h := (G - B) / D
else
if G = Cmax then
h := 2 + (B - R) / D
else
h := 4 + (R - G) / D;
h := h / 6;
if h < 0 then
h := h + 1;
//Finally lets test H2
h := h * 100;
if h > i.H then
Result := min(h - i.H, abs(h - (i.H + 100) )) <= i.hueMod
else
Result := min(i.H - h, abs(i.H - (h + 100) )) <= i.hueMod;
end;
function ColorSame_cts3(ctsInfo: Pointer; C2: PRGB32): boolean;
var
i: TCTS3Info;
r, g, b : extended;
x, y, z, L, A, bb: Extended;
begin
i := PCTS3Info(ctsInfo)^;
{ RGBToXYZ(C2^.R, C2^.G, C2^.B, X, Y, Z); }
{ XYZToCIELab(X, Y, Z, L, A, B); }
R := Percentage[C2^.r];
G := Percentage[C2^.g];
B := Percentage[C2^.b];
if r > 0.04045 then
r := Power( ( r + 0.055 ) / 1.055 , 2.4) * 100
else
r := r * 7.73993808;
if g > 0.04045 then
g := Power( ( g + 0.055 ) / 1.055 , 2.4) * 100
else
g := g * 7.73993808;
if b > 0.04045 then
b := Power( ( b + 0.055 ) / 1.055 , 2.4) * 100
else
b := b * 7.73993808;
y := (r * 0.2126 + g * 0.7152 + b * 0.0722)/100.000;
if ( Y > 0.008856 ) then
Y := Power(Y, 1.0/3.0)
else
Y := ( 7.787 * Y ) + ( 16.0 / 116.0 );
x := (r * 0.4124 + g * 0.3576 + b * 0.1805)/95.047;
if ( X > 0.008856 ) then
X := Power(X, 1.0/3.0)
else
X := ( 7.787 * X ) + ( 16.0 / 116.0 );
z := (r * 0.0193 + g * 0.1192 + b * 0.9505)/108.883;
if ( Z > 0.008856 ) then
Z := Power(Z, 1.0/3.0)
else
Z := ( 7.787 * Z ) + ( 16.0 / 116.0 );
l := (116.0 * Y ) - 16.0;
a := 500.0 * ( X - Y );
bb := 200.0 * ( Y - Z );
L := L - i.L;
A := A - i.A;
Bb := Bb - i.B;
Result := (L*L + A*A + bB*Bb) <= i.Tol;
end;
and:
Simba Code:
function Create_CTSInfo_helper(cts: integer; Color, Tol: Integer;
hueMod, satMod, CTS3Modifier: extended): Pointer; overload;
var
R, G, B: Integer;
X, Y, Z: Extended;
begin
case cts of
-1:
begin
Result := AllocMem(SizeOf(TCTSNoInfo));
ColorToRGB(Color, PCTSNoInfo(Result)^.R, PCTSNoInfo(Result)^.G,
PCTSNoInfo(Result)^.B);
end;
0:
begin
Result := AllocMem(SizeOf(TCTS0Info));
ColorToRGB(Color, PCTS0Info(Result)^.R, PCTS0Info(Result)^.G,
PCTS0Info(Result)^.B);
PCTS0Info(Result)^.Tol := Tol;
end;
1:
begin
Result := AllocMem(SizeOf(TCTS1Info));
ColorToRGB(Color, PCTS1Info(Result)^.R, PCTS1Info(Result)^.G,
PCTS1Info(Result)^.B);
PCTS1Info(Result)^.Tol := Tol * Tol;
end;
2:
begin
Result := AllocMem(SizeOf(TCTS2Info));
ColorToRGB(Color, R, G, B);
RGBToHSL(R, G, B, PCTS2Info(Result)^.H, PCTS2Info(Result)^.S,
PCTS2Info(Result)^.L);
PCTS2Info(Result)^.hueMod := Tol * hueMod;
PCTS2Info(Result)^.satMod := Tol * satMod;
PCTS2Info(Result)^.Tol := Tol;
end;
3:
begin
Result := AllocMem(SizeOf(TCTS3Info));
ColorToRGB(Color, R, G, B);
RGBToXYZ(R, G, B, X, Y, Z);
XYZToCIELab(X, Y, Z, PCTS3Info(Result)^.L, PCTS3Info(Result)^.A,
PCTS3Info(Result)^.B);
{ XXX: TODO: Make all Tolerance extended }
PCTS3Info(Result)^.Tol := Ceil(Sqr(Tol*CTS3Modifier));
end;
end;
end;