• 【Openxml】颜色变化属性计算


    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的类:

    Copy
    /// <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的相互转换逻辑方法:

    Copy
    /// <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相关属性逻辑:

    Copy
    /// <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

    处理透明度#

    以下为处理透明度的逻辑代码:

    Copy
    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的红色、蓝色、绿色的逻辑代码:

    Copy
    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的反函数的逻辑代码:

    Copy
    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的补码的逻辑代码:

    Copy
    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 的逆运算以抵消显示器的作用

    因此计算伽玛校正的逻辑代码如下:

    Copy
    /// <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,代码如下:

    Copy
    /// <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空间的计算灰阶的公式如下:

    Copy
    //简化 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的计算公式,那么逻辑代码如下:

    Copy
    /// <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; }

    参考#

  • 相关阅读:
    安全队列和曲线拟合
    Vue 2与Vue 3生命周期钩子的对比分析
    【MySQL】数据类型
    如何让你网站统计的更加精准?
    MaxCompute实例相关操作
    安卓逆向(二)httpClient使用
    Spring Boot - devtools 热部署
    解决在 Spring Boot 中运行 JUnit 测试遇到的 NoSuchMethodError 错误
    保险行业采购管理痛点及解决方案(数智化采购系统)
    C++学习日记——函数指针
  • 原文地址:https://www.cnblogs.com/ryzen/p/16370464.html