5

I want to have a static method, which whenever called will return a color value that didn't appear yet, and is not too close to last returned color (i.e. return new Color(last_value += 10) won't do). It also should be not random, so everytime the application is launched, the sequence of returned colors would be the same.

First thing that popped to my head, is to iterate over an array with a primal number step, something like this:

    private static HashMap<Integer, Boolean> used = new HashMap<>();
    private static int[] values = new int[0xfffff]; // 1/16th of possible colors
    private static int current = 0, jump = values.length / 7;

     public static Color getColour(){
        int value = values[current];
        used.put(current, true);
        current += jump;
        current %= values.length;
        //have some check here if all colors were used
        while (used.containsKey(current)){
            current++;
            current%=values.length;
        }
        return new Color(value);
    }

But I don't like it, since colors will be close to each other from some calls back.

Coderino Javarino
  • 2,819
  • 4
  • 21
  • 43
  • how do you exactly define nearness for the RGB color scheme? – sve Apr 21 '16 at 12:20
  • why you dont need a random colour ? i think at some point you have to use random . – Priyamal Apr 21 '16 at 12:24
  • @svs Well in terms that the colors are distinct from each other just by glancing at it, and the same time consist of wide pallete, so it's not everything just e.g. red. – Coderino Javarino Apr 21 '16 at 12:27
  • @Priyamal the colors used are part of user interface, and it would definitely be bothering for the user if every time he started the application, everything looked differently. – Coderino Javarino Apr 21 '16 at 12:28
  • @CoderinoJavarino `colors are distinct from each other just by glancing at it` and how do you define mathematically? – sve Apr 21 '16 at 12:29
  • How many colors do you want to generate this way? Also, I would caution you against using random or random-like colors in a user interface. The number of hideously ugly or unreadable colors and color combinations is astonishingly high. Probably much higher than the number of visually pleasing colors / combinations. – Jim Mischel Apr 21 '16 at 12:35
  • The "distance" between colors is difficult (e.g. see [this answer](http://stackoverflow.com/a/35114586/3182664)). It would be good to know whether there are further constraints. For example, how many distinct colors do you want? (Currently alternating between "black" and "white" would fulfill all constraints that you mentioned...). Should all colors have full saturation or brightness? It's hard to distinguish two RGB colors like `(0,0,0)` and (0,0,16)`, but it's easy (or easier) to distinguish HSV colors like `(0,255,255)` and `(16,255,255)`. – Marco13 Apr 21 '16 at 12:37

1 Answers1

5

A good way of generating non-repeating random-like sequences is using an LFSR.

/**
 * Linear feedback shift register
 *
 * Taps can be found at: See http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf See http://mathoverflow.net/questions/46961/how-are-taps-proven-to-work-for-lfsrs/46983#46983 See
 * http://www.newwaveinstruments.com/resources/articles/m_sequence_linear_feedback_shift_register_lfsr.htm See http://www.yikes.com/~ptolemy/lfsr_web/index.htm See
 * http://seanerikoconnor.freeservers.com/Mathematics/AbstractAlgebra/PrimitivePolynomials/overview.html
 *
 * @author OldCurmudgeon
 */
public class LFSR implements Iterable<BigInteger> {

    // Bit pattern for taps.
    private final BigInteger taps;
    // Where to start (and end).
    private final BigInteger start;

    // The poly must be primitive to span the full sequence.
    public LFSR(BigInteger primitivePoly, BigInteger start) {
        // Where to start from (and stop).
        this.start = start.equals(BigInteger.ZERO) ? BigInteger.ONE : start;
        // Knock off the 2^0 coefficient of the polynomial for the TAP.
        this.taps = primitivePoly.shiftRight(1);
    }

    public LFSR(BigInteger primitivePoly) {
        this(primitivePoly, BigInteger.ONE);
    }

    // Constructor from an array of taps.
    public LFSR(int[] taps) {
        this(asPoly(taps));
    }

    private static BigInteger asPoly(int[] taps) {
        // Build the BigInteger.
        BigInteger primitive = BigInteger.ZERO;
        for (int bit : taps) {
            primitive = primitive.or(BigInteger.ONE.shiftLeft(bit));
        }
        return primitive;
    }

    @Override
    public Iterator<BigInteger> iterator() {
        return new LFSRIterator(start);
    }

    private class LFSRIterator implements Iterator<BigInteger> {
        // The last one we returned.

        private BigInteger last = null;
        // The next one to return.
        private BigInteger next = null;

        public LFSRIterator(BigInteger start) {
            // Do not return the seed.
            last = start;
        }

        @Override
        public boolean hasNext() {
            if (next == null) {
                /*
                 * Uses the Galois form.
                 *
                 * Shift last right one.
                 *
                 * If the bit shifted out was a 1 - xor with the tap mask.
                 */
                boolean shiftedOutA1 = last.testBit(0);
                // Shift right.
                next = last.shiftRight(1);
                if (shiftedOutA1) {
                    // Tap!
                    next = next.xor(taps);
                }
                // Never give them `start` again.
                if (next.equals(start)) {
                    // Could set a finished flag here too.
                    next = null;
                }
            }
            return next != null;
        }

        @Override
        public BigInteger next() {
            // Remember this one.
            last = hasNext() ? next : null;
            // Don't deliver it again.
            next = null;
            return last;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("Not supported.");
        }

        @Override
        public String toString() {
            return LFSR.this.toString()
                    + "[" + (last != null ? last.toString(16) : "")
                    + "-" + (next != null ? next.toString(16) : "") + "]";
        }
    }

    @Override
    public String toString() {
        return "(" + taps.toString(32) + ")-" + start.toString(32);
    }

}

Now you just need an 8+8+8=24 tap value.

Using it is simple.

public void test() {
    // Sample 24-bit tap found on page 5 of
    // http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf
    int[] taps = new int[]{24, 23, 22, 17};
    LFSR lfsr = new LFSR(taps);
    int count = 100;
    for (BigInteger i : lfsr) {
        System.out.println("Colour: " + new Color(i.intValue()));
        if (--count <= 0) {
            break;
        }
    }
}

Features of an LFSR:

  • It will repeat - but not until all possible bit patterns have been generated (except 0).
  • The number generated is statistically a good random number.
  • The same sequence will be generated each time (choose a different tap if you want a different sequence).

To achieve the spacing you require I would suggest you add a filter and discard any that are too close (by your criterion) to the previous one.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213