常見顏色空間、模型及標準之演算法與源程式大全
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
{
/// <summary>
/// 常見的顏色空間、模型及其標準的計算方法與代碼大全
/// 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.
/// </summary>
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);
}
/// <summary>
/// RGB顏色值的規范化(0-255)(避免超界)
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
public static byte Clamp(int v)
{
return (byte)((v > 255) ? 255 : (v < 0) ? 0 : v);
}
/// <summary>
/// RGB顏色值的規范化(浮點數,0.0-255.0)
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
public static double Clamp(double v)
{
return ((v > 255.0) ? 255.0 : (v < 0.0) ? 0.0 : v);
}
#region 灰度Gray的多種實用計算方法
/// <summary>
/// RGB計算灰度值
/// (常見演算法:據說按一個很著名的心理學公式)
/// 灰度計算等于有損壓縮,必然要損失一些細節,
/// 實際上計算灰度是沒有最好公式的,可以根據情況選擇與分布這些引數,
/// 比如紅黑色的圖片,那么公式是:R*0.8+G*0.1+B*0.1,也未嘗不可,
/// 基于統計的灰度計算是最合理的,可以使得細節的損失盡量減少,但計算量會大一些,
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns>(0-255)</returns>
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);
}
/// <summary>
/// 按RGB平均值法計算灰度值
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns>(0-255)</returns>
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);
}
/// <summary>
/// 適應Gamma校正的灰度計算公式
/// https://baike.baidu.com/item/%E7%81%B0%E5%BA%A6%E5%8C%96
/// 為什么是2.2?
/// 將人眼感受到的灰階轉換成自然界真實的灰階,
/// 比如:(0.5)^2.2=0.2
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns></returns>
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;
/// <summary>
/// 適用于快速查表演算法的Gamma指數表及開方表
/// Math.Pow(x,2.2) 與 Math.Pow(x,1.0/2.2)
/// </summary>
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
}
}
/// <summary>
/// 適應Gamma校正的灰度值快速查表演算法
/// (不用求冪,近似演算法,有誤差)
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns></returns>
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);
}
/// <summary>
/// Photoshop(RGB1998)灰度計算方法
/// gamma = 2.20
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns></returns>
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);
}
/// <summary>
/// Photoshop(RGB1998)灰度計算的快速查表演算法
/// gamma = 2.20
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns></returns>
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);
}
/// <summary>
/// 按RGB計算灰度值(浮點數)
/// </summary>
/// <param name="R">(0-255) or (0-1.0)</param>
/// <param name="G">(0-255) or (0-1.0)</param>
/// <param name="B">(0-255) or (0-1.0)</param>
/// <returns>(0-255) or (0-1.0)</returns>
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
/// <summary>
/// RGB 轉 YUV(YUV444)
/// YUV是三個分量,Y表示明亮度(Luminance或Luma),也就是灰度值,
/// U和V表示的是彩色資訊,分別為色度和濃度(Chrominance和Chroma),
/// </summary>
/// <param name="R">byte(0-255)</param>
/// <param name="G">byte(0-255)</param>
/// <param name="B">byte(0-255)</param>
/// <returns>double[3](Y:0-1,U:-0.436~0.436,V:-0.615~0.615)</returns>
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]);
}
/// <summary>
/// YUV(YUV444) 轉 RGB
/// https://blog.csdn.net/timini/article/details/78949768
/// </summary>
/// <param name="Y">(0,1.0)</param>
/// <param name="U">(-0.436,+0.436)</param>
/// <param name="V">(-0.615,+0.615)</param>
/// <returns>byte[3]{R,G,B}</returns>
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
/// <summary>
/// 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
/// </summary>
/// <param name="R">byte(0-255)</param>
/// <param name="G">byte(0-255)</param>
/// <param name="B">byte(0-255)</param>
/// <returns>byte[3]{Y,Cb,Cr}</returns>
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]);
}
/// <summary>
/// YCbCr(YIO) 轉 RGB
/// YCbCr(簡稱YCC)中,Y代表亮度,Cb和Cr藍色(blue)和紅色(red)的色度,
/// YCbCr是YUV的壓縮和偏移的版本,
/// 演算法好多, 選擇JPEG演算法,
/// https://www.cnblogs.com/Imageshop/archive/2013/02/14/2911309.html
/// </summary>
/// <param name="Y">byte(0-255)</param>
/// <param name="Cb">byte(0-255)</param>
/// <param name="Cr">byte(0-255)</param>
/// <returns>byte[3]{R,G,B}</returns>
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
/// <summary>
/// RGB 轉 YDbDr
/// YDbDr也類似YCbCr,同樣也是色度坐標不同,
/// YDbDr是SECAM制式電視系統所用的顏色模型,
/// https://en.wikipedia.org/wiki/YDbDr
/// https://www.cnblogs.com/Imageshop/archive/2013/02/15/2912907.html
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns>double[3]{Y:0-1,Db:-1.333~1.333,Dr:-1.333~1.333}</returns>
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__
/// <summary>
/// RGB 轉 YDbDr
/// https://blog.csdn.net/leansmall/article/details/78896937
/// </summary>
/// <param name="R">0-255</param>
/// <param name="G">0-255</param>
/// <param name="B">0-255</param>
/// <returns>byte[3]{Y:0-255,Db:0-255,Dr:0-255}</returns>
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
/// <summary>
/// YDbDr 轉 RGB
/// YDbDr也類似YCbCr,同樣也是色度坐標不同,
/// YDbDr是SECAM制式電視系統所用的顏色模型,
/// https://www.cnblogs.com/Imageshop/archive/2013/02/15/2912907.html
/// </summary>
/// <param name="Y">0.0-1.0</param>
/// <param name="Db">-1.333~1.333</param>
/// <param name="Dr">-1.333~1.333</param>
/// <returns>byte[3]{R,G,B}</returns>
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__
/// <summary>
/// YDbDr 轉 RGB
/// https://www.cnblogs.com/Imageshop/archive/2013/02/15/2912907.html
/// </summary>
/// <param name="Y">0-255</param>
/// <param name="Db">0-255</param>
/// <param name="Dr">0-255</param>
/// <returns></returns>
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
/// <summary>
/// RGB 轉 HSV(Hue,Saturation,Value)
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns>[3]{H:0-360,S:0-1,V:0-1}</returns>
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]);
}
/// <summary>
/// HSV(Hue,Saturation,Value) 轉 RGB
/// HSV中的V表示明度(Value/Brightness),
/// 根據縮寫不同,HSV有時也被稱作HSB(就是說HSV和HSB是一回事),
/// </summary>
/// <param name="H">(0.0-360.0)</param>
/// <param name="S">(0.0-1.0)</param>
/// <param name="V">(0.0-1.0)</param>
/// <returns>byte[3]{R,G,B}</returns>
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
/// <summary>
/// RGB 轉 HSL(Hue,Saturation,Lightness=Luminance)
/// https://blog.csdn.net/timini/article/details/78949768
/// </summary>
/// <param name="R">(0,255)</param>
/// <param name="G">(0,255)</param>
/// <param name="B">(0,255)</param>
/// <returns>double[3]{H:0-360,S:0.0-1.0,L:0.0-1.0}</returns>
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__
/// <summary>
/// RGB 轉 HSL
/// https://blog.csdn.net/qq_35247586/article/details/109919637
/// </summary>
/// <param name="R">(0,255)</param>
/// <param name="G">(0,255)</param>
/// <param name="B">(0,255)</param>
/// <returns>double[3]{H:0-360,S:0-1,L:0-1}</returns>
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
/// <summary>
/// HSL 轉 RGB
/// https://blog.csdn.net/qq_35247586/article/details/109919637
/// </summary>
/// <param name="H">0-360</param>
/// <param name="S">0-1.0</param>
/// <param name="L">0-1.0</param>
/// <returns>byte[3]{R,G,B}</returns>
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__
/// <summary>
/// HSL(Hue,Saturation,Lightness=Luminance) 轉 RGB.
/// https://blog.csdn.net/timini/article/details/78949768
/// </summary>
/// <param name="H">Hue, must be in [0, 360].</param>
/// <param name="S">Saturation, must be in [0, 1].</param>
/// <param name="L">Luminance, must be in [0, 1].</param>
/// <returns>byte[3]{R,G,B}</returns>
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
/// <summary>
/// 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
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns>double[3]{H:0-360,S:0-1.0,B:0-1.0}</returns>
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]);
}
/// <summary>
/// HSB 轉 RGB
/// </summary>
/// <param name="H">0-360</param>
/// <param name="S">0-1</param>
/// <param name="B">0-1</param>
/// <returns>byte[3]{R,G,B}</returns>
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
/// <summary>
/// RGB 轉 HSI
/// HSI顏色空間是從人的視覺系統出發,
/// 用色調(Hue)、色飽和度(Saturation或Chroma)
/// 和亮度(Intensity或Brightness)來描述色彩,
/// https://github.com/DrNickRedfern/RGB2HSI
/// http://fourier.eng.hmc.edu/e161/lectures/ColorProcessing/node3.html
/// </summary>
/// <param name="R">0-255</param>
/// <param name="G">0-255</param>
/// <param name="B">0-255</param>
/// <returns>double[3]{H:0-360,S:0-1,I:0-1}</returns>
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]);
}
/// <summary>
/// HSI 轉 RGB
/// HSI顏色空間是從人的視覺系統出發,
/// 用色調(Hue)、色飽和度(Saturation或Chroma)
/// 和亮度(Intensity或Brightness)來描述色彩,
/// http://fourier.eng.hmc.edu/e161/lectures/ColorProcessing/node3.html
/// </summary>
/// <param name="H">0.0-360.0</param>
/// <param name="S">0.0-1.0</param>
/// <param name="I">0.0-1.0</param>
/// <returns>byte[3]{R,G,B}</returns>
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
/// <summary>
/// RGB 轉 CMY
/// </summary>
/// <param name="R"></param>
/// <param name="G"></param>
/// <param name="B"></param>
/// <returns>double[3]{C:0-1,M:0-1,Y:0-1}</returns>
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]);
}
/// <summary>
/// CMY 轉 RGB
/// </summary>
/// <param name="C">0-1</param>
/// <param name="M">0-1</param>
/// <param name="Y">0-1</param>
/// <returns></returns>
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
/// <summary>
/// RGB 轉 CMYK(CMJN)
/// CMYK表示青(Cyan)品紅(Magenta)黃(Yellow)黑(BlacK)四種顏料,
/// https://blog.csdn.net/rentian1/article/details/81185515
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns>double[4]{C,M,Y,K}:(0-1.0)</returns>
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]);
}
/// <summary>
/// CMYK(CMJN) 轉 RGB
/// CMYK表示青(Cyan)品紅(Magenta)黃(Yellow)黑(BlacK)四種顏料,
/// https://blog.csdn.net/rentian1/article/details/81185515
/// </summary>
/// <param name="C">Cyan(0.0-1.0)</param>
/// <param name="M">Magenta(0.0-1.0)</param>
/// <param name="Y">Yellow(0.0-1.0)</param>
/// <param name="K">BlacK(0.0-1.0)</param>
/// <returns>byte[3]{R,G,B}</returns>
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
/// <summary>
/// RGB 轉 CIE L*A*B*
/// https://blog.csdn.net/qq_38701868/article/details/89433038
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns>double[3]{L:0-100,A:-128~127,B:-128~127}</returns>
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]);
}
/// <summary>
/// CIE LAB 轉 RGB
/// https://blog.csdn.net/qq_38701868/article/details/89433038
/// </summary>
/// <param name="L">(0-100)</param>
/// <param name="A">(-128~127)</param>
/// <param name="B">(-128~127param>
/// <returns>byte[3]{r,g,b}</returns>
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__
/// <summary>
/// RGB 轉 CIE XYZ
/// https://www.cnblogs.com/Imageshop/archive/2013/01/31/2888097.html
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns>double[3]{X,Y,Z}</returns>
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
/// <summary>
/// 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
/// </summary>
/// <param name="R">(0-255)</param>
/// <param name="G">(0-255)</param>
/// <param name="B">(0-255)</param>
/// <returns>double[3]{X:0-0.9505,Y:0-1.0000,Z:0-1.0890}</returns>
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__
/// <summary>
/// CIE 1931 XYZ 轉 RGB
/// https://www.cnblogs.com/Imageshop/archive/2013/01/31/2888097.html
/// </summary>
/// <param name="X">(0-255)</param>
/// <param name="Y">(0-255)</param>
/// <param name="Z">(0-255)</param>
/// <returns>byte[3]{R,G,B}</returns>
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
/// <summary>
/// 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
/// </summary>
/// <param name="X">0-0.9505</param>
/// <param name="Y">0-1.0000</param>
/// <param name="Z">0-1.0890</param>
/// <returns>byte[3]{R,G,B]</returns>
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
/// <summary>
/// CIE 1931 XYZ 轉 CIE LAB
/// https://www.cnblogs.com/Imageshop/archive/2013/02/02/2889897.html
/// </summary>
/// <param name="X">(0-0.9505)</param>
/// <param name="Y">(0-1)</param>
/// <param name="Z">(0-1.089)</param>
/// <returns>byte[3]{L:0-100,A:-128~127,B:-128&127}</returns>
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
}
/// <summary>
/// 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
/// </summary>
/// <param name="L">0-100</param>
/// <param name="A">-128~127</param>
/// <param name="B">-128~127</param>
/// <returns>double[3]{X:0-0.9505,Y:0-1,Z:0-1.089}</returns>
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,...
/// <summary>
/// HSB 轉 HSL
/// </summary>
/// <param name="H">0-360</param>
/// <param name="S">0-1</param>
/// <param name="B">0-1</param>
/// <returns>double[3]{H:0-360,S:0-1,L:0-1}</returns>
public static double[] HSB2HSL(double H, double S, double B)
{
return RGB2HSL(HSB2RGB(H, S, B));
}
/// <summary>
/// HSL 轉 HSB
/// </summary>
/// <param name="H">0-360</param>
/// <param name="S">0-1.0</param>
/// <param name="L">0-1.0</param>
/// <returns>double[3]{H:0-360,S:0-1,B:0-1}</returns>
public static double[] HSL2HSB(double H, double S, double L)
{
return RGB2HSB(HSL2RGB(H, S, L));
}
/// <summary>
/// HSL 轉 CMYK
/// </summary>
/// <param name="H">0-360</param>
/// <param name="S">0-1.0</param>
/// <param name="L">0-1.0</param>
/// <returns></returns>
public static double[] HSL2CMYK(double H, double S, double L)
{
return RGB2CMYK(HSL2RGB(H, S, L));
}
/// <summary>
/// HSL 轉 YUV
/// </summary>
/// <param name="H">0-360</param>
/// <param name="S">0-1.0</param>
/// <param name="L">0-1.0</param>
/// <returns>double[3](Y:0-1,U:-0.436~0.436,V:-0.615~0.615)</returns>
public static double[] HSL2YUV(double H, double S, double L)
{
return RGB2YUV(HSL2RGB(H, S, L));
}
/// <summary>
/// HSB 轉 CMYK
/// </summary>
/// <param name="H">0-360</param>
/// <param name="S">0-1</param>
/// <param name="B">0-1</param>
/// <returns>double[4]{C,M,Y,K}(0-1)</returns>
public static double[] HSB2CMYK(double H, double S, double B)
{
return RGB2CMYK(HSB2RGB(H, S, B));
}
/// <summary>
/// HSB 轉 YUV
/// </summary>
/// <param name="H">0-360</param>
/// <param name="S">0-1</param>
/// <param name="B">0-1</param>
/// <returns>double[3](Y:0-1,U:-0.436~0.436,V:-0.615~0.615)</returns>
public static double[] HSB2YUV(double H, double S, double B)
{
return RGB2YUV(HSB2RGB(H, S, B));
}
/// <summary>
/// CMYK 轉 HSL
/// </summary>
/// <param name="C">Cyan(0.0-1.0)</param>
/// <param name="M">Magenta(0.0-1.0)</param>
/// <param name="Y">Yellow(0.0-1.0)</param>
/// <param name="K">BlacK(0.0-1.0)</param>
/// <returns>double[3]{H:0-360,S:0-1,L:0-1}</returns>
public static double[] CMYK2HSL(double C, double M, double Y, double K)
{
return RGB2HSL(CMYK2RGB(C, M, Y, K));
}
/// <summary>
/// CMYK 轉 HSB
/// </summary>
/// <param name="C">Cyan(0.0-1.0)</param>
/// <param name="M">Magenta(0.0-1.0)</param>
/// <param name="Y">Yellow(0.0-1.0)</param>
/// <param name="K">BlacK(0.0-1.0)</param>
/// <returns>double[3]{H:0-360,S:0-1,B:0-1}</returns>
public static double[] CMYK2HSB(double C, double M, double Y, double K)
{
return RGB2HSB(CMYK2RGB(C, M, Y, K));
}
/// <summary>
/// CMYK 轉 YUV
/// </summary>
/// <param name="C">Cyan(0.0-1.0)</param>
/// <param name="M">Magenta(0.0-1.0)</param>
/// <param name="Y">Yellow(0.0-1.0)</param>
/// <param name="K">BlacK(0.0-1.0)</param>
/// <returns>double[3](Y:0-1,U:-0.436~0.436,V:-0.615~0.615)</returns>
public static double[] CMYK2YUV(double C, double M, double Y, double K)
{
return RGB2YUV(CMYK2RGB(C, M, Y, K));
}
/// <summary>
/// YUV 轉 HSL
/// </summary>
/// <param name="Y">byte(0-255)</param>
/// <param name="U">byte(0-255),實際數值范圍沒有這么大,</param>
/// <param name="V">byte(0-255),實際數值范圍沒有這么大,</param>
/// <returns>double[3]{H:0-360,S:0-1,L:0-1}</returns>
public static double[] YUV2HSL(byte Y, byte U, byte V)
{
return RGB2HSL(YUV2RGB(Y, U, V));
}
/// <summary>
/// YUV 轉 HSL
/// </summary>
/// <param name="Y">(0,1.0)</param>
/// <param name="U">(-0.436,+0.436)</param>
/// <param name="V">(-0.615,+0.615)</param>
/// <returns>double[3]{H:0-360,S:0-1,L:0-1}</returns>
public static double[] YUV2HSL(double Y, double U, double V)
{
return RGB2HSL(YUV2RGB(Y, U, V));
}
/// <summary>
/// YUV 轉 HSB
/// </summary>
/// <param name="Y">(0,1.0)</param>
/// <param name="U">(-0.436,+0.436)</param>
/// <param name="V">(-0.615,+0.615)</param>
/// <returns>double[3]{H:0-360,S:0-1,B:0-1}</returns>
public static double[] YUV2HSB(double Y, double U, double V)
{
return RGB2HSB(YUV2RGB(Y, U, V));
}
/// <summary>
/// YUV 轉 CMYK
/// </summary>
/// <param name="Y">(0,1.0)</param>
/// <param name="U">(-0.436,+0.436)</param>
/// <param name="V">(-0.615,+0.615)</param>
/// <returns>double[4]{C,M,Y,K}(0-1)</returns>
public static double[] YUV2CMYK(double Y, double U, double V)
{
return RGB2CMYK(YUV2RGB(Y, U, V));
}
#endregion
}
#region 測驗代碼
public static class ColorspaceTest
{
/// <summary>
/// 生成典型顏色值的換算 與 逆換算 的結果對照 html
/// Usage:
/// (1)輸出為檔案
/// File.WriteAllText("colorspace.html", ColorspaceTest.Execute(), Encoding.UTF8);
/// (2)直接顯示
/// webBrowser.DocumentText = ColorspaceTest.Execute();
/// </summary>
/// <returns></returns>
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("<style>");
sb.AppendLine("table { border-collapse:collapse; } ");
sb.AppendLine("tr { height:35px;} ");
sb.AppendLine("td { padding:5px;text-align:center;} ");
sb.AppendLine("</style>");
sb.AppendLine("<table border=1>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>CMYK</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
double[] v = ColorspaceHelper.RGB2CMYK(rgb);
sb.AppendLine(Fi(v) + "<br>");
rgb = ColorspaceHelper.CMYK2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>HSB</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
double[] v = ColorspaceHelper.RGB2HSB(rgb);
sb.AppendLine(Fi(v) + "<br>");
rgb = ColorspaceHelper.HSB2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>HSV</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
double[] v = ColorspaceHelper.RGB2HSV(rgb);
sb.AppendLine(Fi(v) + "<br>");
rgb = ColorspaceHelper.HSV2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>HSL</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
double[] v = ColorspaceHelper.RGB2HSL(rgb);
sb.AppendLine(Fi(v) + "<br>");
rgb = ColorspaceHelper.HSL2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>HSI</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
double[] v = ColorspaceHelper.RGB2HSI(rgb);
sb.AppendLine(Fi(v) + "<br>");
rgb = ColorspaceHelper.HSI2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>XYZ</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
double[] v = ColorspaceHelper.RGB2XYZ(rgb);
sb.AppendLine(Fi(v) + "<br>");
rgb = ColorspaceHelper.XYZ2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>LAB</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
double[] v = ColorspaceHelper.RGB2LAB(rgb);
sb.AppendLine(Fi(v) + "<br>");
rgb = ColorspaceHelper.LAB2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>YUV</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
double[] v = ColorspaceHelper.RGB2YUV(rgb);
sb.AppendLine(Fi(v) + "<br>");
rgb = ColorspaceHelper.YUV2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>YCbCr</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
byte[] v = ColorspaceHelper.RGB2YCbCr(rgb);
sb.AppendLine(Bi(v) + "<br>");
rgb = ColorspaceHelper.YCbCr2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>YDbDr</td>");
foreach (Color c in colors)
{
sb.AppendLine("<td>");
byte[] rgb = ColorspaceHelper.FromColor(c);
sb.AppendLine(Bi(rgb) + "<br>");
double[] v = ColorspaceHelper.RGB2YDbDr(rgb);
sb.AppendLine(Fi(v) + "<br>");
rgb = ColorspaceHelper.YDbDr2RGB(v);
sb.AppendLine(Bi(rgb) + "<br>");
sb.AppendLine("</td>");
}
sb.AppendLine("</tr>");
sb.AppendLine("</table>");
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
}
歡迎討論指正,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/261462.html
標籤:其他
上一篇:B - Eastern Exhibition(思維)
下一篇:c語言中的字串函式的模擬實作
