dotnet/winforms

Correct System.Windows.Forms.ControlPaint.HSLColor.Darker function

Opened this issue · 0 comments

Background and motivation

At least since .NET Framework 4, the System.WIndows.Forms.ControlPaint.HSLColor.Darker function (which is called by System.Windows.Forms.ControlPaint.Dark()) has been wrong for non-system colors.

The Lighter function logically interpolates, based on the passed percentage value, from the current luminosity towards a luminosity value 50% higher. The Darker function illogically interpolates, based on the passed percentage value, from a luminosity value 33% lower than the current luminosity towards zero.

A call to Lighter(0) reasonably returns the same color. A call to Darker(0) unreasonably returns a color 33% darker. Lighter(0.5) returns a color 25% lighter; Darker(0.5) returns a color 67% darker. Lighter(1) returns a color 50% lighter; Darker(1) returns black.

API Proposal

namespace System.Windows.Forms;

public static partial class ControlPaint
{
    private readonly struct HLSColor : IEquatable<HLSColor>
    {
        public Color Darker(float percDarker)
        {
            if (!_isSystemColors_Control)
            {
                // *** this block is the only changed code ***
                // match the Lighter function and use current luminosity as a baseline instead of zero
                int zeroLum = luminosity;
                int oneLum = NewLuma(ShadowAdjustment, true);
                return ColorFromHLS(_hue, zeroLum + (int)((oneLum - 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)));
                }
            }
        }
    }
}

API Usage

using System.Drawing.Color;

Color c = Color.FromArgb(128, 128, 128);
Color sameColor = System.Windows.Forms.ControlPaint.Dark(c, 0.0);
Color darkColor = System.Windows.Forms.ControlPaint.Dark(c, 0.5);
Color darkerColor = System.Windows.Forms.ControlPaint.Dark(c, 1.0);

Color approxSameColor = System.Windows.Forms.ControlPaint.Light(System.Windows.Forms.ControlPaint.Dark(c));
Color approxSameColor2 = System.Windows.Forms.ControlPaint.Dark(System.Windows.Forms.ControlPaint.Light(c));

Alternative Designs

No response

Risks

No response

Will this feature affect UI controls?

Yes.

  • no
  • none
  • no