1 常见的颜色空间与模型有:
1.1 CAM:CIECAM02 ;iCAM
1.2 CIE:XYZ (1931);RGB (1931);CAM (2002);YUV (1960);UVW (1964);CIELAB (1976);CIELUV (1976)
1.3 RGB:RGB color space;sRGB;rg chromaticity;Adobe;Wide-gamut;ProPhoto;scRGB;DCI-P3;Rec. 709;Rec. 2020;Rec. 2100
1.4 YUV:YUV (PAL);YDbDr (SECAM;PAL-N);YIQ(NTSC);YCbCr(Rec. 601;Rec. 709;Rec. 2020;Rec. 2100);ICtCp(Rec. 2100);YPbPr;xvYCC;YCoCg
1.5 Other:CcMmYK;CMY,CMYK;Coloroid;LMS;Hexachrome;HSL, HSV;HCL;Imaginary color;OSA-UCS;PCCS;RG;RYB;HWB
2 颜色系统与标准(组织):
ACES;ANPA;Colour Index International( CI list of dyes);DIC;Federal Standard 595;HKS;ICC profile;ISCC–NBS;Munsell;NCS;Ostwald;Pantone;RAL
3 图像处理主要颜色空间的数值范围:
GRAY: 0~255
RGB: 0~255, 0~255, 0~255
YCbCr(JPEG): 0~255, 0~255, 0~255
YDbDr: 0.0~1.0, -1.333~1.333, -1.333~1.333
HSB=HSV: 0.0~360.0, 0.0~1.0, 0.0~1.0
HSL: 0.0~360.0, 0.0~1.0, 0.0~1.0
HSI: 0.0~360.0, 0.0~1.0, 0.0~1.0
CMYK: 0.0~1.0, 0.0-1.0, 0.0-1.0, 0.0~1.0
CMY: 0.0~1.0, 0.0-1.0, 0.0-1.0
YUV: 0.0~1.0, -0.436~0.436, -0.615~0.615
CIE 1931 XYZ: 0.0~0.9505, 0.0~1.0, 0.0~1.0890
CIE L*A*B*: 0.0~100.0, -128.0~127.0, -128.0~127.0
本代码的数值范围就是按上述规划的,请注意不要超界哈!
4 预编译option说明
4.1 __OTHER__ 参考代码,不一定是对的哈!
4.2 __ORIGINAL_SOURCECODE 一般是原始代码,对应的是稍微做了优化的;
本程序在 Visual Studio 2019 下调试运行正常无误。
//#define __ORIGINAL_SOURSECODE__
//#define __OTHER__
using System;
using System.Text;
using System.Drawing;
namespace Legal.ImageProcess
{
///
/// 常见的颜色空间、模型及其标准的计算方法与代码大全
/// 1 常见的颜色空间与模型有:
/// 1.1 CAM:CIECAM02 ;iCAM
/// 1.2 CIE:XYZ (1931);RGB (1931);CAM (2002);YUV (1960);UVW (1964);CIELAB (1976);CIELUV (1976)
/// 1.3 RGB:RGB color space;sRGB;rg chromaticity;Adobe;Wide-gamut;ProPhoto;scRGB;DCI-P3;Rec. 709;Rec. 2020;Rec. 2100
/// 1.4 YUV:YUV (PAL);YDbDr (SECAM;PAL-N);YIQ(NTSC);YCbCr(Rec. 601;Rec. 709;Rec. 2020;Rec. 2100);ICtCp(Rec. 2100);YPbPr;xvYCC;YCoCg
/// 1.5 Other:CcMmYK;CMY,CMYK;Coloroid;LMS;Hexachrome;HSL, HSV;HCL;Imaginary color;OSA-UCS;PCCS;RG;RYB;HWB
/// 2 颜色系统与标准(组织):ACES;ANPA;Colour Index International( CI list of dyes);DIC;Federal Standard 595;HKS;ICC profile;ISCC–NBS;Munsell;NCS;Ostwald;Pantone;RAL
/// 3 计算机图形图像学领域主要颜色空间的数值范围:
/// GRAY: 0~255
/// RGB: 0~255, 0~255, 0~255
/// YCbCr(JPEG): 0~255, 0~255, 0~255
/// YDbDr: 0.0~1.0, -1.333~1.333, -1.333~1.333
/// HSB=HSV: 0.0~360.0, 0.0~1.0, 0.0~1.0
/// HSL: 0.0~360.0, 0.0~1.0, 0.0~1.0
/// HSI: 0.0~360.0, 0.0~1.0, 0.0~1.0
/// CMYK: 0.0~1.0, 0.0-1.0, 0.0-1.0, 0.0~1.0
/// CMY: 0.0~1.0, 0.0-1.0, 0.0-1.0
/// YUV: 0.0~1.0, -0.436~0.436, -0.615~0.615
/// CIE 1931 XYZ: 0.0~0.9505, 0.0~1.0, 0.0~1.0890
/// CIE L*A*B*: 0.0~100.0, -128.0~127.0, -128.0~127.0
/// 本代码的数值范围就是按上述规划的,请注意不要超界哈!
/// 4 预编译option说明
/// 4.1 __OTHER__ 参考代码,不一定是对的哈!
/// 4.2 __ORIGINAL_SOURCECODE 一般是原始代码,对应的是稍微做了优化的;
/// 5 Easter eggs
/// 5.1 Visit www.Legalsoft.com.cn TO FIND MAGICAL IMAGE-PROCESSING SOFTWARES.
///
public static class ColorspaceHelper
{
public static byte[] FromColor(Color c)
{
return new byte[3] { c.R, c.G, c.B };
}
public static Color ToColor(byte R, byte G, byte B)
{
return Color.FromArgb(R, G, B);
}
///
/// RGB颜色值的规范化(0-255)(避免超界)
///
///
///
public static byte Clamp(int v)
{
return (byte)((v > 255) ? 255 : (v < 0) ? 0 : v);
}
///
/// RGB颜色值的规范化(浮点数,0.0-255.0)
///
///
///
public static double Clamp(double v)
{
return ((v > 255.0) ? 255.0 : (v < 0.0) ? 0.0 : v);
}
#region 灰度Gray的多种实用计算方法
///
/// RGB计算灰度值
/// (常见算法:据说按一个很著名的心理学公式)
/// 灰度计算等于有损压缩,必然要损失一些细节。
/// 实际上计算灰度是没有最好公式的,可以根据情况选择与分布这些参数。
/// 比如红黑色的图片,那么公式是:R*0.8+G*0.1+B*0.1,也未尝不可。
/// 基于统计的灰度计算是最合理的,可以使得细节的损失尽量减少,但计算量会大一些。
///
/// (0-255)
/// (0-255)
/// (0-255)
/// (0-255)
public static byte RGB2GRAY(byte R, byte G, byte B)
{
int c = (int)(R * 0.299 + G * 0.587 + B * 0.114);
return Clamp(c);
}
///
/// 按RGB平均值法计算灰度值
///
/// (0-255)
/// (0-255)
/// (0-255)
/// (0-255)
public static byte RGB2GRAY_AVERAGE(byte R, byte G, byte B)
{
#if __ORIGINAL_SOURSECODE__
int c = (int)((R + G + B) / 3.0);
#else
int c = (int)((R + G + B) * 0.333333333333333333);
#endif
return Clamp(c);
}
///
/// 适应Gamma校正的灰度计算公式
/// https://baike.baidu.com/item/%E7%81%B0%E5%BA%A6%E5%8C%96
/// 为什么是2.2?
/// 将人眼感受到的灰阶转换成自然界真实的灰阶。
/// 比如:(0.5)^2.2=0.2
///
/// (0-255)
/// (0-255)
/// (0-255)
///
public static byte RGB2GRAY_GAMMA(byte R, byte G, byte B)
{
double r = Math.Pow(R, 2.2);
double g = Math.Pow(G * 1.5, 2.2);
double b = Math.Pow(B * 0.6, 2.2);
#if __ORIGINAL_SOURSECODE__
double av = Math.Pow((r + g + b) / (1.0 + Math.Pow(1.5, 2.2) + Math.Pow(0.6, 2.2)), 1.0 / 2.2);
#else
double av = Math.Pow((r + g + b) * 0.265597304794686000, 0.454545454545455000);
#endif
return Clamp((int)av);
}
private static double[] tableGamma = null;
private static double[] tableGammaRev = null;
///
/// 适用于快速查表算法的Gamma指数表及开方表
/// Math.Pow(x,2.2) 与 Math.Pow(x,1.0/2.2)
///
private static void TableGamma()
{
tableGamma = new double[384];
tableGammaRev = new double[384];
for (int i = 0; i < 385; i++)
{
tableGamma[i] = Math.Pow(i, 2.2);
#if __ORIGINAL_SOURSECODE__
tableGammaRev[i] = Math.Pow(i, 1.0 / 2.2);
#else
tableGammaRev[i] = Math.Pow(i, 0.454545454545455000);
#endif
}
}
///
/// 适应Gamma校正的灰度值快速查表算法
/// (不用求幂,近似算法,有误差)
///
/// (0-255)
/// (0-255)
/// (0-255)
///
public static byte RGB2GRAY_GAMMA_QUICK(byte R, byte G, byte B)
{
if (tableGamma == null || tableGammaRev == null)
{
TableGamma();
}
double aR = tableGamma[(int)R];
double aG = tableGamma[(int)(G * 1.5)];
double aB = tableGamma[(int)(B * 0.6)];
#if __ORIGINAL_SOURSECODE__
double av = tableGammaRev[(int)((aR + aG + aB) / (1.0 + Math.Pow(1.5, 2.2) + Math.Pow(0.6, 2.2)))];
#else
double av = tableGammaRev[(int)((aR + aG + aB) * 0.265597304794686000)];
#endif
return Clamp((int)av);
}
///
/// Photoshop(RGB1998)灰度计算方法
/// gamma = 2.20
///
/// (0-255)
/// (0-255)
/// (0-255)
///
public static byte RGB2GRAY_PHOTOSHOP(byte R, byte G, byte B)
{
double aR = Math.Pow(R, 2.2) * 0.2973;
double aG = Math.Pow(G, 2.2) * 0.6274;
double aB = Math.Pow(B, 2.2) * 0.0753;
#if __ORIGINAL_SOURSECODE__
double av = Math.Pow(aR + aG + aB, 1 / 2.2);
#else
double av = Math.Pow(aR + aG + aB, 0.454545454545455000);
#endif
return Clamp((int)av);
}
///
/// Photoshop(RGB1998)灰度计算的快速查表算法
/// gamma = 2.20
///
/// (0-255)
/// (0-255)
/// (0-255)
///
public static byte RGB2GRAY_PHOTOSHOP_QUICK(byte R, byte G, byte B)
{
if (tableGamma == null || tableGammaRev == null)
{
TableGamma();
}
double aR = tableGamma[R] * 0.2973;
double aG = tableGamma[G] * 0.6274;
double aB = tableGamma[B] * 0.0753;
double av = tableGammaRev[(int)(aR + aG + aB)];
return Clamp((int)av);
}
///
/// 按RGB计算灰度值(浮点数)
///
/// (0-255) or (0-1.0)
/// (0-255) or (0-1.0)
/// (0-255) or (0-1.0)
/// (0-255) or (0-1.0)
public static double RGB2GRAY(double R, double G, double B)
{
return Clamp(R * 0.299 + G * 0.587 + B * 0.114);
}
#endregion
#region RGB <-> YUV
///
/// RGB 转 YUV(YUV444)
/// YUV是三个分量。Y表示明亮度(Luminance或Luma),也就是灰度值。
/// U和V表示的是彩色信息,分别为色度和浓度(Chrominance和Chroma)。
///
/// byte(0-255)
/// byte(0-255)
/// byte(0-255)
/// double[3](Y:0-1,U:-0.436~0.436,V:-0.615~0.615)
public static double[] RGB2YUV(byte R, byte G, byte B)
{
double r = R / 255.0;
double g = G / 255.0;
double b = B / 255.0;
double Y = +0.299 * r + 0.587 * g + 0.114 * b;
double U = -0.148 * r - 0.289 * g + 0.437 * b;
double V = +0.615 * r - 0.515 * g - 0.100 * b;
return new double[3] { Y, U, V };
}
public static double[] RGB2YUV(byte[] RGB)
{
return RGB2YUV(RGB[0], RGB[1], RGB[2]);
}
///
/// YUV(YUV444) 转 RGB
/// https://blog.csdn.net/timini/article/details/78949768
///
/// (0,1.0)
/// (-0.436,+0.436)
/// (-0.615,+0.615)
/// byte[3]{R,G,B}
public static byte[] YUV2RGB(double Y, double U, double V)
{
int R = (int)((Y + 1.1398373983739837400 * V) * 255.0);
int G = (int)((Y - 0.3946517043589703515 * U - 0.5805986066674976801 * V) * 255.0);
int B = (int)((Y + 2.0321100917431192660 * U) * 255.0);
return new byte[3] { Clamp(R), Clamp(G), Clamp(B) };
}
public static byte[] YUV2RGB(double[] YUV)
{
return YUV2RGB(YUV[0], YUV[1], YUV[2]);
}
#endregion
#region RGB <-> YCbCr
///
/// RGB 转 YCbCr(YIO,YPbPr)
/// YCbCr(简称YCC)中,Y代表亮度,Cb和Cr蓝色(blue)和红色(red)的色度。
/// YCbCr是YUV的压缩和偏移的版本。
/// 算法好多, 选择JPEG算法。
/// https://www.cnblogs.com/Imageshop/archive/2013/02/14/2911309.html
///
/// byte(0-255)
/// byte(0-255)
/// byte(0-255)
/// byte[3]{Y,Cb,Cr}
public static byte[] RGB2YCbCr(byte R, byte G, byte B)
{
// JPEG conversion Method
int Ya = (int)(000 + 0.299000 * R + 0.587000 * G + 0.114000 * B);
int Cb = (int)(128 - 0.168736 * R - 0.331264 * G + 0.500000 * B);
int Cr = (int)(128 + 0.500000 * R - 0.418688 * G - 0.081312 * B);
return new byte[3] { Clamp(Ya), Clamp(Cb), Clamp(Cr) };
}
public static byte[] RGB2YCbCr(byte[] RGB)
{
return RGB2YCbCr(RGB[0], RGB[1], RGB[2]);
}
///
/// YCbCr(YIO) 转 RGB
/// YCbCr(简称YCC)中,Y代表亮度,Cb和Cr蓝色(blue)和红色(red)的色度。
/// YCbCr是YUV的压缩和偏移的版本。
/// 算法好多, 选择JPEG算法。
/// https://www.cnblogs.com/Imageshop/archive/2013/02/14/2911309.html
///
/// byte(0-255)
/// byte(0-255)
/// byte(0-255)
/// byte[3]{R,G,B}
public static byte[] YCbCr2RGB(byte Y, byte Cb, byte Cr)
{
// JPEG conversion Method
int R = (int)(Y + 1.402000 * (Cr - 128));
int G = (int)(Y - 0.344136 * (Cb - 128) - 0.714136 * (Cr - 128));
int B = (int)(Y + 1.772000 * (Cb - 128));
return new byte[3] { Clamp(R), Clamp(G), Clamp(B) };
}
public static byte[] YCbCr2RGB(byte[] YCbCr)
{
return YCbCr2RGB(YCbCr[0], YCbCr[1], YCbCr[2]);
}
#endregion
#region RGB <-> YDbDr
///
/// RGB 转 YDbDr
/// YDbDr也类似YCbCr,同样也是色度坐标不同。
/// YDbDr是SECAM制式电视系统所用的颜色模型。
/// https://en.wikipedia.org/wiki/YDbDr
/// https://www.cnblogs.com/Imageshop/archive/2013/02/15/2912907.html
///
/// (0-255)
/// (0-255)
/// (0-255)
/// double[3]{Y:0-1,Db:-1.333~1.333,Dr:-1.333~1.333}
public static double[] RGB2YDbDr(byte R, byte G, byte B)
{
//https://en.wikipedia.org/wiki/YDbDr
double r = R / 255.0;
double g = G / 255.0;
double b = B / 255.0;
double Ya = +0.299 * r + 0.587 * g + 0.114 * b;
double Db = -0.450 * r - 0.883 * g + 1.333 * b;
double Dr = -1.333 * r + 1.116 * g + 0.217 * b;
// laviewpbt修改
//double Ya = 0.2990 * R - 0.1688 * G - 0.5000 * B;
//double Db = 0.5870 * R - 0.3312 * G + 0.4186 * B;
//double Dr = 0.1140 * R + 0.5000 * G + 0.0814 * B;
return new double[3] { Ya, Db, Dr };
}
public static double[] RGB2YDbDr(byte[] RGB)
{
return RGB2YDbDr(RGB[0], RGB[1], RGB[2]);
}
#if __OTHER__
///
/// RGB 转 YDbDr
/// https://blog.csdn.net/leansmall/article/details/78896937
///
/// 0-255
/// 0-255
/// 0-255
/// byte[3]{Y:0-255,Db:0-255,Dr:0-255}
public static byte[] RGB2YDbDr_OTHER(byte R, byte G, byte B)
{
double Y = 0.2990 * R - 0.1688 * G - 0.5000 * B;
double Db = 0.5870 * R - 0.3312 * G + 0.4186 * B;
double Dr = 0.1140 * R + 0.5000 * G + 0.0814 * B;
return new byte[3] { Clamp((int)Y), Clamp((int)Db), Clamp((int)Dr) };
}
#endif
///
/// YDbDr 转 RGB
/// YDbDr也类似YCbCr,同样也是色度坐标不同。
/// YDbDr是SECAM制式电视系统所用的颜色模型。
/// https://www.cnblogs.com/Imageshop/archive/2013/02/15/2912907.html
///
/// 0.0-1.0
/// -1.333~1.333
/// -1.333~1.333
/// byte[3]{R,G,B}
public static byte[] YDbDr2RGB(double Y, double Db, double Dr)
{
//https://en.wikipedia.org/wiki/YDbDr
double R = Y + 0.000092303716148 * Db - 0.525912630661865 * Dr;
double G = Y - 0.129132898890509 * Db + 0.267899328207599 * Dr;
double B = Y + 0.664679059978955 * Db - 0.000079202543533 * Dr;
// laviewpbt修改
// 需要调整 1.333 倍
//double R = Y + 0.000246081707249 * Db - 1.402083073344533 * Dr;
//double G = Y - 0.344268308442098 * Db + 0.714219609001458 * Dr;
//double B = Y + 1.772034373903893 * Db - 0.000211153981059 * Dr;
R *= 255.0;
G *= 255.0;
B *= 255.0;
return new byte[3] { Clamp((int)R), Clamp((int)G), Clamp((int)B) };
}
public static byte[] YDbDr2RGB(double[] YDbDr)
{
return YDbDr2RGB(YDbDr[0], YDbDr[1], YDbDr[2]);
}
#if __OTHER__
///
/// YDbDr 转 RGB
/// https://www.cnblogs.com/Imageshop/archive/2013/02/15/2912907.html
///
/// 0-255
/// 0-255
/// 0-255
///
public static byte[] YDbDr2RGB_OTHER(byte Y, byte Db, byte Dr)
{
double R = Y + 0.000246081707249 * Db - 1.402083073344533 * Dr;
double G = Y - 0.344268308442098 * Db + 0.714219609001458 * Dr;
double B = Y + 1.772034373903893 * Db - 0.000211153981059 * Dr;
return new byte[3] { Clamp((int)R), Clamp((int)G), Clamp((int)B) };
}
#endif
#endregion
#region RGB <-> HSV
///
/// RGB 转 HSV(Hue,Saturation,Value)
///
/// (0-255)
/// (0-255)
/// (0-255)
/// [3]{H:0-360,S:0-1,V:0-1}
public static double[] RGB2HSV(byte R, byte G, byte B)
{
double r = R / 255.0;
double g = G / 255.0;
double b = B / 255.0;
double H, S, V;
double min = Math.Min(r, Math.Min(g, b));
double max = Math.Max(r, Math.Max(g, b));
double delta = max - min;
V = max;
// max != 0
if (Math.Abs(max) > float.Epsilon)
{
S = delta / max;
}
else
{
// r = g = b = 0
// s = 0, v 未定义
S = 0.0;
H = -1.0;
return new double[3] { H, S, V };
}
H = 0.0;
if (Math.Abs(delta) > float.Epsilon)
{
if (Math.Abs(r - max) < float.Epsilon)
{
// 在 yellow & magenta 之间
H = (g - b) / delta;
}
else if (Math.Abs(g - max) < float.Epsilon)
{
// 在 cyan & yellow 之间
H = 2.0 + (b - r) / delta;
}
else
{
// 在 magenta & cyan 之间
H = 4.0 + (r - g) / delta;
}
}
// 角度
H *= 60.0;
if (H < 0.0)
{
H += 360.0;
}
return new double[3] { H, S, V };
}
public static double[] RGB2HSV(byte[] RGB)
{
return RGB2HSV(RGB[0], RGB[1], RGB[2]);
}
///
/// HSV(Hue,Saturation,Value) 转 RGB
/// HSV中的V表示明度(Value/Brightness)。
/// 根据缩写不同,HSV有时也被称作HSB(就是说HSV和HSB是一回事)。
///
/// (0.0-360.0)
/// (0.0-1.0)
/// (0.0-1.0)
/// byte[3]{R,G,B}
public static byte[] HSV2RGB(double H, double S, double V)
{
double R, G, B;
if (Math.Abs(S) < float.Epsilon)
{
// 灰度
// R = G = B = V;
int vi = (int)(V * 255.0);
return new byte[3] { Clamp(vi), Clamp(vi), Clamp(vi) };
}
H /= 60.0;
int i = (int)Math.Floor(H);
double f = H - i;
double p = V * (1 - S);
double q = V * (1 - S * f);
double t = V * (1 - S * (1 - f));
// 按扇区 0 到 5 赋值
switch (i)
{
case 0:
R = V;
G = t;
B = p;
break;
case 1:
R = q;
G = V;
B = p;
break;
case 2:
R = p;
G = V;
B = t;
break;
case 3:
R = p;
G = q;
B = V;
break;
case 4:
R = t;
G = p;
B = V;
break;
default:
R = V;
G = p;
B = q;
break;
}
int ri = (int)(R * 255.0);
int gi = (int)(G * 255.0);
int bi = (int)(B * 255.0);
return new byte[3] { Clamp(ri), Clamp(gi), Clamp(bi) };
}
public static byte[] HSV2RGB(double[] HSV)
{
return HSB2RGB(HSV);
}
#endregion
#region RGB <-> HSL
///
/// RGB 转 HSL(Hue,Saturation,Lightness=Luminance)
/// https://blog.csdn.net/timini/article/details/78949768
///
/// (0,255)
/// (0,255)
/// (0,255)
/// double[3]{H:0-360,S:0.0-1.0,L:0.0-1.0}
public static double[] RGB2HSL(byte R, byte G, byte B)
{
// 换算0-1
double r = R / 255.0;
double g = G / 255.0;
double b = B / 255.0;
double max = Math.Max(r, Math.Max(g, b));
double min = Math.Min(r, Math.Min(g, b));
double H = 0.0, S = 0.0, L = 0.0;
// Hue
// max = min
if (Math.Abs(max - min) < float.Epsilon)
{
H = 0.0; // undefined
}
else if (Math.Abs(max - r) < float.Epsilon && g >= b)
{
H = 60.0 * (g - b) / (max - min);
}
else if (Math.Abs(max - r) < float.Epsilon && g < b)
{
H = 60.0 * (g - b) / (max - min) + 360.0;
}
else if (Math.Abs(max - g) < float.Epsilon)
{
H = 60.0 * (b - r) / (max - min) + 120.0;
}
else if (Math.Abs(max - b) < float.Epsilon)
{
H = 60.0 * (r - g) / (max - min) + 240.0;
}
// Luminance
L = (max + min) / 2.0;
// Saturation
if (Math.Abs(L) < float.Epsilon || Math.Abs(max - min) < float.Epsilon)
{
S = 0.0;
}
else if (0 < L && L <= 0.5)
{
S = (max - min) / (max + min);
}
else if (L > 0.5)
{
S = (max - min) / (2 - (max + min));
}
return new double[3] { H, S, L };
}
public static double[] RGB2HSL(byte[] RGB)
{
return RGB2HSL(RGB[0], RGB[1], RGB[2]);
}
#if __OTHER__
///
/// RGB 转 HSL
/// https://blog.csdn.net/qq_35247586/article/details/109919637
///
/// (0,255)
/// (0,255)
/// (0,255)
/// double[3]{H:0-360,S:0-1,L:0-1}
public static double[] RGB2HSL(byte R, byte G, byte B)
{
// normalize red, green, blue values
double r = (double)R / 255.0;
double g = (double)G / 255.0;
double b = (double)B / 255.0;
double max = Math.Max(r, Math.Max(g, b));
double min = Math.Min(r, Math.Min(g, b));
double delta = max - min;
double sum = max + min;
double L = sum / 2.0;
// delta==0, max==min
if (Math.Abs(delta) < float.Epsilon)
{
return new double[3] { 0, 0, L };
}
double S = delta / ((sum < 1.0) ? sum : (2.0 - sum));
double H = 0.0;
if (Math.Abs(r - max) < float.Epsilon) { H = (g - b) / delta / 6.0; }
else if (Math.Abs(g - max) < float.Epsilon) { H = (2.0 + (b - r) / delta) / 6.0; }
else { H = (4.0 + (r - g) / delta) / 6.0; }
if (H < 0) H += 1.0;
// 原文输出 H,0--1.0;这里转换为 0--360
H *= 360.0;
return new double[3] { H, S, L };
}
#endif
///
/// HSL 转 RGB
/// https://blog.csdn.net/qq_35247586/article/details/109919637
///
/// 0-360
/// 0-1.0
/// 0-1.0
/// byte[3]{R,G,B}
public static byte[] HSL2RGB(double H, double S, double L)
{
// 原文是 0-1,这里转换一下。
H /= 360.0;
if (Math.Abs(S) < float.Epsilon)
{
L = L * 255.0;
return new byte[3] { Clamp((int)L), Clamp((int)L), Clamp((int)L) };
}
double q = (L <= 0.5) ? (L * (1.0 + S)) : (L + S - (L * S));
if (q <= 0.0)
{
return new byte[3] { 0, 0, 0 };
}
double m = L + L - q;
double v = (q - m) / q;
if (Math.Abs(H - 1.0) < float.Epsilon) H = 0.0;
H *= 6.0;
int n = (int)H;
double f = H - n;
double t = q * v * f;
double y = m + t;
double z = q - t;
double R = 0.0, G = 0.0, B = 0.0;
switch (n)
{
case 0: R = q; G = y; B = m; break;
case 1: R = z; G = q; B = m; break;
case 2: R = m; G = q; B = y; break;
case 3: R = m; G = z; B = q; break;
case 4: R = y; G = m; B = q; break;
case 5: R = q; G = m; B = z; break;
}
R *= 255.0;
G *= 255.0;
B *= 255.0;
return new byte[3] { Clamp((int)R), Clamp((int)G), Clamp((int)B) };
}
public static byte[] HSL2RGB(double[] HSL)
{
return HSL2RGB(HSL[0], HSL[1], HSL[2]);
}
#if __OTHER__
///
/// HSL(Hue,Saturation,Lightness=Luminance) 转 RGB.
/// https://blog.csdn.net/timini/article/details/78949768
///
/// Hue, must be in [0, 360].
/// Saturation, must be in [0, 1].
/// Luminance, must be in [0, 1].
/// byte[3]{R,G,B}
public static byte[] HSL2RGB(double H, double S, double L)
{
if (Math.Abs(S) < float.Epsilon)
{
// achromatic color (gray scale)
byte aL = (byte)(L * 255.0);
return new byte[3] { aL, aL, aL };
}
else
{
double q = (L < 0.5) ? (L * (1.0 + S)) : (L + S - (L * S));
double p = (2.0 * L) - q;
double Hk = H / 360.0;
double[] T = new double[3];
T[0] = Hk + (1.0 / 3.0); // Tr
T[1] = Hk; // Tb
T[2] = Hk - (1.0 / 3.0); // Tg
for (int i = 0; i < 3; i++)
{
if (T[i] < 0) T[i] += 1.0;
if (T[i] > 1) T[i] -= 1.0;
if ((T[i] * 6) < 1)
{
T[i] = p + ((q - p) * 6.0 * T[i]);
}
else if ((T[i] * 2.0) < 1) //(1.0/6.0)<=T[i] && T[i]<0.5
{
T[i] = q;
}
else if ((T[i] * 3.0) < 2) // 0.5<=T[i] && T[i]<(2.0/3.0)
{
T[i] = p + (q - p) * ((2.0 / 3.0) - T[i]) * 6.0;
}
else
{
T[i] = p;
}
T[i] *= 255.0;
}
return new byte[3] { Clamp((int)T[0]), Clamp((int)T[1]), Clamp((int)T[2]) };
}
}
#endif
#endregion
#region RGB <-> HSB
///
/// RGB 转 HSB(Hue, Saturation, Brightness)
/// HSV中的V表示明度(Value/Brightness)。
/// 根据缩写不同,HSV有时也被称作HSB(就是说HSV和HSB是一回事)。
/// https://blog.csdn.net/timini/article/details/78949768
/// https://www.cnblogs.com/Free-Thinker/p/4950364.html
///
/// (0-255)
/// (0-255)
/// (0-255)
/// double[3]{H:0-360,S:0-1.0,B:0-1.0}
public static double[] RGB2HSB(byte R, byte G, byte B)
{
// Normalize red, green and blue values
double r = R / 255.0;
double g = G / 255.0;
double b = B / 255.0;
// conversion start
double max = Math.Max(r, Math.Max(g, b));
double min = Math.Min(r, Math.Min(g, b));
double H = 0.0;
if (Math.Abs(max - min) > 0.0)
{
if (Math.Abs(max - r) < float.Epsilon && g >= b)
{
H = 60.0 * (g - b) / (max - min);
}
else if (Math.Abs(max - r) < float.Epsilon && g < b)
{
H = 60.0 * (g - b) / (max - min) + 360.0;
}
else if (Math.Abs(max - g) < float.Epsilon)
{
H = 60.0 * (b - r) / (max - min) + 120.0;
}
else if (Math.Abs(max - b) < float.Epsilon)
{
H = 60.0 * (r - g) / (max - min) + 240.0;
}
}
double S = (Math.Abs(max) < float.Epsilon) ? 0.0 : (1.0 - (min / max));
double V = max;
return new double[3] { H, S, V };
}
public static double[] RGB2HSB(byte[] RGB)
{
return RGB2HSB(RGB[0], RGB[1], RGB[2]);
}
///
/// HSB 转 RGB
///
/// 0-360
/// 0-1
/// 0-1
/// byte[3]{R,G,B}
public static byte[] HSB2RGB(double H, double S, double B)
{
double r = 0.0;
double g = 0.0;
double b = 0.0;
if (Math.Abs(S) < float.Epsilon)
{
r = g = b = B;
}
else
{
// the color wheel consists of 6 sectors. Figure out which sector you're in.
double sectorPos = H / 60.0;
int sectorNumber = (int)(Math.Floor(sectorPos));
// get the fractional part of the sector
double fractionalSector = sectorPos - sectorNumber;
// calculate values for the three axes of the color.
double p = B * (1.0 - S);
double q = B * (1.0 - (S * fractionalSector));
double t = B * (1.0 - (S * (1.0 - fractionalSector)));
// assign the fractional colors to r, g, and b based on the sector the angle is in.
switch (sectorNumber)
{
case 0:
r = B;
g = t;
b = p;
break;
case 1:
r = q;
g = B;
b = p;
break;
case 2:
r = p;
g = B;
b = t;
break;
case 3:
r = p;
g = q;
b = B;
break;
case 4:
r = t;
g = p;
b = B;
break;
case 5:
r = B;
g = p;
b = q;
break;
}
}
r *= 255.0;
g *= 255.0;
b *= 255.0;
return new byte[3] { Clamp((int)r), Clamp((int)g), Clamp((int)b) };
}
public static byte[] HSB2RGB(double[] HSB)
{
return HSB2RGB(HSB[0], HSB[1], HSB[2]);
}
#endregion
#region RGB <-> HSI
///
/// RGB 转 HSI
/// HSI颜色空间是从人的视觉系统出发,
/// 用色调(Hue)、色饱和度(Saturation或Chroma)
/// 和亮度(Intensity或Brightness)来描述色彩。
/// https://github.com/DrNickRedfern/RGB2HSI
/// http://fourier.eng.hmc.edu/e161/lectures/ColorProcessing/node3.html
///
/// 0-255
/// 0-255
/// 0-255
/// double[3]{H:0-360,S:0-1,I:0-1}
public static double[] RGB2HSI(byte R, byte G, byte B)
{
#if __OTHER__
//https://github.com/DrNickRedfern/RGB2HSI
// 原文是 0-1,规范化一下适用于 0-255
double r = R / 255.0;
double g = G / 255.0;
double b = B / 255.0;
double va = ((r - g) + (r - b)) / 2.0;
double kb = (r - g) * (r - g) + (r - b) * (g - b);
if (Math.Abs(kb) < float.Epsilon) { return new double[3] { 0.0, 0.0, r }; }
double vb = Math.Sqrt(kb);
double theta = Math.Acos(va / vb);
double H = (b <= g) ? theta : (2.0 * Math.PI - theta);
if (Math.Abs(r + g + b) < float.Epsilon) { return new double[3] { 0.0, 0.0, 0.0 }; }
double S = 1.0 - 3.0 * Math.Min(r, Math.Min(g, b)) / (r + g + b);
double I = (r + g + b) / 3.0;
H = H * 180.0 / Math.PI;
return new double[3] { H, S, I };
#endif
//http://fourier.eng.hmc.edu/e161/lectures/ColorProcessing/node3.html
double I = (R + G + B);
if (Math.Abs(I) < float.Epsilon) { return new double[3] { 0.0, 0.0, 0.0 }; }
double r = R / I;
double g = G / I;
double b = B / I;
I = I / 3.0;
if (Math.Abs(R - G) < float.Epsilon && Math.Abs(G - B) < float.Epsilon)
{
return new double[3] { 0.0, 0.0, I };
}
double w = 0.5 * (R - G + R - B) / Math.Sqrt((R - G) * (R - G) + (R - B) * (G - B));
if (w > 1.0) w = 1.0;
if (w < -1.0) w = -1.0;
double H = Math.Acos(w);
if (B > G) H = 2.0 * Math.PI - H;
double S = 0.0;
if (r <= g && r <= b) S = 1.0 - 3.0 * r;
if (g <= r && g <= b) S = 1.0 - 3.0 * g;
if (b <= r && b <= g) S = 1.0 - 3.0 * b;
H = H * 180.0 / Math.PI;
return new double[3] { H, S, I };
}
public static double[] RGB2HSI(byte[] RGB)
{
return RGB2HSI(RGB[0], RGB[1], RGB[2]);
}
///
/// HSI 转 RGB
/// HSI颜色空间是从人的视觉系统出发,
/// 用色调(Hue)、色饱和度(Saturation或Chroma)
/// 和亮度(Intensity或Brightness)来描述色彩。
/// http://fourier.eng.hmc.edu/e161/lectures/ColorProcessing/node3.html
///
/// 0.0-360.0
/// 0.0-1.0
/// 0.0-1.0
/// byte[3]{R,G,B}
public static byte[] HSI2RGB(double H, double S, double I)
{
#if __OTHER__
// 这段代码不严谨!
double aB = I * (1.0 - S);
double aR = I * (1.0 + (S * Math.Cos(H * Math.PI / 180.0)) / (Math.Cos((60.0 - H) * Math.PI / 180.0)));
double aG = 3.0 * I - (aR + aB);
// 原文是 0-1,规范化一下适用于 0-255
aR *= 255.0; aG *= 255.0; aB *= 255.0;
return new byte[3] { Clamp((int)aR), Clamp((int)aG), Clamp((int)aB) };
#endif
//http://fourier.eng.hmc.edu/e161/lectures/ColorProcessing/node3.html
H *= Math.PI / 180.0;
double r = 0.0, g = 0.0, b = 0.0;
// S = 0
if (Math.Abs(S) < float.Epsilon)
{
r = g = b = I;
}
else
{
if ((H >= 0) && (H < 2.0 * Math.PI / 3.0))
{
b = (1.0 - S) / 3.0;
r = (1.0 + S * Math.Cos(H) / Math.Cos(Math.PI / 3.0 - H)) / 3.0;
g = 1.0 - r - b;
}
else if ((H >= 2.0 * Math.PI / 3.0) && (H < 4.0 * Math.PI / 3.0))
{
H = H - 2.0 * Math.PI / 3.0;
r = (1.0 - S) / 3.0;
g = (1.0 + S * Math.Cos(H) / Math.Cos(Math.PI / 3.0 - H)) / 3.0;
b = 1.0 - r - g;
}
else if ((H >= 4.0 * Math.PI / 3.0) && (H < 2.0 * Math.PI))
{
H = H - 4.0 * Math.PI / 3.0;
g = (1.0 - S) / 3.0;
b = (1.0 + S * Math.Cos(H) / Math.Cos(Math.PI / 3.0 - H)) / 3.0;
r = 1.0 - b - g;
}
else
{
// H out of range!
}
r = r * 3.0 * I;
g = g * 3.0 * I;
b = b * 3.0 * I;
}
r *= 255.0; g *= 255.0; b *= 255.0;
return new byte[3] { Clamp((int)r), Clamp((int)g), Clamp((int)b) };
}
public static byte[] HSI2RGB(double[] HSI)
{
return HSI2RGB(HSI[0], HSI[1], HSI[2]);
}
#endregion
#region RGB <-> CMY
///
/// RGB 转 CMY
///
///
///
///
/// double[3]{C:0-1,M:0-1,Y:0-1}
public static double[] RGB2CMY(byte R, byte G, byte B)
{
return new double[3] {
(1.0 - R/255.0),
(1.0 - G/255.0),
(1.0 - B/255.0)
};
}
public static double[] RGB2CMY(byte[] RGB)
{
return RGB2CMY(RGB[0], RGB[1], RGB[2]);
}
///
/// CMY 转 RGB
///
/// 0-1
/// 0-1
/// 0-1
///
public static byte[] CMY2RGB(double C, double M, double Y)
{
return new byte[3] {
(byte)(255 - C*255.0),
(byte)(255 - M*255.0),
(byte)(255 - Y*255.0)
};
}
public static byte[] CMY2RGB(double[] CMY)
{
return CMY2RGB(CMY[0], CMY[1], CMY[2]);
}
#endregion
#region RGB <-> CMYK
///
/// RGB 转 CMYK(CMJN)
/// CMYK表示青(Cyan)品红(Magenta)黄(Yellow)黑(BlacK)四种颜料。
/// https://blog.csdn.net/rentian1/article/details/81185515
///
/// (0-255)
/// (0-255)
/// (0-255)
/// double[4]{C,M,Y,K}:(0-1.0)
public static double[] RGB2CMYK(byte R, byte G, byte B)
{
#if __ORIGINAL_SOURSECODE__
double C = 1.0 - R / 255.0;
double M = 1.0 - G / 255.0;
double Y = 1.0 - B / 255.0;
double K = Math.Min(C, Math.Min(M, Y));
if (Math.Abs(K - 1.0) < float.Epsilon)
{
return new double[4] { 0.0, 0.0, 0.0, K };
}
C = (C - K) / (1.0 - K);
M = (M - K) / (1.0 - K);
Y = (Y - K) / (1.0 - K);
return new double[4] { C, M, Y, K };
#else
double V = 0.0039215686274;
double C = 1.0 - R * V;
double M = 1.0 - G * V;
double Y = 1.0 - B * V;
double K = Math.Min(C, Math.Min(M, Y));
if (Math.Abs(K - 1.0) < float.Epsilon)
{
return new double[4] { 0.0, 0.0, 0.0, K };
}
double Z = 1.0 / (1.0 - K);
C = (C - K) * Z;
M = (M - K) * Z;
Y = (Y - K) * Z;
return new double[4] { C, M, Y, K };
#endif
}
public static double[] RGB2CMYK(byte[] RGB)
{
return RGB2CMYK(RGB[0], RGB[1], RGB[2]);
}
///
/// CMYK(CMJN) 转 RGB
/// CMYK表示青(Cyan)品红(Magenta)黄(Yellow)黑(BlacK)四种颜料。
/// https://blog.csdn.net/rentian1/article/details/81185515
///
/// Cyan(0.0-1.0)
/// Magenta(0.0-1.0)
/// Yellow(0.0-1.0)
/// BlacK(0.0-1.0)
/// byte[3]{R,G,B}
public static byte[] CMYK2RGB(double C, double M, double Y, double K)
{
int R = (int)(255.0 * (1.0 - C) * (1.0 - K));
int G = (int)(255.0 * (1.0 - M) * (1.0 - K));
int B = (int)(255.0 * (1.0 - Y) * (1.0 - K));
return new byte[3] { Clamp(R), Clamp(G), Clamp(B) };
}
public static byte[] CMYK2RGB(double[] CMYK)
{
return CMYK2RGB(CMYK[0], CMYK[1], CMYK[2], CMYK[3]);
}
#endregion
#region RGB <-> CIE LAB
///
/// RGB 转 CIE L*A*B*
/// https://blog.csdn.net/qq_38701868/article/details/89433038
///
/// (0-255)
/// (0-255)
/// (0-255)
/// double[3]{L:0-100,A:-128~127,B:-128~127}
public static double[] RGB2LAB(byte R, byte G, byte B)
{
double L = 0.3811 * R + 0.5783 * G + 0.0402 * B;
double M = 0.1967 * R + 0.7244 * G + 0.0782 * B;
double S = 0.0241 * R + 0.1288 * G + 0.8444 * B;
//若RGB值均为0,则LMS为0,防止数学错误log0
if ((int)L != 0) L = Math.Log(L) / Math.Log(10.0);
if ((int)M != 0) M = Math.Log(M) / Math.Log(10.0);
if ((int)S != 0) S = Math.Log(S) / Math.Log(10.0);
double aL = (L + M + S) / Math.Sqrt(3.0);
double aA = (L + M - 2 * S) / Math.Sqrt(6.0);
double aB = (L - M) / Math.Sqrt(2.0);
return new double[3] { aL, aA, aB };
}
public static double[] RGB2LAB(byte[] RGB)
{
return RGB2LAB(RGB[0], RGB[1], RGB[2]);
}
///
/// CIE LAB 转 RGB
/// https://blog.csdn.net/qq_38701868/article/details/89433038
///
/// (0-100)
/// (-128~127)
/// (-128~127param>
/// byte[3]{r,g,b}
public static byte[] LAB2RGB(double L, double A, double B)
{
L /= Math.Sqrt(3.0);
A /= Math.Sqrt(6.0);
B /= Math.Sqrt(2.0);
double xL = L + A + B;
double xM = L + A - B;
double xS = L - 2 * A;
xL = Math.Pow(10.0, xL);
xM = Math.Pow(10.0, xM);
xS = Math.Pow(10.0, xS);
double dR = +4.4679 * xL - 3.5873 * xM + 0.1193 * xS;
double dG = -1.2186 * xL + 2.3809 * xM - 0.1624 * xS;
double dB = +0.0497 * xL - 0.2439 * xM + 1.2045 * xS;
//防止溢出,若求得RGB值大于255则置为255,若小于0则置为0
return new byte[3] { Clamp((int)dR), Clamp((int)dG), Clamp((int)dB) };
}
public static byte[] LAB2RGB(double[] LAB)
{
return LAB2RGB(LAB[0], LAB[1], LAB[2]);
}
#endregion
#region RGB <-> CIE 1931 XYZ
#if __OTHER__
///
/// RGB 转 CIE XYZ
/// https://www.cnblogs.com/Imageshop/archive/2013/01/31/2888097.html
///
/// (0-255)
/// (0-255)
/// (0-255)
/// double[3]{X,Y,Z}
public static double[] RGB2XYZ(byte R, byte G, byte B)
{
double X = 0.412453 * R + 0.357580 * G + 0.180423 * B;
double Y = 0.212671 * R + 0.715160 * G + 0.072169 * B;
double Z = 0.019334 * R + 0.119193 * G + 0.950227 * B;
//laviewpbt修正后的公式
//double X = 0.433953 * R + 0.376219 * G + 0.189828 * B;
//double Y = 0.212671 * R + 0.715160 * G + 0.072169 * B;
//double Z = 0.017758 * R + 0.109477 * G + 0.872765 * B;
return new double[3] { X / 255.0, Y / 255.0, Z / 255.0 };
}
#endif
///
/// RGB 转 CIE 1931/1976 XYZ
/// (Observer = 2°, Illuminant = D65)
/// https://www.cnblogs.com/Free-Thinker/p/4950364.html
/// https://blog.csdn.net/timini/article/details/78949768
///
/// (0-255)
/// (0-255)
/// (0-255)
/// double[3]{X:0-0.9505,Y:0-1.0000,Z:0-1.0890}
public static double[] RGB2XYZ(byte R, byte G, byte B)
{
// Normalize red, green, blue values
double r = R / 255.0;
double g = G / 255.0;
double b = B / 255.0;
// convert to a sRGB form
// 按https://www.cnblogs.com/Free-Thinker/p/4950364.html 2.2
// 按https://github.com/hvalidi/ColorMine 2.4
// CIE 1931 XYZ:2.2 --> 2.4
double X = (r > 0.04045) ? Math.Pow((r + 0.055) / (1.055), 2.4) : (r / 12.92);
double Y = (g > 0.04045) ? Math.Pow((g + 0.055) / (1.055), 2.4) : (g / 12.92);
double Z = (b > 0.04045) ? Math.Pow((b + 0.055) / (1.055), 2.4) : (b / 12.92);
// converts
#if __ORIGINAL_SOURCECODE__
return new double[3] {
(0.4124 * X + 0.3576 * Y + 0.1805 * Z),
(0.2126 * X + 0.7152 * Y + 0.0722 * Z),
(0.0193 * X + 0.1192 * Y + 0.9505 * Z)
};
#endif
return new double[3] {
(0.4124564 * X + 0.3575761 * Y + 0.1804375 * Z),
(0.2126729 * X + 0.7151522 * Y + 0.0722750 * Z),
(0.0193339 * X + 0.1191920 * Y + 0.9503041 * Z)
};
}
public static double[] RGB2XYZ(byte[] RGB)
{
return RGB2XYZ(RGB[0], RGB[1], RGB[2]);
}
#if __OTHER__
///
/// CIE 1931 XYZ 转 RGB
/// https://www.cnblogs.com/Imageshop/archive/2013/01/31/2888097.html
///
/// (0-255)
/// (0-255)
/// (0-255)
/// byte[3]{R,G,B}
public static byte[] XYZ2RGB(double X, double Y, double Z)
{
double R = +3.240479 * X - 1.537150 * Y - 0.498535 * Z; R *= 255.0;
double G = -0.969256 * X + 1.875992 * Y + 0.041556 * Z; G *= 255.0;
double B = +0.055648 * X - 0.204043 * Y + 1.057311 * Z; B *= 255.0;
//laviewpbt修正后的公式
//double R = +3.0799327 * X - 1.537150 * Y - 0.5427820 * Z; R *= 255.0;
//double G = -0.9212350 * X + 1.875992 * Y + 0.0452442 * Z; G *= 255.0;
//double B = +0.0528909 * X - 0.204043 * Y + 1.1511515 * Z; B *= 255.0;
return new byte[3] { Clamp((int)R), Clamp((int)G), Clamp((int)B) };
}
#endif
///
/// CIE 1931/1976 XYZ 转 RGB
/// (Observer = 2°, Illuminant = D65)
/// https://www.cnblogs.com/Free-Thinker/p/4950364.html
/// https://blog.csdn.net/timini/article/details/78949768
///
/// 0-0.9505
/// 0-1.0000
/// 0-1.0890
/// byte[3]{R,G,B]
public static byte[] XYZ2RGB(double X, double Y, double Z)
{
#if __ORIGINAL_SOURCECODE__
double[] c = new double[3];
c[0] = +X * 3.2410 - Y * 1.5374 - Z * 0.4986; // red
// G -0.0416 WRONG!应该是 +0.415
c[1] = -X * 0.9692 + Y * 1.8760 - Z * 0.0416; // green
c[2] = +X * 0.0556 - Y * 0.2040 + Z * 1.0570; // blue
for (int i = 0; i < 3; i++)
{
c[i] = (c[i] <= 0.0031308) ? 12.92 * c[i] : (1 + 0.055) * Math.Pow(c[i], (1.0 / 2.4)) - 0.055;
c[i] *= 255.0;
}
return new byte[3] {
Clamp((int)c[0]),
Clamp((int)c[1]),
Clamp((int)c[2])
};
#endif
// https://www.cnblogs.com/Free-Thinker/p/4950364.html
//double R = +X * 3.2410 - Y * 1.5374 - Z * 0.4986;
// 这行系数不同 -0.0416 ? +0.415
//double G = -X * 0.9692 + Y * 1.8760 - Z * 0.0416;
//double B = +X * 0.0556 - Y * 0.2040 + Z * 1.0570;
// https://github.com/hvalidi/ColorMine
double R = +3.2406 * X - 1.5372 * Y - 0.4986 * Z;
double G = -0.9689 * X + 1.8758 * Y + 0.0415 * Z;
double B = +0.0557 * X - 0.2040 * Y + 1.0570 * Z;
R = (R <= 0.0031308) ? 12.92 * R : 1.055 * Math.Pow(R, (1.0 / 2.4)) - 0.055;
G = (G <= 0.0031308) ? 12.92 * G : 1.055 * Math.Pow(G, (1.0 / 2.4)) - 0.055;
B = (B <= 0.0031308) ? 12.92 * B : 1.055 * Math.Pow(B, (1.0 / 2.4)) - 0.055;
R *= 255.0; G *= 255.0; B *= 255.0;
return new byte[3] { Clamp((int)R), Clamp((int)G), Clamp((int)B) };
}
public static byte[] XYZ2RGB(double[] XYZ)
{
return XYZ2RGB(XYZ[0], XYZ[1], XYZ[2]);
}
#endregion
#region XYZ <-> LAB
///
/// CIE 1931 XYZ 转 CIE LAB
/// https://www.cnblogs.com/Imageshop/archive/2013/02/02/2889897.html
///
/// (0-0.9505)
/// (0-1)
/// (0-1.089)
/// byte[3]{L:0-100,A:-128~127,B:-128&127}
public static double[] XYZ2LAB(double X, double Y, double Z)
{
double L = 116.0 * LABFunction(Y) - 16.0;
double A = 500.0 * (LABFunction(X) - LABFunction(Y));
double B = 200.0 * (LABFunction(Y) - LABFunction(Z));
return new double[3] { L, A, B };
}
public static double[] XYZ2LAB(double[] XYZ)
{
return XYZ2LAB(XYZ[0], XYZ[1], XYZ[2]);
}
private static double LABFunction(double t)
{
#if __ORIGINAL_SOURSECODE__
if (t > Math.Pow(6.0 / 29.0, 3.0)) return Math.Pow(t, 1.0 / 3.0);
return (Math.Pow(29.0 / 6.0, 2.0) * t / 3.0 + 4.0 / 29.0);
#else
return ((t > 0.008856) ? Math.Pow(t, (1.0 / 3.0)) : (7.787 * t + 16.0 / 116.0));
#endif
}
///
/// CIE LAB 转 CIE 1931 XYZ
/// https://www.cnblogs.com/Imageshop/archive/2013/02/02/2889897.html
/// https://www.cnblogs.com/Free-Thinker/p/4950364.html
///
/// 0-100
/// -128~127
/// -128~127
/// double[3]{X:0-0.9505,Y:0-1,Z:0-1.089}
public static double[] LAB2XYZ(double L, double A, double B)
{
double Y = LABFunctionRev((L + 16.0) / 116.0);
double X = LABFunctionRev((L + 16.0) / 116.0 + A / 500.0);
double Z = LABFunctionRev((L + 16.0) / 116.0 + B / 200.0);
return new double[3] { X, Y, Z };
}
public static double[] LAB2XYZ(double[] LAB)
{
return LAB2XYZ(LAB[0], LAB[1], LAB[2]);
}
private static double LABFunctionRev(double t)
{
if (t > (6.0 / 29.0)) return Math.Pow(t, 3.0);
return (3.0 * Math.Pow(6.0 / 29.0, 2.0) * (t - 4.0 / 29.0));
}
#endregion
#region HSB,HSL,CMYK,YUV,...
///
/// HSB 转 HSL
///
/// 0-360
/// 0-1
/// 0-1
/// double[3]{H:0-360,S:0-1,L:0-1}
public static double[] HSB2HSL(double H, double S, double B)
{
return RGB2HSL(HSB2RGB(H, S, B));
}
///
/// HSL 转 HSB
///
/// 0-360
/// 0-1.0
/// 0-1.0
/// double[3]{H:0-360,S:0-1,B:0-1}
public static double[] HSL2HSB(double H, double S, double L)
{
return RGB2HSB(HSL2RGB(H, S, L));
}
///
/// HSL 转 CMYK
///
/// 0-360
/// 0-1.0
/// 0-1.0
///
public static double[] HSL2CMYK(double H, double S, double L)
{
return RGB2CMYK(HSL2RGB(H, S, L));
}
///
/// HSL 转 YUV
///
/// 0-360
/// 0-1.0
/// 0-1.0
/// double[3](Y:0-1,U:-0.436~0.436,V:-0.615~0.615)
public static double[] HSL2YUV(double H, double S, double L)
{
return RGB2YUV(HSL2RGB(H, S, L));
}
///
/// HSB 转 CMYK
///
/// 0-360
/// 0-1
/// 0-1
/// double[4]{C,M,Y,K}(0-1)
public static double[] HSB2CMYK(double H, double S, double B)
{
return RGB2CMYK(HSB2RGB(H, S, B));
}
///
/// HSB 转 YUV
///
/// 0-360
/// 0-1
/// 0-1
/// double[3](Y:0-1,U:-0.436~0.436,V:-0.615~0.615)
public static double[] HSB2YUV(double H, double S, double B)
{
return RGB2YUV(HSB2RGB(H, S, B));
}
///
/// CMYK 转 HSL
///
/// Cyan(0.0-1.0)
/// Magenta(0.0-1.0)
/// Yellow(0.0-1.0)
/// BlacK(0.0-1.0)
/// double[3]{H:0-360,S:0-1,L:0-1}
public static double[] CMYK2HSL(double C, double M, double Y, double K)
{
return RGB2HSL(CMYK2RGB(C, M, Y, K));
}
///
/// CMYK 转 HSB
///
/// Cyan(0.0-1.0)
/// Magenta(0.0-1.0)
/// Yellow(0.0-1.0)
/// BlacK(0.0-1.0)
/// double[3]{H:0-360,S:0-1,B:0-1}
public static double[] CMYK2HSB(double C, double M, double Y, double K)
{
return RGB2HSB(CMYK2RGB(C, M, Y, K));
}
///
/// CMYK 转 YUV
///
/// Cyan(0.0-1.0)
/// Magenta(0.0-1.0)
/// Yellow(0.0-1.0)
/// BlacK(0.0-1.0)
/// double[3](Y:0-1,U:-0.436~0.436,V:-0.615~0.615)
public static double[] CMYK2YUV(double C, double M, double Y, double K)
{
return RGB2YUV(CMYK2RGB(C, M, Y, K));
}
///
/// YUV 转 HSL
///
/// byte(0-255)
/// byte(0-255),实际数值范围没有这么大。
/// byte(0-255),实际数值范围没有这么大。
/// double[3]{H:0-360,S:0-1,L:0-1}
public static double[] YUV2HSL(byte Y, byte U, byte V)
{
return RGB2HSL(YUV2RGB(Y, U, V));
}
///
/// YUV 转 HSL
///
/// (0,1.0)
/// (-0.436,+0.436)
/// (-0.615,+0.615)
/// double[3]{H:0-360,S:0-1,L:0-1}
public static double[] YUV2HSL(double Y, double U, double V)
{
return RGB2HSL(YUV2RGB(Y, U, V));
}
///
/// YUV 转 HSB
///
/// (0,1.0)
/// (-0.436,+0.436)
/// (-0.615,+0.615)
/// double[3]{H:0-360,S:0-1,B:0-1}
public static double[] YUV2HSB(double Y, double U, double V)
{
return RGB2HSB(YUV2RGB(Y, U, V));
}
///
/// YUV 转 CMYK
///
/// (0,1.0)
/// (-0.436,+0.436)
/// (-0.615,+0.615)
/// double[4]{C,M,Y,K}(0-1)
public static double[] YUV2CMYK(double Y, double U, double V)
{
return RGB2CMYK(YUV2RGB(Y, U, V));
}
#endregion
}
#region 测试代码
public static class ColorspaceTest
{
///
/// 生成典型颜色值的换算 与 逆换算 的结果对照 html
/// Usage:
/// (1)输出为文件
/// File.WriteAllText("colorspace.html", ColorspaceTest.Execute(), Encoding.UTF8);
/// (2)直接显示
/// webBrowser.DocumentText = ColorspaceTest.Execute();
///
///
public static string Execute()
{
Color[] colors = new Color[7] {
Color.Cyan,Color.Black, Color.White,Color.Gray,
Color.FromArgb(255,0,0),Color.FromArgb(0,255,0),
Color.FromArgb(0,0,255)
};
// 第一个颜色为随机颜色
Random rnd = new Random((int)DateTime.Now.Ticks);
colors[0] = Color.FromArgb((byte)rnd.Next(255), (byte)rnd.Next(255), (byte)rnd.Next(255));
StringBuilder sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("");
sb.AppendLine("");
sb.AppendLine("CMYK ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
double[] v = ColorspaceHelper.RGB2CMYK(rgb);
sb.AppendLine(Fi(v) + "
");
rgb = ColorspaceHelper.CMYK2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("");
sb.AppendLine("HSB ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
double[] v = ColorspaceHelper.RGB2HSB(rgb);
sb.AppendLine(Fi(v) + "
");
rgb = ColorspaceHelper.HSB2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("");
sb.AppendLine("HSV ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
double[] v = ColorspaceHelper.RGB2HSV(rgb);
sb.AppendLine(Fi(v) + "
");
rgb = ColorspaceHelper.HSV2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("");
sb.AppendLine("HSL ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
double[] v = ColorspaceHelper.RGB2HSL(rgb);
sb.AppendLine(Fi(v) + "
");
rgb = ColorspaceHelper.HSL2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("");
sb.AppendLine("HSI ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
double[] v = ColorspaceHelper.RGB2HSI(rgb);
sb.AppendLine(Fi(v) + "
");
rgb = ColorspaceHelper.HSI2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("");
sb.AppendLine("XYZ ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
double[] v = ColorspaceHelper.RGB2XYZ(rgb);
sb.AppendLine(Fi(v) + "
");
rgb = ColorspaceHelper.XYZ2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("");
sb.AppendLine("LAB ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
double[] v = ColorspaceHelper.RGB2LAB(rgb);
sb.AppendLine(Fi(v) + "
");
rgb = ColorspaceHelper.LAB2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("");
sb.AppendLine("YUV ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
double[] v = ColorspaceHelper.RGB2YUV(rgb);
sb.AppendLine(Fi(v) + "
");
rgb = ColorspaceHelper.YUV2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("");
sb.AppendLine("YCbCr ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
byte[] v = ColorspaceHelper.RGB2YCbCr(rgb);
sb.AppendLine(Bi(v) + "
");
rgb = ColorspaceHelper.YCbCr2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("");
sb.AppendLine("YDbDr ");
foreach (Color c in colors)
{
sb.AppendLine("");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "
");
double[] v = ColorspaceHelper.RGB2YDbDr(rgb);
sb.AppendLine(Fi(v) + "
");
rgb = ColorspaceHelper.YDbDr2RGB(v);
sb.AppendLine(Bi(rgb) + "
");
sb.AppendLine(" ");
}
sb.AppendLine(" ");
sb.AppendLine("
");
return sb.ToString();
}
private static string Bi(byte[] v)
{
return v[0] + "," + v[1] + "," + v[2];
}
private static string Fi(double[] v)
{
string s = "";
for (int i = 0; i < v.GetLength(0); i++)
{
s += String.Format("{0:F4}", v[i]) + ",";
}
return s.Substring(0, s.Length - 1);
}
}
#endregion
}
欢迎讨论指正。