Jak automatycznie generować N „wyraźnych” kolorów?

194

Napisałem dwie metody poniżej, aby automatycznie wybrać N różnych kolorów. Działa poprzez zdefiniowanie częściowej funkcji liniowej na kostce RGB. Zaletą tego jest to, że możesz również uzyskać skalę progresywną, jeśli tego właśnie chcesz, ale kiedy N staje się duży, kolory mogą zacząć wyglądać podobnie. Mogę również wyobrazić sobie równomierne podzielenie kostki RGB na sieć, a następnie rysowanie punktów. Czy ktoś zna jakieś inne metody? Wykluczam definiowanie listy, a potem po prostu jej przeglądanie. Powinienem również powiedzieć, że generalnie nie dbam o to, czy kolidują ze sobą, czy też nie wyglądają ładnie, muszą być tylko wizualnie odrębne.

public static List<Color> pick(int num) {
    List<Color> colors = new ArrayList<Color>();
    if (num < 2)
        return colors;
    float dx = 1.0f / (float) (num - 1);
    for (int i = 0; i < num; i++) {
        colors.add(get(i * dx));
    }
    return colors;
}

public static Color get(float x) {
    float r = 0.0f;
    float g = 0.0f;
    float b = 1.0f;
    if (x >= 0.0f && x < 0.2f) {
        x = x / 0.2f;
        r = 0.0f;
        g = x;
        b = 1.0f;
    } else if (x >= 0.2f && x < 0.4f) {
        x = (x - 0.2f) / 0.2f;
        r = 0.0f;
        g = 1.0f;
        b = 1.0f - x;
    } else if (x >= 0.4f && x < 0.6f) {
        x = (x - 0.4f) / 0.2f;
        r = x;
        g = 1.0f;
        b = 0.0f;
    } else if (x >= 0.6f && x < 0.8f) {
        x = (x - 0.6f) / 0.2f;
        r = 1.0f;
        g = 1.0f - x;
        b = 0.0f;
    } else if (x >= 0.8f && x <= 1.0f) {
        x = (x - 0.8f) / 0.2f;
        r = 1.0f;
        g = 0.0f;
        b = x;
    }
    return new Color(r, g, b);
}
praca
źródło
4
Bardzo istotne programiści zadają interesujące odpowiedzi: „ Generowanie schematów kolorów - teoria i algorytmy ”.
Alexey Popkov,
2
Niestety postrzeganie kolorów przez człowieka nie jest liniowe. Może być konieczne uwzględnienie przesunięcia Bezolda-Brücke, jeśli używasz różnych intensywności. Są tu również dobre informacje: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

Odpowiedzi:

80

Za pomocą modelu kolorów HSL można tworzyć kolory.

Jeśli wszystko, czego chcesz, to różne odcienie (prawdopodobnie) i niewielkie różnice w jasności lub nasyceniu, możesz rozdzielić odcienie w następujący sposób:

// assumes hue [0, 360), saturation [0, 100), lightness [0, 100)

for(i = 0; i < 360; i += 360 / num_colors) {
    HSLColor c;
    c.hue = i;
    c.saturation = 90 + randf() * 10;
    c.lightness = 50 + randf() * 10;

    addColor(c);
}
strager
źródło
2
Ta technika jest mądra. Założę się, że przyniesie więcej efektów estetycznych niż moje.
mqp
45
Zakłada się, że równomiernie rozłożone wartości odcienia są równie percepcyjnie różne. Nawet pomijając różne formy ślepoty na kolory, nie jest to prawdą dla większości ludzi: różnica między 120 ° (zielony) a 135 ° (bardzo lekko miętowy zielony) jest niedostrzegalna, podczas gdy różnica między 30 ° (pomarańczowy) a 45 ° (brzoskwinia) jest dość oczywiste. Aby uzyskać najlepsze rezultaty, potrzebujesz nieliniowego odstępu wzdłuż odcienia.
Phrogz
18
@mquander - To wcale nie jest mądre. Nic nie stoi na przeszkodzie, aby ten algorytm przypadkowo wybrał dwa prawie identyczne kolory. Moja odpowiedź jest lepsza, a odpowiedź ohadsc jest znacznie lepsza.
Rocketmagnet
1
Jest to błędne z wyżej wymienionych powodów, ale także dlatego, że nie wybierasz jednorodnie .
sam hocevar,
3
@strager jaka jest oczekiwana wartość randf ()
Killrawr
242

To pytanie pojawia się w kilku dyskusjach SO:

Proponowane są różne rozwiązania, ale żadne nie jest optymalne. Na szczęście na ratunek przychodzi nauka

Arbitralne N.

Ostatnie 2 będą darmowe przez większość bibliotek uniwersyteckich / serwerów proxy.

N jest skończony i stosunkowo mały

W takim przypadku można wybrać rozwiązanie listowe. Bardzo interesujący artykuł na ten temat jest dostępny bezpłatnie:

Do rozważenia jest kilka list kolorów:

  • Lista 11 kolorów Boyntona, które prawie nigdy nie są mylone (dostępne w pierwszym artykule z poprzedniej sekcji)
  • 22 kolory maksymalnego kontrastu Kelly (dostępne na powyższym papierze)

Wpadłem również na paletę przez studenta MIT. Wreszcie następujące linki mogą być przydatne w konwersji między różnymi systemami kolorów / współrzędnymi (na przykład niektóre kolory w artykułach nie są określone w RGB):

Dla listy Kelly'ego i Boyntona dokonałem już konwersji na RGB (z wyjątkiem bieli i czerni, co powinno być oczywiste). Trochę kodu C #:

public static ReadOnlyCollection<Color> KellysMaxContrastSet
{
    get { return _kellysMaxContrastSet.AsReadOnly(); }
}

private static readonly List<Color> _kellysMaxContrastSet = new List<Color>
{
    UIntToColor(0xFFFFB300), //Vivid Yellow
    UIntToColor(0xFF803E75), //Strong Purple
    UIntToColor(0xFFFF6800), //Vivid Orange
    UIntToColor(0xFFA6BDD7), //Very Light Blue
    UIntToColor(0xFFC10020), //Vivid Red
    UIntToColor(0xFFCEA262), //Grayish Yellow
    UIntToColor(0xFF817066), //Medium Gray

    //The following will not be good for people with defective color vision
    UIntToColor(0xFF007D34), //Vivid Green
    UIntToColor(0xFFF6768E), //Strong Purplish Pink
    UIntToColor(0xFF00538A), //Strong Blue
    UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink
    UIntToColor(0xFF53377A), //Strong Violet
    UIntToColor(0xFFFF8E00), //Vivid Orange Yellow
    UIntToColor(0xFFB32851), //Strong Purplish Red
    UIntToColor(0xFFF4C800), //Vivid Greenish Yellow
    UIntToColor(0xFF7F180D), //Strong Reddish Brown
    UIntToColor(0xFF93AA00), //Vivid Yellowish Green
    UIntToColor(0xFF593315), //Deep Yellowish Brown
    UIntToColor(0xFFF13A13), //Vivid Reddish Orange
    UIntToColor(0xFF232C16), //Dark Olive Green
};

public static ReadOnlyCollection<Color> BoyntonOptimized
{
    get { return _boyntonOptimized.AsReadOnly(); }
}

private static readonly List<Color> _boyntonOptimized = new List<Color>
{
    Color.FromArgb(0, 0, 255),      //Blue
    Color.FromArgb(255, 0, 0),      //Red
    Color.FromArgb(0, 255, 0),      //Green
    Color.FromArgb(255, 255, 0),    //Yellow
    Color.FromArgb(255, 0, 255),    //Magenta
    Color.FromArgb(255, 128, 128),  //Pink
    Color.FromArgb(128, 128, 128),  //Gray
    Color.FromArgb(128, 0, 0),      //Brown
    Color.FromArgb(255, 128, 0),    //Orange
};

static public Color UIntToColor(uint color)
{
    var a = (byte)(color >> 24);
    var r = (byte)(color >> 16);
    var g = (byte)(color >> 8);
    var b = (byte)(color >> 0);
    return Color.FromArgb(a, r, g, b);
}

A oto wartości RGB w reprezentacjach szesnastkowych i 8-bitowych na kanał:

kelly_colors_hex = [
    0xFFB300, # Vivid Yellow
    0x803E75, # Strong Purple
    0xFF6800, # Vivid Orange
    0xA6BDD7, # Very Light Blue
    0xC10020, # Vivid Red
    0xCEA262, # Grayish Yellow
    0x817066, # Medium Gray

    # The following don't work well for people with defective color vision
    0x007D34, # Vivid Green
    0xF6768E, # Strong Purplish Pink
    0x00538A, # Strong Blue
    0xFF7A5C, # Strong Yellowish Pink
    0x53377A, # Strong Violet
    0xFF8E00, # Vivid Orange Yellow
    0xB32851, # Strong Purplish Red
    0xF4C800, # Vivid Greenish Yellow
    0x7F180D, # Strong Reddish Brown
    0x93AA00, # Vivid Yellowish Green
    0x593315, # Deep Yellowish Brown
    0xF13A13, # Vivid Reddish Orange
    0x232C16, # Dark Olive Green
    ]

kelly_colors = dict(vivid_yellow=(255, 179, 0),
                    strong_purple=(128, 62, 117),
                    vivid_orange=(255, 104, 0),
                    very_light_blue=(166, 189, 215),
                    vivid_red=(193, 0, 32),
                    grayish_yellow=(206, 162, 98),
                    medium_gray=(129, 112, 102),

                    # these aren't good for people with defective color vision:
                    vivid_green=(0, 125, 52),
                    strong_purplish_pink=(246, 118, 142),
                    strong_blue=(0, 83, 138),
                    strong_yellowish_pink=(255, 122, 92),
                    strong_violet=(83, 55, 122),
                    vivid_orange_yellow=(255, 142, 0),
                    strong_purplish_red=(179, 40, 81),
                    vivid_greenish_yellow=(244, 200, 0),
                    strong_reddish_brown=(127, 24, 13),
                    vivid_yellowish_green=(147, 170, 0),
                    deep_yellowish_brown=(89, 51, 21),
                    vivid_reddish_orange=(241, 58, 19),
                    dark_olive_green=(35, 44, 22))

Dla wszystkich programistów Java, oto kolory JavaFX:

// Don't forget to import javafx.scene.paint.Color;

private static final Color[] KELLY_COLORS = {
    Color.web("0xFFB300"),    // Vivid Yellow
    Color.web("0x803E75"),    // Strong Purple
    Color.web("0xFF6800"),    // Vivid Orange
    Color.web("0xA6BDD7"),    // Very Light Blue
    Color.web("0xC10020"),    // Vivid Red
    Color.web("0xCEA262"),    // Grayish Yellow
    Color.web("0x817066"),    // Medium Gray

    Color.web("0x007D34"),    // Vivid Green
    Color.web("0xF6768E"),    // Strong Purplish Pink
    Color.web("0x00538A"),    // Strong Blue
    Color.web("0xFF7A5C"),    // Strong Yellowish Pink
    Color.web("0x53377A"),    // Strong Violet
    Color.web("0xFF8E00"),    // Vivid Orange Yellow
    Color.web("0xB32851"),    // Strong Purplish Red
    Color.web("0xF4C800"),    // Vivid Greenish Yellow
    Color.web("0x7F180D"),    // Strong Reddish Brown
    Color.web("0x93AA00"),    // Vivid Yellowish Green
    Color.web("0x593315"),    // Deep Yellowish Brown
    Color.web("0xF13A13"),    // Vivid Reddish Orange
    Color.web("0x232C16"),    // Dark Olive Green
};

poniżej przedstawiono nieposortowane kolory kelly zgodnie z powyższą kolejnością.

nieposortowane kelly kolory

poniżej są posortowane kolory kelly według odcieni (zauważ, że niektóre żółcienie nie kontrastują)

 posortowane kolory kelly

Ohad Schneider
źródło
+1 Dziękuję bardzo za tę świetną odpowiedź! BTW, link color-journal.org/2010/5/10 nie żyje, ten artykuł jest nadal dostępny na stronie web.archive.org .
Aleksiej Popkow
16
Świetna odpowiedź, dzięki! Pozwoliłem sobie
David Mills,
1
Zauważyłem, że na tych listach jest odpowiednio tylko 20 i 9 kolorów. Chyba dlatego, że pomija się biel i czerń.
David Mills
2
Czy usługa sieciowa jest już dostępna?
Janus Troelsen,
38

Podobnie jak odpowiedź Uri Cohen, ale zamiast tego jest generatorem. Zacznie od używania kolorów daleko od siebie. Deterministyczny.

Próbka, najpierw pozostawione kolory: próba

#!/usr/bin/env python3.5
from typing import Iterable, Tuple
import colorsys
import itertools
from fractions import Fraction
from pprint import pprint

def zenos_dichotomy() -> Iterable[Fraction]:
    """
    http://en.wikipedia.org/wiki/1/2_%2B_1/4_%2B_1/8_%2B_1/16_%2B_%C2%B7_%C2%B7_%C2%B7
    """
    for k in itertools.count():
        yield Fraction(1,2**k)

def fracs() -> Iterable[Fraction]:
    """
    [Fraction(0, 1), Fraction(1, 2), Fraction(1, 4), Fraction(3, 4), Fraction(1, 8), Fraction(3, 8), Fraction(5, 8), Fraction(7, 8), Fraction(1, 16), Fraction(3, 16), ...]
    [0.0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.1875, ...]
    """
    yield Fraction(0)
    for k in zenos_dichotomy():
        i = k.denominator # [1,2,4,8,16,...]
        for j in range(1,i,2):
            yield Fraction(j,i)

# can be used for the v in hsv to map linear values 0..1 to something that looks equidistant
# bias = lambda x: (math.sqrt(x/3)/Fraction(2,3)+Fraction(1,3))/Fraction(6,5)

HSVTuple = Tuple[Fraction, Fraction, Fraction]
RGBTuple = Tuple[float, float, float]

def hue_to_tones(h: Fraction) -> Iterable[HSVTuple]:
    for s in [Fraction(6,10)]: # optionally use range
        for v in [Fraction(8,10),Fraction(5,10)]: # could use range too
            yield (h, s, v) # use bias for v here if you use range

def hsv_to_rgb(x: HSVTuple) -> RGBTuple:
    return colorsys.hsv_to_rgb(*map(float, x))

flatten = itertools.chain.from_iterable

def hsvs() -> Iterable[HSVTuple]:
    return flatten(map(hue_to_tones, fracs()))

def rgbs() -> Iterable[RGBTuple]:
    return map(hsv_to_rgb, hsvs())

def rgb_to_css(x: RGBTuple) -> str:
    uint8tuple = map(lambda y: int(y*255), x)
    return "rgb({},{},{})".format(*uint8tuple)

def css_colors() -> Iterable[str]:
    return map(rgb_to_css, rgbs())

if __name__ == "__main__":
    # sample 100 colors in css format
    sample_colors = list(itertools.islice(css_colors(), 100))
    pprint(sample_colors)
Janus Troelsen
źródło
+1 za próbkę, bardzo ładnie i pokazuje, że schemat jest również atrakcyjny. Pozostałe odpowiedzi tutaj zostałyby poprawione przez zrobienie tego samego, a następnie mogłyby być łatwo porównane.
Don Hatch,
3
Ilość jagniąt jest zbyt wysoka. Sonda lambda jest silna w tym przypadku.
Gyfis,
Wygląda świetnie, ale zacina się, gdy próbuję uruchomić go w wersji 2.7
Elad Weiss
33

Oto pomysł. Wyobraź sobie cylinder HSV

Zdefiniuj górną i dolną granicę jasności i nasycenia. Określa kwadratowy pierścień o przekroju w przestrzeni.

Teraz rozprosz losowo N punktów w tej przestrzeni.

Następnie zastosuj na nich algorytm iteracyjnego odpychania, albo dla określonej liczby iteracji, albo do momentu ustabilizowania się punktów.

Teraz powinieneś mieć N punktów reprezentujących N kolorów, które są tak różne, jak to możliwe, w przestrzeni kolorów, którą jesteś zainteresowany.

Hugo

Rocketmagnet
źródło
30

Dla przyszłych pokoleń dodaję tutaj akceptowaną odpowiedź w Pythonie.

import numpy as np
import colorsys

def _get_colors(num_colors):
    colors=[]
    for i in np.arange(0., 360., 360. / num_colors):
        hue = i/360.
        lightness = (50 + np.random.rand() * 10)/100.
        saturation = (90 + np.random.rand() * 10)/100.
        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    return colors
Uri Cohen
źródło
18

Wydaje się, że wszyscy przegapili istnienie bardzo użytecznej przestrzeni kolorów YUV, która została zaprojektowana w celu reprezentowania postrzeganych różnic kolorów w ludzkim układzie wzrokowym. Odległości w YUV reprezentują różnice w postrzeganiu człowieka. Potrzebowałem tej funkcjonalności dla MagicCube4D, która implementuje 4-wymiarowe kostki Rubika i nieograniczoną liczbę innych krętych łamigłówek 4D o dowolnej liczbie twarzy.

Moje rozwiązanie zaczyna się od wybrania losowych punktów w YUV, a następnie iteracyjnego rozbicia dwóch najbliższych punktów i konwersji do RGB tylko po zwróceniu wyniku. Metoda to O (n ^ 3), ale nie ma to znaczenia dla małych liczb lub tych, które można buforować. Z pewnością można go usprawnić, ale wyniki wydają się doskonałe.

Funkcja pozwala na opcjonalne określenie progów jasności, aby nie wytwarzać kolorów, w których żaden komponent nie jest jaśniejszy ani ciemniejszy niż podane ilości. IE możesz nie chcieć wartości zbliżonych do czerni lub bieli. Jest to przydatne, gdy powstałe kolory zostaną użyte jako kolory podstawowe, które zostaną później zacienione za pomocą oświetlenia, warstw, przezroczystości itp. I nadal muszą wyglądać inaczej niż kolory podstawowe.

import java.awt.Color;
import java.util.Random;

/**
 * Contains a method to generate N visually distinct colors and helper methods.
 * 
 * @author Melinda Green
 */
public class ColorUtils {
    private ColorUtils() {} // To disallow instantiation.
    private final static float
        U_OFF = .436f,
        V_OFF = .615f;
    private static final long RAND_SEED = 0;
    private static Random rand = new Random(RAND_SEED);    

    /*
     * Returns an array of ncolors RGB triplets such that each is as unique from the rest as possible
     * and each color has at least one component greater than minComponent and one less than maxComponent.
     * Use min == 1 and max == 0 to include the full RGB color range.
     * 
     * Warning: O N^2 algorithm blows up fast for more than 100 colors.
     */
    public static Color[] generateVisuallyDistinctColors(int ncolors, float minComponent, float maxComponent) {
        rand.setSeed(RAND_SEED); // So that we get consistent results for each combination of inputs

        float[][] yuv = new float[ncolors][3];

        // initialize array with random colors
        for(int got = 0; got < ncolors;) {
            System.arraycopy(randYUVinRGBRange(minComponent, maxComponent), 0, yuv[got++], 0, 3);
        }
        // continually break up the worst-fit color pair until we get tired of searching
        for(int c = 0; c < ncolors * 1000; c++) {
            float worst = 8888;
            int worstID = 0;
            for(int i = 1; i < yuv.length; i++) {
                for(int j = 0; j < i; j++) {
                    float dist = sqrdist(yuv[i], yuv[j]);
                    if(dist < worst) {
                        worst = dist;
                        worstID = i;
                    }
                }
            }
            float[] best = randYUVBetterThan(worst, minComponent, maxComponent, yuv);
            if(best == null)
                break;
            else
                yuv[worstID] = best;
        }

        Color[] rgbs = new Color[yuv.length];
        for(int i = 0; i < yuv.length; i++) {
            float[] rgb = new float[3];
            yuv2rgb(yuv[i][0], yuv[i][1], yuv[i][2], rgb);
            rgbs[i] = new Color(rgb[0], rgb[1], rgb[2]);
            //System.out.println(rgb[i][0] + "\t" + rgb[i][1] + "\t" + rgb[i][2]);
        }

        return rgbs;
    }

    public static void hsv2rgb(float h, float s, float v, float[] rgb) {
        // H is given on [0->6] or -1. S and V are given on [0->1]. 
        // RGB are each returned on [0->1]. 
        float m, n, f;
        int i;

        float[] hsv = new float[3];

        hsv[0] = h;
        hsv[1] = s;
        hsv[2] = v;
        System.out.println("H: " + h + " S: " + s + " V:" + v);
        if(hsv[0] == -1) {
            rgb[0] = rgb[1] = rgb[2] = hsv[2];
            return;
        }
        i = (int) (Math.floor(hsv[0]));
        f = hsv[0] - i;
        if(i % 2 == 0)
            f = 1 - f; // if i is even 
        m = hsv[2] * (1 - hsv[1]);
        n = hsv[2] * (1 - hsv[1] * f);
        switch(i) {
            case 6:
            case 0:
                rgb[0] = hsv[2];
                rgb[1] = n;
                rgb[2] = m;
                break;
            case 1:
                rgb[0] = n;
                rgb[1] = hsv[2];
                rgb[2] = m;
                break;
            case 2:
                rgb[0] = m;
                rgb[1] = hsv[2];
                rgb[2] = n;
                break;
            case 3:
                rgb[0] = m;
                rgb[1] = n;
                rgb[2] = hsv[2];
                break;
            case 4:
                rgb[0] = n;
                rgb[1] = m;
                rgb[2] = hsv[2];
                break;
            case 5:
                rgb[0] = hsv[2];
                rgb[1] = m;
                rgb[2] = n;
                break;
        }
    }


    // From http://en.wikipedia.org/wiki/YUV#Mathematical_derivations_and_formulas
    public static void yuv2rgb(float y, float u, float v, float[] rgb) {
        rgb[0] = 1 * y + 0 * u + 1.13983f * v;
        rgb[1] = 1 * y + -.39465f * u + -.58060f * v;
        rgb[2] = 1 * y + 2.03211f * u + 0 * v;
    }

    public static void rgb2yuv(float r, float g, float b, float[] yuv) {
        yuv[0] = .299f * r + .587f * g + .114f * b;
        yuv[1] = -.14713f * r + -.28886f * g + .436f * b;
        yuv[2] = .615f * r + -.51499f * g + -.10001f * b;
    }

    private static float[] randYUVinRGBRange(float minComponent, float maxComponent) {
        while(true) {
            float y = rand.nextFloat(); // * YFRAC + 1-YFRAC);
            float u = rand.nextFloat() * 2 * U_OFF - U_OFF;
            float v = rand.nextFloat() * 2 * V_OFF - V_OFF;
            float[] rgb = new float[3];
            yuv2rgb(y, u, v, rgb);
            float r = rgb[0], g = rgb[1], b = rgb[2];
            if(0 <= r && r <= 1 &&
                0 <= g && g <= 1 &&
                0 <= b && b <= 1 &&
                (r > minComponent || g > minComponent || b > minComponent) && // don't want all dark components
                (r < maxComponent || g < maxComponent || b < maxComponent)) // don't want all light components

                return new float[]{y, u, v};
        }
    }

    private static float sqrdist(float[] a, float[] b) {
        float sum = 0;
        for(int i = 0; i < a.length; i++) {
            float diff = a[i] - b[i];
            sum += diff * diff;
        }
        return sum;
    }

    private static double worstFit(Color[] colors) {
        float worst = 8888;
        float[] a = new float[3], b = new float[3];
        for(int i = 1; i < colors.length; i++) {
            colors[i].getColorComponents(a);
            for(int j = 0; j < i; j++) {
                colors[j].getColorComponents(b);
                float dist = sqrdist(a, b);
                if(dist < worst) {
                    worst = dist;
                }
            }
        }
        return Math.sqrt(worst);
    }

    private static float[] randYUVBetterThan(float bestDistSqrd, float minComponent, float maxComponent, float[][] in) {
        for(int attempt = 1; attempt < 100 * in.length; attempt++) {
            float[] candidate = randYUVinRGBRange(minComponent, maxComponent);
            boolean good = true;
            for(int i = 0; i < in.length; i++)
                if(sqrdist(candidate, in[i]) < bestDistSqrd)
                    good = false;
            if(good)
                return candidate;
        }
        return null; // after a bunch of passes, couldn't find a candidate that beat the best.
    }


    /**
     * Simple example program.
     */
    public static void main(String[] args) {
        final int ncolors = 10;
        Color[] colors = generateVisuallyDistinctColors(ncolors, .8f, .3f);
        for(int i = 0; i < colors.length; i++) {
            System.out.println(colors[i].toString());
        }
        System.out.println("Worst fit color = " + worstFit(colors));
    }

}
Melinda Green
źródło
Czy jest gdzieś wersja tego kodu w języku C #? Próbowałem przekonwertować go i uruchomić z tymi samymi argumentami, które przekazałeś do generowaniaVisallyDistinctColors () i wydaje się, że działa bardzo wolno. Czy to jest oczekiwane?
Chris Smith
Czy masz takie same wyniki? Jest dość szybki jak na moje potrzeby, ale jak powiedziałem, nie próbowałem go optymalizować, więc jeśli to twój jedyny problem, prawdopodobnie powinieneś zwrócić uwagę na przydzielanie / zwalnianie pamięci. Nic nie wiem o zarządzaniu pamięcią C #. W najgorszym przypadku można zredukować stałą 1000 pętli zewnętrznej do czegoś mniejszego, a różnica w jakości może nawet nie być zauważalna.
Melinda Green,
1
Moja paleta musi zawierać określone kolory, ale chciałam uzupełnić dodatki. Podoba mi się twoja metoda b / c Mogę umieścić moje wymagane kolory najpierw w tablicy yuv, a następnie zmodyfikować „j = 0”, aby rozpocząć optymalizację po moich wymaganych kolorach. Chciałbym, żeby zerwanie najgorszych par było trochę mądrzejsze, ale rozumiem, dlaczego to takie trudne.
Ryan
Myślę, że w twojej metodzie yuv2rgb brakuje zacisku (0,255).
Ryan
yuv2rgb to wszystko zmiennoprzecinkowe, a nie bajty Ryan. Napisz do [email protected] w celu omówienia.
Melinda Green,
6

Model kolorów HSL może dobrze nadawać się do „sortowania” kolorów, ale jeśli szukasz wizualnie odrębnych kolorów, zdecydowanie potrzebujesz zamiast tego modelu kolorów Lab .

CIELAB został zaprojektowany w taki sposób, aby był percepcyjnie jednolity pod względem widzenia kolorów u ludzi, co oznacza, że ​​ta sama ilość zmian numerycznych tych wartości odpowiada mniej więcej tej samej ilości postrzeganej wizualnie zmiany.

Kiedy już to wiesz, znalezienie optymalnego podzbioru N kolorów z szerokiej gamy kolorów jest nadal trudnym problemem (NP), trochę podobnym do problemu Podróżującego sprzedawcy i wszystkie rozwiązania wykorzystujące algorytmy k-średnich lub coś takiego nie będzie tak naprawdę Wsparcie.

To powiedziawszy, jeśli N nie jest zbyt duży i jeśli zaczniesz od ograniczonego zestawu kolorów, z łatwością znajdziesz bardzo dobry podzbiór różnych kolorów zgodnie z odległością Lab z prostą losową funkcją.

Zakodowałem takie narzędzie na własny użytek (można je znaleźć tutaj: https://mokole.com/palette.html ), oto, co dostałem dla N = 7: wprowadź opis zdjęcia tutaj

Wszystko jest w javascript, więc zajrzyj do źródła strony i dostosuj ją do własnych potrzeb.

fbparis
źródło
1
Odnośnie »tej samej wielkości zmiany liczbowej [...] tej samej wielkości zmiany postrzeganej wizualnie «. Bawiłem się próbnikiem kolorów CIE Lab i w ogóle nie mogłem tego potwierdzić. Ja kolorów oznaczenia laboratoryjne z wykorzystaniem pasma Lod 0 do 128, ai bod -128 do 128. ¶ że rozpoczęła L= 0, a= -128, b= -128, który stanowi jasnoniebieskiej barwy. Potem wzrosłem atrzy razy. ❶ Duża zmiana (+128) a= 50 powoduje tylko nieco ciemniejszy niebieski. ❷ (+85) a= 85 wyników wciąż w kolorze niebieskim. ❸ Jednak stosunkowo niewielka zmiana (+43) a= 128 całkowicie zmienia kolor na fuksja.
Socowi
Jest to dla mnie bardzo przydatne. Byłoby jednak idealnie, gdyby wyniki można było łatwo skopiować i wkleić.
Mitchell van Zuylen
5

Oto rozwiązanie problemu „wyraźnego” problemu, który jest całkowicie przesadzony:

Utwórz kulę jednostkową i upuść na nią punkty za pomocą odpychających ładunków. Uruchom układ cząsteczkowy, aż przestaną się poruszać (lub delta będzie „wystarczająco mała”). W tym momencie każdy z punktów jest jak najdalej od siebie. Konwertuj (x, y, z) na rgb.

Wspominam o tym, ponieważ w przypadku niektórych klas problemów ten rodzaj rozwiązania może działać lepiej niż brutalna siła.

Pierwotnie widziałem to podejście tutaj do mozaikowania kuli.

Ponownie, najbardziej oczywiste rozwiązania przechodzenia przez przestrzeń HSL lub przestrzeń RGB prawdopodobnie będą działać dobrze.

cokół
źródło
1
To dobry pomysł, ale prawdopodobnie warto użyć sześcianu zamiast kuli.
Rocketmagnet
1
Tak właśnie działa moje rozwiązanie oparte na YUV, ale nie dla pudełka 3D (nie sześcianu).
Melinda Green,
3

Spróbowałbym naprawić nasycenie i luminację do maksimum i skupić się tylko na odcieniu. Widzę, że H może przejść od 0 do 255, a następnie się zawija. Teraz, jeśli chcesz dwa kontrastujące kolory, weźmiesz przeciwne strony tego pierścienia, tj. 0 i 128. Jeśli chcesz 4 kolory, weźmiesz niektóre oddzielone przez 1/4 długości 256 koła, tj. 0, 64, 128, 192. I oczywiście, jak sugerowali inni, gdy potrzebujesz N kolorów, możesz po prostu oddzielić je od 256 / N.

Dodałbym do tego pomysłu użycie odwróconej reprezentacji liczby binarnej do utworzenia tej sekwencji. Spójrz na to:

0 = 00000000  after reversal is 00000000 = 0
1 = 00000001  after reversal is 10000000 = 128
2 = 00000010  after reversal is 01000000 = 64
3 = 00000011  after reversal is 11000000 = 192

... w ten sposób, jeśli potrzebujesz N różnych kolorów, możesz po prostu wziąć pierwsze N ​​liczb, odwrócić je i uzyskać jak najwięcej odległych punktów (bo N jest potęgą dwóch), zachowując jednocześnie każdy prefiks sekwencja bardzo się różni.

Był to ważny cel w moim przypadku użycia, ponieważ miałem tabelę, w której kolory zostały posortowane według obszaru objętego tym kolorem. Chciałem, aby największe obszary na wykresie miały duży kontrast, i nie przeszkadzało mi to, że niektóre małe obszary miały kolory podobne do kolorów z pierwszej dziesiątki, ponieważ dla czytelnika było oczywiste, który z nich po prostu obserwując ten obszar.

qbolec
źródło
Tak właśnie zrobiłem w swojej odpowiedzi, choć trochę bardziej „ matematycznie ”. Zobacz funkcję getfracs. Ale twoje podejście jest szybkie i „proste” w językach niskopoziomowych: nieco cofania w C .
Janus Troelsen,
Właśnie zauważyłem, że zrobił to także Ridiculous Fish
Janus Troelsen
1

Jeśli N jest wystarczająco duży, dostaniesz podobne kolory. Na świecie jest ich tylko tyle.

Dlaczego nie po prostu równomiernie rozdzielić je w widmie, tak:

IEnumerable<Color> CreateUniqueColors(int nColors)
{
    int subdivision = (int)Math.Floor(Math.Pow(nColors, 1/3d));
    for(int r = 0; r < 255; r += subdivision)
        for(int g = 0; g < 255; g += subdivision)
            for(int b = 0; b < 255; b += subdivision)
                yield return Color.FromArgb(r, g, b);
}

Jeśli chcesz pomieszać sekwencję, aby podobne kolory nie były obok siebie, możesz przetasować wynikową listę.

Czy to rozumiem?

mqp
źródło
2
Tak, nie myślisz o tym. Niestety postrzeganie kolorów przez człowieka nie jest liniowe. Może być konieczne uwzględnienie przesunięcia Bezolda-Brücke, jeśli używasz różnych intensywności. Są tu również dobre informacje: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex
1

Jest to trywialne w MATLAB (istnieje polecenie hsv):

cmap = hsv(number_of_colors)
Arturo
źródło
1

Napisałem pakiet dla R o nazwie Qualpalr, który został zaprojektowany specjalnie do tego celu. Polecam spojrzeć na winietę, aby dowiedzieć się, jak to działa, ale postaram się podsumować główne punkty.

Qualpalr bierze specyfikację kolorów w przestrzeni kolorów HSL (która została wcześniej opisana w tym wątku), rzutuje ją na przestrzeń kolorów DIN99d (która jest percepcyjnie jednolita) i znajduje tę, nktóra maksymalizuje minimalną odległość między nimi.

# Create a palette of 4 colors of hues from 0 to 360, saturations between
# 0.1 and 0.5, and lightness from 0.6 to 0.85
pal <- qualpal(n = 4, list(h = c(0, 360), s = c(0.1, 0.5), l = c(0.6, 0.85)))

# Look at the colors in hex format
pal$hex
#> [1] "#6F75CE" "#CC6B76" "#CAC16A" "#76D0D0"

# Create a palette using one of the predefined color subspaces
pal2 <- qualpal(n = 4, colorspace = "pretty")

# Distance matrix of the DIN99d color differences
pal2$de_DIN99d
#>        #69A3CC #6ECC6E #CA6BC4
#> 6ECC6E      22                
#> CA6BC4      21      30        
#> CD976B      24      21      21

plot(pal2)

wprowadź opis zdjęcia tutaj

Johan Larsson
źródło
1

Myślę, że ten prosty algorytm rekurencyjny uzupełnia przyjętą odpowiedź w celu wygenerowania wyraźnych wartości odcienia. Zrobiłem to dla hsv, ale może być również używane do innych przestrzeni kolorów.

Generuje odcienie w cyklach, możliwie jak najbardziej osobno w każdym cyklu.

/**
 * 1st cycle: 0, 120, 240
 * 2nd cycle (+60): 60, 180, 300
 * 3th cycle (+30): 30, 150, 270, 90, 210, 330
 * 4th cycle (+15): 15, 135, 255, 75, 195, 315, 45, 165, 285, 105, 225, 345
 */
public static float recursiveHue(int n) {
    // if 3: alternates red, green, blue variations
    float firstCycle = 3;

    // First cycle
    if (n < firstCycle) {
        return n * 360f / firstCycle;
    }
    // Each cycle has as much values as all previous cycles summed (powers of 2)
    else {
        // floor of log base 2
        int numCycles = (int)Math.floor(Math.log(n / firstCycle) / Math.log(2));
        // divDown stores the larger power of 2 that is still lower than n
        int divDown = (int)(firstCycle * Math.pow(2, numCycles));
        // same hues than previous cycle, but summing an offset (half than previous cycle)
        return recursiveHue(n % divDown) + 180f / divDown;
    }
}

Nie mogłem znaleźć tego rodzaju algorytmu tutaj. Mam nadzieję, że to pomoże, to mój pierwszy post tutaj.

David Fernandez
źródło
0

Ta funkcja OpenCV wykorzystuje model kolorów HSV do generowania nrównomiernie rozłożonych kolorów wokół 0 <= H <= 360º z maksymalnym S = 1,0 i V = 1,0. Funkcja generuje kolory BGR w bgr_mat:

void distributed_colors (int n, cv::Mat_<cv::Vec3f> & bgr_mat) {
  cv::Mat_<cv::Vec3f> hsv_mat(n,CV_32F,cv::Vec3f(0.0,1.0,1.0));
  double step = 360.0/n;
  double h= 0.0;
  cv::Vec3f value;
  for (int i=0;i<n;i++,h+=step) {
    value = hsv_mat.at<cv::Vec3f>(i);
    hsv_mat.at<cv::Vec3f>(i)[0] = h;
  }
  cv::cvtColor(hsv_mat, bgr_mat, CV_HSV2BGR);
  bgr_mat *= 255;
}
Obywatel
źródło