Як автоматично генерувати N "різних" кольорів?


194

Я написав два способи нижче, щоб автоматично вибрати N різних кольорів. Він працює, визначаючи кусочно-лінійну функцію на RGB-кубі. Перевага цього полягає в тому, що ви також можете отримати прогресивну шкалу, якщо саме цього ви хочете, але коли N стає великим, кольори можуть почати виглядати схожим. Я також можу уявити рівномірний розподіл RGB-куба на решітку, а потім нанесення точок. Хтось знає якісь інші методи? Я виключаю визначення списку, а потім просто перегляд його. Я також повинен сказати, що мене взагалі не хвилює, стикаються вони чи не виглядають приємно, вони просто мають бути візуально чіткими.

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);
}

5
Надзвичайно актуальне запитання програмістів із цікавими відповідями: " Генерація кольорових схем - теорія та алгоритми ".
Олексій Попков

2
На жаль, сприйняття кольором людини не є лінійним. Можливо, вам також знадобиться врахувати зміну Безольда-Брюкке, якщо ви використовуєте різну інтенсивність. Тут також є добра інформація: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

Відповіді:


80

Для створення кольорів ви можете використовувати кольорову модель HSL .

Якщо все, що вам потрібно - це різні відтінки (ймовірно), і незначні зміни легкості або насиченості, ви можете розподілити відтінки так:

// 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);
}

2
Ця техніка розумна. Б'юсь об заклад, це отримає більш естетичні результати, ніж у мене.
mqp

45
Це передбачає, що однаково розташовані значення відтінку однаково сприймаються. Навіть дисконтуючи різні форми сліпоти кольорів, це не відповідає дійсності більшості людей: різниця між 120 ° (зелений) і 135 ° (дуже злегка м'ятний зелений) непомітна, тоді як різниця між 30 ° (помаранчевий) і 45 ° (персиковий) цілком очевидно. Для отримання найкращих результатів вам потрібен нелінійний інтервал по відтінку.
Фрогз

18
@mquander - Це зовсім не розумно. Ніщо не заважає цьому алгоритму випадково вибрати два майже однакових кольору. Моя відповідь краща, а відповідь ohadsc набагато краща.
Rocketmagnet

1
Це неправильно з вищезгаданих причин, а також тому, що ви не збираєтесь рівномірно .
sam hocevar

3
@strager, яке очікуване значення randf ()
Killrawr

242

Ці запитання з'являються у досить багатьох дискусіях ТА

Запропоновано різні рішення, але жодне не є оптимальним. На щастя, на допомогу приходить наука

Довільна N

Останні 2 будуть безкоштовними через більшість університетських бібліотек / проксі.

N є кінцевим і відносно невеликим

У цьому випадку можна було б вирішити список. Дуже цікава стаття в темі є у вільному доступі:

Існує кілька списків кольорів, які слід врахувати:

  • Список Бойтона з 11 кольорів, які майже ніколи не плутають (доступний у першій статті попереднього розділу)
  • 22 кольори максимальної контрастності Келлі (доступні в статті вище)

Я також наткнувся на цю Палітру студентом MIT. Нарешті, наступні посилання можуть бути корисними для перетворення між різними кольоровими системами / координатами (деякі кольори у статтях не вказані, наприклад, у RGB):

Щодо списку Келлі та Бойтона, я вже здійснив перетворення на RGB (за винятком білого та чорного, що має бути очевидним). Деякі 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);
}

Ось значення RGB у шістнадцяткових та 8-бітових поданнях на канал:

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))

Для всіх ваших розробників Java ось кольори 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
};

далі - несорті кольори келі, згідно з порядком, наведеним вище.

несорті колірні кольори

далі - відсортовані кольори келі відповідно до відтінків (зауважте, що деякі жовті не дуже контрастні)

 сортовані кольори келлі


+1 Дякую вам дуже за цю чудову відповідь! До речі, посилання colour-journal.org/2010/5/10 мертва, ця стаття все ще доступна на веб-сайті web.archive.org .
Олексій Попков


16
Чудова відповідь, дякую! Я взяв на себе сміливість перетворити ці два кольорові набори у зручну jsfiddle, де можна побачити кольори в дії.
Девід Міллс

1
Щойно помічено, у цих списках є лише 20 та 9 кольорів відповідно. Я здогадуюсь тому, що білі та чорні опущені.
Девід Міллс

2
Чи доступний веб-сервіс ще?
Янус Трольсен

38

Як і відповідь Урі Коена, але натомість є генератором. Почнемо з використання кольорів далеко один від одного. Детермінований.

Зразок спочатку зліва кольорів: зразок

#!/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)

+1 для вибірки, дуже приємно, і показує, що схема також приваблива. Інші відповіді тут можна було б покращити, зробивши те саме, а потім можна було б легко порівняти.
Дон Хетч

3
Кількість лямбдашів занадто чортово велика. Лямбда сильна з цим.
Gyfis

Виглядає чудово, але застрягає, коли я намагаюся запустити його 2,7
Elad Weiss

33

Ось ідея. Уявіть циліндр HSV

Визначте верхню та нижню межі, необхідні для яскравості та насиченості. Це визначає кільце квадратного перерізу в просторі.

Тепер розкидайте N точок випадковим чином у цьому просторі.

Потім застосуйте до них ітеративний алгоритм відштовхування або для фіксованої кількості ітерацій, або до точки стабілізації.

Тепер у вас повинно бути N точок, що представляють N кольорів, які є приблизно максимально різними в кольоровому просторі, який вас цікавить.

Гюго


30

Ради наступних поколінь я додаю сюди прийняту відповідь на Python.

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

18

Начебто всі пропустили існування дуже корисного кольорового простору YUV, який був розроблений для відображення сприйнятих кольорових відмінностей у зоровій системі людини. Відстані в YUV представляють відмінності у сприйнятті людини. Мені була потрібна ця функціональність для MagicCube4D, який реалізує 4-мірні кубики Рубіка та необмежену кількість інших 4D крутих головоломок, що мають довільну кількість граней.

Моє рішення починається з вибору випадкових точок в YUV, а потім ітераційним розбиттям найближчих двох точок і перетворенням лише в RGB при поверненні результату. Метод є O (n ^ 3), але це не має значення для малих чисел, або тих, які можна кешувати. Це, безумовно, може бути більш ефективним, але результати здаються відмінними.

Ця функція дозволяє додатково вказати пороги яскравості, щоб не створювати кольори, в яких жоден компонент не яскравіший або темніший від заданих кількостей. IE, можливо, вам не потрібні значення, близькі до чорного або білого. Це корисно, коли отримані кольори будуть використовуватися як основні кольори, які пізніше затіняються за допомогою освітлення, шаруватості, прозорості тощо.

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));
    }

}

Чи є де-небудь версія C # цього коду? Я спробував перетворити його та запустити з тими ж аргументами, які ви передали дляgenerationVisualDistinctColors (), і, здається, він працює дуже повільно. Це очікується?
Кріс Сміт

Чи отримуєте ви однакові результати? Це досить швидко для моїх потреб, але, як я вже сказав, я не намагався його оптимізувати, тому, якщо це єдина ваша проблема, ви, мабуть, повинні звернути увагу на розподіл / розсилку пам'яті. Я нічого не знаю про управління пам'яттю C #. У гіршому випадку, ви можете зменшити константу 1000 зовнішньої петлі до чогось меншого, і різниця в якості може бути навіть не помітна.
Мелінда Грін

1
Моя палітра повинна містити певні кольори, але я хотів заповнити додаткові елементи. Мені подобається ваш метод b / c. Я можу розмістити потрібні кольори спочатку у своєму масиві yuv, а потім змінити "j = 0", щоб почати оптимізацію після потрібних кольорів. Я б хотів, щоб розпад гірших пар був трохи розумнішим, але я можу зрозуміти, чому це важко.
Райан

Я думаю, що у вашому методі yuv2rgb відсутній затискач (0,255).
Райан

yuv2rgb - це все плаває, а не байти Райана. Будь ласка, напишіть на melinda@superliminal.com для обговорення.
Мелінда Грін

7

Кольорова модель HSL цілком може підходити для сортування кольорів, але якщо ви шукаєте візуально виразні кольори, вам, безумовно, потрібна кольорова модель лабораторії .

CIELAB був розроблений таким чином, щоб бути однорідним у відношенні до кольорового зору людини, що означає, що однакова кількість числових змін цих значень відповідає приблизно однаковій кількості візуально сприйнятих змін.

Коли ви знаєте, що пошук оптимального набору з N кольорів із широкого спектру кольорів все ще є важкою проблемою (NP), подібною до проблеми продавця подорожей та всіх рішень за допомогою алгоритмів k-mean або чогось насправді не буде допомогу.

Це означає, що якщо N не надто великий і якщо ви почнете з обмеженого набору кольорів, ви легко знайдете дуже гарний підмножина різних кольорів відповідно до відстані в лабораторії з простою випадковою функцією.

Я зашифрував такий інструмент для власного використання (його можна знайти тут: https://mokole.com/palette.html ), ось що я отримав за N = 7: введіть тут опис зображення

Це все javascript, тому не соромтеся зазирнути до джерела сторінки та адаптуйте його для власних потреб.


1
Щодо » однакової кількості числових змін [...] однакова кількість візуально сприйнятих змін «. Я пограв із засобом вибору кольорів у лабораторії CIE і зовсім не зміг цього підтвердити. Я буду позначати лабораторії кольору , використовуючи діапазони Lвід 0 до 128 , а aі bвід -128 до 128. ¶ Я почав з L= 0, a= -128, b= -128 , який є яскраво - синім. Потім я збільшився aвтричі. ❶ Велика зміна (+128) a= 50 призводить до лише трохи темніше синього. ❷ (+85) a= 85 результатів ще синім кольором. ❸ Однак порівняно невелика зміна (+43) a= 128 повністю змінює колір на фуксію.
Socowi

Це дуже корисно для мене. Ідеально було б, якби результати було легко скопіювати та вставити.
Мітчелл ван Зуйлен

5

Ось рішення щодо керування вашою "чіткою" проблемою, яка повністю перекрита:

Створіть одиничну сферу і опустіть на ній точки, відбиваючи заряди. Запускайте систему частинок до тих пір, поки вони більше не рухаються (або дельта «недостатньо мала»). У цей момент кожна з точок знаходиться якнайдалі одна від одної. Перетворити (x, y, z) в rgb.

Я згадую це, тому що для певних класів проблем цей тип рішення може працювати краще, ніж груба сила.

Я спочатку бачив тут такий підхід для тесселяції сфери.

Знову ж таки, найбільш очевидні рішення проходження простору HSL або простору RGB, ймовірно, спрацюють чудово.


1
Це гарна ідея, але, мабуть, має сенс використовувати куб, а не сферу.
Rocketmagnet

1
Саме це робить моє рішення на основі YUV, окрім 3D-коробки (не куба).
Мелінда Грін

3

Я б спробував максимально наситити насиченість та освітленість та зосередитись лише на відтінку. Як я бачу, Н може переходити від 0 до 255 і потім обертатися. Тепер, якщо ви хотіли двох контрастних кольорів, ви взяли б протилежні сторони цього кільця, тобто 0 та 128. Якщо ви хотіли 4 кольори, ви взяли б кілька розділених на 1/4 256 довжини кола, тобто 0, 64,128,192. І звичайно, як запропонували інші, коли вам потрібно N кольорів, ви можете просто розділити їх на 256 / N.

Що я хотів би додати до цієї ідеї, це використовувати зворотне подання двійкового числа для формування цієї послідовності. Подивись на це:

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

... таким чином, якщо вам потрібно N різних кольорів, ви можете просто взяти перші N чисел, повернути їх назад, і ви отримаєте якомога більше віддалених точок (бо N - потужність двох), одночасно зберігаючи при цьому кожен префікс послідовність сильно відрізняється.

Це було важливою метою в моєму випадку використання, оскільки у мене була діаграма, де кольори сортували за площею, покритою цим кольором. Я хотів, щоб найбільші області діаграми мали великий контраст, і мені було нормально, щоб деякі невеликі області мали кольори, схожі на кольори з вершини 10, так як для читача було очевидно, який саме, який саме спостерігаючи за зоною.


Це я зробив у своїй відповіді, хоча трохи більше " математичної ". Дивіться функцію getfracs. Але ваш підхід швидко і «просто» на мовах низького рівня: біт реверсивний в C .
Янус Трольсен


1

Якщо N досить великий, ви отримаєте кілька схожих на вигляд кольорів. Їх у світі лише стільки.

Чому б просто не рівномірно розподілити їх по спектру, як так:

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);
}

Якщо ви хочете змішати послідовність так, щоб подібні кольори не знаходилися поруч, ви, можливо, перетасувати отриманий список.

Чи я це замислююся?


2
Так, ви це занадто не думаєте. На жаль, сприйняття кольором людини не є лінійним. Можливо, вам також знадобиться врахувати зміну Безольда-Брюкке, якщо ви використовуєте різну інтенсивність. Тут також є добра інформація: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex


1

Я написав пакет для R під назвою qualpalr, який розроблений спеціально для цієї мети. Рекомендую поглянути на віньєтку щоб дізнатись, як вона працює, але спробую підсумувати основні моменти.

kvalpalr приймає специфікацію кольорів у колірному просторі HSL (що було описано раніше в цій нитці), проектує його на кольоровий простір DIN99d (який є перцептивно рівномірним) і знаходить те, nщо максимально збільшує мінімальну відстань між будь-якими з них.

# 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)

введіть тут опис зображення


1

Я думаю, що цей простий рекурсивний алгоритм доповнює прийняту відповідь, щоб генерувати чіткі значення відтінку. Я зробив це для hsv, але його можна використовувати і для інших кольорових просторів.

Він генерує відтінки в циклах, максимально відокремлених один від одного в кожному циклі.

/**
 * 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;
    }
}

Мені не вдалося знайти такий алгоритм тут. Я сподіваюся, що це допомагає, це мій перший пост тут.


0

Ця функція OpenCV використовує кольорову модель HSV для генерування nрівномірно розподілених кольорів навколо 0 <= H <= 360º з максимальним S = 1,0 та V = 1,0. Функція виводить кольори BGR у 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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.