File: System\Windows\Media\Color.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
 
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Media.Imaging;
using MS.Internal;
using MS.Internal.PresentationCore;
 
using SR=MS.Internal.PresentationCore.SR;
 
#pragma warning disable 1634, 1691  // suppressing PreSharp warnings
 
namespace System.Windows.Media
{
    /// <summary>
    /// Color
    /// The Color structure, composed of a private, synchronized ScRgb (IEC 61966-2-2) value
    /// a color context, composed of an ICC profile and the native color values.
    /// </summary>
    [TypeConverter(typeof(ColorConverter))]
    [Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
    public struct Color : IFormattable, IEquatable<Color>
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
 
        ///<summary>
        /// Color constructor based on ICC or Ashland profile context name. The returned value
        /// is opaque black.
        ///</summary>
        private static Color FromProfile(Uri profileUri)
        {
            Color c1 = new Color();
 
            c1.context = new ColorContext(profileUri);
            c1.scRgbColor.a = 1.0f;
            c1.scRgbColor.r = 0.0f;
            c1.scRgbColor.g = 0.0f;
            c1.scRgbColor.b = 0.0f;
            c1.sRgbColor.a = 255;
            c1.sRgbColor.r = 0;
            c1.sRgbColor.g = 0;
            c1.sRgbColor.b = 0;
            if (c1.context != null)
            {
                c1.nativeColorValue = new float[c1.context.NumChannels];
                for (int i = 0; i < c1.nativeColorValue.Length; i++)
                {
                    c1.nativeColorValue[i] = 0.0f;
                }
            }
 
            c1.isFromScRgb = false;
 
            return c1;
        }
 
        ///<summary>
        /// FromAValues - general constructor for multichannel color values with explicit alpha channel and color context, i.e. spectral colors
        ///</summary>
        public static Color FromAValues(float a, float[] values, Uri profileUri)
        {
            Color c1 = Color.FromProfile(profileUri);
 
            if (values == null)
            {
                throw new ArgumentException(SR.Format(SR.Color_DimensionMismatch, null));
            }
 
            if (values.Length != c1.nativeColorValue.Length)
            {
                throw new ArgumentException(SR.Format(SR.Color_DimensionMismatch, null));
            }
 
            for (int numChannels = 0; numChannels < values.Length; numChannels++)
            {
                c1.nativeColorValue[numChannels] = values[numChannels];
            }
            c1.ComputeScRgbValues();
            c1.scRgbColor.a = a;
            if (a < 0.0f)
            {
                a = 0.0f;
            }
            else if (a > 1.0f)
            {
                a = 1.0f;
            }
 
            c1.sRgbColor.a = (byte)((a * 255.0f) + 0.5f);
            c1.sRgbColor.r = ScRgbTosRgb(c1.scRgbColor.r);
            c1.sRgbColor.g = ScRgbTosRgb(c1.scRgbColor.g);
            c1.sRgbColor.b = ScRgbTosRgb(c1.scRgbColor.b);
 
            return c1;
        }
 
        ///<summary>
        /// FromValues - general color constructor for multichannel color values with opaque alpha channel and explicit color context, i.e. spectral colors
        ///</summary>
        public static Color FromValues(float[] values, Uri profileUri)
        {
            Color c1 = Color.FromAValues(1.0f, values, profileUri);
 
            return c1;
        }
 
        ///<summary>
        /// Color - sRgb legacy interface, assumes Rgb values are sRgb
        ///</summary>
        internal static Color FromUInt32(uint argb)// internal legacy sRGB interface
        {
            Color c1 = new Color();
 
            c1.sRgbColor.a = (byte)((argb & 0xff000000) >> 24);
            c1.sRgbColor.r = (byte)((argb & 0x00ff0000) >> 16);
            c1.sRgbColor.g = (byte)((argb & 0x0000ff00) >> 8);
            c1.sRgbColor.b = (byte)(argb & 0x000000ff);
            c1.scRgbColor.a = (float)c1.sRgbColor.a / 255.0f;
            c1.scRgbColor.r = sRgbToScRgb(c1.sRgbColor.r);  // note that context is undefined and thus unloaded
            c1.scRgbColor.g = sRgbToScRgb(c1.sRgbColor.g);
            c1.scRgbColor.b = sRgbToScRgb(c1.sRgbColor.b);
            c1.context = null;
 
            c1.isFromScRgb = false;
 
            return c1;
        }
 
        ///<summary>
        /// FromScRgb
        ///</summary>
        public static Color FromScRgb(float a, float r, float g, float b)
        {
            Color c1 = new Color();
 
            c1.scRgbColor.r = r;
            c1.scRgbColor.g = g;
            c1.scRgbColor.b = b;
            c1.scRgbColor.a = a;
            if (a < 0.0f)
            {
                a = 0.0f;
            }
            else if (a > 1.0f)
            {
                a = 1.0f;
            }
 
            c1.sRgbColor.a = (byte)((a * 255.0f) + 0.5f);
            c1.sRgbColor.r = ScRgbTosRgb(c1.scRgbColor.r);
            c1.sRgbColor.g = ScRgbTosRgb(c1.scRgbColor.g);
            c1.sRgbColor.b = ScRgbTosRgb(c1.scRgbColor.b);
            c1.context = null;
 
            c1.isFromScRgb = true;
 
            return c1;
        }
 
        ///<summary>
        /// Color - sRgb legacy interface, assumes Rgb values are sRgb, alpha channel is linear 1.0 gamma
        ///</summary>
        public static Color FromArgb(byte a, byte r, byte g, byte b)// legacy sRGB interface, bytes are required to properly round trip
        {
            Color c1 = new Color();
 
            c1.scRgbColor.a = (float)a / 255.0f;
            c1.scRgbColor.r = sRgbToScRgb(r);  // note that context is undefined and thus unloaded
            c1.scRgbColor.g = sRgbToScRgb(g);
            c1.scRgbColor.b = sRgbToScRgb(b);
            c1.context = null;
            c1.sRgbColor.a = a;
            c1.sRgbColor.r = ScRgbTosRgb(c1.scRgbColor.r);
            c1.sRgbColor.g = ScRgbTosRgb(c1.scRgbColor.g);
            c1.sRgbColor.b = ScRgbTosRgb(c1.scRgbColor.b);
 
            c1.isFromScRgb = false;
 
            return c1;
        }
 
        ///<summary>
        /// Color - sRgb legacy interface, assumes Rgb values are sRgb
        ///</summary>
        public static Color FromRgb(byte r, byte g, byte b)// legacy sRGB interface, bytes are required to properly round trip
        {
            Color c1 = Color.FromArgb(0xff, r, g, b);
            return c1;
        }
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
        ///<summary>
        /// GetHashCode
        ///</summary>
        public override int GetHashCode()
        {
            return this.scRgbColor.GetHashCode(); //^this.context.GetHashCode();
        }
 
        /// <summary>
        /// Creates a string representation of this object based on the current culture.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        public override string ToString()
        {
            // Delegate to the internal method which implements all ToString calls.
 
            string format = isFromScRgb ? c_scRgbFormat : null;
 
            return ConvertToString(format, null);
        }
 
        /// <summary>
        /// Creates a string representation of this object based on the IFormatProvider
        /// passed in.  If the provider is null, the CurrentCulture is used.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        public string ToString(IFormatProvider provider)
        {
            // Delegate to the internal method which implements all ToString calls.
 
            string format = isFromScRgb ? c_scRgbFormat : null;
 
            return ConvertToString(format, provider);
        }
 
        /// <summary>
        /// Creates a string representation of this object based on the format string
        /// and IFormatProvider passed in.
        /// If the provider is null, the CurrentCulture is used.
        /// See the documentation for IFormattable for more information.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        string IFormattable.ToString(string format, IFormatProvider provider)
        {
            // Delegate to the internal method which implements all ToString calls.
            return ConvertToString(format, provider);
        }
 
        /// <summary>
        /// Creates a string representation of this object based on the format string 
        /// and IFormatProvider passed in.  
        /// If the provider is null, the CurrentCulture is used.
        /// See the documentation for IFormattable for more information.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        internal string ConvertToString(string format, IFormatProvider provider)
        {
            if (context == null)
            {
                if (format == null)
                {
                    return string.Create(provider, stackalloc char[128], $"#{this.sRgbColor.a:X2}{this.sRgbColor.r:X2}{this.sRgbColor.g:X2}{this.sRgbColor.b:X2}");
                }
                else
                {
                    // Helper to get the numeric list separator for a given culture.
                    char separator = MS.Internal.TokenizerHelper.GetNumericListSeparator(provider);
                    return string.Format(provider,
                        $"sc#{{1:{format}}}{{0}} {{2:{format}}}{{0}} {{3:{format}}}{{0}} {{4:{format}}}",
                        separator, scRgbColor.a, scRgbColor.r, scRgbColor.g, scRgbColor.b);
                }
            }
            else
            {
                char separator = MS.Internal.TokenizerHelper.GetNumericListSeparator(provider);
 
                format = c_scRgbFormat;
 
                //First Stepmake sure that nothing that should not be escaped is escaped
                Uri safeUnescapedUri = new Uri(context.ProfileUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.SafeUnescaped),
                                                    context.ProfileUri.IsAbsoluteUri ? UriKind.Absolute : UriKind.Relative);
                //Second Step make sure that everything that should escaped is escaped
                String uriString = safeUnescapedUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
 
                var sb = new StringBuilder();
                sb.AppendFormat(provider, "{0}{1} ", Parsers.s_ContextColor, uriString);
                sb.AppendFormat(provider,"{1:" + format + "}{0}",separator,scRgbColor.a);
                for (int i = 0; i < nativeColorValue.Length; ++i )
                {
                    sb.AppendFormat(provider,"{0:" + format + "}",nativeColorValue[i]);
                    if (i < nativeColorValue.Length - 1)
                    {
                        sb.AppendFormat(provider,"{0}",separator);
                    }
                }
                return sb.ToString();
            }
        }
 
        /// <summary>
        /// Compares two colors for fuzzy equality.  This function
        /// helps compensate for the fact that float values can
        /// acquire error when operated upon
        /// </summary>
        /// <param name='color1'>The first color to compare</param>
        /// <param name='color2'>The second color to compare</param>
        /// <returns>Whether or not the two colors are equal</returns>
        public static bool AreClose(Color color1, Color color2)
        {
            return color1.IsClose(color2);
        }
 
        /// <summary>
        /// Compares two colors for fuzzy equality.  This function
        /// helps compensate for the fact that float values can
        /// acquire error when operated upon
        /// </summary>
        /// <param name='color'>The color to compare to this</param>
        /// <returns>Whether or not the two colors are equal</returns>
        private bool IsClose(Color color)
        {
            // Alpha is the least likely channel to differ
            bool result = true;
 
            if (context == null || color.nativeColorValue == null)
            {
                result = result && FloatUtil.AreClose(scRgbColor.r, color.scRgbColor.r);
                result = result && FloatUtil.AreClose(scRgbColor.g, color.scRgbColor.g);
                result = result && FloatUtil.AreClose(scRgbColor.b, color.scRgbColor.b);
            }
            else
            {
                for (int i = 0; i < color.nativeColorValue.Length; i++)
                    result = result && FloatUtil.AreClose(nativeColorValue[i], color.nativeColorValue[i]);
            }
 
            return result && FloatUtil.AreClose(scRgbColor.a, color.scRgbColor.a);
        }
 
        ///<summary>
        /// Clamp - the color channels to the gamut [0..1].  If a channel is out
        /// of gamut, it will be set to 1, which represents full saturation.
        /// We need to sync up context values if they exist
        ///</summary>
        public void Clamp()
        {
            scRgbColor.r = (scRgbColor.r < 0) ? 0 : (scRgbColor.r > 1.0f) ? 1.0f : scRgbColor.r;
            scRgbColor.g = (scRgbColor.g < 0) ? 0 : (scRgbColor.g > 1.0f) ? 1.0f : scRgbColor.g;
            scRgbColor.b = (scRgbColor.b < 0) ? 0 : (scRgbColor.b > 1.0f) ? 1.0f : scRgbColor.b;
            scRgbColor.a = (scRgbColor.a < 0) ? 0 : (scRgbColor.a > 1.0f) ? 1.0f : scRgbColor.a;
            sRgbColor.a = (byte)(scRgbColor.a * 255f);
            sRgbColor.r = ScRgbTosRgb(scRgbColor.r);
            sRgbColor.g = ScRgbTosRgb(scRgbColor.g);
            sRgbColor.b = ScRgbTosRgb(scRgbColor.b);
 
            //add code to check if context is null and if not null then clamp native values
        }
 
        ///<summary>
        /// GetNativeColorValues - return color values from color context
        ///</summary>
        public float[] GetNativeColorValues()
        {
            if (context != null)
            {
                return (float[])nativeColorValue.Clone();
            }
            else
            {
                throw new InvalidOperationException(SR.Format(SR.Color_NullColorContext, null));
            }
        }
        #endregion Public Methods
 
        //------------------------------------------------------
        //
        //  Public Operators
        //
        //------------------------------------------------------
 
        #region Public Operators
        ///<summary>
        /// Addition operator - Adds each channel of the second color to each channel of the
        /// first and returns the result
        ///</summary>
        public static Color operator +(Color color1, Color color2)
        {
            if (color1.context == null && color2.context == null)
            {
            Color c1 = FromScRgb(
                  color1.scRgbColor.a + color2.scRgbColor.a,
                  color1.scRgbColor.r + color2.scRgbColor.r,
                  color1.scRgbColor.g + color2.scRgbColor.g,
                  color1.scRgbColor.b + color2.scRgbColor.b);
                return c1;
            }
            else if (color1.context == color2.context)
            {
                Color c1 = new Color();
                c1.context = color1.context;
                
                #pragma warning suppress 6506 // c1.context is obviously not null - both color1.context AND color2.context are not null
                c1.nativeColorValue = new float[c1.context.NumChannels];
                for (int i = 0; i < c1.nativeColorValue.Length; i++)
                {
                    c1.nativeColorValue[i] = color1.nativeColorValue[i] + color2.nativeColorValue[i];
                }
 
                Color c2 = Color.FromRgb(0, 0, 0);
 
                c2.context = new ColorContext(PixelFormats.Bgra32);
 
                ColorTransform colorTransform = new ColorTransform(c1.context, c2.context);
                Span<float> sRGBValue = stackalloc float[3];
 
                colorTransform.Translate(c1.nativeColorValue, sRGBValue);
 
                if (sRGBValue[0] < 0.0f)
                {
                    c1.sRgbColor.r = 0;
                }
                else if (sRGBValue[0] > 1.0f)
                {
                    c1.sRgbColor.r = 255;
                }
                else
                {
                    c1.sRgbColor.r = (byte)((sRGBValue[0] * 255.0f) + 0.5f);
                }
 
                if (sRGBValue[1] < 0.0f)
                {
                    c1.sRgbColor.g = 0;
                }
                else if (sRGBValue[1] > 1.0f)
                {
                    c1.sRgbColor.g = 255;
                }
                else
                {
                    c1.sRgbColor.g = (byte)((sRGBValue[1] * 255.0f) + 0.5f);
                }
 
                if (sRGBValue[2] < 0.0f)
                {
                    c1.sRgbColor.b = 0;
                }
                else if (sRGBValue[2] > 1.0f)
                {
                    c1.sRgbColor.b = 255;
                }
                else
                {
                    c1.sRgbColor.b = (byte)((sRGBValue[2] * 255.0f) + 0.5f);
                }
 
                c1.scRgbColor.r = sRgbToScRgb(c1.sRgbColor.r);
                c1.scRgbColor.g = sRgbToScRgb(c1.sRgbColor.g);
                c1.scRgbColor.b = sRgbToScRgb(c1.sRgbColor.b);
                c1.scRgbColor.a = color1.scRgbColor.a + color2.scRgbColor.a;
                if (c1.scRgbColor.a < 0.0f)
                {
                    c1.scRgbColor.a = 0.0f;
                    c1.sRgbColor.a = 0;
                }
                else if (c1.scRgbColor.a > 1.0f)
                {
                    c1.scRgbColor.a = 1.0f;
                    c1.sRgbColor.a = 255;
                }
                else
                {
                    c1.sRgbColor.a = (byte)((c1.scRgbColor.a * 255.0f) + 0.5f);
                }
 
                return c1;
            }
            else
            {
                throw new ArgumentException(SR.Format(SR.Color_ColorContextTypeMismatch, null));
            }
        }
 
        ///<summary>
        /// Addition method - Adds each channel of the second color to each channel of the
        /// first and returns the result
        ///</summary>
        public static Color Add(Color color1, Color color2)
        {
            return (color1 + color2);
        }
 
        /// <summary>
        /// Subtract operator - substracts each channel of the second color from each channel of the
        /// first and returns the result
        /// </summary>
        /// <param name='color1'>The minuend</param>
        /// <param name='color2'>The subtrahend</param>
        /// <returns>Returns the unclamped differnce</returns>
        public static Color operator -(Color color1, Color color2)
        {
            if (color1.context == null && color2.context == null)
            {
                Color c1 = FromScRgb(
                    color1.scRgbColor.a - color2.scRgbColor.a,
                    color1.scRgbColor.r - color2.scRgbColor.r,
                    color1.scRgbColor.g - color2.scRgbColor.g,
                    color1.scRgbColor.b - color2.scRgbColor.b
                    );
                return c1;
            }
            else if (color1.context == null || color2.context == null)
            {
                throw new ArgumentException(SR.Format(SR.Color_ColorContextTypeMismatch, null));
            }
            else if (color1.context == color2.context)
            {
                Color c1 = new Color();
                c1.context = color1.context;
 
                #pragma warning suppress 6506 // c1.context is obviously not null - both color1.context AND color2.context are not null
                c1.nativeColorValue = new float[c1.context.NumChannels];
                for (int i = 0; i < c1.nativeColorValue.Length; i++)
                {
                    c1.nativeColorValue[i] = color1.nativeColorValue[i] - color2.nativeColorValue[i];
                }
 
                Color c2 = Color.FromRgb(0, 0, 0);
 
                c2.context = new ColorContext(PixelFormats.Bgra32);
 
                ColorTransform colorTransform = new ColorTransform(c1.context, c2.context);
                Span<float> sRGBValue = stackalloc float[3];
 
                colorTransform.Translate(c1.nativeColorValue, sRGBValue);
 
                if (sRGBValue[0] < 0.0f)
                {
                    c1.sRgbColor.r = 0;
                }
                else if (sRGBValue[0] > 1.0f)
                {
                    c1.sRgbColor.r = 255;
                }
                else
                {
                    c1.sRgbColor.r = (byte)((sRGBValue[0] * 255.0f) + 0.5f);
                }
 
                if (sRGBValue[1] < 0.0f)
                {
                    c1.sRgbColor.g = 0;
                }
                else if (sRGBValue[1] > 1.0f)
                {
                    c1.sRgbColor.g = 255;
                }
                else
                {
                    c1.sRgbColor.g = (byte)((sRGBValue[1] * 255.0f) + 0.5f);
                }
 
                if (sRGBValue[2] < 0.0f)
                {
                    c1.sRgbColor.b = 0;
                }
                else if (sRGBValue[2] > 1.0f)
                {
                    c1.sRgbColor.b = 255;
                }
                else
                {
                    c1.sRgbColor.b = (byte)((sRGBValue[2] * 255.0f) + 0.5f);
                }
 
                c1.scRgbColor.r = sRgbToScRgb(c1.sRgbColor.r);
                c1.scRgbColor.g = sRgbToScRgb(c1.sRgbColor.g);
                c1.scRgbColor.b = sRgbToScRgb(c1.sRgbColor.b);
                c1.scRgbColor.a = color1.scRgbColor.a - color2.scRgbColor.a;
                if (c1.scRgbColor.a < 0.0f)
                {
                    c1.scRgbColor.a = 0.0f;
                    c1.sRgbColor.a = 0;
                }
                else if (c1.scRgbColor.a > 1.0f)
                {
                    c1.scRgbColor.a = 1.0f;
                    c1.sRgbColor.a = 255;
                }
                else
                {
                    c1.sRgbColor.a = (byte)((c1.scRgbColor.a * 255.0f) + 0.5f);
                }
 
                return c1;
            }
            else
            {
                throw new ArgumentException(SR.Format(SR.Color_ColorContextTypeMismatch, null));
            }
        }
 
        ///<summary>
        /// Subtract method - subtracts each channel of the second color from each channel of the
        /// first and returns the result
        ///</summary>
        public static Color Subtract(Color color1, Color color2)
        {
            return (color1 - color2);
        }
 
        /// <summary>
        /// Multiplication operator - Multiplies each channel of the color by a coefficient and returns the result
        /// </summary>
        /// <param name='color'>The color</param>
        /// <param name='coefficient'>The coefficient</param>
        /// <returns>Returns the unclamped product</returns>
        public static Color operator *(Color color, float coefficient)
        {
            Color c1 = FromScRgb(color.scRgbColor.a * coefficient, color.scRgbColor.r * coefficient, color.scRgbColor.g * coefficient, color.scRgbColor.b * coefficient);
 
            if (color.context == null)
            {
                return c1;
            }
            else
            {
                c1.context = color.context;
 
                #pragma warning suppress 6506 // c1.context is obviously not null
                c1.ComputeNativeValues(c1.context.NumChannels);
            }
 
            return c1;
        }
 
        ///<summary>
        /// Multiplication method - Multiplies each channel of the color by a coefficient and returns the result
        ///</summary>
        public static Color Multiply(Color color, float coefficient)
        {
            return (color * coefficient);
        }
 
        ///<summary>
        /// Equality method for two colors - return true of colors are equal, otherwise returns false
        ///</summary>
        public static bool Equals(Color color1, Color color2)
        {
            return (color1 == color2);
        }
 
        /// <summary>
        /// Compares two colors for exact equality.  Note that float values can acquire error
        /// when operated upon, such that an exact comparison between two values which are logically
        /// equal may fail. see cref="AreClose" for a "fuzzy" version of this comparison.
        /// </summary>
        /// <param name='color'>The color to compare to "this"</param>
        /// <returns>Whether or not the two colors are equal</returns>
        public bool Equals(Color color)
        {
            return this == color;
        }
 
        /// <summary>
        /// Compares two colors for exact equality.  Note that float values can acquire error
        /// when operated upon, such that an exact comparison between two vEquals(color);alues which are logically
        /// equal may fail. see cref="AreClose" for a "fuzzy" version of this comparison.
        /// </summary>
        /// <param name='o'>The object to compare to "this"</param>
        /// <returns>Whether or not the two colors are equal</returns>
        public override bool Equals(object o)
        {
            if (o is Color)
            {
                Color color = (Color)o;
 
                return (this == color);
            }
            else
            {
                return false;
            }
        }
 
       ///<summary>
        /// IsEqual operator - Compares two colors for exact equality.  Note that float values can acquire error
        /// when operated upon, such that an exact comparison between two values which are logically
        /// equal may fail. see cref="AreClose".
        ///</summary>
        public static bool operator ==(Color color1, Color color2)
        {
            if (color1.context == null && color2.context == null)
            {
                if (color1.scRgbColor.r != color2.scRgbColor.r)
                {
                    return false;
                }
 
                if (color1.scRgbColor.g != color2.scRgbColor.g)
                {
                    return false;
                }
 
                if (color1.scRgbColor.b != color2.scRgbColor.b)
                {
                    return false;
                }
 
                if (color1.scRgbColor.a != color2.scRgbColor.a)
                {
                    return false;
                }
 
                return true;
            }
            else if (color1.context == null || color2.context == null)
            {
                return false;
            }
            else if (color1.context.ColorSpaceFamily == color2.context.ColorSpaceFamily)
            {
                if (color1.nativeColorValue == null && color2.nativeColorValue == null)
                {
                    return true;
                }
 
                if (color1.nativeColorValue == null || color2.nativeColorValue == null)
                {
                    return false;
                }
 
                if (color1.nativeColorValue.Length != color2.nativeColorValue.Length)
                {
                    return false;
                }
 
                for (int i = 0; i < color1.nativeColorValue.Length; i++)
                {
                    if (color1.nativeColorValue[i] != color2.nativeColorValue[i])
                    {
                        return false;
                    }
                }
 
                if (color1.scRgbColor.a != color2.scRgbColor.a)
                {
                    return false;
                }
 
                return true;
            }
 
            return false;
        }
 
        ///<summary>
        /// !=
        ///</summary>
        public static bool operator !=(Color color1, Color color2)
        {
            return (!(color1 == color2));
        }
        #endregion Public Operators
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        #region Public Properties
 
        ///<summary>
        /// ColorContext
        ///</summary>
        public ColorContext ColorContext
        {
            get
            {
                return context;
            }
        }
 
        ///<summary>
        /// A
        ///</summary>
        public byte A
        {
            get
            {
                return sRgbColor.a;
            }
            set
            {
                scRgbColor.a = (float)value / 255.0f;
                sRgbColor.a = value;
            }
        }
 
        /// <value>The Red channel as a byte whose range is [0..255].
        /// the value is not allowed to be out of range</value>
        /// <summary>
        /// R
        /// </summary>
        public byte R
        {
            get
            {
                return sRgbColor.r;
            }
            set
            {
                if (context == null || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.Srgb) || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.ScRgb))
                {
                    scRgbColor.r = sRgbToScRgb(value);
                    sRgbColor.r = value;
                }
                else
                {
                    throw new InvalidOperationException(SR.Format(SR.Color_ColorContextNotsRGB_or_scRGB, null));
                }
            }
        }
 
        ///<value>The Green channel as a byte whose range is [0..255].
        /// the value is not allowed to be out of range</value><summary>
        /// G
        ///</summary>
        public byte G
        {
            get
            {
                return sRgbColor.g;
            }
            set
            {
                if (context == null || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.Srgb) || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.ScRgb))
                {
                    scRgbColor.g = sRgbToScRgb(value);
                    sRgbColor.g = value;
                }
                else
                {
                    throw new InvalidOperationException(SR.Format(SR.Color_ColorContextNotsRGB_or_scRGB, null));
                }
            }
        }
 
        ///<value>The Blue channel as a byte whose range is [0..255].
        /// the value is not allowed to be out of range</value><summary>
        /// B
        ///</summary>
        public byte B
        {
            get
            {
                return sRgbColor.b;
            }
            set
            {
                if (context == null || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.Srgb) || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.ScRgb))
                {
                    scRgbColor.b = sRgbToScRgb(value);
                    sRgbColor.b = value;
                }
                else
                {
                    throw new InvalidOperationException(SR.Format(SR.Color_ColorContextNotsRGB_or_scRGB, null));
                }
            }
        }
 
        ///<value>The Alpha channel as a float whose range is [0..1].
        /// the value is allowed to be out of range</value><summary>
        /// ScA
        ///</summary>
        public float ScA
        {
            get
            {
                return scRgbColor.a;
            }
            set
            {
                scRgbColor.a = value;
                if (value < 0.0f)
                {
                    sRgbColor.a = 0;
                }
                else if (value > 1.0f)
                {
                    sRgbColor.a = (byte)255;
                }
                else
                {
                    sRgbColor.a = (byte)(value * 255f);
                }
            }
        }
 
        ///<value>The Red channel as a float whose range is [0..1].
        /// the value is allowed to be out of range</value>
        ///<summary>
        /// ScR
        ///</summary>
        public float ScR
        {
            get
            {
                return scRgbColor.r;
                // throw new ArgumentException(SR.Format(SR.Color_ColorContextNotsRgb_or_ScRgb, null));
            }
            set
            {
                if (context == null || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.Srgb) || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.ScRgb))
                {
                    scRgbColor.r = value;
                    sRgbColor.r = ScRgbTosRgb(value);
                }
                else
                {
                    throw new InvalidOperationException(SR.Format(SR.Color_ColorContextNotsRGB_or_scRGB, null));
                }
            }
        }
 
        ///<value>The Green channel as a float whose range is [0..1].
        /// the value is allowed to be out of range</value><summary>
        /// ScG
        ///</summary>
        public float ScG
        {
            get
            {
                return scRgbColor.g;
                // throw new ArgumentException(SR.Format(SR.Color_ColorContextNotsRgb_or_ScRgb, null));
            }
            set
            {
                if (context == null || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.Srgb) || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.ScRgb))
                {
                    scRgbColor.g = value;
                    sRgbColor.g = ScRgbTosRgb(value);
                }
                else
                {
                    throw new InvalidOperationException(SR.Format(SR.Color_ColorContextNotsRGB_or_scRGB, null));
                }
            }
        }
 
        ///<value>The Blue channel as a float whose range is [0..1].
        /// the value is allowed to be out of range</value><summary>
        /// ScB
        ///</summary>
        public float ScB
        {
            get
            {
                return scRgbColor.b;
                // throw new ArgumentException(SR.Format(SR.Color_ColorContextNotsRgb_or_ScRgb, null));
            }
            set
            {
                if (context == null || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.Srgb) || (context.ColorSpaceFamily == ColorContext.StandardColorSpace.ScRgb))
                {
                    scRgbColor.b = value;
                    sRgbColor.b = ScRgbTosRgb(value);
                }
                else
                {
                    throw new InvalidOperationException(SR.Format(SR.Color_ColorContextNotsRGB_or_scRGB, null));
                }
            }
        }
 
        #endregion Public Properties
 
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Internal Events
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
        #region Private Methods
 
        ///<summary>
        /// private helper function to set context values from a color value with a set context and ScRgb values
        ///</summary>
        private static float sRgbToScRgb(byte bval)
        {
            float val = ((float)bval / 255.0f);
 
            if (!(val > 0.0))       // Handles NaN case too. (Though, NaN isn't actually
                                    // possible in this case.)
            {
                return (0.0f);
            }
            else if (val <= 0.04045)
            {
                return (val / 12.92f);
            }
            else if (val < 1.0f)
            {
                return (float)Math.Pow(((double)val + 0.055) / 1.055, 2.4);
            }
            else
            {
                return (1.0f);
            }
        }
 
        ///<summary>
        /// private helper function to set context values from a color value with a set context and ScRgb values
        ///</summary>
        ///
        private static byte ScRgbTosRgb(float val)
        {
            if (!(val > 0.0))       // Handles NaN case too
            {
                return (0);
            }
            else if (val <= 0.0031308)
            {
                return ((byte)((255.0f * val * 12.92f) + 0.5f));
            }
            else if (val < 1.0)
            {
                return ((byte)((255.0f * ((1.055f * (float)Math.Pow((double)val, (1.0 / 2.4))) - 0.055f)) + 0.5f));
            }
            else
            {
                return (255);
            }
        }
 
        ///<summary>
        /// private helper function to set context values from a color value with a set context and ScRgb values
        ///</summary>
        ///
        private void ComputeScRgbValues()
        {
            if (this.context != null)
            {
                Color c2 = Color.FromRgb(0, 0, 0);
 
                c2.context = new ColorContext(PixelFormats.Bgra32);
 
                ColorTransform colorTransform = new ColorTransform(this.context, c2.context);
                Span<float> scRGBValue = stackalloc float[3];
 
                colorTransform.Translate(this.nativeColorValue, scRGBValue);
 
                this.scRgbColor.r = sRgbToScRgb((byte)((255.0f * scRGBValue[0]) + 0.5f));
                this.scRgbColor.g = sRgbToScRgb((byte)((255.0f * scRGBValue[1]) + 0.5f));
                this.scRgbColor.b = sRgbToScRgb((byte)((255.0f * scRGBValue[2]) + 0.5f));
            }
        }
 
        private void ComputeNativeValues(int numChannels)
        {
            this.nativeColorValue = new float[numChannels];
            if (this.nativeColorValue.Length > 0)
            {
                Span<float> sRGBValue = [this.sRgbColor.r / 255.0f, this.sRgbColor.g / 255.0f, this.sRgbColor.b / 255.0f];
 
                ColorTransform colorTransform = new ColorTransform(this.context, new ColorContext(PixelFormats.Bgra32));
                colorTransform.Translate(sRGBValue, this.nativeColorValue);
            }
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Private Events
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        [MarshalAs(UnmanagedType.Interface)]
        ColorContext context;
 
        private struct MILColorF // this structure is the "milrendertypes.h" structure and should be identical for performance
        {
            public float a, r, g, b;
 
            public override int GetHashCode()
            {
                return a.GetHashCode() ^ r.GetHashCode() ^ g.GetHashCode() ^ b.GetHashCode();
            }
            public override bool Equals(object obj)
            {
                return base.Equals(obj);
            }
        };
 
        private MILColorF scRgbColor;
 
        private struct MILColor
        {
            public byte a, r, g, b;
        }
 
        private MILColor sRgbColor;
 
        private float[] nativeColorValue;
 
        private bool isFromScRgb;
 
        private const string c_scRgbFormat = "R";
 
        #endregion Private Fields
    }
}