我正在创建一个应用程序来查找图像最常用的颜色,我正在获取颜色的 RGB 值,但是如何获取颜色名称,请帮助。
6 回答
如评论中所述,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
,但它永远不会有一个名字。据我所知,您需要使用反射来获得命名的颜色。
这是科尔坎贝尔解决方案的另一个版本,我正在同时研究它......
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 类,它有一个静态方法 FromARGB,它返回一个 Color 对象。它有几个重载,一个允许您输入 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.Drawing
或System.Reflection
允许用户保留在System.Windows
WPF 命名空间内。System.Windows.Media
如果您在 WPF 中使用颜色,它确实需要您可能已经拥有的参考。如果您像我一样,请尽量不要在System.Drawing
没有必要的情况下添加到您的 WPF 应用程序中。至于为什么要留在 WPF 命名空间内,这是一个偏好问题。例如,参见WPF v/s System.Drawing中的讨论。
我正在使用 .NETCF 3.5,并且“System.Drawing.KnowColor 枚举”不存在。仅用于调试,我使用此函数返回已知颜色;
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)
是我编写的一个辅助函数,用于将 a 转换为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 方法的坚实基础。这样,原始海报除了查找单独的颜色名称外,还可以给出最接近的近似颜色名称。