Creating a C# Color from HSL values

后端 未结 2 1867
逝去的感伤
逝去的感伤 2021-01-01 14:15

How can I create a C# Color from HSL values?

2条回答
  •  误落风尘
    2021-01-01 14:45

    I have a class that does RGB, HSB, and HSL transformations. I am NOT a color expert, and I also do not claim that this class has the fastest algorithms (it uses string.Convert in places to ensure floating point conversions come out as expected --- which might now be able to be revisited with some new BCL floating point conversion changes ...)

    However, this class has been tested and working for quote a while, and seems to do the work correctly. I use it to create Frozen Wpf Brushes, which do not run in tight loops repeatedly (or in some performance critical rendering path); and the results seem to be as expected.

    If you have critical concerns you should examine the code ... Here it is:

    using System;
    using System.Drawing;
    using SystemMath = System.Math;
    
    
    namespace Sc.Util.Rendering
    {
        /// 
        /// Static methods for transforming argb spaces and argb values.
        /// 
        public static class SimpleColorTransforms
        {
            private static double tolerance
                => 0.000000000000001;
    
    
            /// 
            /// Defines brightness levels.
            /// 
            public enum Brightness
                    : byte
            {
                Bright = 255,
                MediumBright = 210,
                Medium = 142,
                Dim = 98,
                XDim = 50
            }
    
    
            /// 
            /// Defines alpha levels.
            /// 
            public enum Alpha
                    : byte
            {
                Opaque = 255,
                MediumHigh = 230,
                Medium = 175,
                MediumLow = 142,
                Low = 109,
                XLow = 45
            }
    
    
            /// 
            /// Defines hint alpha levels.
            /// 
            public enum HintAlpha
                    : byte
            {
                Low = 64,
                XLow = 48,
                XxLow = 32,
                XxxLow = 16
            }
    
    
            /// 
            /// Specifies a mode for argb transformations.
            /// 
            public enum ColorTransformMode
                    : byte
            {
                Hsl,
                Hsb
            }
    
    
            /// 
            /// Converts RGB to HSL. Alpha is ignored.
            /// Output is: { H: [0, 360], S: [0, 1], L: [0, 1] }.
            /// 
            /// The color to convert.
            public static double[] RgBtoHsl(Color color)
            {
                double h = 0D;
                double s = 0D;
                double l;
    
                // normalize red, green, blue values
                double r = color.R / 255D;
                double g = color.G / 255D;
                double b = color.B / 255D;
    
                double max = SystemMath.Max(r, SystemMath.Max(g, b));
                double min = SystemMath.Min(r, SystemMath.Min(g, b));
    
                // hue
                if (SystemMath.Abs(max - min) < SimpleColorTransforms.tolerance)
                    h = 0D; // undefined
                else if ((SystemMath.Abs(max - r) < SimpleColorTransforms.tolerance)
                        && (g >= b))
                    h = (60D * (g - b)) / (max - min);
                else if ((SystemMath.Abs(max - r) < SimpleColorTransforms.tolerance)
                        && (g < b))
                    h = ((60D * (g - b)) / (max - min)) + 360D;
                else if (SystemMath.Abs(max - g) < SimpleColorTransforms.tolerance)
                    h = ((60D * (b - r)) / (max - min)) + 120D;
                else if (SystemMath.Abs(max - b) < SimpleColorTransforms.tolerance)
                    h = ((60D * (r - g)) / (max - min)) + 240D;
    
                // luminance
                l = (max + min) / 2D;
    
                // saturation
                if ((SystemMath.Abs(l) < SimpleColorTransforms.tolerance)
                        || (SystemMath.Abs(max - min) < SimpleColorTransforms.tolerance))
                    s = 0D;
                else if ((0D < l)
                        && (l <= .5D))
                    s = (max - min) / (max + min);
                else if (l > .5D)
                    s = (max - min) / (2D - (max + min)); //(max-min > 0)?
    
                return new[]
                {
                    SystemMath.Max(0D, SystemMath.Min(360D, double.Parse($"{h:0.##}"))),
                    SystemMath.Max(0D, SystemMath.Min(1D, double.Parse($"{s:0.##}"))),
                    SystemMath.Max(0D, SystemMath.Min(1D, double.Parse($"{l:0.##}")))
                };
            }
    
    
            /// 
            /// Converts HSL to RGB, with a specified output Alpha.
            /// Arguments are limited to the defined range:
            /// does not raise exceptions.
            /// 
            /// Hue, must be in [0, 360].
            /// Saturation, must be in [0, 1].
            /// Luminance, must be in [0, 1].
            /// Output Alpha, must be in [0, 255].
            public static Color HsLtoRgb(double h, double s, double l, int a = 255)
            {
                h = SystemMath.Max(0D, SystemMath.Min(360D, h));
                s = SystemMath.Max(0D, SystemMath.Min(1D, s));
                l = SystemMath.Max(0D, SystemMath.Min(1D, l));
                a = SystemMath.Max(0, SystemMath.Min(255, a));
    
                // achromatic argb (gray scale)
                if (SystemMath.Abs(s) < SimpleColorTransforms.tolerance) {
                    return Color.FromArgb(
                            a,
                            SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{l * 255D:0.00}")))),
                            SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{l * 255D:0.00}")))),
                            SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{l * 255D:0.00}")))));
                }
    
                double q = l < .5D
                        ? l * (1D + s)
                        : (l + s) - (l * s);
                double p = (2D * l) - q;
    
                double hk = h / 360D;
                double[] T = new double[3];
                T[0] = hk + (1D / 3D); // Tr
                T[1] = hk; // Tb
                T[2] = hk - (1D / 3D); // Tg
    
                for (int i = 0; i < 3; i++) {
                    if (T[i] < 0D)
                        T[i] += 1D;
                    if (T[i] > 1D)
                        T[i] -= 1D;
    
                    if ((T[i] * 6D) < 1D)
                        T[i] = p + ((q - p) * 6D * T[i]);
                    else if ((T[i] * 2D) < 1)
                        T[i] = q;
                    else if ((T[i] * 3D) < 2)
                        T[i] = p + ((q - p) * ((2D / 3D) - T[i]) * 6D);
                    else
                        T[i] = p;
                }
    
                return Color.FromArgb(
                        a,
                        SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{T[0] * 255D:0.00}")))),
                        SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{T[1] * 255D:0.00}")))),
                        SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{T[2] * 255D:0.00}")))));
            }
    
    
            /// 
            /// Converts RGB to HSB. Alpha is ignored.
            /// Output is: { H: [0, 360], S: [0, 1], B: [0, 1] }.
            /// 
            /// The color to convert.
            public static double[] RgBtoHsb(Color color)
            {
                // normalize red, green and blue values
                double r = color.R / 255D;
                double g = color.G / 255D;
                double b = color.B / 255D;
    
                // conversion start
                double max = SystemMath.Max(r, SystemMath.Max(g, b));
                double min = SystemMath.Min(r, SystemMath.Min(g, b));
    
                double h = 0D;
                if ((SystemMath.Abs(max - r) < SimpleColorTransforms.tolerance)
                        && (g >= b))
                    h = (60D * (g - b)) / (max - min);
                else if ((SystemMath.Abs(max - r) < SimpleColorTransforms.tolerance)
                        && (g < b))
                    h = ((60D * (g - b)) / (max - min)) + 360D;
                else if (SystemMath.Abs(max - g) < SimpleColorTransforms.tolerance)
                    h = ((60D * (b - r)) / (max - min)) + 120D;
                else if (SystemMath.Abs(max - b) < SimpleColorTransforms.tolerance)
                    h = ((60D * (r - g)) / (max - min)) + 240D;
    
                double s = SystemMath.Abs(max) < SimpleColorTransforms.tolerance
                        ? 0D
                        : 1D - (min / max);
    
                return new[]
                {
                    SystemMath.Max(0D, SystemMath.Min(360D, h)),
                    SystemMath.Max(0D, SystemMath.Min(1D, s)),
                    SystemMath.Max(0D, SystemMath.Min(1D, max))
                };
            }
    
    
            /// 
            /// Converts HSB to RGB, with a specified output Alpha.
            /// Arguments are limited to the defined range:
            /// does not raise exceptions.
            /// 
            /// Hue, must be in [0, 360].
            /// Saturation, must be in [0, 1].
            /// Brightness, must be in [0, 1].
            /// Output Alpha, must be in [0, 255].
            public static Color HsBtoRgb(double h, double s, double b, int a = 255)
            {
                h = SystemMath.Max(0D, SystemMath.Min(360D, h));
                s = SystemMath.Max(0D, SystemMath.Min(1D, s));
                b = SystemMath.Max(0D, SystemMath.Min(1D, b));
                a = SystemMath.Max(0, SystemMath.Min(255, a));
    
                double r = 0D;
                double g = 0D;
                double bl = 0D;
    
                if (SystemMath.Abs(s) < SimpleColorTransforms.tolerance)
                    r = g = bl = b;
                else {
                    // the argb wheel consists of 6 sectors. Figure out which sector
                    // you're in.
                    double sectorPos = h / 60D;
                    int sectorNumber = (int)SystemMath.Floor(sectorPos);
                    // get the fractional part of the sector
                    double fractionalSector = sectorPos - sectorNumber;
    
                    // calculate values for the three axes of the argb.
                    double p = b * (1D - s);
                    double q = b * (1D - (s * fractionalSector));
                    double t = b * (1D - (s * (1D - 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;
                            bl = p;
                            break;
                        case 1 :
                            r = q;
                            g = b;
                            bl = p;
                            break;
                        case 2 :
                            r = p;
                            g = b;
                            bl = t;
                            break;
                        case 3 :
                            r = p;
                            g = q;
                            bl = b;
                            break;
                        case 4 :
                            r = t;
                            g = p;
                            bl = b;
                            break;
                        case 5 :
                            r = b;
                            g = p;
                            bl = q;
                            break;
                    }
                }
    
                return Color.FromArgb(
                        a,
                        SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{r * 255D:0.00}")))),
                        SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{g * 255D:0.00}")))),
                        SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{bl * 250D:0.00}")))));
            }
    
    
            /// 
            /// Multiplies the Color's Luminance or Brightness by the argument;
            /// and optionally specifies the output Alpha.
            /// 
            /// The color to transform.
            /// Transform mode.
            /// The transformation multiplier.
            /// Can optionally specify the Alpha to directly
            /// set on the output. If null, then the input 
            /// Alpha is used.
            public static Color TransformBrightness(
                    Color color,
                    ColorTransformMode colorTransformMode,
                    double brightnessTransform,
                    byte? outputAlpha = null)
            {
                double[] hsl = colorTransformMode == ColorTransformMode.Hsl
                        ? SimpleColorTransforms.RgBtoHsl(color)
                        : SimpleColorTransforms.RgBtoHsb(color);
                if ((SystemMath.Abs(hsl[2]) < SimpleColorTransforms.tolerance)
                        && (brightnessTransform > 1D))
                    hsl[2] = brightnessTransform - 1D;
                else
                    hsl[2] *= brightnessTransform;
                return colorTransformMode == ColorTransformMode.Hsl
                        ? SimpleColorTransforms.HsLtoRgb(hsl[0], hsl[1], hsl[2], outputAlpha ?? color.A)
                        : SimpleColorTransforms.HsBtoRgb(hsl[0], hsl[1], hsl[2], outputAlpha ?? color.A);
            }
    
    
            /// 
            /// Multiplies the Color's Saturation, and Luminance or Brightness by the argument;
            /// and optionally specifies the output Alpha.
            /// 
            /// The color to transform.
            /// Transform mode.
            /// The transformation multiplier.
            /// The transformation multiplier.
            /// Can optionally specify the Alpha to directly
            /// set on the output. If null, then the input 
            /// Alpha is used.
            public static Color TransformSaturationAndBrightness(
                    Color color,
                    ColorTransformMode colorTransformMode,
                    double saturationTransform,
                    double brightnessTransform,
                    byte? outputAlpha = null)
            {
                double[] hsl = colorTransformMode == ColorTransformMode.Hsl
                        ? SimpleColorTransforms.RgBtoHsl(color)
                        : SimpleColorTransforms.RgBtoHsb(color);
                if ((SystemMath.Abs(hsl[1]) < SimpleColorTransforms.tolerance)
                        && (saturationTransform > 1D))
                    hsl[1] = saturationTransform - 1D;
                else
                    hsl[1] *= saturationTransform;
                if ((SystemMath.Abs(hsl[2]) < SimpleColorTransforms.tolerance)
                        && (brightnessTransform > 1D))
                    hsl[2] = brightnessTransform - 1D;
                else
                    hsl[2] *= brightnessTransform;
                return colorTransformMode == ColorTransformMode.Hsl
                        ? SimpleColorTransforms.HsLtoRgb(hsl[0], hsl[1], hsl[2], outputAlpha ?? color.A)
                        : SimpleColorTransforms.HsBtoRgb(hsl[0], hsl[1], hsl[2], outputAlpha ?? color.A);
            }
    
    
            /// 
            /// Creates a new Color by combining R, G, and B from each Color, scaled by the Color's Alpha.
            /// The R, G, B of each Color is scaled by the Color's Alpha. The R, G, B of both results is
            /// then added together and divided by 2. The valuea are limited to [0, 255].
            /// The Alpha of the output Color is specified; and is also limited to [0, 255]
            /// (does not raise exceptions).
            /// 
            /// Combined by scaling RGB by the A.
            /// Combined by scaling RGB by the A.
            /// The Alpha of the output Color.
            public static Color AlphaCombine(Color color1, Color color2, byte outputAlpha)
            {
                double a1 = color1.A / 255D;
                double a2 = color2.A / 255D;
                return Color.FromArgb(
                        outputAlpha,
                        (byte)SystemMath.Max(0D, SystemMath.Min(255D, ((color1.R * a1) + (color2.R * a2)) * .5D)),
                        (byte)SystemMath.Max(0D, SystemMath.Min(255D, ((color1.G * a1) + (color2.G * a2)) * .5D)),
                        (byte)SystemMath.Max(0D, SystemMath.Min(255D, ((color1.B * a1) + (color2.B * a2)) * .5D)));
            }
        }
    }
    

提交回复
热议问题