Results 1 to 11 of 11

Thread: Finding colors using bitmaps / masks

  1. #1
    Join Date
    Oct 2013
    Location
    East Coast USA
    Posts
    770
    Mentioned
    61 Post(s)
    Quoted
    364 Post(s)

    Default Finding colors using bitmaps / masks

    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.

  2. #2
    Join Date
    Jul 2014
    Location
    Europe
    Posts
    182
    Mentioned
    7 Post(s)
    Quoted
    103 Post(s)

    Default

    Thank you very much for this tutorial, it's very kind of you. I think this is very useful for people that aren't familiar with finding colours, like me. I'll definitely look more into this when I have some more time on my hands, this certainly looks interesting!

  3. #3
    Join Date
    Aug 2014
    Location
    Australia
    Posts
    932
    Mentioned
    53 Post(s)
    Quoted
    495 Post(s)

    Default

    I didn't realise that you could use bitmaps to help with finding NPCs, I thought they were just for static objects. Ditto to Spaceblow's comment, thank you for the very well written tutorial.

  4. #4
    Join Date
    Jul 2014
    Location
    Europe
    Posts
    182
    Mentioned
    7 Post(s)
    Quoted
    103 Post(s)

    Default

    I don't understand something, you're saying:
    Quote Originally Posted by bonsai
    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.
    But if I look at your last snippet (program showus) you didn't delete any blue colours from that list?

  5. #5
    Join Date
    Oct 2013
    Location
    East Coast USA
    Posts
    770
    Mentioned
    61 Post(s)
    Quoted
    364 Post(s)

    Default

    Quote Originally Posted by Spaceblow View Post
    I don't understand something, you're saying:

    But if I look at your last snippet (program showus) you didn't delete any blue colours from that list?
    True. The last script/pic is displaying all the colors found.

    The previous comment was about trying to use your found colors in ACA.

    You need to weed the list to similar colors to be of any use in ACA. ACA is trying to find a generic color to match everything you give it. If you give it wildy different colors you get things that match the whole screen.

    Of course, this is the long way to get an ACA color. You can just open it up and pick a few colors and do ok.

    Ultimately, it would be nice if the presented code could start with the bmp and mask and output a few nice CTS 2 colors you could use.

    It's been interesting playing with all this. Before I started I would have guessed an NPC had 50, maybe 100 colors. Not the 2-3000 I'm seeing.

  6. #6
    Join Date
    Jul 2014
    Location
    Europe
    Posts
    182
    Mentioned
    7 Post(s)
    Quoted
    103 Post(s)

    Default

    Quote Originally Posted by bonsai View Post
    It's been interesting playing with all this. Before I started I would have guessed an NPC had 50, maybe 100 colors. Not the 2-3000 I'm seeing.
    Hmm alright. This is definitely interesting and very educational. You must be a genius to come up with something like this.

  7. #7
    Join Date
    Sep 2014
    Posts
    37
    Mentioned
    0 Post(s)
    Quoted
    18 Post(s)

    Default

    This one cleared some questions in my head ^^

  8. #8
    Join Date
    Feb 2009
    Location
    Irvine, CA
    Posts
    2,873
    Mentioned
    8 Post(s)
    Quoted
    138 Post(s)

    Default

    Hi Bonsai,

    Thanks for this guide. It is a very interesting way of getting better coloring. I usually just use ACA, but this method has proved to be much more accurate with my most recent script. +REP

  9. #9
    Join Date
    Oct 2013
    Location
    East Coast USA
    Posts
    770
    Mentioned
    61 Post(s)
    Quoted
    364 Post(s)

    Default

    Quote Originally Posted by TomTuff View Post
    Hi Bonsai,

    Thanks for this guide. It is a very interesting way of getting better coloring. I usually just use ACA, but this method has proved to be much more accurate with my most recent script. +REP
    Thanks. Someone else PMed me and said they were finding this faster than ACA. I found that curious. This method seems tedious to me.

    I see it as a nice way to get a list of all the relevant colors but probably only worth it for tough cases.

  10. #10
    Join Date
    Feb 2009
    Location
    Irvine, CA
    Posts
    2,873
    Mentioned
    8 Post(s)
    Quoted
    138 Post(s)

    Default

    Quote Originally Posted by bonsai View Post
    Thanks. Someone else PMed me and said they were finding this faster than ACA. I found that curious. This method seems tedious to me.

    I see it as a nice way to get a list of all the relevant colors but probably only worth it for tough cases.
    While there's no way this is faster than ACA, I did it pretty quickly with photoshop using the magic eraser + quick selection tools and the pencil tools for edges/filling in the middle. Took me around 5 minutes.

  11. #11
    Join Date
    Oct 2012
    Posts
    1,258
    Mentioned
    40 Post(s)
    Quoted
    588 Post(s)

    Default

    wow almost 2 years since the last post. Sexy guide, I found it very helpful with picking some tough colors, thanks @bonsai;

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •