2
\$\begingroup\$

This is an implementation of a Color class, primarily targetting the web to be used in a Blazor app.

Color.cs

using System;
using System.Globalization;

namespace Color
{
    /// <summary>
    /// Represents a Color with Red, Green, Blue and Alpha channels, packed into an integer. Also contains utilities to convert between color spaces.
    /// </summary>
    /// 
    /// Since the colors are stored in ARGB, if you create a Color from other color spaces you might lose precision.
    /// This means that converting back and forth between color spaces does not guarantee you getting the original value.
    /// All percentage values are within [0.0, 1.0] and the smallest possible delta is 1 / 255. So an alpha of 0.001 and 0.002 are the same.
    /// Static constructors take tuples and constructors take each value separately.
    /// 
    /// Currently supported color spaces conversion: RGB, HSL, CMYK.
    public struct Color : IEquatable<Color>, ICloneable
    {
        /// <summary>
        /// Represents ARGB as a densely packed integer.
        /// </summary>
        public uint Value { get; private set; }

        #region Checks

        private static bool NotInPctRange(double v)
        {
            return v < 0.0 || v > 1.0;
        }

        private static bool NotInByteRange(uint v)
        {
            return v > 255;
        }

        private static bool NotInRgbRange(double r, double g, double b)
        {
            return NotInPctRange(r) || NotInPctRange(g) || NotInPctRange(b);
        }

        private static bool NotInRgbRange(uint r, uint g, uint b)
        {
            return NotInByteRange(r) || NotInByteRange(g) || NotInByteRange(b);
        }

        private static bool NotInHslRange(double h, double s, double v)
        {
            return h < 0.0 || h > 359.0 ||  NotInPctRange(s) || NotInPctRange(v);
        }

        private static bool NotInCmykRange(double c, double m, double y, double k)
        {
            return NotInPctRange(c) || NotInPctRange(m) || NotInPctRange(y) || NotInPctRange(k);
        }

        #endregion

        #region Getters

        /// <summary>
        /// The value of Alpha within [0, 255]
        /// </summary>
        public uint A => Value >> 24;

        /// <summary>
        /// The value of Red within [0, 255]
        /// </summary>
        public uint R => (Value >> 16) & 0xFF;

        /// <summary>
        /// The value of Green within [0, 255]
        /// </summary>
        public uint G => (Value >> 8) & 0xFF;

        /// <summary>
        /// The value of Blue within [0, 255]
        /// </summary>
        public uint B => Value & 0xFF;

        /// <summary>
        /// Masks off the Alpha value, returning only RGB
        /// </summary>
        public uint RGB => Value & 0xFFFFFF;

        /// <summary>
        /// Returns value of Alpha within [0.0, 1.0]
        /// </summary>
        public double Alpha => A / 255.0;

        /// <summary>
        /// Returns value of Red within [0.0, 1.0]
        /// </summary>
        public double Red => R / 255.0;

        /// <summary>
        /// Returns value of Green within [0.0, 1.0]
        /// </summary>
        public double Green => G / 255.0;

        /// <summary>
        /// Returns value of Blue within [0.0, 1.0]
        /// </summary>
        public double Blue => B / 255.0;

        /// <summary>
        /// Returns value of Hue within [0.0, 359.0]
        /// </summary>
        public double Hue
        {
            get {
                var r = Red;
                var g = Green;
                var b = Blue;

                var max = Math.Max(Math.Max(r, g), b);
                var min = Math.Min(Math.Min(r, g), b);
                var c = max - min;

                if (c != 0)
                {
                    if (max == r)
                    {
                        var segment = (g - b) / c;
                        var shift = 0 / 60;
                        if (segment < 0)
                            shift = 360 / 60;
                        return (segment + shift) * 60;
                    }
                    else if (max == g)
                    {
                        var segment = (b - r) / c;
                        var shift = 120 / 60;
                        return (segment + shift) * 60;
                    }
                    else if (max == b)
                    {
                        var segment = (r - g) / c;
                        var shift = 240 / 60;
                        return (segment + shift) * 60;
                    }
                }

                return 0;
            }
        }

        /// <summary>
        /// Returns value of Saturation within [0.0, 1.0]
        /// </summary>
        public double Saturation
        {
            get
            {
                var r = Red;
                var g = Green;
                var b = Blue;

                var max = Math.Max(Math.Max(r, g), b);
                var min = Math.Min(Math.Min(r, g), b);

                var l = (max + min) / 2; // Lightness

                if (min != max)
                {
                    if (l < 0.5)
                        return (max - min) / (max + min);

                    return (max - min) / (2 - max - min);
                }

                return 0;
            }
        }

        /// <summary>
        /// Returns value of Lightness within [0.0, 1.0]
        /// </summary>
        public double Lightness
        {
            get
            {
                var r = Red;
                var g = Green;
                var b = Blue;

                var max = Math.Max(Math.Max(r, g), b);
                var min = Math.Min(Math.Min(r, g), b);

                return (max + min) / 2;
            }
        }

        /// <summary>
        /// Returns value of Cyan within [0.0, 1.0]
        /// </summary>
        public double Cyan
        {
            get
            {
                var k = Black;
                return k == 1.0 ? 0.0 : (1.0 - Red - k) / (1.0 - k);
            }
        }

        /// <summary>
        /// Returns value of Magenta within [0.0, 1.0]
        /// </summary>
        public double Magenta
        {
            get
            {
                var k = Black;
                return k == 1.0 ? 0.0 : (1.0 - Green - k) / (1.0 - k);
            }
        }

        /// <summary>
        /// Returns value of Yellow within [0.0, 1.0]
        /// </summary>
        public double Yellow
        {
            get
            {
                var k = Black;
                return k == 1.0 ? 0.0 : (1.0 - Blue - k) / (1.0 - k);
            }
        }

        /// <summary>
        /// Returns value of Black within [0.0, 1.0]
        /// </summary>
        public double Black => 1.0 - Math.Max(Math.Max(Red, Green), Blue);

        /// <summary>
        /// Returns the relative Luminance (how bright to the human eye the color is) according to ITU BT.601.
        /// </summary>
        public double Luminance => (0.299 * R + 0.587 * G + 0.114 * B) / 255.0;

        #endregion

        #region Value Tuples

        /// <summary>
        /// A tuple with RGB values as integers within [0, 255].
        /// </summary>
        public (uint, uint, uint)RGBValuesInt()
        {
            return (R, G, B);
        }

        /// <summary>
        /// A tuple with RGB values within [0.0, 1.0].
        /// </summary>
        public (double, double, double)RGBValues()
        {
            return (Red, Green, Blue);
        }

        /// <summary>
        /// A tuple with HSL values within [0.0, 1.0].
        /// </summary>
        public (double, double, double)HSLValues()
        {
            return RgbToHslInt((R, G, B));
        }

        /// <summary>
        /// A tuple with CMYK values within [0.0, 1.0].
        /// </summary>
        public (double, double, double, double)CMYKValues()
        {
            return RgbToCmykInt((R, G, B));
        }

        #endregion

        #region Ctors

        /// <summary>
        /// Creates a new Color from an integer value.
        /// </summary>
        public Color(uint value)
        {
            Value = value;
        }

        /// <summary>
        /// Creates a Color from a hexadecimal string, converting it to an integer and assigning to Value.
        /// </summary>
        /// <param name="hexa">A string as a hexadecimal number</param>
        public Color(string hexa)
        {
            Value = Convert.ToUInt32(hexa, 16);
        }

        /// <summary>
        /// Creates a Color from Red, Green and Blue channels with 100% Alpha, ranging from [0, 255].
        /// </summary>
        /// <param name="r">Red value</param>
        /// <param name="g">Green value</param>
        /// <param name="b">Blue value</param>
        public Color(uint r, uint g, uint b)
        {
            if (NotInRgbRange(r, g, b))
                throw new ArgumentOutOfRangeException("", $"R, G, B and A need to be within [0, 255]. Instead got r: {r}, g: {g}, b: {b}");

            Value = ((uint)0xFF << 24) | (r << 16) | (g << 8) | b;
        }

        /// <summary>
        /// Creates a Color from Red, Green, Blue and Alpha channels, ranging from [0, 255].
        /// </summary>
        /// <param name="r">Red value</param>
        /// <param name="g">Green value</param>
        /// <param name="b">Blue value</param>
        /// <param name="a">Alpha value</param>
        public Color(uint r, uint g, uint b, uint a)
        {
            if (NotInRgbRange(r, g, b) || a > 255)
                throw new ArgumentOutOfRangeException("", $"R, G, B and A need to be within [0, 255]. Instead got r: {r}, g: {g}, b: {b}, a: {a}");

            Value = (a << 24) | (r << 16) | (g << 8) | b;
        }

        /// <summary>
        /// Creates a Color from Red, Green, Blue and Alpha channels, with RGB ranging from [0, 255], and Alpha, from [0.0, 1.0].
        /// </summary>
        /// <param name="r">Red value</param>
        /// <param name="g">Green value</param>
        /// <param name="b">Blue value</param>
        /// <param name="a">Alpha value</param>
        public Color(uint r, uint g, uint b, double a)
        {
            if (NotInRgbRange(r, g, b) || a > 1.0 || a < 0.0)
                throw new ArgumentOutOfRangeException("", $"R, G, B need to be within [0, 255] and A within [0.0, 1.0]. Instead got r: {r}, g: {g}, b: {b}, a: {a}");

            var a_ = (uint)Math.Round(a * 255.0, 0);
            Value = (a_ << 24) | (r << 16) | (g << 8) | b;
        }

        /// <summary>
        /// Creates a Color from Red, Green and Blue channels with 100% Alpha, ranging from [0.0, 1.0].
        /// </summary>
        /// <param name="r">Red value</param>
        /// <param name="g">Green value</param>
        /// <param name="b">Blue value</param>
        public Color(double r, double g, double b)
        {
            if (NotInRgbRange(r, g, b))
                throw new ArgumentOutOfRangeException("", $"R, G and B need to be within [0.0, 1.0]. Instead got r: {r}, g: {g}, b: {b}");

            var (r_, g_, b_) = PctToRgb(r, g, b);
            Value = ((uint)0xFF << 24) | (r_ << 16) | (g_ << 8) | b_;
        }

        /// <summary>
        /// Creates a Color from Red, Green, Blue and Alpha channels, ranging from [0.0, 1.0].
        /// </summary>
        /// <param name="r">Red value</param>
        /// <param name="g">Green value</param>
        /// <param name="a">Alpha value</param>
        public Color(double r, double g, double b, double a)
        {
            if (NotInRgbRange(r, g, b) || a > 1.0 || a < 0.0)
                throw new ArgumentOutOfRangeException("", $"R, G, B and A need to be within [0.0, 1.0]. Instead got r: {r}, g: {g}, b: {b}, a: {a}");

            var (r_, g_, b_) = PctToRgb(r, g, b);
            var a_ = (uint)Math.Round(a * 255.0, 0);
            Value = (a_ << 24) | (r_ << 16) | (g_ << 8) | b_;
        }

        #endregion

        #region Static Ctors

        /// <summary>
        /// Creates a Color from a string, converting it to an integer and assigning to Value.
        /// </summary>
        /// <param name="hexa">A string as a hexadecimal number</param>
        public static Color FromHexa(string hexa)
        {
            return new Color(hexa);
        }

        /// <summary>
        /// Creates a Color from Red, Green, Blue and Alpha channels. All numbers ranging from 0 to 255.
        /// </summary>
        /// <param name="rgb">A tuple of form (R, G, B)</param>
        /// <param name="a">Alpha value</param>
        public static Color FromRGBA((uint, uint, uint) rgb, uint a)
        {
            var (r, g, b) = rgb;
            return new Color(r, g, b, a);
        }

        /// <summary>
        /// Creates a Color from Red, Green, Blue and Alpha channels. RGB ranging from 0 to 255, and Alpha, from 0.0 to 1.0.
        /// </summary>
        /// <param name="rgb">A tuple of form (R, G, B)</param>
        /// <param name="a">Alpha value</param>
        public static Color FromRGBA((uint, uint, uint) rgb, double a)
        {
            var (r, g, b) = rgb;
            return new Color(r, g, b, a);
        }

        /// <summary>
        /// Creates a Color from Red, Green, Blue and Alpha components. All numbers ranging from 0.0 to 1.0.
        /// </summary>
        /// <param name="rgb">A tuple of form (R, G, B)</param>
        /// <param name="a">Alpha value</param>
        public static Color FromRGBA((double, double, double) rgb, double a)
        {
            var (r, g, b) = rgb;
            return new Color(r, g, b, a);
        }

        /// <summary>
        /// Creates a Color from Red, Green and Blue channels with 100% Alpha and values within [0, 255].
        /// </summary>
        /// <param name="rgb">A tuple of form (R, G, B)</param>
        public static Color FromRGB((uint, uint, uint) rgb)
        {
            return FromRGBA(rgb, 255);
        }

        /// <summary>
        /// Creates a Color from Red, Green and Blue channels with 100% Alpha and values within [0.0, 1.0].
        /// </summary>
        /// <param name="rgb">A tuple of form (R, G, B)</param>
        public static Color FromRGB((double, double, double) rgb)
        {
            return FromRGBA(rgb, 1.0);
        }

        /// <summary>
        /// Creates a Color from Hue, Saturation and Value, with Hue within [0.0, 359.0], and the others, within [0.0, 1.0]. Alpha is at 100%.
        /// </summary>
        /// <param name="hsl">A tuple of form (H, S, L)</param>
        public static Color FromHSL((double, double, double) hsl)
        {
            return FromRGBA(HslToRgb(hsl), 1.0);
        }

        /// <summary>
        /// Creates a Color from Hue, Saturation, Value, and Alpha, with Hue within [0.0, 359.0], and the others, within [0.0, 1.0].
        /// </summary>
        /// <param name="hsl">A tuple of form (H, S, L)</param>
        /// <param name="a">Alpha value</param>
        public static Color FromHSLA((double, double, double) hsl, double a)
        {
            return FromRGBA(HslToRgb(hsl), a);
        }

        /// <summary>
        /// Creates a Color from Cyan, Magenta, Yellow and Black, with values within [0.0, 1.0].
        /// </summary>
        /// <param name="hsl">A tuple of form (C, M, Y, K)</param>
        public static Color FromCMYK((double, double, double, double) cmyk)
        {
            return FromRGBA(CmykToRgb(cmyk), 1.0);
        }

        /// <summary>
        /// Creates a Color from Cyan, Magenta, Yellow, Black and Alpha, with values within [0.0, 1.0].
        /// </summary>
        /// <param name="hsl">A tuple of form (C, M, Y, K)</param>
        /// <param name="a">Alpha value</param>
        /// <returns></returns>
        public static Color FromCMYKA((double, double, double, double) cmyk, double a)
        {
            return FromRGBA(CmykToRgb(cmyk), a);
        }

        #endregion

        #region Conversions

        /// <summary>
        /// Transforms RGB color space to HSL.
        /// </summary>
        ///
        /// RGB values must be in range [0.0, 1.0].
        ///
        /// <param name="rgb">A tuple of form (Red, Green, Blue)</param>
        /// <returns>A tuple of form Tuple of form (Hue, Saturation, Lightness)</returns>
        public static (double, double, double) RgbToHsl((double, double, double) rgb)
        {
            double hue = 0.0, sat = 0.0, light;

            (double r, double g, double b) = rgb;

            if (NotInRgbRange(r, g, b))
                throw new ArgumentOutOfRangeException("rbg", $"R, G and B must be in range [0.0, 1.0]. Instead got r: {r}, g: {g}, b: {b}");

            var max = Math.Max(Math.Max(r, g), b);
            var min = Math.Min(Math.Min(r, g), b);

            var c = max - min;

            if (c != 0)
            {
                if (max == r)
                {
                    var segment = (g - b) / c;
                    var shift = 0 / 60;
                    if (segment < 0)
                        shift = 360 / 60;
                    hue = (segment + shift) * 60;
                }
                else if (max == g)
                {
                    var segment = (b - r) / c;
                    var shift = 120 / 60;
                    hue =  (segment + shift) * 60;
                }
                else if (max == b)
                {
                    var segment = (r - g) / c;
                    var shift = 240 / 60;
                    hue = (segment + shift) * 60;
                }
            }

            light = (max + min) / 2;

            if (min != max)
            {
                if (light < 0.5)
                    sat = (max - min) / (max + min);
                else
                    sat = (max - min) / (2 - max - min);
            }

            return (hue, sat, light);
        }

        /// <summary>
        /// Transforms RGB color space to HSL.
        /// </summary>
        ///
        /// RGB values must be in range [0, 255].
        ///
        /// <param name="rgb">A tuple of form (Red, Green, Blue)</param>
        /// <returns>A tuple of form (Hue, Saturation, Lightness) within [0.0, 1.0]</returns>
        public static (double, double, double) RgbToHslInt((uint, uint, uint) rgb)
        {
            (uint r_, uint g_, uint b_) = rgb;

            if (NotInRgbRange(r_, g_, b_))
                throw new ArgumentOutOfRangeException("rbg", $"R, G and B must be in range [0, 255]. Instead got r: {r_}, g: {g_}, b: {b_}");

            (double r, double g, double b) = RgbToPct(r_, g_, b_);

            return RgbToHsl((r, g, b));
        }

        /// <summary>
        /// Transforms RGB color space to CMYK.
        /// </summary>
        /// 
        /// RGB values must be in range [0.0, 1.0].
        /// 
        /// <param name="rgb">A tuple of form (Red, Green, Blue)</param>
        /// <returns>A tuple of form (Cyan, Magenta, Yellow, Black) within [0.0, 1.0]</returns>
        public static (double, double, double, double) RgbToCmyk((double, double, double) rgb)
        {
            double c, m, y, k;

            (double r, double g, double b) = rgb;

            if (NotInRgbRange(r, g, b))
                throw new ArgumentOutOfRangeException("rbg", $"R, G and B must be in range [0.0, 1.0]. Instead got r: {r}, g: {g}, b: {b}");

            var max = Math.Max(Math.Max(r, g), b);

            k = 1 - max;

            if (k == 1)
            {
                c = m = y = 0;
            }
            else
            {
                c = (1 - r - k) / (1 - k);
                m = (1 - g - k) / (1 - k);
                y = (1 - b - k) / (1 - k);
            }

            return (c, m, y, k);
        }

        /// <summary>
        /// Transforms RGB color space to CMYK.
        /// </summary>
        /// 
        /// RGB values must be in range [0, 255].
        /// 
        /// <param name="rgb">A tuple of form (Red, Green, Blue)</param>
        /// <returns>A tuple of form (Cyan, Magenta, Yellow, Black) within [0.0, 1.0]</returns>
        public static (double, double, double, double) RgbToCmykInt((uint, uint, uint) rgb)
        {
            (uint r_, uint g_, uint b_) = rgb;

            if (NotInRgbRange(r_, g_, b_))
                throw new ArgumentOutOfRangeException("rbg", $"R, G and B must be in range [0, 255]. Instead got r: {r_}, g: {g_}, b: {b_}");

            (double r, double g, double b) = RgbToPct(r_, g_, b_);

            return RgbToCmyk((r, g, b));
        }

        /// <summary>
        /// Transforms HSL color space to RGB.
        /// </summary>
        /// 
        /// H value must be in range [0.0, 359.0] and SL values must be in range [0.0, 1.0].
        /// 
        /// <param name="hsl">A tuple of form (Hue, Saturation, Lightness)</param>
        /// <returns>A tuple of form (Red, Green, Blue) within [0.0, 1.0]</returns>
        public static (double, double, double) HslToRgb((double, double, double) hsl)
        {
            (double hue, double sat, double light) = hsl;

            if (NotInHslRange(hue, sat, light))
                throw new ArgumentOutOfRangeException("hsl", $"H must be in range [0.0, 359.0], and S and L must be in range [0.0, 1.0]. Instead got h: {hue}, s: {sat}, l: {light}");

            hue /= 60;

            double t2;
            if (light <= 0.5)
            {
                t2 = light * (sat + 1);
            }
            else
            {
                t2 = light + sat - (light * sat);
            }

            double t1 = light * 2 - t2;

            return (HueToRgb(t1, t2, hue + 2.0), HueToRgb(t1, t2, hue), HueToRgb(t1, t2, hue - 2));
        }

        /// <summary>
        /// Transforms HSL color space to RGB.
        /// </summary>
        /// 
        /// H value must be in range [0.0, 359.0] and SL values must be in range [0.0, 1.0].
        /// 
        /// <param name="hsl">A tuple of form (Hue, Saturation, Lightness)</param>
        /// <returns>A tuple of form (Red, Green, Blue)</returns>
        public static (uint, uint, uint) HslToRgbInt((double, double, double) hsl)
        {
            var (r, g, b) = HslToRgb(hsl);
            return PctToRgb(r, g, b);
        }

        public static double HueToRgb(double t1, double t2, double hue)
        {
            if (hue < 0.0)
                hue += 6.0;
            if (hue >= 6.0)
                hue -= 6.0;

            if (hue < 1.0)
                return (t2 - t1) * hue + t1;
            else if (hue < 3.0)
                return t2;
            else if (hue < 4.0)
                return (t2 - t1) * (4.0 - hue) + t1;

            return t1;
        }

        /// <summary>
        /// Transforms CMYK color space to RGB.
        /// </summary>
        /// 
        /// CMYK values must be in range [0.0, 1.0].
        /// 
        /// <param name="cmyk">A tuple of form (Cyan, Magenta, Yellow, Black)</param>
        /// <returns>A tuple of form (Red, Green, Blue) within [0.0, 1.0]</returns>
        public static (double, double, double) CmykToRgb((double c, double m, double y, double k) cmyk)
        {
            var (c, m, y, k) = cmyk;

            if (NotInCmykRange(c, m, y, k))
                throw new ArgumentOutOfRangeException("cmyk", $"CMYK must be in range [0.0, 1.0]. Instead got c: {c}, m: {m}, y: {y}, k: {k}");

            var r = 1 - (Math.Min(1, c * (1 - k) + k));
            var g = 1 - (Math.Min(1, m * (1 - k) + k));
            var b = 1 - (Math.Min(1, y * (1 - k) + k));

            return (r, g, b);
        }
        
        /// <summary>
        /// Transforms CMYK color space to RGB.
        /// </summary>
        /// 
        /// CMYK values must be in range [0.0, 1.0].
        /// 
        /// <param name="cmyk">A tuple of form (Cyan, Magenta, Yellow, Black)</param>
        /// <returns>A tuple of form (Red, Green, Blue)</returns>
        public static (uint, uint, uint) CmykToRgbInt((double c, double m, double y, double k) cmyk)
        {
            var (r, g, b) = CmykToRgb(cmyk);
            return PctToRgb(r, g, b);
        }

        #endregion

        #region ToCSS

        /// <summary>
        /// Returns the Color to fit into an rgba CSS function, with form rbga(R, G, B, A).
        /// </summary>
        public string ToCssRgba()
        {
            return $"rgba({R}, {G}, {B}, {DefaultFormat(A / 255.0)})";
        }

        /// <summary>
        /// Returns the Color to fit into an hsla CSS function, with form hsl(H, S%, L%, A)
        /// </summary>
        public string ToCssHsla()
        {
            var (h, s, l) = RgbToHsl((Red, Green, Blue));
            return $"hsla({DefaultFormat(h)}, {DefaultFormat(s * 100)}%, {DefaultFormat(l * 100)}%, {DefaultFormat(A / 255.0)})";
        }

        /// <summary>
        /// Returns the Color with a format of #RRGGBB.
        /// </summary>
        public string ToCssColor()
        {
            return $"#{Convert.ToString(RGB, 16).PadLeft(6, '0')}";
        }

        #endregion

        #region Misc

        /// <summary>
        /// [0, 255] -> [0.0, 1.0]
        /// </summary>
        private static (double, double, double) RgbToPct(uint r, uint g, uint b)
        {
            return (r / 255.0, g / 255.0, b / 255.0);
        }

        /// <summary>
        /// [0.0, 1.0] -> [0, 255]
        /// </summary>
        private static (uint, uint, uint) PctToRgb(double r, double g, double b)
        {
            return ((uint)Math.Round(r * 255.0, 0), (uint)Math.Round(g * 255.0, 0), (uint)Math.Round(b * 255.0, 0));
        }

        private static string DefaultFormat(double value)
        {
            return value.ToString("0.##", CultureInfo.InvariantCulture);
        }

        #endregion

        #region Overrides + Impls

        public static bool operator ==(Color lhs, Color rhs)
        {
            return lhs.Value == rhs.Value;
        }

        public static bool operator !=(Color lhs, Color rhs)
        {
            return lhs.Value != rhs.Value;
        }

        public override bool Equals(object obj)
        {
            if (obj is Color c)
                return Value == c.Value;

            return false;
        }

        public override int GetHashCode() => Value.GetHashCode();

        public override string ToString()
        {
            return Value.ToString("X");
        }

        public bool Equals(Color other)
        {
            return Value == other.Value;
        }

        public object Clone()
        {
            return new Color(Value);
        }

        #endregion
    }
}

Here is an example of using it on a console application:

Program.cs

using System;

public class Program
{
    public static void Main()
    {
        var color1 = new Color(49, 100, 200);
        var color2 = Color.FromHSL(color1.HSLValues());

        if (color1 == color2)
            Console.WriteLine("yay!");
        
        var color3 = new Color(color2.ToString());

        if (color1 == color3 && color2 == color3 && color3 == color3)
            Console.WriteLine("yay!");

        Console.WriteLine(color1);
        Console.WriteLine(color2);
        Console.WriteLine(color3);
    }
}

I've taken all the named colors from here and added to a separate static class that can be very usefull:

Colors.cs

using System.Collections.Generic;

namespace Color
{
    /// <summary>
    /// https://www.w3schools.com/colors/colors_names.asp
    /// </summary>
    public static class Colors
    {
        public static Color AliceBlue => new Color(0xFFF0F8FF);
        public static Color AntiqueWhite => new Color(0xFFFAEBD7);
        public static Color Aqua => new Color(0xFF00FFFF);
        public static Color Aquamarine => new Color(0xFF7FFFD4);
        public static Color Azure => new Color(0xFFF0FFFF);
        public static Color Beige => new Color(0xFFF5F5DC);
        public static Color Bisque => new Color(0xFFFFE4C4);
        public static Color Black => new Color(0xFF000000);
        public static Color BlanchedAlmond => new Color(0xFFFFEBCD);
        public static Color Blue => new Color(0xFF0000FF);
        public static Color BlueViolet => new Color(0xFF8A2BE2);
        public static Color Brown => new Color(0xFFA52A2A);
        public static Color BurlyWood => new Color(0xFFDEB887);
        public static Color CadetBlue => new Color(0xFF5F9EA0);
        public static Color Chartreuse => new Color(0xFF7FFF00);
        public static Color Chocolate => new Color(0xFFD2691E);
        public static Color Coral => new Color(0xFFFF7F50);
        public static Color CornflowerBlue => new Color(0xFF6495ED);
        public static Color Cornsilk => new Color(0xFFFFF8DC);
        public static Color Crimson => new Color(0xFFDC143C);
        public static Color Cyan => new Color(0xFF00FFFF);
        public static Color DarkBlue => new Color(0xFF00008B);
        public static Color DarkCyan => new Color(0xFF008B8B);
        public static Color DarkGoldenRod => new Color(0xFFB8860B);
        public static Color DarkGray => new Color(0xFFA9A9A9);
        public static Color DarkGrey => new Color(0xFFA9A9A9);
        public static Color DarkGreen => new Color(0xFF006400);
        public static Color DarkKhaki => new Color(0xFFBDB76B);
        public static Color DarkMagenta => new Color(0xFF8B008B);
        public static Color DarkOliveGreen => new Color(0xFF556B2F);
        public static Color DarkOrange => new Color(0xFFFF8C00);
        public static Color DarkOrchid => new Color(0xFF9932CC);
        public static Color DarkRed => new Color(0xFF8B0000);
        public static Color DarkSalmon => new Color(0xFFE9967A);
        public static Color DarkSeaGreen => new Color(0xFF8FBC8F);
        public static Color DarkSlateBlue => new Color(0xFF483D8B);
        public static Color DarkSlateGray => new Color(0xFF2F4F4F);
        public static Color DarkSlateGrey => new Color(0xFF2F4F4F);
        public static Color DarkTurquoise => new Color(0xFF00CED1);
        public static Color DarkViolet => new Color(0xFF9400D3);
        public static Color DeepPink => new Color(0xFFFF1493);
        public static Color DeepSkyBlue => new Color(0xFF00BFFF);
        public static Color DimGray => new Color(0xFF696969);
        public static Color DimGrey => new Color(0xFF696969);
        public static Color DodgerBlue => new Color(0xFF1E90FF);
        public static Color FireBrick => new Color(0xFFB22222);
        public static Color FloralWhite => new Color(0xFFFFFAF0);
        public static Color ForestGreen => new Color(0xFF228B22);
        public static Color Fuchsia => new Color(0xFFFF00FF);
        public static Color Gainsboro => new Color(0xFFDCDCDC);
        public static Color GhostWhite => new Color(0xFFF8F8FF);
        public static Color Gold => new Color(0xFFFFD700);
        public static Color GoldenRod => new Color(0xFFDAA520);
        public static Color Gray => new Color(0xFF808080);
        public static Color Grey => new Color(0xFF808080);
        public static Color Green => new Color(0xFF008000);
        public static Color GreenYellow => new Color(0xFFADFF2F);
        public static Color HoneyDew => new Color(0xFFF0FFF0);
        public static Color HotPink => new Color(0xFFFF69B4);
        public static Color IndianRed => new Color(0xFFCD5C5C);
        public static Color Indigo => new Color(0xFF4B0082);
        public static Color Ivory => new Color(0xFFFFFFF0);
        public static Color Khaki => new Color(0xFFF0E68C);
        public static Color Lavender => new Color(0xFFE6E6FA);
        public static Color LavenderBlush => new Color(0xFFFFF0F5);
        public static Color LawnGreen => new Color(0xFF7CFC00);
        public static Color LemonChiffon => new Color(0xFFFFFACD);
        public static Color LightBlue => new Color(0xFFADD8E6);
        public static Color LightCoral => new Color(0xFFF08080);
        public static Color LightCyan => new Color(0xFFE0FFFF);
        public static Color LightGoldenRodYellow => new Color(0xFFFAFAD2);
        public static Color LightGray => new Color(0xFFD3D3D3);
        public static Color LightGrey => new Color(0xFFD3D3D3);
        public static Color LightGreen => new Color(0xFF90EE90);
        public static Color LightPink => new Color(0xFFFFB6C1);
        public static Color LightSalmon => new Color(0xFFFFA07A);
        public static Color LightSeaGreen => new Color(0xFF20B2AA);
        public static Color LightSkyBlue => new Color(0xFF87CEFA);
        public static Color LightSlateGray => new Color(0xFF778899);
        public static Color LightSlateGrey => new Color(0xFF778899);
        public static Color LightSteelBlue => new Color(0xFFB0C4DE);
        public static Color LightYellow => new Color(0xFFFFFFE0);
        public static Color Lime => new Color(0xFF00FF00);
        public static Color LimeGreen => new Color(0xFF32CD32);
        public static Color Linen => new Color(0xFFFAF0E6);
        public static Color Magenta => new Color(0xFFFF00FF);
        public static Color Maroon => new Color(0xFF800000);
        public static Color MediumAquaMarine => new Color(0xFF66CDAA);
        public static Color MediumBlue => new Color(0xFF0000CD);
        public static Color MediumOrchid => new Color(0xFFBA55D3);
        public static Color MediumPurple => new Color(0xFF9370DB);
        public static Color MediumSeaGreen => new Color(0xFF3CB371);
        public static Color MediumSlateBlue => new Color(0xFF7B68EE);
        public static Color MediumSpringGreen => new Color(0xFF00FA9A);
        public static Color MediumTurquoise => new Color(0xFF48D1CC);
        public static Color MediumVioletRed => new Color(0xFFC71585);
        public static Color MidnightBlue => new Color(0xFF191970);
        public static Color MintCream => new Color(0xFFF5FFFA);
        public static Color MistyRose => new Color(0xFFFFE4E1);
        public static Color Moccasin => new Color(0xFFFFE4B5);
        public static Color NavajoWhite => new Color(0xFFFFDEAD);
        public static Color Navy => new Color(0xFF000080);
        public static Color OldLace => new Color(0xFFFDF5E6);
        public static Color Olive => new Color(0xFF808000);
        public static Color OliveDrab => new Color(0xFF6B8E23);
        public static Color Orange => new Color(0xFFFFA500);
        public static Color OrangeRed => new Color(0xFFFF4500);
        public static Color Orchid => new Color(0xFFDA70D6);
        public static Color PaleGoldenRod => new Color(0xFFEEE8AA);
        public static Color PaleGreen => new Color(0xFF98FB98);
        public static Color PaleTurquoise => new Color(0xFFAFEEEE);
        public static Color PaleVioletRed => new Color(0xFFDB7093);
        public static Color PapayaWhip => new Color(0xFFFFEFD5);
        public static Color PeachPuff => new Color(0xFFFFDAB9);
        public static Color Peru => new Color(0xFFCD853F);
        public static Color Pink => new Color(0xFFFFC0CB);
        public static Color Plum => new Color(0xFFDDA0DD);
        public static Color PowderBlue => new Color(0xFFB0E0E6);
        public static Color Purple => new Color(0xFF800080);
        public static Color RebeccaPurple => new Color(0xFF663399);
        public static Color Red => new Color(0xFFFF0000);
        public static Color RosyBrown => new Color(0xFFBC8F8F);
        public static Color RoyalBlue => new Color(0xFF4169E1);
        public static Color SaddleBrown => new Color(0xFF8B4513);
        public static Color Salmon => new Color(0xFFFA8072);
        public static Color SandyBrown => new Color(0xFFF4A460);
        public static Color SeaGreen => new Color(0xFF2E8B57);
        public static Color SeaShell => new Color(0xFFFFF5EE);
        public static Color Sienna => new Color(0xFFA0522D);
        public static Color Silver => new Color(0xFFC0C0C0);
        public static Color SkyBlue => new Color(0xFF87CEEB);
        public static Color SlateBlue => new Color(0xFF6A5ACD);
        public static Color SlateGray => new Color(0xFF708090);
        public static Color SlateGrey => new Color(0xFF708090);
        public static Color Snow => new Color(0xFFFFFAFA);
        public static Color SpringGreen => new Color(0xFF00FF7F);
        public static Color SteelBlue => new Color(0xFF4682B4);
        public static Color Tan => new Color(0xFFD2B48C);
        public static Color Teal => new Color(0xFF008080);
        public static Color Thistle => new Color(0xFFD8BFD8);
        public static Color Tomato => new Color(0xFFFF6347);
        public static Color Turquoise => new Color(0xFF40E0D0);
        public static Color Violet => new Color(0xFFEE82EE);
        public static Color Wheat => new Color(0xFFF5DEB3);
        public static Color White => new Color(0xFFFFFFFF);
        public static Color WhiteSmoke => new Color(0xFFF5F5F5);
        public static Color Yellow => new Color(0xFFFFFF00);
        public static Color YellowGreen => new Color(0xFF9ACD32);

        public static readonly Dictionary<string, Color> ByName = new Dictionary<string, Color>
        {
            { "AliceBlue", AliceBlue },
            { "AntiqueWhite", AntiqueWhite },
            { "Aqua", Aqua },
            { "Aquamarine", Aquamarine },
            { "Azure", Azure },
            { "Beige", Beige },
            { "Bisque", Bisque },
            { "Black", Black },
            { "BlanchedAlmond", BlanchedAlmond },
            { "Blue", Blue },
            { "BlueViolet", BlueViolet },
            { "Brown", Brown },
            { "BurlyWood", BurlyWood },
            { "CadetBlue", CadetBlue },
            { "Chartreuse", Chartreuse },
            { "Chocolate", Chocolate },
            { "Coral", Coral },
            { "CornflowerBlue", CornflowerBlue },
            { "Cornsilk", Cornsilk },
            { "Crimson", Crimson },
            { "Cyan", Cyan },
            { "DarkBlue", DarkBlue },
            { "DarkCyan", DarkCyan },
            { "DarkGoldenRod", DarkGoldenRod },
            { "DarkGray", DarkGray },
            { "DarkGrey", DarkGrey },
            { "DarkGreen", DarkGreen },
            { "DarkKhaki", DarkKhaki },
            { "DarkMagenta", DarkMagenta },
            { "DarkOliveGreen", DarkOliveGreen },
            { "DarkOrange", DarkOrange },
            { "DarkOrchid", DarkOrchid },
            { "DarkRed", DarkRed },
            { "DarkSalmon", DarkSalmon },
            { "DarkSeaGreen", DarkSeaGreen },
            { "DarkSlateBlue", DarkSlateBlue },
            { "DarkSlateGray", DarkSlateGray },
            { "DarkSlateGrey", DarkSlateGrey },
            { "DarkTurquoise", DarkTurquoise },
            { "DarkViolet", DarkViolet },
            { "DeepPink", DeepPink },
            { "DeepSkyBlue", DeepSkyBlue },
            { "DimGray", DimGray },
            { "DimGrey", DimGrey },
            { "DodgerBlue", DodgerBlue },
            { "FireBrick", FireBrick },
            { "FloralWhite", FloralWhite },
            { "ForestGreen", ForestGreen },
            { "Fuchsia", Fuchsia },
            { "Gainsboro", Gainsboro },
            { "GhostWhite", GhostWhite },
            { "Gold", Gold },
            { "GoldenRod", GoldenRod },
            { "Gray", Gray },
            { "Grey", Grey },
            { "Green", Green },
            { "GreenYellow", GreenYellow },
            { "HoneyDew", HoneyDew },
            { "HotPink", HotPink },
            { "IndianRed", IndianRed },
            { "Indigo", Indigo },
            { "Ivory", Ivory },
            { "Khaki", Khaki },
            { "Lavender", Lavender },
            { "LavenderBlush", LavenderBlush },
            { "LawnGreen", LawnGreen },
            { "LemonChiffon", LemonChiffon },
            { "LightBlue", LightBlue },
            { "LightCoral", LightCoral },
            { "LightCyan", LightCyan },
            { "LightGoldenRodYellow", LightGoldenRodYellow },
            { "LightGray", LightGray },
            { "LightGrey", LightGrey },
            { "LightGreen", LightGreen },
            { "LightPink", LightPink },
            { "LightSalmon", LightSalmon },
            { "LightSeaGreen", LightSeaGreen },
            { "LightSkyBlue", LightSkyBlue },
            { "LightSlateGray", LightSlateGray },
            { "LightSlateGrey", LightSlateGrey },
            { "LightSteelBlue", LightSteelBlue },
            { "LightYellow", LightYellow },
            { "Lime", Lime },
            { "LimeGreen", LimeGreen },
            { "Linen", Linen },
            { "Magenta", Magenta },
            { "Maroon", Maroon },
            { "MediumAquaMarine", MediumAquaMarine },
            { "MediumBlue", MediumBlue },
            { "MediumOrchid", MediumOrchid },
            { "MediumPurple", MediumPurple },
            { "MediumSeaGreen", MediumSeaGreen },
            { "MediumSlateBlue", MediumSlateBlue },
            { "MediumSpringGreen", MediumSpringGreen },
            { "MediumTurquoise", MediumTurquoise },
            { "MediumVioletRed", MediumVioletRed },
            { "MidnightBlue", MidnightBlue },
            { "MintCream", MintCream },
            { "MistyRose", MistyRose },
            { "Moccasin", Moccasin },
            { "NavajoWhite", NavajoWhite },
            { "Navy", Navy },
            { "OldLace", OldLace },
            { "Olive", Olive },
            { "OliveDrab", OliveDrab },
            { "Orange", Orange },
            { "OrangeRed", OrangeRed },
            { "Orchid", Orchid },
            { "PaleGoldenRod", PaleGoldenRod },
            { "PaleGreen", PaleGreen },
            { "PaleTurquoise", PaleTurquoise },
            { "PaleVioletRed", PaleVioletRed },
            { "PapayaWhip", PapayaWhip },
            { "PeachPuff", PeachPuff },
            { "Peru", Peru },
            { "Pink", Pink },
            { "Plum", Plum },
            { "PowderBlue", PowderBlue },
            { "Purple", Purple },
            { "RebeccaPurple", RebeccaPurple },
            { "Red", Red },
            { "RosyBrown", RosyBrown },
            { "RoyalBlue", RoyalBlue },
            { "SaddleBrown", SaddleBrown },
            { "Salmon", Salmon },
            { "SandyBrown", SandyBrown },
            { "SeaGreen", SeaGreen },
            { "SeaShell", SeaShell },
            { "Sienna", Sienna },
            { "Silver", Silver },
            { "SkyBlue", SkyBlue },
            { "SlateBlue", SlateBlue },
            { "SlateGray", SlateGray },
            { "SlateGrey", SlateGrey },
            { "Snow", Snow },
            { "SpringGreen", SpringGreen },
            { "SteelBlue", SteelBlue },
            { "Tan", Tan },
            { "Teal", Teal },
            { "Thistle", Thistle },
            { "Tomato", Tomato },
            { "Turquoise", Turquoise },
            { "Violet", Violet },
            { "Wheat", Wheat },
            { "White", White },
            { "WhiteSmoke", WhiteSmoke },
            { "Yellow", Yellow },
            { "YellowGreen", YellowGreen }
        };
    }
}

And then made a pretty cool page showcasing all colors:

Colors.razor

@page "/color"
@using Color

<h3>Colors</h3>

<style>
    .square-container {
        display: flex;
        justify-content: space-around;
        flex-wrap: wrap;
    }
    .square {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        width: 400px;
        height: 300px;
        margin: 20px;
    }
</style>

<div class="square-container">
    @foreach ((string name, Color color) in Colors.ByName)
    {
    <div class="square" style="@($"background-color: {color.ToCssRgba()}; color: {(color.Luminance > 0.5 ? "black" : "white")}")">
        <p>@name</p>
        <p>@color.ToCssColor()</p>
        <p>@color.ToCssRgba()</p>
        <p>@color.ToCssHsla()</p>
    </div>
    }
</div>

Concerns:

  • It really feels like the Color struct should be a struct indeed. I've read about structs vs classes in C# but can't quite get to a conclusion;
  • Should I store ARGB into a 64 bit integer for higher precision? It doesn't feels like it is necessary;
  • Should all parameters be double?
  • Shoudl all uint parameters by byte?
  • The use of tuples...? Is there a better way of doing it? It doesn't feel right.
\$\endgroup\$
2
  • \$\begingroup\$ Tip: refer to Color.cs implementation. It can be useful. \$\endgroup\$
    – aepot
    Commented Sep 2, 2020 at 10:01
  • 1
    \$\begingroup\$ @aepot I based this class mostly on Flutter's Color which is a much more modern one \$\endgroup\$
    – LeoVen
    Commented Sep 2, 2020 at 11:22

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.