通过颜色查找像素坐标(彩色选择器控制)

发布于 2025-01-25 11:09:02 字数 6079 浏览 3 评论 0原文

我目前正在尝试修改彩色选择器控件。一切似乎都按预期工作。但是,我希望有可能在初始化时设置“ selectedcolor”。因此,流如下:

  1. 选择所需的颜色
  2. 保存在首选项中
  3. ,请关闭应用程序
  4. 打开应用程序再次
  5. 在先前选择的颜色上初始化了当前

选择器的颜色,仅对Pointer X和Y的坐标进行了考虑。这意味着如果我将提供先前选择的彩色拾取器控制的颜色将无法将指针放在正确的位置,因为它正在等待X,Y坐标而不是颜色。我有一个工作障碍,将所有需要的参数保存到字符串(颜色十六进制代码,以及X和Y坐标)中。它正在工作,但是这增加了与字符串结合,然后在ViewModels中解析的其他复杂性。

我已经熟悉阅读像素,寻找所需颜色并获得其坐标的可能性。以下是一些问题:

  1. 用于阅读像素的环路迭代冻结了UI,尤其是对于较大的彩色拾取器(大图像)
  2. 并不总是
  3. 在初始化期间提供正确的坐标。黑色和白色#00000000和#ffffffff的问题。因此,我将它们添加到If方法中。看起来彩色拾取器实际生成的图像是黑白的吗?在实际情况下,这当然不是一个好的解决方案,因为挑选的颜色可以是白色或黑色:
  •   if(this.pickedcolor.toskcolor()== pixelcolor 
      && this.pickedcolor.toskcolor()!= color.fromhex(“#00000000”)。toskcolor() 
      && this.pickedcolor.toskcolor()!= color.fromhex(“#ffffffff”)。toskcolor())
    {
      //this.selectedpoint =新点(x,y);
      debug.writeline(string.format(“颜色:{0} |坐标:{1} {2}”,pixelcolor,x,y));
    }
     

这是颜色拾取器控件的OnPaintSurface方法(您可以在底部方法看到此.getPixelCoordinates(bitmap); the;

protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
  SKImageInfo skImageInfo = e.Info;
  SKSurface skSurface = e.Surface;
  this.SKCanvas = skSurface.Canvas;

  int skCanvasWidth = skImageInfo.Width;
  int skCanvasHeight = skImageInfo.Height;

  this.SKCanvas.Clear();

  // Draw gradient rainbow Color spectrum
  using (SKPaint paint = new SKPaint())
  {
    paint.IsAntialias = true;

    System.Collections.Generic.List<SKColor> colors = new System.Collections.Generic.List<SKColor>();
    this.ColorList.ForEach((color) => { colors.Add(Color.FromHex(color).ToSKColor()); });

    // create the gradient shader between Colors
    using (SKShader shader = SKShader.CreateLinearGradient(
        new SKPoint(0, 0),
        this.ColorListDirection == ColorListDirection.Horizontal ?
            new SKPoint(skCanvasWidth, 0) : new SKPoint(0, skCanvasHeight),
        colors.ToArray(),
        null,
        SKShaderTileMode.Clamp))
    {
      paint.Shader = shader;
      this.SKCanvas.DrawPaint(paint);
    }
  }

  // Draw darker gradient spectrum
  using (SKPaint paint = new SKPaint())
  {
    paint.IsAntialias = true;

    // Initiate the darkened primary color list
    SKColor[] colors = GetGradientOrder();

    // create the gradient shader 
    using (SKShader shader = SKShader.CreateLinearGradient(
        new SKPoint(0, 0),
        this.ColorListDirection == ColorListDirection.Horizontal ?
            new SKPoint(0, skCanvasHeight) : new SKPoint(skCanvasWidth, 0),
        colors,
        null,
        SKShaderTileMode.Clamp))
    {
      paint.Shader = shader;
      this.SKCanvas.DrawPaint(paint);
    }
  }

  // Picking the Pixel Color values on the Touch Point

  // Represent the color of the current Touch point
  SKColor touchPointColor;

  // Efficient and fast
  // https://forums.xamarin.com/discussion/92899/read-a-pixel-info-from-a-canvas
  // create the 1x1 bitmap (auto allocates the pixel buffer)
  using (SKBitmap bitmap = new SKBitmap(skImageInfo))
  {
    // get the pixel buffer for the bitmap
    IntPtr dstpixels = bitmap.GetPixels();

    // read the surface into the bitmap
    skSurface.ReadPixels(skImageInfo,
        dstpixels,
        skImageInfo.RowBytes,
        (int)this.SelectedPoint.X,
        (int)this.SelectedPoint.Y);

    // access the color
    touchPointColor = bitmap.GetPixel(0, 0);

    //this.GetPixelCoordinates(bitmap);

    //bitmap.SetPixel(50, 50, this.PickedColor.ToSKColor());
  }

这是getPixelCoordinates方法:

private void GetPixelCoordinates(SKBitmap bitmap)
{
  if (bitmap == null)
  {
    return;
  }

  for (int x = 0; x < bitmap.Width; x++)
  {
    for (int y = 0; y < bitmap.Height; y++)
    {
      SKColor pixelColor = bitmap.GetPixel(x, y);

      if (this.PickedColor.ToSKColor() == pixelColor 
        && this.PickedColor.ToSKColor() != Color.FromHex("#00000000").ToSKColor() 
        && this.PickedColor.ToSKColor() != Color.FromHex("#FFFFFFFF").ToSKColor())
      {
        //this.SelectedPoint = new Point(x, y);
        Debug.WriteLine(String.Format("Color: {0} | Coordinate: {1} {2}", pixelColor, x, y));
      }
    }
  }
}

这里是pickEdColor属性:

public static readonly BindableProperty PickedColorProperty
  = BindableProperty.Create(
    propertyName: nameof(PickedColor),
    returnType: typeof(Color),
    declaringType: typeof(ColorPicker),
    defaultValue: Color.Green,
    defaultBindingMode: BindingMode.TwoWay,
    propertyChanged: OnColorChanged);

private static void OnColorChanged(BindableObject bindable, object oldValue, object newValue)
{
  ColorPicker control = (ColorPicker)bindable;
  control.PickedColor = (Color)newValue;
}

/// <summary>
/// Set the Color Spectrum Gradient Style
/// </summary>
public GradientColorStyle GradientColorStyle
{
  get { return (GradientColorStyle)GetValue(GradientColorStyleProperty); }
  set { SetValue(GradientColorStyleProperty, value); }
}

public static readonly BindableProperty ColorListProperty
  = BindableProperty.Create(
        propertyName: nameof(ColorList),
        returnType: typeof(string[]),
        declaringType: typeof(ColorPicker),
        defaultValue: new string[]
        {
          new Color(255, 0, 0).ToHex(), // Red
                new Color(255, 255, 0).ToHex(), // Yellow
                new Color(0, 255, 0).ToHex(), // Green (Lime)
                new Color(0, 255, 255).ToHex(), // Aqua
                new Color(0, 0, 255).ToHex(), // Blue
                new Color(255, 0, 255).ToHex(), // Fuchsia
                new Color(255, 0, 0).ToHex(), // Red
        },
        defaultBindingMode: BindingMode.OneTime, null);

我的问题是:我的问题是:唯一生成carameter走路(颜色十六进制代码,以及X和Y坐标)?是否有可能以某种有效的方式将指针放在控制初始化的情况下,而无需持续的循环迭代和冻结UI?

调色板:

“在此处输入图像描述”

I am currently trying to modify color picker control. Everything seems to be working as expected. However I would like to have possibility to set "selectedColor" on initialization. So that flow would be as follows:

  1. Pick needed color
  2. Save it in Preferences
  3. Close application
  4. Open application again
  5. Color picker is initialized on previously selected color

Currently picker is taking in account only coordinates of pointer X and Y. This means if I will provide previously selected color for Color picker control it will not be able to place pointer in a right place, because it is waiting for X, Y coordinates and not color. I have got a work-around where I save all needed parameters into string (Color Hex code, as well as X and Y coordinates). It is working, however this is adding additional complexity for combining strings and then parsing them inside ViewModels.

I have been getting familiar with possibility to read pixels, searching for needed color and getting it's coordinates. Here are some problems:

  1. Loop iteration for reading pixels is freezing UI, especially for larger color pickers (large image)
  2. Not always providing correct coordinates
  3. During initialization there is problem with black and white colors #00000000 and #FFFFFFFF. So I have added them into if method. It looks like before color picker is actually generated image is black and white? This is of course not a good solution in real case scenario as picked color can be white or black:
  • if (this.PickedColor.ToSKColor() == pixelColor 
      && this.PickedColor.ToSKColor() != Color.FromHex("#00000000").ToSKColor() 
      && this.PickedColor.ToSKColor() != Color.FromHex("#FFFFFFFF").ToSKColor())
    {
      //this.SelectedPoint = new Point(x, y);
      Debug.WriteLine(String.Format("Color: {0} | Coordinate: {1} {2}", pixelColor, x, y));
    }
    

Here is OnPaintSurface method of Color Picker control (you can see at the bottom method this.GetPixelCoordinates(bitmap); that is commented out):

protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
  SKImageInfo skImageInfo = e.Info;
  SKSurface skSurface = e.Surface;
  this.SKCanvas = skSurface.Canvas;

  int skCanvasWidth = skImageInfo.Width;
  int skCanvasHeight = skImageInfo.Height;

  this.SKCanvas.Clear();

  // Draw gradient rainbow Color spectrum
  using (SKPaint paint = new SKPaint())
  {
    paint.IsAntialias = true;

    System.Collections.Generic.List<SKColor> colors = new System.Collections.Generic.List<SKColor>();
    this.ColorList.ForEach((color) => { colors.Add(Color.FromHex(color).ToSKColor()); });

    // create the gradient shader between Colors
    using (SKShader shader = SKShader.CreateLinearGradient(
        new SKPoint(0, 0),
        this.ColorListDirection == ColorListDirection.Horizontal ?
            new SKPoint(skCanvasWidth, 0) : new SKPoint(0, skCanvasHeight),
        colors.ToArray(),
        null,
        SKShaderTileMode.Clamp))
    {
      paint.Shader = shader;
      this.SKCanvas.DrawPaint(paint);
    }
  }

  // Draw darker gradient spectrum
  using (SKPaint paint = new SKPaint())
  {
    paint.IsAntialias = true;

    // Initiate the darkened primary color list
    SKColor[] colors = GetGradientOrder();

    // create the gradient shader 
    using (SKShader shader = SKShader.CreateLinearGradient(
        new SKPoint(0, 0),
        this.ColorListDirection == ColorListDirection.Horizontal ?
            new SKPoint(0, skCanvasHeight) : new SKPoint(skCanvasWidth, 0),
        colors,
        null,
        SKShaderTileMode.Clamp))
    {
      paint.Shader = shader;
      this.SKCanvas.DrawPaint(paint);
    }
  }

  // Picking the Pixel Color values on the Touch Point

  // Represent the color of the current Touch point
  SKColor touchPointColor;

  // Efficient and fast
  // https://forums.xamarin.com/discussion/92899/read-a-pixel-info-from-a-canvas
  // create the 1x1 bitmap (auto allocates the pixel buffer)
  using (SKBitmap bitmap = new SKBitmap(skImageInfo))
  {
    // get the pixel buffer for the bitmap
    IntPtr dstpixels = bitmap.GetPixels();

    // read the surface into the bitmap
    skSurface.ReadPixels(skImageInfo,
        dstpixels,
        skImageInfo.RowBytes,
        (int)this.SelectedPoint.X,
        (int)this.SelectedPoint.Y);

    // access the color
    touchPointColor = bitmap.GetPixel(0, 0);

    //this.GetPixelCoordinates(bitmap);

    //bitmap.SetPixel(50, 50, this.PickedColor.ToSKColor());
  }

Here is GetPixelCoordinates method:

private void GetPixelCoordinates(SKBitmap bitmap)
{
  if (bitmap == null)
  {
    return;
  }

  for (int x = 0; x < bitmap.Width; x++)
  {
    for (int y = 0; y < bitmap.Height; y++)
    {
      SKColor pixelColor = bitmap.GetPixel(x, y);

      if (this.PickedColor.ToSKColor() == pixelColor 
        && this.PickedColor.ToSKColor() != Color.FromHex("#00000000").ToSKColor() 
        && this.PickedColor.ToSKColor() != Color.FromHex("#FFFFFFFF").ToSKColor())
      {
        //this.SelectedPoint = new Point(x, y);
        Debug.WriteLine(String.Format("Color: {0} | Coordinate: {1} {2}", pixelColor, x, y));
      }
    }
  }
}

Here is PickedColor property:

public static readonly BindableProperty PickedColorProperty
  = BindableProperty.Create(
    propertyName: nameof(PickedColor),
    returnType: typeof(Color),
    declaringType: typeof(ColorPicker),
    defaultValue: Color.Green,
    defaultBindingMode: BindingMode.TwoWay,
    propertyChanged: OnColorChanged);

private static void OnColorChanged(BindableObject bindable, object oldValue, object newValue)
{
  ColorPicker control = (ColorPicker)bindable;
  control.PickedColor = (Color)newValue;
}

/// <summary>
/// Set the Color Spectrum Gradient Style
/// </summary>
public GradientColorStyle GradientColorStyle
{
  get { return (GradientColorStyle)GetValue(GradientColorStyleProperty); }
  set { SetValue(GradientColorStyleProperty, value); }
}

public static readonly BindableProperty ColorListProperty
  = BindableProperty.Create(
        propertyName: nameof(ColorList),
        returnType: typeof(string[]),
        declaringType: typeof(ColorPicker),
        defaultValue: new string[]
        {
          new Color(255, 0, 0).ToHex(), // Red
                new Color(255, 255, 0).ToHex(), // Yellow
                new Color(0, 255, 0).ToHex(), // Green (Lime)
                new Color(0, 255, 255).ToHex(), // Aqua
                new Color(0, 0, 255).ToHex(), // Blue
                new Color(255, 0, 255).ToHex(), // Fuchsia
                new Color(255, 0, 0).ToHex(), // Red
        },
        defaultBindingMode: BindingMode.OneTime, null);

My question is: Is generating string with parameters the only way to go (Color Hex code, as well as X and Y coordinates)? Is there some possibility to place pointer on control initialization by provided color in some efficient way without constant loop iteration and freeze of UI?

Color palette:

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

浅沫记忆 2025-02-01 11:09:02

Stackoverflow专为每个帖子而设计,以询问和回答一个编码问题。
(要问第二个问题,请撰写新帖子。在该帖子中包括理解该问题所需的基本细节。链接回这个问题,因此您不必重复提供其他背景/上下文的信息。)

您的主要问题可以说:

给定:一个调色板[请参见图片] [请参见OnpaintSurface代码,从//绘制渐变彩虹色谱。如何计算(x,y)与给定颜色相对应的坐标?


首先,观察。该2D调色板给出3个颜色轴中的2个。您将需要单独的“饱和”滑块,以允许选择任何颜色。

您显示的调色板是“ HSV”颜色模型的近似。
wiki hsl-hsv型号,单击右图。 矩形

您的调色板看起来像是标有S(HSV)=1。Hue +饱和 +值的 。
您的配色列表应具有最大值的完全饱和颜色。
从屏幕上行驶,调色板将价值降低到接近零。


这是答案的开始。

需要的是一个数学公式,与绘制的内容相对应。
让我们看看如何生成该矩形图像。

重命名颜色列表,因此更容易使用。
存储为字段,因此可以稍后使用。使用原始的颜色,从中生成skcolors,以便于操作。

    private List<Color> saturatedColors;
    private List<Color> darkenedColors;
    private int nColors => saturatedColors.Count;
    private int maxX, maxY;   // From your UI rectangle.

顶行(y = 0)具有饱和度,在x上均匀间隔。

底部行(y = maxy)具有darkenedColors,均匀跨越x。

从顶行到底部的像素颜色是线性插值的。

目标是找到最接近给定颜色的像素,“ 颜色goalcolor”。

考虑每个高的,薄的矩形,它们的角落为两个底色和相应的两个底色。目标是查找哪个矩形包含守门员,然后在该矩形中找到最接近守门颜色的矩形的像素。

最棘手的部分是“比较”颜色,以确定两种颜色之间的颜色何时”。在RGB中很难做到这一点。将颜色转换为HSV以匹配您使用的调色板。参见 Greg的答案-Colortohsv

如果您进行HSV类:

using System;
using System.Collections.Generic;
using System.Linq;
// OR could use System.Drawing.Color.
using Color = Xamarin.Forms.Color;
...
    public class HSV
    {
        #region --- static ---
        public static HSV FromColor(Color color)
        {
            ColorToHSV(color, out double hue, out double saturation, out double value);
            return new HSV(hue, saturation, value);
        }

        public static List<HSV> FromColors(IEnumerable<Color> colors)
        {
            return colors.Select(color => FromColor(color)).ToList();
        }

        const double Epsilon = 0.000001;

        // returns Tuple<int colorIndex, double wgtB>.
        public static Tuple<int, double> FindHueInColors(IList<HSV> colors, double goalHue)
        {
            int colorIndex;
            double wgtB = 0;
            // "- 1": because each iteration needs colors[colorIndex+1].
            for (colorIndex = 0; colorIndex < colors.Count - 1; colorIndex++)
            {
                wgtB = colors[colorIndex].WgtFromHue(colors[colorIndex + 1], goalHue);
                // Epsilon compensates for possible round-off error in WgtFromHue.
                // To ensure the color is considered within one of the ranges.
                if (wgtB >= 0 - Epsilon && wgtB < 1)
                    break;
            }

            return new Tuple<int, double>(colorIndex, wgtB);
        }

        // From https://stackoverflow.com/a/1626175/199364.
        public static void ColorToHSV(Color color, out double hue, out double saturation, out double value)
        {
            int max = Math.Max(color.R, Math.Max(color.G, color.B));
            int min = Math.Min(color.R, Math.Min(color.G, color.B));

            hue = color.GetHue();
            saturation = (max == 0) ? 0 : 1d - (1d * min / max);
            value = max / 255d;
        }
        // From https://stackoverflow.com/a/1626175/199364.
        public static Color ColorFromHSV(double hue, double saturation, double value)
        {
            int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
            double f = hue / 60 - Math.Floor(hue / 60);

            value = value * 255;
            int v = Convert.ToInt32(value);
            int p = Convert.ToInt32(value * (1 - saturation));
            int q = Convert.ToInt32(value * (1 - f * saturation));
            int t = Convert.ToInt32(value * (1 - (1 - f) * saturation));

            if (hi == 0)
                return Color.FromArgb(255, v, t, p);
            else if (hi == 1)
                return Color.FromArgb(255, q, v, p);
            else if (hi == 2)
                return Color.FromArgb(255, p, v, t);
            else if (hi == 3)
                return Color.FromArgb(255, p, q, v);
            else if (hi == 4)
                return Color.FromArgb(255, t, p, v);
            else
                return Color.FromArgb(255, v, p, q);
        }
        #endregion


        public double H { get; set; }
        public double S { get; set; }
        public double V { get; set; }

        // c'tors
        public HSV()
        {
        }
        public HSV(double h, double s, double v)
        {
            H = h;
            S = s;
            V = v;
        }

        public Color ToColor()
        {
            return ColorFromHSV(H, S, V);
        }

        public HSV Lerp(HSV b, double wgtB)
        {
            return new HSV(
                MathExt.Lerp(H, b.H, wgtB),
                MathExt.Lerp(S, b.S, wgtB),
                MathExt.Lerp(V, b.V, wgtB));
        }

        // Returns "wgtB", such that goalHue = Lerp(H, b.H, wgtB).
        // If a and b have same S and V, then this is a measure of
        // how far to move along segment (a, b), to reach goalHue.
        public double WgtFromHue(HSV b, double goalHue)
        {
            return MathExt.Lerp(H, b.H, goalHue);
        }
        // Returns "wgtB", such that goalValue = Lerp(V, b.V, wgtB).
        public double WgtFromValue(HSV b, double goalValue)
        {
            return MathExt.Lerp(V, b.V, goalValue);
        }
    }

    public static class MathExt
    {
        public static double Lerp(double a, double b, double wgtB)
        {
            return a + (wgtB * (b - a));
        }

        // Converse of Lerp:
        // returns "wgtB", such that
        //   result == lerp(a, b, wgtB)
        public static double WgtFromResult(double a, double b, double result)
        {
            double denominator = b - a;

            if (Math.Abs(denominator) < 0.00000001)
            {
                if (Math.Abs(result - a) < 0.00000001)
                    // Any value is "valid"; return the average.
                    return 0.5;

                // Unsolvable - no weight can return this result.
                return double.NaN;
            }

            double wgtB = (result - a) / denominator;
            return wgtB;
        }
    }

用法:

    public static class Tests {
        public static void TestFindHueInColors(List<Color> saturatedColors, Color goalColor)
        {
            List<HSV> hsvColors = HSV.FromColors(saturatedColors);
            HSV goalHSV = HSV.FromColor(goalColor);
            var hueAt = HSV.FindHueInColors(hsvColors, goalHSV.H);
            int colorIndex = hueAt.Item1;
            double wgtB = hueAt.Item2;
            // ...
        }
    }

这是方法的本质,则更容易。来自colorIndexncolorswgtbmaxx,可以计算x。我建议写几个测试用例,以弄清楚如何做。

计算y要简单得多。应该使用GOORHSV.VMaxy可以使用。

如您所见,这对代码并不微不足道。

最重要的一点:

  • 转换为HSV颜色空间。
  • 调色板由高大的矩形组成。每个矩形在顶部两个角上具有两个饱和和最大值的颜色:(H1,1.0,1.0)和(H2,1.0,1.0)。底部的两个角是相同的色调和饱和度,但值很小。也许(H1、1.0、0.01)和(H2、1.0、0.01)。将您的实际黑暗值转换为HSV,以查看确切的值。
  • 查找哪个H的GOORHSV在之间。
  • 了解“线性插值”(“ LERP”)。在该矩形中,顶部边缘是两种饱和颜色之间的LERP,侧边缘是从明亮的颜色到相应的深色颜色的LERP。

如果以上数学太激烈,则只用其中一个矩形绘制一个盒子。也就是说,在顶部列表中仅具有两种颜色的梯度。试图在该矩形内定位一个颜色像素。

重要的是:可能没有像素正是您要开始的颜色。查找哪个像素最接近该颜色。
如果您不确定自己具有“最佳”像素,请阅读附近的一些像素,请确定哪个是“最接近的”。也就是说,具有最小的var error =(r2-r1)*(r2-r1) +(g2-g1)*(g2-g1) +(b2-b1)*(b2-b1)*(b2-b1); << /代码>。

StackOverflow is designed for each post to ask and answer one coding question.
(To ask a second question, please make a new post. Include in that post the essential details needed to understand that question. Link back to this question, so you don't have to repeat the information that gives additional background/context.)

Your primary question can be stated as:

Given: A color palette [see picture] generated by [see OnPaintSurface code, starting at // Draw gradient rainbow Color spectrum. How calculate (x,y) coordinates that correspond to a given color?


First, an observation. That 2D palette gives 2 of 3 color axes. You'll need a separate "saturation" slider, to allow picking of any color.

The palette you show is an approximation to an "HSV" color model.
In wiki HSL-HSV models, click on diagram at right. Your palette looks like the rectangle labeled S(HSV) = 1.

Hue + Saturation + Value.
Your ColorList should have fully Saturated colors at max Value.
Going down the screen, the palette reduces Value to near zero.


This is the beginning of an answer to that.

What is needed, is a mathematical formula that corresponds to what is drawn.
Lets look at how that rectangular image was generated.

Rename the color lists so easier to work with.
Store as fields, so can use them later. Use the original Colors from which the SkColors were generated, for easier manipulation.

    private List<Color> saturatedColors;
    private List<Color> darkenedColors;
    private int nColors => saturatedColors.Count;
    private int maxX, maxY;   // From your UI rectangle.

The top row (y=0) has saturatedColors, evenly spaced across x.

The bottom row (y=maxY) has darkenedColors, evenly spaced across x.

The pixel colors are linearly interpolated from top row to bottom row.

Goal is to find pixel closest to a given color, "Color goalColor".

Consider each tall, thin rectangle whose corners are two topColors and the corresponding two bottomColors. Goal is to find which rectangle contains goalColor, then find the pixel within that rectangle that is closest to goalColor.

The trickiest part is "comparing" colors, to decide when a color is "between" two colors. This is hard to do in RGB; convert colors to HSV to match the palette you are using. See Greg's answer - ColorToHSV.

Its easier if you make an HSV class:

using System;
using System.Collections.Generic;
using System.Linq;
// OR could use System.Drawing.Color.
using Color = Xamarin.Forms.Color;
...
    public class HSV
    {
        #region --- static ---
        public static HSV FromColor(Color color)
        {
            ColorToHSV(color, out double hue, out double saturation, out double value);
            return new HSV(hue, saturation, value);
        }

        public static List<HSV> FromColors(IEnumerable<Color> colors)
        {
            return colors.Select(color => FromColor(color)).ToList();
        }

        const double Epsilon = 0.000001;

        // returns Tuple<int colorIndex, double wgtB>.
        public static Tuple<int, double> FindHueInColors(IList<HSV> colors, double goalHue)
        {
            int colorIndex;
            double wgtB = 0;
            // "- 1": because each iteration needs colors[colorIndex+1].
            for (colorIndex = 0; colorIndex < colors.Count - 1; colorIndex++)
            {
                wgtB = colors[colorIndex].WgtFromHue(colors[colorIndex + 1], goalHue);
                // Epsilon compensates for possible round-off error in WgtFromHue.
                // To ensure the color is considered within one of the ranges.
                if (wgtB >= 0 - Epsilon && wgtB < 1)
                    break;
            }

            return new Tuple<int, double>(colorIndex, wgtB);
        }

        // From https://stackoverflow.com/a/1626175/199364.
        public static void ColorToHSV(Color color, out double hue, out double saturation, out double value)
        {
            int max = Math.Max(color.R, Math.Max(color.G, color.B));
            int min = Math.Min(color.R, Math.Min(color.G, color.B));

            hue = color.GetHue();
            saturation = (max == 0) ? 0 : 1d - (1d * min / max);
            value = max / 255d;
        }
        // From https://stackoverflow.com/a/1626175/199364.
        public static Color ColorFromHSV(double hue, double saturation, double value)
        {
            int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
            double f = hue / 60 - Math.Floor(hue / 60);

            value = value * 255;
            int v = Convert.ToInt32(value);
            int p = Convert.ToInt32(value * (1 - saturation));
            int q = Convert.ToInt32(value * (1 - f * saturation));
            int t = Convert.ToInt32(value * (1 - (1 - f) * saturation));

            if (hi == 0)
                return Color.FromArgb(255, v, t, p);
            else if (hi == 1)
                return Color.FromArgb(255, q, v, p);
            else if (hi == 2)
                return Color.FromArgb(255, p, v, t);
            else if (hi == 3)
                return Color.FromArgb(255, p, q, v);
            else if (hi == 4)
                return Color.FromArgb(255, t, p, v);
            else
                return Color.FromArgb(255, v, p, q);
        }
        #endregion


        public double H { get; set; }
        public double S { get; set; }
        public double V { get; set; }

        // c'tors
        public HSV()
        {
        }
        public HSV(double h, double s, double v)
        {
            H = h;
            S = s;
            V = v;
        }

        public Color ToColor()
        {
            return ColorFromHSV(H, S, V);
        }

        public HSV Lerp(HSV b, double wgtB)
        {
            return new HSV(
                MathExt.Lerp(H, b.H, wgtB),
                MathExt.Lerp(S, b.S, wgtB),
                MathExt.Lerp(V, b.V, wgtB));
        }

        // Returns "wgtB", such that goalHue = Lerp(H, b.H, wgtB).
        // If a and b have same S and V, then this is a measure of
        // how far to move along segment (a, b), to reach goalHue.
        public double WgtFromHue(HSV b, double goalHue)
        {
            return MathExt.Lerp(H, b.H, goalHue);
        }
        // Returns "wgtB", such that goalValue = Lerp(V, b.V, wgtB).
        public double WgtFromValue(HSV b, double goalValue)
        {
            return MathExt.Lerp(V, b.V, goalValue);
        }
    }

    public static class MathExt
    {
        public static double Lerp(double a, double b, double wgtB)
        {
            return a + (wgtB * (b - a));
        }

        // Converse of Lerp:
        // returns "wgtB", such that
        //   result == lerp(a, b, wgtB)
        public static double WgtFromResult(double a, double b, double result)
        {
            double denominator = b - a;

            if (Math.Abs(denominator) < 0.00000001)
            {
                if (Math.Abs(result - a) < 0.00000001)
                    // Any value is "valid"; return the average.
                    return 0.5;

                // Unsolvable - no weight can return this result.
                return double.NaN;
            }

            double wgtB = (result - a) / denominator;
            return wgtB;
        }
    }

Usage:

    public static class Tests {
        public static void TestFindHueInColors(List<Color> saturatedColors, Color goalColor)
        {
            List<HSV> hsvColors = HSV.FromColors(saturatedColors);
            HSV goalHSV = HSV.FromColor(goalColor);
            var hueAt = HSV.FindHueInColors(hsvColors, goalHSV.H);
            int colorIndex = hueAt.Item1;
            double wgtB = hueAt.Item2;
            // ...
        }
    }

This is the essence of the approach. From colorIndex, nColors, wgtB, and maxX, it is possible to calculate x. I recommend writing several test cases, to figure out how to do so.

Calculating y is much simpler. Should be possible using goalHSV.V and maxY.

As you can see, this is not trivial to code.

The most important points:

  • Convert to HSV color space.
  • The palette is composed of tall, thin rectangles. Each rectangle has two saturated and max-value colors at the top two corners: (H1, 1.0, 1.0) and (H2, 1.0, 1.0). The bottom two corners are same hues and saturation, but small value. Perhaps (H1, 1.0, 0.01) and (H2, 1.0, 0.01). Convert your actual darkened values to HSV, to see the exact values.
  • Find which H's goalHSV is between.
  • Learn about "Linear Interpolation" ("Lerp"). In that rectangle, the top edge is a Lerp between the two saturated colors, the side edges is a Lerp from a bright color to the corresponding darkened color.

If the above math is too intense, then draw a box with just one of those rectangles. That is, make the gradient with only TWO colors in the top list. Experiment with trying to locate a color pixel within that rectangle.

IMPORTANT: There might not be a pixel that is EXACTLY the color you are starting with. Find which pixel is closest to that color.
If you aren't sure you have the "best" pixel, then read a few nearby pixels, decide which is "closest". That is, which has the smallest var error = (r2-r1)*(r2-r1) + (g2-g1)*(g2-g1) + (b2-b1)*(b2-b1);.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文