corrade-vassal – Rev 1

Subversion Repositories:
Rev:
/*
 * Copyright (c) 2006-2014, openmetaverse.org
 * All rights reserved.
 *
 * - Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * - Neither the name of the openmetaverse.org nor the names
 *   of its contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Runtime.InteropServices;

namespace OpenMetaverse
{
    /// <summary>
    /// An 8-bit color structure including an alpha channel
    /// </summary>
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Color4 : IComparable<Color4>, IEquatable<Color4>
    {
        /// <summary>Red</summary>
        public float R;
        /// <summary>Green</summary>
        public float G;
        /// <summary>Blue</summary>
        public float B;
        /// <summary>Alpha</summary>
        public float A;

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <param name="a"></param>
        public Color4(byte r, byte g, byte b, byte a)
        {
            const float quanta = 1.0f / 255.0f;

            R = (float)r * quanta;
            G = (float)g * quanta;
            B = (float)b * quanta;
            A = (float)a * quanta;
        }

        public Color4(float r, float g, float b, float a)
        {
            // Quick check to see if someone is doing something obviously wrong
            // like using float values from 0.0 - 255.0
            if (r > 1f || g > 1f || b > 1f || a > 1f)
                throw new ArgumentException(
                    String.Format("Attempting to initialize Color4 with out of range values <{0},{1},{2},{3}>",
                    r, g, b, a));

            // Valid range is from 0.0 to 1.0
            R = Utils.Clamp(r, 0f, 1f);
            G = Utils.Clamp(g, 0f, 1f);
            B = Utils.Clamp(b, 0f, 1f);
            A = Utils.Clamp(a, 0f, 1f);
        }

        /// <summary>
        /// Builds a color from a byte array
        /// </summary>
        /// <param name="byteArray">Byte array containing a 16 byte color</param>
        /// <param name="pos">Beginning position in the byte array</param>
        /// <param name="inverted">True if the byte array stores inverted values,
        /// otherwise false. For example the color black (fully opaque) inverted
        /// would be 0xFF 0xFF 0xFF 0x00</param>
        public Color4(byte[] byteArray, int pos, bool inverted)
        {
            R = G = B = A = 0f;
            FromBytes(byteArray, pos, inverted);
        }

        /// <summary>
        /// Returns the raw bytes for this vector
        /// </summary>
        /// <param name="byteArray">Byte array containing a 16 byte color</param>
        /// <param name="pos">Beginning position in the byte array</param>
        /// <param name="inverted">True if the byte array stores inverted values,
        /// otherwise false. For example the color black (fully opaque) inverted
        /// would be 0xFF 0xFF 0xFF 0x00</param>
        /// <param name="alphaInverted">True if the alpha value is inverted in
        /// addition to whatever the inverted parameter is. Setting inverted true
        /// and alphaInverted true will flip the alpha value back to non-inverted,
        /// but keep the other color bytes inverted</param>
        /// <returns>A 16 byte array containing R, G, B, and A</returns>
        public Color4(byte[] byteArray, int pos, bool inverted, bool alphaInverted)
        {
            R = G = B = A = 0f;
            FromBytes(byteArray, pos, inverted, alphaInverted);
        }

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="color">Color to copy</param>
        public Color4(Color4 color)
        {
            R = color.R;
            G = color.G;
            B = color.B;
            A = color.A;
        }

        #endregion Constructors

        #region Public Methods

        /// <summary>
        /// IComparable.CompareTo implementation
        /// </summary>
        /// <remarks>Sorting ends up like this: |--Grayscale--||--Color--|.
        /// Alpha is only used when the colors are otherwise equivalent</remarks>
        public int CompareTo(Color4 color)
        {
            float thisHue = GetHue();
            float thatHue = color.GetHue();

            if (thisHue < 0f && thatHue < 0f)
            {
                // Both monochromatic
                if (R == color.R)
                {
                    // Monochromatic and equal, compare alpha
                    return A.CompareTo(color.A);
                }
                else
                {
                    // Compare lightness
                    return R.CompareTo(R);
                }
            }
            else
            {
                if (thisHue == thatHue)
                {
                    // RGB is equal, compare alpha
                    return A.CompareTo(color.A);
                }
                else
                {
                    // Compare hues
                    return thisHue.CompareTo(thatHue);
                }
            }
        }

        public void FromBytes(byte[] byteArray, int pos, bool inverted)
        {
            const float quanta = 1.0f / 255.0f;

            if (inverted)
            {
                R = (float)(255 - byteArray[pos]) * quanta;
                G = (float)(255 - byteArray[pos + 1]) * quanta;
                B = (float)(255 - byteArray[pos + 2]) * quanta;
                A = (float)(255 - byteArray[pos + 3]) * quanta;
            }
            else
            {
                R = (float)byteArray[pos] * quanta;
                G = (float)byteArray[pos + 1] * quanta;
                B = (float)byteArray[pos + 2] * quanta;
                A = (float)byteArray[pos + 3] * quanta;
            }
        }

        /// <summary>
        /// Builds a color from a byte array
        /// </summary>
        /// <param name="byteArray">Byte array containing a 16 byte color</param>
        /// <param name="pos">Beginning position in the byte array</param>
        /// <param name="inverted">True if the byte array stores inverted values,
        /// otherwise false. For example the color black (fully opaque) inverted
        /// would be 0xFF 0xFF 0xFF 0x00</param>
        /// <param name="alphaInverted">True if the alpha value is inverted in
        /// addition to whatever the inverted parameter is. Setting inverted true
        /// and alphaInverted true will flip the alpha value back to non-inverted,
        /// but keep the other color bytes inverted</param>
        public void FromBytes(byte[] byteArray, int pos, bool inverted, bool alphaInverted)
        {
            FromBytes(byteArray, pos, inverted);

            if (alphaInverted)
                A = 1.0f - A;
        }

        public byte[] GetBytes()
        {
            return GetBytes(false);
        }

        public byte[] GetBytes(bool inverted)
        {
            byte[] byteArray = new byte[4];
            ToBytes(byteArray, 0, inverted);
            return byteArray;
        }

        public byte[] GetFloatBytes()
        {
            byte[] bytes = new byte[16];
            ToFloatBytes(bytes, 0);
            return bytes;
        }

        /// <summary>
        /// Writes the raw bytes for this color to a byte array
        /// </summary>
        /// <param name="dest">Destination byte array</param>
        /// <param name="pos">Position in the destination array to start
        /// writing. Must be at least 16 bytes before the end of the array</param>
        public void ToBytes(byte[] dest, int pos)
        {
            ToBytes(dest, pos, false);
        }

        /// <summary>
        /// Serializes this color into four bytes in a byte array
        /// </summary>
        /// <param name="dest">Destination byte array</param>
        /// <param name="pos">Position in the destination array to start
        /// writing. Must be at least 4 bytes before the end of the array</param>
        /// <param name="inverted">True to invert the output (1.0 becomes 0
        /// instead of 255)</param>
        public void ToBytes(byte[] dest, int pos, bool inverted)
        {
            dest[pos + 0] = Utils.FloatToByte(R, 0f, 1f);
            dest[pos + 1] = Utils.FloatToByte(G, 0f, 1f);
            dest[pos + 2] = Utils.FloatToByte(B, 0f, 1f);
            dest[pos + 3] = Utils.FloatToByte(A, 0f, 1f);

            if (inverted)
            {
                dest[pos + 0] = (byte)(255 - dest[pos + 0]);
                dest[pos + 1] = (byte)(255 - dest[pos + 1]);
                dest[pos + 2] = (byte)(255 - dest[pos + 2]);
                dest[pos + 3] = (byte)(255 - dest[pos + 3]);
            }
        }

        /// <summary>
        /// Writes the raw bytes for this color to a byte array
        /// </summary>
        /// <param name="dest">Destination byte array</param>
        /// <param name="pos">Position in the destination array to start
        /// writing. Must be at least 16 bytes before the end of the array</param>
        public void ToFloatBytes(byte[] dest, int pos)
        {
            Buffer.BlockCopy(BitConverter.GetBytes(R), 0, dest, pos + 0, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(G), 0, dest, pos + 4, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(B), 0, dest, pos + 8, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(A), 0, dest, pos + 12, 4);
        }

        public float GetHue()
        {
            const float HUE_MAX = 360f;

            float max = Math.Max(Math.Max(R, G), B);
            float min = Math.Min(Math.Min(R, B), B);

            if (max == min)
            {
                // Achromatic, hue is undefined
                return -1f;
            }
            else if (R == max)
            {
                float bDelta = (((max - B) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min);
                float gDelta = (((max - G) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min);
                return bDelta - gDelta;
            }
            else if (G == max)
            {
                float rDelta = (((max - R) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min);
                float bDelta = (((max - B) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min);
                return (HUE_MAX / 3f) + rDelta - bDelta;
            }
            else // B == max
            {
                float gDelta = (((max - G) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min);
                float rDelta = (((max - R) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min);
                return ((2f * HUE_MAX) / 3f) + gDelta - rDelta;
            }
        }

        /// <summary>
        /// Ensures that values are in range 0-1
        /// </summary>
        public void ClampValues()
        {
            if (R < 0f)
                R = 0f;
            if (G < 0f)
                G = 0f;
            if (B < 0f)
                B = 0f;
            if (A < 0f)
                A = 0f;
            if (R > 1f)
                R = 1f;
            if (G > 1f)
                G = 1f;
            if (B > 1f)
                B = 1f;
            if (A > 1f)
                A = 1f;
        }

        #endregion Public Methods

        #region Static Methods

        /// <summary>
        /// Create an RGB color from a hue, saturation, value combination
        /// </summary>
        /// <param name="hue">Hue</param>
        /// <param name="saturation">Saturation</param>
        /// <param name="value">Value</param>
        /// <returns>An fully opaque RGB color (alpha is 1.0)</returns>
        public static Color4 FromHSV(double hue, double saturation, double value)
        {
            double r = 0d;
            double g = 0d;
            double b = 0d;

            if (saturation == 0d)
            {
                // If s is 0, all colors are the same.
                // This is some flavor of gray.
                r = value;
                g = value;
                b = value;
            }
            else
            {
                double p;
                double q;
                double t;

                double fractionalSector;
                int sectorNumber;
                double sectorPos;

                // The color wheel consists of 6 sectors.
                // Figure out which sector you//re in.
                sectorPos = hue / 60d;
                sectorNumber = (int)(Math.Floor(sectorPos));

                // get the fractional part of the sector.
                // That is, how many degrees into the sector
                // are you?
                fractionalSector = sectorPos - sectorNumber;

                // Calculate values for the three axes
                // of the color. 
                p = value * (1d - saturation);
                q = value * (1d - (saturation * fractionalSector));
                t = value * (1d - (saturation * (1d - fractionalSector)));

                // Assign the fractional colors to r, g, and b
                // based on the sector the angle is in.
                switch (sectorNumber)
                {
                    case 0:
                        r = value;
                        g = t;
                        b = p;
                        break;
                    case 1:
                        r = q;
                        g = value;
                        b = p;
                        break;
                    case 2:
                        r = p;
                        g = value;
                        b = t;
                        break;
                    case 3:
                        r = p;
                        g = q;
                        b = value;
                        break;
                    case 4:
                        r = t;
                        g = p;
                        b = value;
                        break;
                    case 5:
                        r = value;
                        g = p;
                        b = q;
                        break;
                }
            }

            return new Color4((float)r, (float)g, (float)b, 1f);
        }

        /// <summary>
        /// Performs linear interpolation between two colors
        /// </summary>
        /// <param name="value1">Color to start at</param>
        /// <param name="value2">Color to end at</param>
        /// <param name="amount">Amount to interpolate</param>
        /// <returns>The interpolated color</returns>
        public static Color4 Lerp(Color4 value1, Color4 value2, float amount)
        {
            return new Color4(
                Utils.Lerp(value1.R, value2.R, amount),
                Utils.Lerp(value1.G, value2.G, amount),
                Utils.Lerp(value1.B, value2.B, amount),
                Utils.Lerp(value1.A, value2.A, amount));
        }

        #endregion Static Methods

        #region Overrides

        public override string ToString()
        {
            return String.Format(Utils.EnUsCulture, "<{0}, {1}, {2}, {3}>", R, G, B, A);
        }

        public string ToRGBString()
        {
            return String.Format(Utils.EnUsCulture, "<{0}, {1}, {2}>", R, G, B);
        }

        public override bool Equals(object obj)
        {
            return (obj is Color4) ? this == (Color4)obj : false;
        }

        public bool Equals(Color4 other)
        {
            return this == other;
        }

        public override int GetHashCode()
        {
            return R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode();
        }

        #endregion Overrides

        #region Operators

        public static bool operator ==(Color4 lhs, Color4 rhs)
        {
            return (lhs.R == rhs.R) && (lhs.G == rhs.G) && (lhs.B == rhs.B) && (lhs.A == rhs.A);
        }

        public static bool operator !=(Color4 lhs, Color4 rhs)
        {
            return !(lhs == rhs);
        }

        public static Color4 operator +(Color4 lhs, Color4 rhs)
        {
            lhs.R += rhs.R;
            lhs.G += rhs.G;
            lhs.B += rhs.B;
            lhs.A += rhs.A;
            lhs.ClampValues();

            return lhs;
        }

        public static Color4 operator -(Color4 lhs, Color4 rhs)
        {
            lhs.R -= rhs.R;
            lhs.G -= rhs.G;
            lhs.B -= rhs.B;
            lhs.A -= rhs.A;
            lhs.ClampValues();

            return lhs;
        }

        public static Color4 operator *(Color4 lhs, Color4 rhs)
        {
            lhs.R *= rhs.R;
            lhs.G *= rhs.G;
            lhs.B *= rhs.B;
            lhs.A *= rhs.A;
            lhs.ClampValues();

            return lhs;
        }

        #endregion Operators

        /// <summary>A Color4 with zero RGB values and fully opaque (alpha 1.0)</summary>
        public readonly static Color4 Black = new Color4(0f, 0f, 0f, 1f);

        /// <summary>A Color4 with full RGB values (1.0) and fully opaque (alpha 1.0)</summary>
        public readonly static Color4 White = new Color4(1f, 1f, 1f, 1f);
    }
}