【Openxml】顏色變化屬性計算

2022-06-13 15:03:28

Openxml的顏色變化屬性

目前Openxml存在顏色變化屬性如下:

引數 說明
Hue 色調(色相)
HueModulate 色調調變,百分比
HueOffset 色調偏移量,角度值
Saturation 飽和度
SaturationModulation 飽和度調變,百分比
SaturationOffset 飽和度偏移量
Luminance 亮度
LuminanceModulation 亮度調變,百分比
LuminanceOffset 亮度偏移量
Alpha Alpha
AlphaModulation Alpha 調變,百分比
AlphaOffset Alpha 偏移量
Red 紅色
RedModulation 紅色調變,百分比
RedOffset 紅色偏移量
Blue 藍色
BlueModification 藍色調變
BlueOffse 藍色偏移量,百分比
Green 綠色
GreenModification 綠色調變,百分比
GreenOffset 綠色偏移量
Complement 補充
Gamma 伽瑪
Gray 灰色
Inverse 反函數
Inverse Gamma 反函數伽瑪
Shade 底紋,百分比
Tint 底紋,百分比

RGB和Hsl的相互轉換

其中有關RGB和Hsl的相互轉換的公式如下:

RGB轉Hsl公式如下:

Hsl轉RGB公式如下:

其中涉及到有Hsl計算的有以下九個屬性:

  • Hue、HueModulate、HueOffset
  • Saturation、SaturationModulation、SaturationOffset
  • Luminance、LuminanceModulation、LuminanceOffset

那麼我們開始寫程式碼:

定義RGB的類:

    /// <summary>
    /// 用 A R G B 表示的顏色
    /// </summary>
    public class ARgbColor
    {
        /// <summary>
        /// 建立 A R G B 顏色
        /// </summary>
        public ARgbColor()
        {
        }

        /// <summary>
        /// 建立 A R G B 顏色
        /// </summary>
        /// <param name="a"></param>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        public ARgbColor(byte a, byte r, byte g, byte b)
        {
            A = a;
            R = r;
            G = g;
            B = b;
        }

        /// <summary>
        /// 表示透明色
        /// </summary>
        public byte A { set; get; }

        /// <summary>
        /// 表示紅色
        /// </summary>
        public byte R { set; get; }

        /// <summary>
        /// 表示綠色
        /// </summary>
        public byte G { set; get; }

        /// <summary>
        /// 表示藍色
        /// </summary>
        public byte B { set; get; }

    }

定義顏色變化相關類ColorTransform,並且定義RGB和Hsl的相互轉換邏輯方法:

    /// <summary>
    ///     處理顏色之間的變換,調整,格式轉換
    /// </summary>
    public static class ColorTransform
    {

        /// <summary>
        ///  將<see cref="Color" />的資料轉換為Hsl
        /// </summary>
        /// <param name="color"></param>
        /// <returns></returns>
        public static (Degree hue, Percentage sat, Percentage lum, byte alpha) ColorToHsl(Color color)
        {
            var max = System.Math.Max(color.R, System.Math.Max(color.G, color.B));
            var min = System.Math.Min(color.R, System.Math.Min(color.G, color.B));
            var delta = max - min;
            var l = Percentage.FromDouble((max + min) / 2.0 / 255.0);
            var h = Degree.FromDouble(0);
            var s = Percentage.Zero;

            if (delta > 0)
            {
                s = l < Percentage.FromDouble(0.5)
                    ? Percentage.FromDouble((max - min) * 1.0 / (max + min))
                    : Percentage.FromDouble((max - min) * 1.0 / (2 * 255 - max - min));

                if (max == color.R)
                {
                    h = Degree.FromDouble((0 + (color.G - color.B) * 1.0 / delta) * 60);
                }
                else if (max == color.G)
                {
                    h = Degree.FromDouble((2 + (color.B - color.R) * 1.0 / delta) * 60);
                }
                else
                {
                    h = Degree.FromDouble((4 + (color.R - color.G) * 1.0 / delta) * 60);
                }
            }

            return (h, s, l, color.A);
        }

    }

            /// <summary>
        ///     將Hsl的資料轉換為<see cref="Color" />
        /// </summary>
        /// <param name="hue">色相</param>
        /// <param name="saturation">飽和度</param>
        /// <param name="lightness">亮度</param>
        /// <param name="a">透明度</param>
        /// <returns></returns>
        public static Color HslToColor(Degree hue, Percentage saturation, Percentage lightness, byte a = 0xFF)
        {
            var color = new Color { A = a };

            var hueValue = hue.DoubleValue;
            var saturationValue = saturation.DoubleValue;
            var lightnessValue = lightness.DoubleValue;

            var c = (1 - System.Math.Abs(2 * lightnessValue - 1)) * saturationValue;
            var x = c * (1 - System.Math.Abs((hueValue / 60) % 2 - 1));
            var m = lightnessValue - c / 2;

            var r = 0d;
            var g = 0d;
            var b = 0d;

            if (hueValue is >= 0 and < 60)
            {
                r = c;
                g = x;
                b = 0;
            }

            if (hueValue is >= 60 and < 120)
            {
                r = x;
                g = c;
                b = 0;
            }

            if (hueValue is >= 120 and < 180)
            {
                r = 0;
                g = c;
                b = x;
            }

            if (hueValue is >= 180 and < 240)
            {
                r = 0;
                g = x;
                b = c;
            }

            if (hueValue is >= 240 and < 300)
            {
                r = x;
                g = 0;
                b = c;
            }

            if (hueValue is >= 300 and < 360)
            {
                r = c;
                g = 0;
                b = x;
            }

            color.R = (byte) ((r + m) * 255);
            color.G = (byte) ((g + m) * 255);
            color.B = (byte) ((b + m) * 255);

            return color;
        }


然後我們來寫真正處理Openxml的Hsl相關屬性邏輯:

       /// <summary>
        ///     將<see cref="RgbColorModelHex" />轉換為<see cref="Color" />
        /// </summary>
        /// <param name="color"></param>
        /// <returns></returns>
        public static Color? ToColor(this RgbColorModelHex color)
        {
            if (color.Val is not null)
            {
                if (uint.TryParse(color.Val.Value, NumberStyles.HexNumber, null, out var result))
                {
                    var solidColor = result.HexToColor();
                    var modifiedColor = ColorTransform.AppendColorModify(solidColor, color.ChildElements);
                    return modifiedColor;
                }
            }

            return null;
        }


        private static Color HexToColor(this uint rgb)
        {
            var color = new Color();
            const int maxByte = 0xff;

            color.B = (byte) (rgb & maxByte);
            color.G = (byte) ((rgb >> 8) & maxByte);
            color.R = (byte) ((rgb >> 16) & maxByte);
            color.A = 0xFF;

            return color;
        }

        /// <summary>
        /// 給顏色疊加轉換
        /// </summary>
        /// <param name="color"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        public static Color AppendColorModify(ARgbColor color, OpenXmlElementList list)
        {
            var updatedColor = color;
            foreach (var element in list)
            {
                if (element is Hue hue)
                {
                    updatedColor = HandleHue(updatedColor, hue, null, null);
                    continue;
                }

                if (element is HueModulation hueModulation)
                {
                    updatedColor = HandleHue(updatedColor, null, hueModulation, null);
                    continue;
                }

                if (element is HueOffset hueOffset)
                {
                    updatedColor = HandleHue(updatedColor, null, null, hueOffset);
                    continue;
                }

                if (element is Saturation saturation)
                {
                    updatedColor = HandleSaturation(updatedColor, saturation, null, null);
                    continue;
                }

                if (element is SaturationModulation saturationModulation)
                {
                    updatedColor = HandleSaturation(updatedColor, null, saturationModulation, null);
                    continue;
                }

                if (element is SaturationOffset saturationOffset)
                {
                    updatedColor = HandleSaturation(updatedColor, null, null, saturationOffset);
                    continue;
                }

                if (element is Luminance luminance)
                {
                    updatedColor = HandleLuminance(updatedColor, luminance, null, null);
                    continue;
                }

                if (element is LuminanceModulation luminanceModulation)
                {
                    updatedColor = HandleLuminance(updatedColor, null, luminanceModulation, null);
                    continue;
                }

                if (element is LuminanceOffset luminanceOffset)
                {
                    updatedColor = HandleLuminance(updatedColor, null, null, luminanceOffset);
                    continue;
                }
            }


        private static Color HandleHue(Color color, Hue? hueElement, HueModulation? hueModElement,
            HueOffset? hueOffsetElement)
        {
            if (hueElement is null && hueModElement is null && hueOffsetElement is null)
            {
                return color;
            }

            var updatedColor = HandleHslCore(color, hueElement: hueElement, hueModElement: hueModElement, hueOffsetElement: hueOffsetElement);

            return updatedColor;
        }

        private static Color HandleSaturation(Color color, Saturation? satElement, SaturationModulation? satModElement,
            SaturationOffset? satOffsetElement)
        {
            if (satElement is null && satModElement is null && satOffsetElement is null)
            {
                return color;
            }

            var updatedColor = HandleHslCore(color, satElement: satElement, satModElement: satModElement, satOffsetElement: satOffsetElement);

            return updatedColor;
        }

        private static Color HandleLuminance(Color color, Luminance? lumElement, LuminanceModulation? lumModElement,
            LuminanceOffset? lumOffsetElement)
        {
            if (lumElement is null && lumModElement is null && lumOffsetElement is null)
            {
                return color;
            }

            var updatedColor = HandleHslCore(color, lumElement: lumElement, lumModElement: lumModElement, lumOffsetElement: lumOffsetElement);

            return updatedColor;
        }


        private static Color HandleHslCore(Color color,
            Hue? hueElement = null, HueModulation? hueModElement = null, HueOffset? hueOffsetElement = null,
            Saturation? satElement = null, SaturationModulation? satModElement = null, SaturationOffset? satOffsetElement = null,
            Luminance? lumElement = null, LuminanceModulation? lumModElement = null, LuminanceOffset? lumOffsetElement = null)
        {
            if (hueElement is null && hueModElement is null && hueOffsetElement is null
                && satElement is null && satModElement is null && satOffsetElement is null
                && lumElement is null && lumModElement is null && lumOffsetElement is null)
            {
                return color;
            }

            var (hue, sat, lum, alpha) = ColorToHsl(color);

            var hueElementVal = hueElement?.Val;
            var hueValue = hueElementVal is not null ? new Angle(hueElementVal).ToDegreeValue() : hue.DoubleValue;
            var satElementVal = satElement?.Val;
            var satValue = satElementVal is not null ? new Percentage(satElementVal).DoubleValue : sat.DoubleValue;
            var lumElementVal = lumElement?.Val;
            var lumValue = lumElementVal is not null ? new Percentage(lumElementVal).DoubleValue : lum.DoubleValue;

            var hueModElementVal = hueModElement?.Val;
            var hueModValue = hueModElementVal is not null && hueModElementVal.HasValue
                ? new Percentage(hueModElementVal)
                : Percentage.FromDouble(1);
            var satModElementVal = satModElement?.Val;
            var satModValue = satModElementVal is not null && satModElementVal.HasValue
                ? new Percentage(satModElementVal)
                : Percentage.FromDouble(1);
            var lumModElementVal = lumModElement?.Val;
            var lumModValue = lumModElementVal is not null && lumModElementVal.HasValue
                ? new Percentage(lumModElementVal)
                : Percentage.FromDouble(1);

            var hueOffsetVal = hueOffsetElement?.Val;
            var hueOffset = hueOffsetVal is not null && hueOffsetVal.HasValue
                ? new Angle(hueOffsetVal).ToDegreeValue()
                : new Angle(0).ToDegreeValue();
            var saturationOffsetVal = satOffsetElement?.Val;
            var saturationOffset = saturationOffsetVal is not null && saturationOffsetVal.HasValue
                ? new Percentage(saturationOffsetVal)
                : Percentage.Zero;
            var lumOffsetElementVal = lumOffsetElement?.Val;
            var lumOffset = lumOffsetElementVal is not null && lumOffsetElementVal.HasValue
                ? new Percentage(lumOffsetElementVal)
                : Percentage.Zero;

            var hueResult = hueValue * hueModValue.DoubleValue + hueOffset;
            hue = Degree.FromDouble(hueResult);

            var satResult = satValue * satModValue.DoubleValue + saturationOffset.DoubleValue;
            sat = Percentage.FromDouble(satResult);
            sat = sat > Percentage.FromDouble(1) ? Percentage.FromDouble(1) : sat;
            sat = sat < Percentage.Zero ? Percentage.Zero : sat;

            var lumResult = lumValue * lumModValue.DoubleValue + lumOffset.DoubleValue;
            lum = Percentage.FromDouble(lumResult);
            lum = lum > Percentage.FromDouble(1) ? Percentage.FromDouble(1) : lum;
            lum = lum < Percentage.Zero ? Percentage.Zero : lum;

            return HslToColor(hue, sat, lum, alpha);

        }



處理RGB相關屬性

涉及到RGB相關的Openxml屬性如下:

  • 透明度:Alpha、AlphaModulation、AlphaOffset
  • RGB的紅色:Red、RedModulation、RedOffset
  • RGB的藍色:Blue、BlueModulation、BlueOffset
  • RGB的綠色:Green、GreenModulation、GreenOffset
  • RGB的反函數:Inverse
  • RGB的二補數: Complement
  • RGB的伽瑪校正和反伽瑪矯正: Gamma、InverseGamma
  • RGB的灰階(灰度):Gray

處理透明度

以下為處理透明度的邏輯程式碼:

  
        private static Color HandleAlphaModify(Color color, Alpha? alphaElement, AlphaModulation? alphaModulation, AlphaOffset? alphaOffset)
        {
            if (alphaElement is null && alphaModulation is null && alphaOffset is null)
            {
                return color;
            }

            var alphaValue = alphaElement?.Val;
            var modulationVal = alphaModulation?.Val;
            var offsetVal = alphaOffset?.Val;

            var alpha = alphaValue is not null && alphaValue.HasValue
                ? new Percentage(alphaValue)
                : Percentage.FromDouble(1);

            var mod = modulationVal is not null && modulationVal.HasValue
                ? new Percentage(modulationVal)
                : Percentage.FromDouble(1);

            var off = offsetVal is not null && offsetVal.HasValue
                ? new Percentage(offsetVal)
                : Percentage.Zero;


            var alphaResult = alpha.DoubleValue * mod.DoubleValue + off.DoubleValue;
            color.A = (byte) (color.A * alphaResult);


            return color;
        }        

處理RGB的紅色、藍色、綠色

以下為處理RGB的紅色、藍色、綠色的邏輯程式碼:


        private static Color HandleRgb(Color color, Red? redElement, Green? greenElement, Blue? blueElement)
        {
            if (redElement is null && greenElement is null && blueElement is null)
            {
                return color;
            }

            var updatedColor = HandleRgbCore(color, redElement: redElement, greenElement: greenElement,
                blueElement: blueElement);


            return updatedColor;
        }
        private static Color HandleRgbModulation(Color color, RedModulation? redModulationElement, GreenModulation? greenModulationElement, BlueModulation? blueModulationElement)
        {
            if (redModulationElement is null && greenModulationElement is null && blueModulationElement is null)
            {
                return color;
            }

            var updatedColor = HandleRgbCore(color, redModulationElement: redModulationElement,
                greenModulationElement: greenModulationElement, blueModulationElement: blueModulationElement);


            return updatedColor;
        }


        private static Color HandleRgbOffset(Color color, RedOffset? redOffsetElement, GreenOffset? greenOffsetElement, BlueOffset? blueOffsetElement)
        {
            if (redOffsetElement is null && blueOffsetElement is null && greenOffsetElement is null)
            {
                return color;
            }

            var updatedColor = HandleRgbCore(color, redOffsetElement: redOffsetElement,
                greenOffsetElement: greenOffsetElement, blueOffsetElement: blueOffsetElement);


            return updatedColor;
        }


         private static Color HandleRgbCore(Color color,
            Red? redElement = null, Green? greenElement = null, Blue? blueElement = null,
            RedModulation? redModulationElement = null, GreenModulation? greenModulationElement = null, BlueModulation? blueModulationElement = null,
            RedOffset? redOffsetElement = null, GreenOffset? greenOffsetElement = null, BlueOffset? blueOffsetElement = null)
        {
            if (redElement is null && greenElement is null && blueElement is null
                && redModulationElement is null && greenModulationElement is null && blueModulationElement is null
                && redOffsetElement is null && greenOffsetElement is null && blueOffsetElement is null)
            {
                return color;
            }

            var updatedColor = color;

            var redModulationValue = redModulationElement?.Val;
            var redMod = redModulationValue is not null ? new Percentage(redModulationValue) : Percentage.FromDouble(1);

            var greenModulationValue = greenModulationElement?.Val;
            var greenMod = greenModulationValue is not null ? new Percentage(greenModulationValue) : Percentage.FromDouble(1);

            var blueModulationValue = blueModulationElement?.Val;
            var blueMod = blueModulationValue is not null ? new Percentage(blueModulationValue) : Percentage.FromDouble(1);

            var redOffsetValue = redOffsetElement?.Val;
            var redOffset = redOffsetValue is not null ? new Percentage(redOffsetValue) : Percentage.FromDouble(0);

            var greenOffsetValue = greenOffsetElement?.Val;
            var greenOffset = greenOffsetValue is not null ? new Percentage(greenOffsetValue) : Percentage.FromDouble(0);

            var blueOffsetValue = blueOffsetElement?.Val;
            var blueOffset = blueOffsetValue is not null ? new Percentage(blueOffsetValue) : Percentage.FromDouble(0);


            var linearR = SRgbToLinearRgb(updatedColor.R / 255.0);
            var linearG = SRgbToLinearRgb(updatedColor.G / 255.0);
            var linearB = SRgbToLinearRgb(updatedColor.B / 255.0);

            var redValue = redElement?.Val;
            var red = redValue is not null ? new Percentage(redValue).DoubleValue : linearR;

            var greenValue = greenElement?.Val;
            var green = greenValue is not null ? new Percentage(greenValue).DoubleValue : linearG;

            var blueValue = blueElement?.Val;
            var blue = blueValue is not null ? new Percentage(blueValue).DoubleValue : linearB;

            var redResult = red * redMod.DoubleValue + redOffset.DoubleValue;
            var greenResult = green * greenMod.DoubleValue + greenOffset.DoubleValue;
            var blueResult = blue * blueMod.DoubleValue + blueOffset.DoubleValue;


            var r = redResult < 0 ? 0 : redResult > 1 ? 1 : redResult;
            var g = greenResult < 0 ? 0 : greenResult > 1 ? 1 : greenResult;
            var b = blueResult < 0 ? 0 : blueResult > 1 ? 1 : blueResult;
            updatedColor.R = (byte) System.Math.Round(255 * LinearRgbToSRgb(r));
            updatedColor.G = (byte) System.Math.Round(255 * LinearRgbToSRgb(g));
            updatedColor.B = (byte) System.Math.Round(255 * LinearRgbToSRgb(b));

            return updatedColor;
        }

        /// <summary>
        /// https://en.wikipedia.org/wiki/SRGB#The_forward_transformation_.28CIE_xyY_or_CIE_XYZ_to_sRGB.29
        /// </summary>
        /// <param name="sRgb"></param>
        /// <returns></returns>
        private static double SRgbToLinearRgb(double sRgb)
        {
            if (sRgb <= 0.04045) return sRgb / 12.92;

            return System.Math.Pow((sRgb + 0.055) / 1.055, 2.4);
        }


RGB的反函數

以下為處理RGB的反函數的邏輯程式碼:

        private static Color HandleInverse(Color color, Inverse? inverseElement)
        {
            var updatedColor = color;
            if (inverseElement != null)
            {
                var linearR = SRgbToLinearRgb(updatedColor.R / 255.0);
                var linearG = SRgbToLinearRgb(updatedColor.G / 255.0);
                var linearB = SRgbToLinearRgb(updatedColor.B / 255.0);
                var r = System.Math.Abs(1.0 - linearR);
                var g = System.Math.Abs(1.0 - linearG);
                var b = System.Math.Abs(1.0 - linearB);
                updatedColor.R = (byte) System.Math.Round(255 * LinearRgbToSRgb(r));
                updatedColor.G = (byte) System.Math.Round(255 * LinearRgbToSRgb(g));
                updatedColor.B = (byte) System.Math.Round(255 * LinearRgbToSRgb(b));
            }

            return updatedColor;
        }

RGB的二補數

以下為處理RGB的二補數的邏輯程式碼:


        private static Color HandleComplement(Color color, Complement? complementElement)
        {
            var updatedColor = color;
            if (complementElement != null)
            {
                var r = updatedColor.B;
                var g = updatedColor.R + updatedColor.B - updatedColor.G;
                var b = updatedColor.R;
                updatedColor.R = r;
                updatedColor.G = (byte) g;
                updatedColor.B = b;
            }

            return updatedColor;
        }

RGB的伽瑪校正和反伽瑪矯正

伽瑪校正

 實際上就是顯示器的非線性特性讓亮度在我們眼中看起來更好, 但是在渲染時反而會因此導致問題. 我們的渲染計算都是在伽馬值為 1 的理想線性空間進行的,而顯示器的非線性則是伽馬值為 2.2計算的即為輸入值的pow 2.2,伽馬校正的思路就是在顏色被輸送到顯示器之前, 我們先對其進行 pow 1/2.2 的逆運算以抵消顯示器的作用

因此計算伽瑪校正的邏輯程式碼如下:


        /// <summary>
        /// 對於sRGB的伽瑪校正,也就是 1/2.2的冪運算
        /// </summary>
        /// <param name="color"></param>
        /// <param name="gammaElement"></param>
        /// <returns></returns>
        private static Color HandleGamma(Color color, Gamma? gammaElement)
        {
            var updatedColor = color;
            if (gammaElement != null)
            {
                var r = System.Math.Pow(updatedColor.R / 255.0, 1 / 2.2);
                var g = System.Math.Pow(updatedColor.G / 255.0, 1 / 2.2);
                var b = System.Math.Pow(updatedColor.B / 255.0, 1 / 2.2);
                updatedColor.R = (byte) System.Math.Round(255 * r);
                updatedColor.G = (byte) System.Math.Round(255 * g);
                updatedColor.B = (byte) System.Math.Round(255 * b);
            }

            return updatedColor;
        }

而對於反伽瑪校正,則其指數為2.2,程式碼如下:


        /// <summary>
        /// 對於sRGB的反伽瑪校正,也就是2.2的冪運算
        /// </summary>
        /// <param name="color"></param>
        /// <param name="inverseGammaElement"></param>
        /// <returns></returns>
        private static Color HandleInverseGamma(Color color, InverseGamma? inverseGammaElement)
        {
            var updatedColor = color;
            if (inverseGammaElement != null)
            {
                var r = System.Math.Pow(updatedColor.R / 255.0, 2.2);
                var g = System.Math.Pow(updatedColor.G / 255.0, 2.2);
                var b = System.Math.Pow(updatedColor.B / 255.0, 2.2);
                updatedColor.R = (byte) System.Math.Round(255 * r);
                updatedColor.G = (byte) System.Math.Round(255 * g);
                updatedColor.B = (byte) System.Math.Round(255 * b);
            }

            return updatedColor;
        }

RGB的灰階

不同的RGB空間,灰階的計算公式有所不同,常見的幾種RGB空間的計算灰階的公式如下:

//簡化 sRGB IEC61966-2.1 [gamma=2.20]
Gray = (R^2.2 * 0.2126  + G^2.2  * 0.7152  + B^2.2  * 0.0722)^(1/2.2)

//Adobe RGB (1998) [gamma=2.20]
Gray = (R^2.2 * 0.2973  + G^2.2  * 0.6274  + B^2.2  * 0.0753)^(1/2.2)

//Apple RGB [gamma=1.80]
Gray = (R^1.8 * 0.2446  + G^1.8  * 0.6720  + B^1.8  * 0.0833)^(1/1.8)

//ColorMatch RGB [gamma=1.8]
Gray = (R^1.8 * 0.2750  + G^1.8  * 0.6581  + B^1.8  * 0.0670)^(1/1.8)

//簡化 KODAK DC Series Digital Camera [gamma=2.2]
Gray = (R^2.2 * 0.2229  + G^2.2  * 0.7175  + B^2.2  * 0.0595)^(1/2.2)

而我們選擇了灰度係數2.2,即伽馬值為2.2的sRGB的計算公式,那麼邏輯程式碼如下:

        /// <summary>
        /// 對於sRGB的灰階計算
        /// </summary>
        /// <param name="color"></param>
        /// <param name="grayElement"></param>
        /// <returns></returns>
        /// sRGB IEC61966-2.1 [gamma=2.20]:sRGB計算灰階:Gray = (R^2.2 * 0.2126  + G^2.2  * 0.7152  + B^2.2  * 0.0722)^(1/2.2)
        private static Color HandleGray(Color color, Gray? grayElement)
        {
            var updatedColor = color;
            if (grayElement != null)
            {
                var gray = System.Math.Pow(
                          System.Math.Pow(updatedColor.R, 2.2) * 0.2126 +
                          System.Math.Pow(updatedColor.G, 2.2) * 0.7152 +
                          System.Math.Pow(updatedColor.B, 2.2) * 0.0722,
                          1 / 2.2);

                var grayResult = (byte) System.Math.Round(gray);

                updatedColor.R = grayResult;
                updatedColor.G = grayResult;
                updatedColor.B = grayResult;
            }

            return updatedColor;
        }

參考