Получить цвет в определенном месте на градиенте

У меня есть следующее GradientStopCollection:

GradientStopCollection grsc = new GradientStopCollection(3);
grsc.Add(new GradientStop(Colors.Red, 0));
grsc.Add(new GradientStop(Colors.Yellow, .5));
grsc.Add(new GradientStop(Colors.Green, 1));

Могу ли я получить цвет в определенном «месте»? Например:

  • Местоположение 0: Красный
  • Расположение .5: Желтый
  • Местоположение .75: Yellow<~>Green

Есть ли API в WPF/какой-то сторонней библиотеке, которая могла бы это сделать?


person Matan Shahar    schedule 10.03.2012    source источник
comment
Я не думаю, что это определено где-либо в WPF. Я ожидаю, что это будет зависеть от реализации драйвера вашей видеокарты, уровня масштабирования, глубины цвета пользователей и т. д. Вы можете использовать метод Visual.PointToScreen, а затем Graphics.CopyFromScreen, чтобы захватить этот пиксель. Затем используйте Bitmap.GetPixel для получения сведений о цвете.   -  person akhisp    schedule 11.03.2012


Ответы (2)


Чтобы получить цвет в определенной точке, необходимо понимать рассматриваемый градиент, и это не роль класса GradientStopCollection. Концепция этого класса не в том, чтобы понимать градиент, а в том, чтобы быть простой коллекцией поддержки градиента.

Важно, чтобы вы понимали концепцию каждого класса.

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

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

public static class GradientStopCollectionExtensions
{
    public static Color GetRelativeColor(this GradientStopCollection gsc, double offset)
    {
        var point = gsc.SingleOrDefault(f => f.Offset == offset);
        if (point != null) return point.Color;

        GradientStop before = gsc.Where(w => w.Offset == gsc.Min(m => m.Offset)).First();
        GradientStop after = gsc.Where(w => w.Offset == gsc.Max(m => m.Offset)).First();

        foreach (var gs in gsc)
        {
            if (gs.Offset < offset && gs.Offset > before.Offset)
            {
                before = gs;
            }
            if (gs.Offset > offset && gs.Offset < after.Offset)
            {
                after = gs;
            }
        }

        var color = new Color();

        color.ScA = (float)((offset - before.Offset) * (after.Color.ScA - before.Color.ScA) / (after.Offset - before.Offset) + before.Color.ScA);
        color.ScR = (float)((offset - before.Offset) * (after.Color.ScR - before.Color.ScR) / (after.Offset - before.Offset) + before.Color.ScR);
        color.ScG = (float)((offset - before.Offset) * (after.Color.ScG - before.Color.ScG) / (after.Offset - before.Offset) + before.Color.ScG);
        color.ScB = (float)((offset - before.Offset) * (after.Color.ScB - before.Color.ScB) / (after.Offset - before.Offset) + before.Color.ScB);

        return color;
    }
}

PS: Этот алгоритм предполагает, что нет остановок с одинаковым смещением. Если есть несколько остановок с одинаковым смещением, будет выбрано InvalidOperationException.

Добавьте этот класс в текущий контекст (контекст пространства имен)

Чтобы получить свой цвет в любом месте, вы вставляете что-то вроде этого:

var color = grsc.GetRelativeColor(.75);
person Jonny Piazzi    schedule 10.03.2012
comment
Джонни, как вы думаете, вы могли бы перейти к stackoverflow.com/questions/16161931/ и опубликовать этот ответ? Я хочу, чтобы вы набрали очки. - person Rob Perkins; 24.04.2013
comment
Это именно то, что я искал, с одним недостатком: если смещение точно равно остановке градиента, она будет полностью игнорировать эту остановку градиента. Отсюда моя редакция. - person Underdetermined; 03.06.2016
comment
@Underdetermined: о, о, ... и где это редактирование? - person quetzalcoatl; 25.02.2018
comment
@quetzalcoatl: к сожалению, мое редактирование, похоже, не прошло рецензирование и/или потерялось. Прошло 1,5 года, и я не помню точно, что я изменил (и не могу искать свой исходный код, так как тогда я работал в другой компании. Поскольку это был единственный раз, когда я работал на С#, я оставлю кого-то более компетентного для внесите изменения, чтобы этот код работал. - person Underdetermined; 04.03.2018
comment
Второй оператор if должен начинаться с gs.Offset ›= offset, иначе граничные случаи обрабатываются неправильно. - person Lokno; 06.09.2018
comment
Код был обновлен, и к объяснению был добавлен отказ от ответственности для решения проблемы. - person Jonny Piazzi; 10.09.2018

Я попробовал метод, написанный Джонни Пьяцци. Но это не сработало правильно.
Поэтому я пишу свой собственный ниже:

private static Color GetColorByOffset(GradientStopCollection collection, double offset)
{
    GradientStop[] stops = collection.OrderBy(x => x.Offset).ToArray();
    if (offset <= 0) return stops[0].Color;
    if (offset >= 1) return stops[stops.Length - 1].Color;
    GradientStop left = stops[0], right = null;
    foreach (GradientStop stop in stops)
    {
        if (stop.Offset >= offset)
        {
            right = stop;
            break;
        }
        left = stop;
    }
    Debug.Assert(right != null);
    offset = Math.Round((offset - left.Offset)/(right.Offset - left.Offset), 2);
    byte a = (byte) ((right.Color.A - left.Color.A)*offset + left.Color.A);
    byte r = (byte) ((right.Color.R - left.Color.R)*offset + left.Color.R);
    byte g = (byte) ((right.Color.G - left.Color.G)*offset + left.Color.G);
    byte b = (byte) ((right.Color.B - left.Color.B)*offset + left.Color.B);
    return Color.FromArgb(a, r, g, b);
}

Я надеюсь, что это работает для вас!

Я использовал этот метод в своем коде xaml ниже, чтобы показать указанное число в качестве позиции тепловой карты.

<LinearGradientBrush x:Key="CountBrush" StartPoint="0 0" EndPoint="1 0">
    <GradientStop Offset="0.00" Color="ForestGreen"/>
    <GradientStop Offset="0.50" Color="Yellow"/>
    <GradientStop Offset="1.00" Color="OrangeRed"/>
</LinearGradientBrush>
<local:Int32ToColorConverter x:Key="CountToColorConverter" Min="0" Max="200" LinearBrush="{StaticResource CountBrush}"/>
person walterlv    schedule 12.09.2016
comment
Мне нравится использовать break, чтобы избежать ненужных итераций - person Wobbles; 03.01.2017
comment
Вы можете упростить код, удалив видимые итерации и используя что-то вроде GradientStop left = stops.Where(s => s.Offset <= offset).Last(); GradientStop right = stops.Where(s => s.Offset > offset).First(); - person Wobbles; 14.01.2017
comment
Спасибо! Это действительно упростило мой код. В этом случае все мои { и } пропали. Но я думаю, вам следует использовать FirstOrDefault и LastOrDefault с ?? вместо First и Last. - person walterlv; 19.01.2017
comment
В моем использовании я использовал отдельные условия, чтобы проверить, находится ли смещение перед первой остановкой или после последней, чтобы избежать даже итераторов в .Where(). так что это произойдет до того, как .First() или .Last() успеют вернуть null. - person Wobbles; 19.01.2017