簡體   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