File: System\Windows\Forms\Rendering\ControlPaint.HLSColor.cs
Web Access
Project: src\src\System.Windows.Forms\src\System.Windows.Forms.csproj (System.Windows.Forms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Drawing;
 
namespace System.Windows.Forms;
 
public static partial class ControlPaint
{
    /// <summary>
    ///  Logic copied from Windows sources to copy the lightening and darkening of colors.
    /// </summary>
    private readonly struct HLSColor : IEquatable<HLSColor>
    {
        private const int ShadowAdjustment = -333;
        private const int HighlightAdjustment = 500;
 
        private const int Range = 240;
        private const int HLSMax = Range;
        private const int RGBMax = 255;
        private const int Undefined = HLSMax * 2 / 3;
 
        private readonly int _hue;
        private readonly int _saturation;
        private readonly bool _isSystemColors_Control;
 
        public HLSColor(Color color)
        {
            _isSystemColors_Control = color.ToKnownColor() == SystemColors.Control.ToKnownColor();
 
            ARGB argb = color;
            int r = argb.R;
            int g = argb.G;
            int b = argb.B;
            int Rdelta, Gdelta, Bdelta;  // intermediate value: % of spread from max
 
            // calculate lightness
            int max = Math.Max(Math.Max(r, g), b);
            int min = Math.Min(Math.Min(r, g), b);
            int sum = max + min;
 
            Luminosity = ((sum * HLSMax) + RGBMax) / (2 * RGBMax);
 
            int dif = max - min;
            if (dif == 0)
            {
                // r=g=b --> achromatic case
                _saturation = 0;
                _hue = Undefined;
            }
            else
            {
                // chromatic case
                _saturation = Luminosity <= (HLSMax / 2)
                    ? ((dif * HLSMax) + (sum / 2)) / sum
                    : ((dif * HLSMax) + (2 * RGBMax - sum) / 2) / (2 * RGBMax - sum);
 
                Rdelta = (((max - r) * (HLSMax / 6)) + (dif / 2)) / dif;
                Gdelta = (((max - g) * (HLSMax / 6)) + (dif / 2)) / dif;
                Bdelta = (((max - b) * (HLSMax / 6)) + (dif / 2)) / dif;
 
                if (r == max)
                {
                    _hue = Bdelta - Gdelta;
                }
                else if (g == max)
                {
                    _hue = (HLSMax / 3) + Rdelta - Bdelta;
                }
                else
                {
                    // B == cMax
                    _hue = (2 * HLSMax / 3) + Gdelta - Rdelta;
                }
 
                if (_hue < 0)
                {
                    _hue += HLSMax;
                }
 
                if (_hue > HLSMax)
                {
                    _hue -= HLSMax;
                }
            }
        }
 
        public int Luminosity { get; }
 
        public Color Darker(float percDarker)
        {
            if (!_isSystemColors_Control)
            {
                int zeroLum = NewLuma(ShadowAdjustment, true);
                return ColorFromHLS(_hue, zeroLum - (int)(zeroLum * percDarker), _saturation);
            }
            else
            {
                // With the usual color scheme, ControlDark/DarkDark is not exactly
                // what we would otherwise calculate
                if (percDarker == 0.0f)
                {
                    return SystemColors.ControlDark;
                }
                else if (percDarker == 1.0f)
                {
                    return SystemColors.ControlDarkDark;
                }
                else
                {
                    ARGB dark = SystemColors.ControlDark;
                    ARGB darkDark = SystemColors.ControlDarkDark;
 
                    return Color.FromArgb(
                        (byte)(dark.R - (byte)((dark.R - darkDark.R) * percDarker)),
                        (byte)(dark.G - (byte)((dark.G - darkDark.G) * percDarker)),
                        (byte)(dark.B - (byte)((dark.B - darkDark.B) * percDarker)));
                }
            }
        }
 
        public override bool Equals(object? o)
        {
            if (o is not HLSColor hlsColor)
            {
                return false;
            }
 
            return Equals(hlsColor);
        }
 
        public bool Equals(HLSColor other)
        {
            return _hue == other._hue
                && _saturation == other._saturation
                && Luminosity == other.Luminosity
                && _isSystemColors_Control == other._isSystemColors_Control;
        }
 
        public static bool operator ==(HLSColor a, HLSColor b) => a.Equals(b);
 
        public static bool operator !=(HLSColor a, HLSColor b) => !a.Equals(b);
 
        public override int GetHashCode() => HashCode.Combine(_hue, _saturation, Luminosity);
 
        public Color Lighter(float percentLighter)
        {
            if (_isSystemColors_Control)
            {
                // With the usual color scheme, ControlLight/LightLight is not exactly
                // what we would otherwise calculate
                if (percentLighter == 0.0f)
                {
                    return SystemColors.ControlLight;
                }
                else if (percentLighter == 1.0f)
                {
                    return SystemColors.ControlLightLight;
                }
                else
                {
                    ARGB light = SystemColors.ControlLight;
                    ARGB lightLight = SystemColors.ControlLightLight;
 
                    return Color.FromArgb(
                        (byte)(light.R - (byte)((light.R - lightLight.R) * percentLighter)),
                        (byte)(light.G - (byte)((light.G - lightLight.G) * percentLighter)),
                        (byte)(light.B - (byte)((light.B - lightLight.B) * percentLighter)));
                }
            }
            else
            {
                int zeroLum = Luminosity;
                int oneLum = NewLuma(HighlightAdjustment, true);
                return ColorFromHLS(_hue, zeroLum + (int)((oneLum - zeroLum) * percentLighter), _saturation);
            }
        }
 
        private int NewLuma(int n, bool scale)
        {
            return NewLuma(Luminosity, n, scale);
        }
 
        private static int NewLuma(int luminosity, int n, bool scale)
        {
            if (n == 0)
            {
                return luminosity;
            }
 
            if (scale)
            {
                return n > 0
                    ? (int)((luminosity * (1000 - n) + (Range + 1L) * n) / 1000)
                    : luminosity * (n + 1000) / 1000;
            }
 
            luminosity += (int)((long)n * Range / 1000);
 
            if (luminosity < 0)
            {
                return 0;
            }
            else if (luminosity > HLSMax)
            {
                return HLSMax;
            }
 
            return luminosity;
        }
 
        private static Color ColorFromHLS(int hue, int luminosity, int saturation)
        {
            byte r, g, b;
            int magic1, magic2;
 
            if (saturation == 0)
            {
                // achromatic case
                r = g = b = (byte)(luminosity * RGBMax / HLSMax);
                if (hue != Undefined)
                {
                    /* ERROR */
                }
            }
            else
            {
                // chromatic case
                if (luminosity <= (HLSMax / 2))
                {
                    magic2 = ((luminosity * (HLSMax + saturation)) + (HLSMax / 2)) / HLSMax;
                }
                else
                {
                    magic2 = luminosity + saturation - (((luminosity * saturation) + (HLSMax / 2)) / HLSMax);
                }
 
                magic1 = 2 * luminosity - magic2;
 
                // get RGB, change units from HLSMax to RGBMax
                r = (byte)((HueToRGB(magic1, magic2, hue + HLSMax / 3) * RGBMax + (HLSMax / 2)) / HLSMax);
                g = (byte)((HueToRGB(magic1, magic2, hue) * RGBMax + (HLSMax / 2)) / HLSMax);
                b = (byte)((HueToRGB(magic1, magic2, hue - HLSMax / 3) * RGBMax + (HLSMax / 2)) / HLSMax);
            }
 
            return Color.FromArgb(r, g, b);
        }
 
        private static int HueToRGB(int n1, int n2, int hue)
        {
            // range check: note values passed add/subtract thirds of range
 
            /* The following is redundant for WORD (unsigned int) */
            if (hue < 0)
            {
                hue += HLSMax;
            }
 
            if (hue > HLSMax)
            {
                hue -= HLSMax;
            }
 
            // return r, g, or b value from this sector
            if (hue < (HLSMax / 6))
            {
                return n1 + (((n2 - n1) * hue + (HLSMax / 12)) / (HLSMax / 6));
            }
 
            if (hue < (HLSMax / 2))
            {
                return n2;
            }
 
            if (hue < (HLSMax * 2 / 3))
            {
                return n1 + (((n2 - n1) * ((HLSMax * 2 / 3) - hue) + (HLSMax / 12)) / (HLSMax / 6));
            }
            else
            {
                return n1;
            }
        }
    }
}