PURPOSE
This is an atricle about gathering colors using bitmap files.
Two steps are used to determine colors which should be good choices for object location. The first, obviously, is to figure out the colors of the target object! The second step is deciding if those colors are unique to the object. A color is not of much use if it matches everything else on the screen.
Things are presented so you should be able to do the work along with the article and produce the results on your own system.
The code presented is not discussed. I'll be glad to answer any questions in the thread. The OP goal is more to present the general idea and process.
FIRST THINGS FIRST
Let's start off with gathering colors from a bitmap.
Create a work folder, preferably something simple like c:\test because you'll need to type it.
Save this test image and script there. This script will gather all of the colors from a bitmap.
Test image:
displayColors.simba:
Simba Code:
program displayColors;
{$include_once srl-6/srl.simba}
var
bmpFile: string;
bmp, w, h, i, j, idx: integer;
areaColors: T2DIntegerArray;
colList, countList: TIntegerArray;
function TIntegerArray.returnInArray(const int : Integer): Integer; override;
var
i: integer;
begin
result := -1;
for i := 0 to high(self) do
if (self[i] = int) then
begin
result := i;
exit;
end;
end;
begin
InputQuery('Original', 'Enter bitmap filename:', bmpFile);
bmp := LoadBitmap(bmpFile);
writeln('Gathering colors');
GetBitmapSize(bmp, w, h);
areaColors := GetBitmapAreaColors(bmp, 0, 0, w-1, h-1);
for i := 0 to high(areaColors) do
for j := 0 to high(areaColors[i]) do
begin
idx := colList.returnInArray(areaColors[i][j]);
if ( (length(colList) = 0) or (idx = -1)) then // it is not in the list, add it
begin
colList.append(areaColors[i][j]);
countList.append(1);
end
else // it was in the list, add to the counter for it
inc(countList[idx]);
end;
for i := 0 to high(colList) do
writeln(format('%12d %12d', [colList[i], countList[i]]));
freeBitmap(bmp);
end.
Open up simba, open the script, and run it.
Enter the path to your bitmap, like c:\test\testimage.png
It should output this:
Progress Report:
Gathering colors
16777215 6254
255 9148
65280 8872
16711680 9054
0 5999
12632256 3650
8421504 3643
16776960 9091
16711935 8591
65535 8836
4210752 3662
Successfully executed.
The script looked at every pixel in the bitmap and tallied up how many times each color was encountered. The color is on the left, the count on the right.
If you haven't worked much with simba colors, they're packed in a way that doesn't translate well to a lot of websites or basic tools. There's conversion functions available, though.
Here's a little script to show the RGB values of what we just found:
Simba Code:
program rgb;
var
r, g, b, i: integer;
colors: TIntegerArray = [
16777215,
255,
65280,
16711680,
0,
12632256,
8421504,
16776960,
16711935,
65535,
4210752];
begin
for i := 0 to high(colors) do
begin
ColorToRGB(colors[i], r, g, b);
writeln('Color [', colors[i], '] is rgb [', r, ',', g, ',', b, ']');
end;
end;
That outputs these. You can use a site like
http://www.color-hex.com/color/ffffff or
http://colorcalc.com/ to see what they are. For instance, 16711935 is hot pink.
Progress Report:
Successfully executed.
Compiled successfully in 249 ms.
Color [16777215] is rgb [255,255,255]
Color [255] is rgb [255,0,0]
Color [65280] is rgb [0,255,0]
Color [16711680] is rgb [0,0,255]
Color [0] is rgb [0,0,0]
Color [12632256] is rgb [192,192,192]
Color [8421504] is rgb [128,128,128]
Color [16776960] is rgb [0,255,255]
Color [16711935] is rgb [255,0,255]
Color [65535] is rgb [255,255,0]
Color [4210752] is rgb [64,64,64]
STEP 1: OUR TARGET
Let's use the dark blue area as our target.
Take your test bitmap, open it up in your favorite editor, and carefully black out all of the dark blue area in the bottom left. I use gimp to edit images since it's free.
Save the new image in your test folder. Don't overwrite your original!! I'm going to call this the mask file.
Why would we do that? Because we can use this to get all of the information we need!
We are going to look at the mask file for black pixels. When we find one, we're going to look at the original image and see if the original had some real color in that spot.
If it did, we're going to save it because that's one of the colors of our target.
Let's run the displayColors script from above on the mask file. Here's all the colors in the mask file:
Progress Report:
Gathering colors
16777215 5708
255 9148
65280 8872
0 15599
12632256 3650
8421504 3643
16776960 9091
16711935 8591
65535 8836
4210752 3662
Successfully executed.
And how does that compare to the original?
We can see that color 16777215 used to have 6524 pixels but now it has 5708.
Color 0 went from 5999 to 15599, and, yes, 0 is black. Of course black went way up, we painted out all the blue.
16711680 used to have 9054. Now it's not even there. Want to bet that's blue if you look it up?
Let's make our script do this comparison work for us.
displayObjectColors.simba
Simba Code:
program displayObjectColors;
{$include_once srl-6/srl.simba}
var
origBMPfile, maskedBMPfile: string;
origBMP, maskedBMP, w, h, oldTarget, i: integer;
tpa: TPointArray;
tia: TIntegerArray;
begin
InputQuery('Original', 'Enter original bitmap filename:', origBMPfile);
InputQuery('Original', 'Enter masked bitmap filename:', maskedBMPfile);
origBMP := LoadBitmap(origBMPfile);
maskedBMP := LoadBitmap(maskedBMPfile);
writeln('Gathering blacked out colors');
GetBitmapSize(origBMP, w, h);
oldTarget := GetImageTarget();
SetTargetBitmap(maskedBMP);
findcolors(tpa, 0, 0, 0, w-1, h-1);
tia := FastGetPixels(origBMP, tpa);
tia.clearEquals();
for i := 0 to high(tia) do
writeln(tia[i]);
SetImageTarget(oldTarget);
freeBitmap(origBMP);
freeBitmap(maskedBMP);
end.
Run that, enter the original and masked bitmap paths, and it outputs this:
Progress Report:
Gathering blacked out colors
0
16711680
16777215
It worked; we got the same results as doing it manually.
We grabbed all the black from the mask into a list of points. Then we grabbed all the colors in those locations from the original.
WHAT'S UP WITH BLACK?
Did anyone notice the elephant in the room? These results are listing color 0 (i.e. black). Hello, people, our target doesn't have any black in it! It's blue and white. Are you paying attention?
Here's how we'll fix that problem:
Move along folks, nothing to see here...
That's right, we're just going to ignore this and throw it away.
It's there because the script didn't really compare the old and new pixels. It just grabbed black from one and color from another. It picked up the black color from the original that was still in the mask.
We could use double masking or compare every pixel to solve this. We could also use a unique color when we paint the mask.
Color 0 is pretty much junk for runescape. It's all over the backgrounds and whatnot, you typically don't want anything to do with it. So we're going to use a single black mask and an "I don't care" attitude for simplicity. Apologies to Brandon for the aneurysm.
ARE WE READY TO LOCATE OBJECTS?
Now that we threw away black, we have an honest list of the colors in the target. Blue (16711680) and White (16777215).
But we're not ready yet. Think about what happens if we use the white.
We're going to get back all kinds of junk. There's white all over this picture. The blue is the only thing that is unique. We need unique.
STEP 2: UNIQUE COLORS
All we need to do to get unique is query our color list back against the mask file.
The mask file has the target object colored out. If we can find a color in the mask file it means the color matches the background. We don't want colors that match the background.
This version of the script adds that logic. Save this script in your test folder (displayUniqueObjectColors.simba):
Simba Code:
program displayUniqueObjectColors;
{$include_once srl-6/srl.simba}
var
origBMPfile, maskedBMPfile, GtolStr: string;
origBMP, maskedBMP, w, h, oldTarget, i, Gtol: integer;
tpa: TPointArray;
possibleColor, goodColor: TIntegerArray;
begin
InputQuery('Original', 'Enter original bitmap filename:', origBMPfile);
InputQuery('Original', 'Enter masked bitmap filename:', maskedBMPfile);
InputQuery('Original', 'Enter tolerance:', GtolStr);
Gtol := StrToInt(GtolStr);
origBMP := LoadBitmap(origBMPfile);
maskedBMP := LoadBitmap(maskedBMPfile);
writeln('Gathering blacked out colors');
GetBitmapSize(origBMP, w, h);
oldTarget := GetImageTarget();
SetTargetBitmap(maskedBMP);
findcolors(tpa, 0, 0, 0, w-1, h-1);
possibleColor := FastGetPixels(origBMP, tpa);
possibleColor.clearEquals();
for i := 0 to high(possibleColor) do
begin
if (possibleColor[i] = 0) then continue;
setLength(tpa, 0);
findcolorstolerance(tpa, possibleColor[i], 0, 0, w-1, h-1, Gtol);
if (length(tpa) > 0) then continue; // this color is also in the background, no good
goodColor.append(possibleColor[i]);
end;
writeln('Good colors:');
for i := 0 to high(goodColor) do
writeln(goodColor[i]);
SetImageTarget(oldTarget);
freeBitmap(origBMP);
freeBitmap(maskedBMP);
end.
Run this against your example and mask. Enter 0 for the tolerance, we don't need that yet.
You should get this:
Progress Report:
Gathering blacked out colors
Good colors:
[16711680]
We did it! Now we have what we want, just the unique blue.
TAKING IT TO THE REAL WORLD:
Let's see what this bad boy does if we run it against something a little more interesting. Here's a snap of the GAs and a mask file.
Run displayUniqueObjectColors against these pictures. It prints a big list of colors (1768 of them)! I'm not going to paste them here, run it yourself.
This means the abominations have 1768 colors that are not found on this background. Did you suspect the numbers would be so big? I didn't. Honestly, it's a little overwhelming.
We can use all of these to query for abominations. Most of the colors are only going to get us a few pixels per npc. I think we would prefer a small set of queries that returns a large of dots. We've done our job too well.
Let's relate this to coloring with the ACA tool. If you open ACA against the example picture and carefully select every single npc pixel, you would end up with this same list of 1768 colors.
What does ACA do when you select a bunch of colors and it gives you a 'best' color to use to match them all? I admit I haven't looked at the algorithms yet, but in general it is finding one color with a larger tolerance that encompasses the other colors entered.
Let's see what impact tolerance has to our color gathering. If you give the script a tolerance, it will use this when it queries the background for the color. So it finds color 117234 but instead of looking only for that specific color, we see if we get matches back with the tolerance boosted up. If the tolerant color bleeds into the background we throw it away just like we did before with the tolerance 0.
Rerun displayUniqueObjectColors against the abomination pictures, but this time enter a tolerance of 100. What happened? We got no results. Which makes sense. Tolerance is 0-255 so 100 is almost half. We're querying green but it's allowing matches to half the other colors in town.
Try a smaller number. Run it with 50.
Progress Report:
Gathering blacked out colors
Good colors:
1128905
1062591
1062074
13344861
13278812
11766611
11702100
10507325
6826184
13215327
12492379
1128907
1194961
1195218
11626044
14656346
14199656
13804124
12425560
996538
4329131
5776040
6629057
Much better! Let's explore what these look like. Save the numbers above in your test folder. Name the file ab.aca
Open the original abomination picture in paint, then open ACA. Do a file-> open and open your ab.aca file. Now you can click through the colors on the right and see what sorts of things we found. Notice how the best color is terrible and if you try to mark color it lights up the whole screen. Did our script fail us?
No. At least, I hope not. The problem is that we have the blues and reds in one list. It's trying to find one color to match them all. Manually delete all the blue colors from the list on the right of ACA. Then you can find a decent cts2 for the reds.
Let's run one last thing to see if we really got a proper list. Run this script against the original abomination file.
Simba Code:
program showus;
var
origBMPfile: string;
origBMP, outputBMP, w, h, oldTarget, i: integer;
tpa: TPointArray;
colors: TIntegerArray = [
1128905,
1062591,
1062074,
13344861,
13278812,
11766611,
11702100,
10507325,
6826184,
13215327,
12492379,
1128907,
1194961,
1195218,
11626044,
14656346,
14199656,
13804124,
12425560,
996538,
4329131,
5776040,
6629057 ];
begin
InputQuery('Original', 'Enter original bitmap filename:', origBMPfile);
origBMP := LoadBitmap(origBMPfile);
GetBitmapSize(origBMP, w, h);
outputBMP := Copybitmap(origBMP);
oldTarget := GetImageTarget();
SetTargetBitmap(origBMP);
for i := 0 to high(colors) do
begin
setLength(tpa, 0);
findcolorstolerance(tpa, colors[i], 0, 0, w-1, h-1, 50);
drawtpabitmap(outputBMP, tpa, 255);
end;
DisplayDebugImgWindow(w, h);
DrawBitmapDebugImg(outputBMP);
SetImageTarget(oldTarget);
freeBitmap(origBMP);
freeBitmap(outputBMP);
end;
You should get this for a result:
I'm going to claim that as a success! Every ab found, not one spec of background.
I guess that wraps up what I was looking to present. I hope some of you enjoyed it and got some new ideas about how to play with colors.