masterBB
2012-02-05 17:57:07
The master tutorial on CTS 0 1 2 and 3
Making your life more colorfull!
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.
From: 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.
From: 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.
From: 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? [INDENT]
- CTS no
- CTS_0
- CTS_1
- CTS_2
- CTS_3
- How does the code behind the methods work? [INDENT]
- 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 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?
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.


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.comThis 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 asume 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:
[INDENT]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.[/INDENT]


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:
[INDENT]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[/INDENT]


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 KyrninAs 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]. If sat is zero than the hue will not change the color cause it would be a grey scale. In this picture there is a third parameter, how light the color is. One is white and zero is black.
CTS 2 requires two additional modifiers, hue and sat. These modifiers are multiplied with your tolerance to get the range for hue and sat.
For example:
Color 1
[INDENT]H = 0.5
S = 0.8
L = 0.7[/INDENT]
Color 2
[INDENT]H = 0.45
S = 0.3
L = 0.65[/INDENT]
Tolerances
[INDENT]tol = 30
hue mod = 0.20
sat mod = 0.90[/INDENT]
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
When I have time, I will add this.
How does the CTS code work?
CTS_No
The source is:
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:
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.
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.
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.
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.
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:
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.
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.
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.
//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.
RGBToHSL(R, G, B, i.H, i.S,
i.L);
i.hueMod := Tol * hueMod;
i.satMod := Tol * satMod;
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!
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/Simba/blob/master/Units/MMLCore/finder.pas
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:
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;