常见颜色空间、模型及标准之算法与源程序大全

常见颜色空间、模型及标准之算法与源程序大全

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("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("");
            sb.AppendLine("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("");
            sb.AppendLine("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("");
            sb.AppendLine("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("");
            sb.AppendLine("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("");
            sb.AppendLine("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("");
            sb.AppendLine("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("");
            sb.AppendLine("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("");
            sb.AppendLine("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("");
            sb.AppendLine("");
            foreach (Color c in colors)
            {
                sb.AppendLine("");
            }
            sb.AppendLine("");

            sb.AppendLine("
CMYK"); 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("
HSB"); 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("
HSV"); 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("
HSL"); 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("
HSI"); 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("
XYZ"); 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("
LAB"); 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("
YUV"); 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("
YCbCr"); 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("
YDbDr"); 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("
"); 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 }

欢迎讨论指正。

你可能感兴趣的:(C#图像处理)