繁体   English   中英

如何在 C# 中获得 RGB 值的同时获取颜色名称?

[英]How to get the name of color while having its RGB value in C#?

我正在创建一个应用程序来查找图像中最常用的颜色,我正在获取颜色的 RGB 值,但是如何获取颜色名称,请帮助。

如注释中所述, KnownColor枚举可用于简化此操作:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reflection;

class Test
{
    static void Main()
    {
        Color color = Color.FromArgb(255, 0, 0);
        Console.WriteLine(color.Name); // ffff0000

        var colorLookup = Enum.GetValues(typeof(KnownColor))
               .Cast<KnownColor>()
               .Select(Color.FromKnownColor)
               .ToLookup(c => c.ToArgb());

        // There are some colours with multiple entries...
        foreach (var namedColor in colorLookup[color.ToArgb()])
        {
            Console.WriteLine(namedColor.Name);
        }
    }
}

原答案

Color.FromArgb会给你一个Color ,但它永远不会有名字。 据我所知,您需要使用反射来获取命名的颜色。

这是我同时研究的 Cole Campbell 解决方案的另一个版本……

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reflection;

class Test
{
    static void Main()
    {
        Color color = Color.FromArgb(255, 0, 0);
        Console.WriteLine(color.Name); // ffff0000

        var colorLookup = typeof(Color)
               .GetProperties(BindingFlags.Public | BindingFlags.Static)
               .Select(f => (Color) f.GetValue(null, null))
               .Where(c => c.IsNamedColor)
               .ToLookup(c => c.ToArgb());

        // There are some colours with multiple entries...
        foreach (var namedColor in colorLookup[color.ToArgb()])
        {
            Console.WriteLine(namedColor.Name);
        }
    }
}

此方法使用反射来检查Color类的预定义颜色,并将它们与作为参数传入的颜色进行比较。 这可以进一步优化,但它应该让您了解一般技术。

private static String GetColorName(Color color)
{
    var predefined = typeof(Color).GetProperties(BindingFlags.Public | BindingFlags.Static);
    var match = (from p in predefined where ((Color)p.GetValue(null, null)).ToArgb() == color.ToArgb() select (Color)p.GetValue(null, null));
    if (match.Any())
       return match.First().Name;
    return String.Empty;
}

您应该能够使用 System.Drawing 命名空间中的 Color 类,它有一个返回 Color 对象的静态方法 FromARGB。 它有几个重载,一个允许您输入 RGB 值,如下所示:

var color = Color.FromArgb(100, 5,5,5).Name;

对于快速简单的操作,请尝试这个(在 WPF 中):

public string GetNameOfColor(Color color) {
    var colorProperty = typeof(Colors).GetProperties().FirstOrDefault(p =>
        (Color)(p.GetValue(p, null)) == color);
    return (colorProperty != null) ? colorProperty.Name : color.ToString();
}

在 Visual Studio 2010 中,需要p.GetValue(p, null) 在 Visual Studio 2013+ 中,您可以只使用p.GetValue(p)

这种技术的优点,除了它的简洁之外,是它不需要对System.DrawingSystem.Reflection的引用,但允许用户保留在System.Windows命名空间内,即 WPF。 它确实需要对System.Windows.Media的引用,如果您在 WPF 中使用颜色,您应该已经拥有了。 如果您像我一样,除非有很好的需要,否则尽量不要将System.Drawing添加到 WPF 应用程序中。 至于为什么留在 WPF 命名空间内,这是一个偏好问题。 请参阅WPF v/s System.Drawing中的示例讨论。

我正在使用 .NETCF 3.5,并且“System.Drawing.KnowColor enumeration”不存在。 仅用于调试,我使用此函数返回已知颜色;

public static string ColorName(System.Drawing.Color c)
{
    var colorName = string.Format("0x{0:X4}", c.ToArgb());
    var colorList = new List<System.Reflection.PropertyInfo>();
    var props = typeof(System.Drawing.Color).GetProperties();
    colorList.AddRange(props);
    props = typeof(System.Drawing.SystemColors).GetProperties();
    var prop = colorList.Where(p1 => (System.Drawing.Color)p1.GetValue(null, null) == c).FirstOrDefault();
    if (prop != null) System.Diagnostics.Debug.WriteLine(prop.Name);
    else System.Diagnostics.Debug.WriteLine("unkown name Color");
    foreach (var item in colorList)
    {
        var argb = (System.Drawing.Color)item.GetValue(null, null);
        System.Diagnostics.Debug.WriteLine(string.Format("ColorName {0} ARGB {1:X4}", item.Name, argb.ToArgb()));
        if (c == argb) return item.Name;
    }

    return colorName;
}

因为我最近致力于解决同样的问题,所以我编写了一个静态查找缓存,它可以预先填充来自系统枚举System.Drawing.KnownColor的众所周知的 .NET Framework 颜色值。 我反编译了 .NET,发现颜色属性使用关联的枚举状态调用构造函数。 我正在使用反射,但这是一次性的,发生在我的类的静态初始化程序中。 这不是一个线程安全的解决方案,但它可以很容易地成为线程安全的,并且还在内存中维护一个永久缓存。

我的方法可能有用的是,当它首先预填充 .NET 命名颜色列表时,您还可以添加应用程序运行时定义的众所周知的颜色 - 从 JSON 读取、从 Web 下载、从数据库解析等等...这里的主要潜在缺点是,由于是静态的,这是一个单例,并且由于这种设计选择,可能会产生不需要的副作用。 在您认为合适的地方创建此类实例。

我使面向公众的界面尽可能简单 - LookupName(Color)返回string.Empty如果存在缓存未命中,否则返回颜色的字符串名称。 AddToCache(Color, string)成功时返回 true,失败时返回 false(例如,已知值已经存在)。 ToNearestNamedColor(Color)是一种成本适中的数值方法,源自颜色量化技术,使用毕达哥拉斯距离选择静态缓存中最接近的“命名”颜色,并返回颜色名称的字符串,即System.Drawing.Color该名称的结构,作为最后的奖励,一个double表示最近的已知/命名Color输入的分数或适应度。

使用毕达哥拉斯距离来选择最接近的命名颜色是次优的,但在使用不同的基线启发式最大化近似相似性之前提供了一个不错的起点。

最后, ToRGB(Color)是我编写的一个辅助函数,用于将System.Drawing.Color转换为代表颜色 RGB 值的整数,修整(归零)前导 Alpha 分量,将 24 位字符串填充到 32-位 int 结构。

ToRGB(Color) Helper(静态扩展)函数:

/// <summary>
/// Converts the specified <see cref="Color"/> to a 24-bitstring on an int, of 00000000RRRRRRRRGGGGGGGGBBBBBBBB.
/// </summary>
/// <param name="toRGB">The color to convert to rgb as a 24 bitstring. Ignores Alpha.</param>
/// <returns>an integer representation of the specified <see cref="Color"/>.</returns>
public static int ToRGB(this Color toRGB)
{
  return
    (toRGB.R << 16) |
    (toRGB.G << 8) |
    toRGB.B;
}

定义了这个辅助函数(可以是公共的,可以是内部的,由你决定),我现在展示我的 NamedColorStaticCache.cs(放置在你想要/需要的任何命名空间中)。

  // Using-statements:
  using System;
  using System.Collections.Generic;
  using System.Drawing;
  using System.Linq;
  using System.Reflection;

  /// <summary>
  /// Static class to assist with looking up known, named colors, by name.
  /// </summary>
  public static class NamedColorStaticCache
  {
    /// <summary>
    /// Stores the lookup cache of RGB colors to known names.
    /// </summary>
    private static Dictionary<int, string> rgbLookupCache;

    /// <summary>
    /// Initializes static members of the <see cref="NamedColorStaticCache"/> class.
    /// </summary>
    static NamedColorStaticCache()
    {
      rgbLookupCache = new Dictionary<int, string>();
      Type colorType = typeof(Color);
      PropertyInfo[] knownColorProperties = colorType
        .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
        .Where(t => t.PropertyType == typeof(Color))
        .ToArray();

      // Avoid treating "transparent" as "white".
      AddToCache(Color.White, "White");

      foreach (PropertyInfo pi in knownColorProperties)
      {
        Color asColor = (Color)pi.GetValue(null);
        AddToCache(asColor, pi.Name);
      }
    }

    /// <summary>
    /// Looks up the name of the specified <see cref="Color"/>.
    /// </summary>
    /// <param name="toLookup">The <see cref="Color"/> to lookup a name for.</param>
    /// <returns>A string of the associated name of the specified <see cref="Color"/>.</returns>
    public static string LookupName(this Color toLookup)
    {
      int rgb = toLookup.ToRGB();
      if (rgbLookupCache.ContainsKey(rgb))
      {
        return rgbLookupCache[rgb];
      }

      return string.Empty;
    }

    /// <summary>
    /// Adds the specified <see cref="Color"/> to a lookup cache of named colors.
    /// </summary>
    /// <param name="toAdd">The <see cref="Color"/> to add to the lookup cache.</param>
    /// <param name="name">The name of the <see cref="Color"/> to add to the lookup cache.</param>
    /// <returns>True if adding successful, else, false (the color was already in the cache).</returns>
    public static bool AddToCache(this Color toAdd, string name)
    {
      int rgb = toAdd.ToRGB();
      if (rgbLookupCache.ContainsKey(rgb))
      {
        return false;
      }

      rgbLookupCache.Add(rgb, name);
      return true;
    }

    /// <summary>
    /// Takes the specified input <see cref="Color"/>, and translates it to its nearest counterpart, using root square sum.
    /// </summary>
    /// <param name="toNearest">The <see cref="Color"/> to look up to the nearest named color.</param>
    /// <returns>A tuple structure of name, color, error.</returns>
    public static Tuple<string, Color, double> ToNearestNamedColor(this Color toNearest)
    {
      string foundName = string.Empty;
      Color foundColor = Color.Black;
      double error = double.MaxValue;

      int toNearestRGB = toNearest.ToRGB();
      if (rgbLookupCache.ContainsKey(toNearestRGB))
      {
        foundName = rgbLookupCache[toNearestRGB];
        foundColor = toNearest;
        error = 0;
      }
      else
      {
        foreach (KeyValuePair<int, string> pair in rgbLookupCache)
        {
          int rgb = pair.Key;
          byte r = (byte)(rgb >> 16);
          byte g = (byte)(rgb << 16 >> 24);
          byte b = (byte)(rgb << 24 >> 24);
          int dr = r - toNearest.R;
          int dg = g - toNearest.G;
          int db = b - toNearest.B;
          double newError =
            Math.Sqrt(
              (dr * dr) +
              (dg * dg) +
              (db * db));

          if (newError < error)
          {
            foundName = pair.Value;
            foundColor = Color.FromArgb(r, g, b);
            error = newError;
          }

          if (newError <= .0005)
          {
            break;
          }
        }
      }

      return Tuple.Create(foundName, foundColor, error);
    }
  }

注意:一小时后编辑以提供查找功能以获取最近的命名颜色。 这不是最好的方法(转换为CIEL*a*b*会“更好”),但这是使用 ToNearestColor 方法的坚实基础。 这样,除了查找单独的离散颜色名称之外,原始海报还可以提供最接近的近似颜色名称。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM