从 HSL 值创建 C# 颜色

Posted

技术标签:

【中文标题】从 HSL 值创建 C# 颜色【英文标题】:Creating a C# Color from HSL values 【发布时间】:2011-05-04 12:11:11 【问题描述】:

如何?

【问题讨论】:

另见:***.com/questions/2942/hsl-in-net/2504318#2504318 【参考方案1】:

我有一个进行 RGB、HSB 和 HSL 转换的类。我不是色彩专家,我也没有声称这个类具有最快的算法(它在一些地方使用 string.Convert 以确保浮点转换按预期进行 --- 现在可以重新访问一些新的 BCL 浮点转换更改 ...)

然而,这个类已经过测试并且工作了一段时间,并且似乎可以正确地完成工作。我用它来创建 Frozen Wpf Brushes,它们不会在紧密循环中重复运行(或在某些性能关键的渲染路径中);结果似乎和预期的一样。

如果您有严重的问题,您应该检查代码......这里是:

using System;
using System.Drawing;
using SystemMath = System.Math;


namespace Sc.Util.Rendering

    /// <summary>
    /// Static methods for transforming argb spaces and argb values.
    /// </summary>
    public static class SimpleColorTransforms
    
        private static double tolerance
            => 0.000000000000001;


        /// <summary>
        /// Defines brightness levels.
        /// </summary>
        public enum Brightness
                : byte
        
            Bright = 255,
            MediumBright = 210,
            Medium = 142,
            Dim = 98,
            XDim = 50
        


        /// <summary>
        /// Defines alpha levels.
        /// </summary>
        public enum Alpha
                : byte
        
            Opaque = 255,
            MediumHigh = 230,
            Medium = 175,
            MediumLow = 142,
            Low = 109,
            XLow = 45
        


        /// <summary>
        /// Defines hint alpha levels.
        /// </summary>
        public enum HintAlpha
                : byte
        
            Low = 64,
            XLow = 48,
            XxLow = 32,
            XxxLow = 16
        


        /// <summary>
        /// Specifies a mode for argb transformations.
        /// </summary>
        public enum ColorTransformMode
                : byte
        
            Hsl,
            Hsb
        


        /// <summary>
        /// Converts RGB to HSL. Alpha is ignored.
        /// Output is:  H: [0, 360], S: [0, 1], L: [0, 1] .
        /// </summary>
        /// <param name="color">The color to convert.</param>
        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.##")))
            ;
        


        /// <summary>
        /// Converts HSL to RGB, with a specified output Alpha.
        /// Arguments are limited to the defined range:
        /// does not raise exceptions.
        /// </summary>
        /// <param name="h">Hue, must be in [0, 360].</param>
        /// <param name="s">Saturation, must be in [0, 1].</param>
        /// <param name="l">Luminance, must be in [0, 1].</param>
        /// <param name="a">Output Alpha, must be in [0, 255].</param>
        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")))));
        


        /// <summary>
        /// Converts RGB to HSB. Alpha is ignored.
        /// Output is:  H: [0, 360], S: [0, 1], B: [0, 1] .
        /// </summary>
        /// <param name="color">The color to convert.</param>
        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))
            ;
        


        /// <summary>
        /// Converts HSB to RGB, with a specified output Alpha.
        /// Arguments are limited to the defined range:
        /// does not raise exceptions.
        /// </summary>
        /// <param name="h">Hue, must be in [0, 360].</param>
        /// <param name="s">Saturation, must be in [0, 1].</param>
        /// <param name="b">Brightness, must be in [0, 1].</param>
        /// <param name="a">Output Alpha, must be in [0, 255].</param>
        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")))));
        


        /// <summary>
        /// Multiplies the Color's Luminance or Brightness by the argument;
        /// and optionally specifies the output Alpha.
        /// </summary>
        /// <param name="color">The color to transform.</param>
        /// <param name="colorTransformMode">Transform mode.</param>
        /// <param name="brightnessTransform">The transformation multiplier.</param>
        /// <param name="outputAlpha">Can optionally specify the Alpha to directly
        /// set on the output. If null, then the input <paramref name="color"/>
        /// Alpha is used.</param>
        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);
        


        /// <summary>
        /// Multiplies the Color's Saturation, and Luminance or Brightness by the argument;
        /// and optionally specifies the output Alpha.
        /// </summary>
        /// <param name="color">The color to transform.</param>
        /// <param name="colorTransformMode">Transform mode.</param>
        /// <param name="saturationTransform">The transformation multiplier.</param>
        /// <param name="brightnessTransform">The transformation multiplier.</param>
        /// <param name="outputAlpha">Can optionally specify the Alpha to directly
        /// set on the output. If null, then the input <paramref name="color"/>
        /// Alpha is used.</param>
        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);
        


        /// <summary>
        /// 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).
        /// </summary>
        /// <param name="color1">Combined by scaling RGB by the A.</param>
        /// <param name="color2">Combined by scaling RGB by the A.</param>
        /// <param name="outputAlpha">The Alpha of the output Color.</param>
        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)));
        
    

【讨论】:

【参考方案2】:

将以下行添加到代码文件中的 "using" 命名空间:

using System.Runtime.InteropServices

并使用 P/Invoke,添加以下函数:

[DllImport("shlwapi.dll")]
public static extern int ColorHLSToRGB(int H, int L, int S);

在你的项目中你想要的任何类。

如果您将色调、亮度和饱和度中的颜色设为整数,则将这些值提供给该函数,所需的 RGB 颜色将以整数形式返回。 然后使用 ColorTranslator.FromWin32 将返回的 rgb 颜色作为整数转换为 System.Drawing.Color 结构。

另一种方法是使用 Color.FromArgb(Int32) 重载方法,根据该整数创建您想要的 System.Drawing.Color 结构的颜色。

另一种方法是创建一个ColorConverter类的实例,然后调用ConvertFrom方法,并给它返回的RGB颜色整数ColorHLSToRGB 函数,或使用相同的第一个参数调用 ConvertTo 方法,但在第二个参数中输入 typeof(System.Drawing.Color)。然后将返回的对象转换为 System.Drawing.Color 结构类型。

结果正是您所寻求和期望的!

您还可以尝试其他算法或方法,您可以在互联网上搜索、查找和学习,了解如何将 RGB 颜色作为整数转换为 System.Drawing.Color 结构或如何将 HLS 值作为三个整数转换为 System .Drawing.Color 结构!

【讨论】:

有几点需要注意:HLS 从 0 到 240。请记住,亮度 0 表示黑色,240 表示白色,所以如果你想要一个纯色(比如#F00)你需要将L设置为120。另外值得注意的是:如果你要使用Color.FromArgb(Int32)重载,颜色的alpha值为0,这意味着它将是透明的。 ColorTranslator.FromWin32() 没有这个问题。

以上是关于从 HSL 值创建 C# 颜色的主要内容,如果未能解决你的问题,请参考以下文章

CSS高级篇——HSL颜色

CSS高级篇——HSL颜色

CSS高级篇——HSL颜色

如何创建用于生成 hsl 颜色数组的标准化分布?

通过分析HSL/HSB获取图片主色调

JavaScript HSL拾色器