Как сравнить цветовой объект и получить ближайший цвет в цвете []?

Допустим, у меня есть массив с цветами (со всем цветовым спектром, от красного до красного.). Более короткая версия будет выглядеть так:

public Color[] ColorArray = new Color[360] { Color.FromArgb(255, 245, 244, 242), Color.FromArgb(255, 245, 244, 240), Color.FromArgb(255, 245, 244, 238) }

Теперь, если у меня есть отдельный

Color object (Color c = Color.FromArgb(255, 14, 4, 5))

Как я могу получить значение в массиве, которое ближе всего к выбранному цвету? И возможно ли это вообще?

2 ответа

Решение

Цветовое расстояние не является точно определенной вещью. Итак, вот три способа измерить это:

  • Один метод, который проверяет только оттенки цветов, игнорируя как насыщенность, так и яркость
  • Тот, который только измеряет прямое расстояние в пространстве RGB
  • И тот, который весит оттенок, насыщенность и яркость в некотором роде.

Очевидно, что вы можете изменить магические числа в 3-м измерении: оттенок в 0-360, яркость и насыщенность в 0-1, поэтому с этими числами оттенок весит примерно в 3,6 раза сильнее, чем насыщенность и яркость.

Обновление: исходное решение, которое я разместил, содержало несколько ошибок:

  • Линк, который я использовал, не нашел ближайшего, но closestFromBelow; это означало 50% шансов быть выключенным одним.
  • В некоторых местах я использовал color.GetBrightness() метод. Это, мягко говоря, совершенно бесполезно. Для остроумия: Blue а также Yellow имеют одинаковое значение 0.5!
  • Значения для оттенка варьируются от 0 до 360, но, конечно, они оборачиваются! Я пропустил это полностью..

Я заменил большую часть исходного ответа исправленным кодом:

Теперь это новая версия методов, каждый из которых возвращает индекс наиболее близкого совпадения:

// closed match for hues only:
int closestColor1(List<Color> colors, Color target)
{
    var hue1 = target.GetHue();
    var diffs = colors.Select(n => getHueDistance(n.GetHue(), hue1));
    var diffMin = diffs.Min(n => n);
    return diffs.ToList().FindIndex(n => n == diffMin);
}

// closed match in RGB space
int closestColor2(List<Color> colors, Color target)
{
    var colorDiffs = colors.Select(n => ColorDiff(n, target)).Min(n =>n);
    return colors.FindIndex(n => ColorDiff(n, target) == colorDiffs);
}

// weighed distance using hue, saturation and brightness
int closestColor3(List<Color> colors, Color target)
{
    float hue1 = target.GetHue();
    var num1 = ColorNum(target);
    var diffs = colors.Select(n => Math.Abs(ColorNum(n) - num1) + 
                                   getHueDistance(n.GetHue(), hue1) );
    var diffMin = diffs.Min(x => x);
    return diffs.ToList().FindIndex(n => n == diffMin);
}

Несколько вспомогательных функций:

 // color brightness as perceived:
float getBrightness(Color c)  
    { return (c.R * 0.299f + c.G * 0.587f + c.B *0.114f) / 256f;}

// distance between two hues:
float getHueDistance(float hue1, float hue2)
{ 
    float d = Math.Abs(hue1 - hue2); return d > 180 ? 360 - d : d; }

//  weighed only by saturation and brightness (from my trackbars)
float ColorNum(Color c) { return c.GetSaturation() * factorSat + 
                                      getBrightness(c) * factorBri; }

// distance in RGB space
int ColorDiff(Color c1, Color c2) 
      { return  (int ) Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R) 
                               + (c1.G - c2.G) * (c1.G - c2.G)
                               + (c1.B - c2.B)*(c1.B - c2.B)); }

Вот небольшой помощник, который я использовал для скриншотов:

Brush tBrush(Color c) { return getBrightness(c) < 0.5 ? Brushes.White : Brushes.Black; }

Я обновил снимок экрана, чтобы отобразить не только 13 цветов, но и ряд в основном красноватых цветов для тестирования; все цвета показаны с их значениями для оттенка, насыщенности и яркости. Последние три числа являются результатами трех методов.

Как вы можете видеть, простой метод расстояний вводит в заблуждение оттенки ярких и ненасыщенных цветов: последний цвет (слоновая кость) на самом деле яркий и бледно-желтый!

Третий метод, который измеряет все цветовые свойства, лучше всего имо. Вы должны поиграть с калибровочными числами, хотя!

В конце концов, это действительно зависит от того, чего вы хотите достичь; если, как вам кажется, вас интересуют только оттенки цветов, просто выберите первый способ! Вы можете вызвать это, используя ваш массив следующим образом:

int indexInArray = closestColor1(clist.ToList(), someColor);

Больше о цветовых расстояниях смотрите в Википедии!

цветовые расстояния

// the colors I used:
// your array
Color[] clist = new Color[13];
clist[0] = Color.Blue;
clist[1] = Color.BlueViolet;
clist[2] = Color.Magenta;
clist[3] = Color.Purple;
clist[4] = Color.Red;
clist[5] = Color.Tomato;
clist[6] = Color.Orange;
clist[7] = Color.Yellow;
clist[8] = Color.YellowGreen;
clist[9] = Color.Green;
clist[10] = Color.SpringGreen;
clist[11] = Color.Cyan;
clist[12] = Color.Ivory;

// and a list of color to test:
List<Color> targets = new List<Color>();
targets.Add(Color.Pink);
targets.Add(Color.OrangeRed);
targets.Add(Color.LightPink);
targets.Add(Color.DarkSalmon);
targets.Add(Color.LightCoral);
targets.Add(Color.DarkRed);
targets.Add(Color.IndianRed);
targets.Add(Color.LavenderBlush);
targets.Add(Color.Lavender);

Попробуй это:

    static void Main()
    {
        Color[] ColorArray =
        {
            Color.FromArgb(255, 245, 244, 242), 
            Color.FromArgb(255, 245, 244, 240),
            Color.FromArgb(255, 245, 244, 238)
        };

        var closest = GetClosestColor(ColorArray, Color.FromArgb(255, 245, 244, 241));
        Console.WriteLine(closest);
    }

    private static Color GetClosestColor(Color[] colorArray, Color baseColor)
    {
        var colors = colorArray.Select(x => new {Value = x, Diff = GetDiff(x, baseColor)}).ToList();
        var min = colors.Min(x => x.Diff);
        return colors.Find(x => x.Diff == min).Value;
    }

    private static int GetDiff(Color color, Color baseColor)
    {
        int a = color.A - baseColor.A,
            r = color.R - baseColor.R,
            g = color.G - baseColor.G,
            b = color.B - baseColor.B;
        return a*a + r*r + g*g + b*b;
    }

здесь я интерпретирую closest как евклидово расстояние в пространстве ARGB

Другие вопросы по тегам