1

I'm wondering what the best way to approach this problem is...

I have an array of user defined colors that get pushed onto a stack as rgb values. I want to find a second color for each user-defined color. These second colors must be completely unique from the user-defined array, but can be completely random and do not need to have any visual relationship with any of the user colors.

Would generating random rgb values and testing it against the stack be fast, since it is highly unlikely that random numbers would find the same color on the first try. Or would simply starting at 255,254,253 and substracting 1 from each value, then testing it against the stack be better, also very unlikely and less operations. Or any other idea?

anson
  • 4,156
  • 2
  • 22
  • 30
  • How likely is it that all of the original colours will be close to (255,255,255)? If it is, then random selection seems a better way to go. – Oliver Charlesworth Dec 03 '11 at 23:52
  • 1
    The one that you implement in the next 5 minutes, unless it's *proven* to be too slow :) I'd choose random numbers, as then it might actually be somewhat interesting, whatever it is. For a small-ish initial array there is no point to switch to a map and/or employ a more sophisticated tree/algorithm. –  Dec 03 '11 at 23:52
  • Also, a stack doesn't seem like the best data structure for this task... – Oliver Charlesworth Dec 03 '11 at 23:52
  • Use random numbers. No reason not to. – Boundless Dec 03 '11 at 23:54
  • @Boundless: The argument against random numbers is that presumably it takes more CPU time to generate the next random number than the next number in a decreasing sequence. – Oliver Charlesworth Dec 03 '11 at 23:55
  • 1
    @OliCharlesworth I'll add that to "the bucket of wasted cycles that makes no difference" ;-) –  Dec 03 '11 at 23:56
  • Whether you pick a random color or start at the top and work your way down, it's the same operational complexity to check for duplicates. – evan Dec 03 '11 at 23:56
  • How many colors are we talking about here? Also, do the generated colors also need to differ from each other or could they all be the same (you only state that they must differ from the user defined colors)? – Roger Lindsjö Dec 04 '11 at 00:00
  • @OliCharlesworth I still see no reason not to use random numbers. The microsecond difference will go unnoticed, and using random colors will make the program more interesting. – Boundless Dec 04 '11 at 00:01
  • @OliCharlesworth: Well it's likely a number would be 255,255,255 but I find it rare that 255,254,253 or any decremented value would be used (I usually dont see #FEFDFC around anywhere). And about the stack, what would make sense to you? – anson Dec 04 '11 at 00:04
  • @RogerLindsjö: It depends on the user, it could any amount of colors, so obviously there comes a point where there will be duplicates and would simply stop working. And yes the generated colors must be unique as well. – anson Dec 04 '11 at 00:08

3 Answers3

2

Here's some code to start off:

var colors = [];

function getRandUniqColor()
{
    var r = getRGBString(),
        g = getRGBString(),
        b = getRGBString(),
        rgb = r + g + b;

    if ( colors.indexOf(rgb) == -1 )
    {
        colors.push(rgb);
        return {
            r: r,
            g: g,
            b: b
        }
    }
    else
    {
        return getRandUniqColor()
    }
}

function getRGBString()
{
    var num = Math.floor( Math.random() * 255 ).toString();
    while (num.length < 3)
    {
        num = '0' + num;
    }
    return num;
}

Just call getRandUniqColor(), and you'll get an object literal of RGB values.

Note: If there'll be a lot of colors involved, you should not use a recursive function.

...and here's the fiddle: http://jsfiddle.net/wV6Mw/

Joseph Silber
  • 214,931
  • 59
  • 362
  • 292
  • Yes, I believe this will be the most efficient way on my end (given exactly what I'm trying to do, most of which I've left out of discussion). I like your way of prepending 0's to numbers, I haven't seen it done that way. – anson Dec 04 '11 at 01:04
  • @andbeyond - Oh, how I wish I could do `str_pad($num, 3, "0", STR_PAD_LEFT)` in JavaScript! On a side note: don't forget to polyfill `indexOf` for IE. – Joseph Silber Dec 04 '11 at 01:13
  • Shouldn't the OP generate new colors that are some distance away from the existing colors (so they are visibly different) rather than just a tiny bit different? – jfriend00 Dec 04 '11 at 02:59
  • I also just may convert all values, user defined and computer defined , to hex values using toString and compare that way, to keep things standard across the app. – anson Dec 04 '11 at 05:15
  • @jfriend00 - The OP [clearly doesn't care](http://stackoverflow.com/questions/8371997/fastest-way-to-generate-a-color-that-is-not-in-an-array-of-colors/8372437#comment-10331191) about this... – Joseph Silber Dec 04 '11 at 19:08
1

I would suggest generating random RGB values (combine three random numbers) and then create a function that measures the color difference between your generated random color and the ones in your data structure. If your generated color is too close to one you already have, generate a new one. You will have to decide how "different" you want the random color to be from what you already have, but a difference of only a few points in R, G an B won't be discernibly different so you should be looking for some minimum separation in at least one channel.

Searching for "calculate rgb color difference" gives you a bunch of references on how to calculate a meaningful difference between two color values. This link from that search has a specific equation and here's a previous question on SO about color differences.

You can also generate complementary colors from what you already have if you just want different colors and don't need/want truly random colors.

You could generate a random color like this in either data form or hex string form:

// generate r,g,b data structure like {r: 255, g: 30, b: 09}
function generateRandomColorData() {
    function randomColorVal() {
        return(Math.floor(Math.random() * 256));
    }
    return {r:randomColorVal(), g: randomeColorVal(), b: randomColorVal()};
}

// generate hex string color value like FF4409
function generateRandomColorHex() {
    function randomColorVal() {
        var val = Math.floor(Math.random() * 256).toString(16);
        if (val.length < 2) {
            val = "0" + val;
        }
        return(val);
    }
    return(randomColorVal() + randomColorVal() + randomColorVal());
}
Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Just to reiterate, the generated color does not need to have any visual relationship with any of the user colors, it could be off by only one value and I will be happy, the user will never see these colors. – anson Dec 04 '11 at 00:10
  • @andbeyond Then why don't you store the numbers as 9 character strings in an array, and just check the array for your randomly generated string? – Joseph Silber Dec 04 '11 at 00:15
  • @JosephSilber: off the top of my head, that sounds like probably the best idea thrown around here yet. Since that would mean I would just need to generate one random number between 1 and 255255255. – anson Dec 04 '11 at 00:25
  • @andbeyond - Wrong. You can't do this by generating a random number between those values, since you might get something like `184736256`, which is out of range. You'll still have to generate three separate numbers in the 0-255 range, but you can then concatenate them (after padding them with 0's), and easily check if they're in your array. – Joseph Silber Dec 04 '11 at 00:49
  • @JosephSilber: Yes obviously, whenever I think off the top of my head I am wrong! Thanks though I still believe this is easier. – anson Dec 04 '11 at 00:59
  • I added a random color function to my answer. – jfriend00 Dec 04 '11 at 02:11
0

As others have said, the easiest way would probably be to just create random colors and check for collisions.

If you are filling up the color space a lot (users selecting millions of colors) then another way would be to create a color cube of 256^3 bits, and let "1" represent the used colors. Then you could start from position 0 and use the first "0" as your generated color, the next "0" as the second and so on. The data structure would be just over 2 MB of ram.

But random is probably the way to go.

Roger Lindsjö
  • 11,330
  • 1
  • 42
  • 53