在渐变中获取特定位置的颜色

14

我有以下的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

WPF / 一些第三方库中是否有 API 可以实现这个功能?


我认为在WPF中没有定义这个。我会期望它取决于您的显卡驱动程序的实现、缩放级别、用户的颜色深度等。您可以使用Visual.PointToScreen方法,然后使用Graphics.CopyFromScreen来抓取该像素。然后使用Bitmap.GetPixel来检索颜色详细信息。 - akhisp
3个回答

19
获取特定点的颜色需要了解相关渐变,而这并不是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;
    }
}

提示:此算法假设没有具有相同偏移量的站点。如果存在多个具有相同偏移量的站点,则会引发 InvalidOperationException

将此类添加到当前上下文(命名空间上下文)中

要在任何位置获取您的颜色,请插入以下内容:

var color = grsc.GetRelativeColor(.75);

1
Johnny,你觉得你能到https://dev59.com/MXHYa4cB1Zd3GeqPJkd5并发布这个答案吗?我希望你能获得这些积分。 - Rob Perkins
这正是我一直在寻找的,但有一个缺陷,如果偏移量恰好等于渐变点,它将完全忽略该渐变点。因此我进行了编辑。 - Underdetermined
@Underdetermined:嗯,哦,那个编辑在哪里? - quetzalcoatl
@quetzalcoatl:不幸的是,我的编辑似乎没有通过同行评审和/或丢失了。已经过去1.5年了,我不记得我改变了什么(也无法查找我的源代码,因为当时我在另一家公司工作)。由于这是我唯一一次使用c#工作,我将让更有能力的人进行编辑,以使此代码正常工作。 - Underdetermined
第二个if语句应该以gs.Offset >= offset开头,否则边界情况将无法正确处理。 - Lokno
代码已更新,并在说明中添加了免责声明以处理问题。 - Jonny Piazzi

4

我尝试了Jonny Piazzi的方法,但它并没有正常工作。
所以我写了自己的方法如下:

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}"/>

我喜欢使用break来避免不必要的迭代。 - Wobbles
你可以通过移除可见的迭代并使用类似于 GradientStop left = stops.Where(s => s.Offset <= offset).Last(); GradientStop right = stops.Where(s => s.Offset > offset).First(); 的方式来简化代码。 - Wobbles
谢谢!这真的简化了我的代码。在这种情况下,我的所有“{”和“}”都消失了。但我想你应该使用FirstOrDefaultLastOrDefault??一起使用,而不是使用FirstLast - walterlv
在我的使用中,我使用了单独的条件来检查偏移量是否在第一个停靠点之前或最后一个停靠点之后,以避免在 .Where() 中使用迭代器。这样它就可以在 .First().Last() 有机会返回 null 之前退出。 - Wobbles

0
  foreach (var gs in gsc)
            {
                if (gs.Offset == offset) return gs.Color; //new line added
                if (gs.Offset < offset && gs.Offset > before.Offset)
                {
                    before = gs;
                }

                if (gs.Offset > offset && gs.Offset < after.Offset)
                {
                    after = gs;
                }
            }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接