Skip to content

Instantly share code, notes, and snippets.

@ForeverZer0
Last active June 5, 2018 16:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ForeverZer0/e3db1075a0a0464f644db83a1959154e to your computer and use it in GitHub Desktop.
Save ForeverZer0/e3db1075a0a0464f644db83a1959154e to your computer and use it in GitHub Desktop.
[C#] RGBA Color structure using a vector
#region MIT License
// Color.cs is distributed under the MIT License (MIT)
//
// Copyright (c) 2018, Eric Freed
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Color.cs created on 06/02/2018
#endregion
using System;
using System.Drawing;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace ARC
{
[StructLayout(LayoutKind.Explicit, Size = 16)]
[Serializable]
public struct Color : IEquatable<Color>, IEquatable<System.Drawing.Color>, IFormattable, ISerializable,
IXmlSerializable
{
#region Fields and Constants
private const float TOLERANCE = 0.001f;
public static readonly Color AQUA = new Color(0.0f, 1.0f, 1.0f, 1.0f);
public static readonly Color BLACK = new Color(0.0f, 0.0f, 0.0f, 1.0f);
public static readonly Color BLUE = new Color(0.0f, 0.0f, 1.0f, 1.0f);
public static readonly Color GRAY = new Color(0.5f, 0.5f, 0.5f, 1.0f);
public static readonly Color GREEN = new Color(0.0f, 1.0f, 0.0f, 1.0f);
public static readonly Color MAGENTA = new Color(1.0f, 0.0f, 1.0f, 1.0f);
public static readonly Color NONE = new Color(0.0f, 0.0f, 0.0f, 0.0f);
public static readonly Color RED = new Color(1.0f, 0.0f, 0.0f, 1.0f);
public static readonly Color WHITE = new Color(1.0f, 1.0f, 1.0f, 1.0f);
public static readonly Color YELLOW = new Color(1.0f, 1.0f, 0.0f, 1.0f);
[FieldOffset(0)] private Vector4 _vec4;
#endregion
#region Constructors
public Color(uint value)
{
var c = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
c = c.Reverse().ToArray();
_vec4 = Vector4.Clamp(new Vector4(c[0], c[1], c[2], c[3]) / 255.0f, Vector4.Zero, Vector4.One);
}
public Color(int red, int green, int blue) : this(new Vector4(red, green, blue, 255) / 255.0f) { }
public Color(int red, int green, int blue, int alpha) : this(new Vector4(red, green, blue, alpha) / 255.0f) { }
public Color(float red, float green, float blue) : this(new Vector4(red, green, blue, 1.0f)) { }
public Color(float red, float green, float blue, float alpha) : this(new Vector4(red, green, blue, alpha)) { }
public Color(Vector3 vector) : this(new Vector4(vector, 1.0f)) { }
public Color(Vector4 vector) => _vec4 = Vector4.Clamp(vector, Vector4.Zero, Vector4.One);
private Color(SerializationInfo info, StreamingContext ctxt) => _vec4 = new Vector4(
info.GetByte("Red") / 255.0f,
info.GetByte("Green") / 255.0f,
info.GetByte("Blue") / 255.0f,
info.GetByte("Alpha") / 255.0f
);
#endregion
#region Interface Implementations
public string ToString(string format, IFormatProvider formatProvider)
{
switch (format)
{
case "X": return Convert.ToString(PackedValue, 16);
case "H": return "#" + Convert.ToString(PackedValue, 16);
case "F": return $"{Math.Round(R, 3)}, {Math.Round(G, 3)}, {Math.Round(B, 3)}, {Math.Round(A, 3)}";
default: return $"{Red}, {Green}, {Blue}, {Alpha}";
}
}
#endregion
#region Properties
public float A
{
get => _vec4.W;
set => _vec4.W = Math.Max(0.0f, Math.Min(1.0f, value));
}
public byte Alpha
{
get => (byte) (_vec4.W * 255);
set => A = value / 255.0f;
}
public float B
{
get => _vec4.Z;
set => _vec4.Z = Math.Max(0.0f, Math.Min(1.0f, value));
}
public byte Blue
{
get => (byte) (_vec4.Z * 255);
set => B = value / 255.0f;
}
public float G
{
get => _vec4.Y;
set => _vec4.Y = Math.Max(0.0f, Math.Min(1.0f, value));
}
public byte Green
{
get => (byte) (_vec4.Y * 255);
set => G = value / 255.0f;
}
public uint PackedValue => BitConverter.ToUInt32(new[] {Red, Green, Blue, Alpha}, 0);
public float R
{
get => _vec4.X;
set => _vec4.X = Math.Max(0.0f, Math.Min(1.0f, value));
}
public byte Red
{
get => (byte) (_vec4.X * 255);
set => R = value / 255.0f;
}
#endregion
#region Operators
public static Color operator *(Color color, float scalar) => new Color(color._vec4 * scalar);
public static Color operator *(Color color1, Color color2) => new Color(color1._vec4 * color2._vec4);
public static Color operator /(Color color, float scalar) => new Color(color._vec4 / scalar);
public static Color operator /(Color color1, Color color2) => new Color(color1._vec4 / color2._vec4);
public static Color operator +(Color color1, Color color2) => new Color(color1._vec4 + color2._vec4);
public static Color operator -(Color color1, Color color2) => new Color(color1._vec4 - color2._vec4);
/// <summary>
/// Performs an explicit conversion from <see cref="System.Drawing.Color" /> to <see cref="Color" />.
/// </summary>
/// <param name="color">The color.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
public static explicit operator Color(System.Drawing.Color color) => FromColor(color);
/// <summary>
/// Performs an implicit conversion from <see cref="Color" /> to <see cref="System.Drawing.Color" />.
/// </summary>
/// <param name="color">The color.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
public static implicit operator System.Drawing.Color(Color color) =>
System.Drawing.Color.FromArgb(color.Alpha, color.Red, color.Green, color.Blue);
#endregion
#region Methods
public static Color FromAbgr(int alpha, int blue, int green, int red) => new Color(red, blue, green, alpha);
public static Color FromArgb(int alpha, int red, int blue, int green) => new Color(red, blue, green, alpha);
public static Color FromBgra(int blue, int green, int red, int alpha) => new Color(red, blue, green, alpha);
public static Color FromColor(Color color) => new Color(color._vec4);
public static Color FromColor(System.Drawing.Color color) => new Color(color.R, color.G, color.B, color.A);
public static Color FromHtml(string html) => FromColor(ColorTranslator.FromHtml(html));
public static Color FromRgba(int red, int blue, int green, int alpha) => new Color(red, blue, green, alpha);
public static Color Lerp(Color color1, Color color2, float amount = 0.5f) =>
new Color(Vector4.Lerp(color1._vec4, color2._vec4, amount));
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
GetObjectData(info, context);
}
public static Color Parse(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input), "Argument cannot be null.");
// RGB/RGBA
var match = Regex.Match(input, @"\s*([\d]+)\s*,\s*([\d]+)\s*,\s*([\d]+)\s*(?:,\s*([\d]+))?\s*");
if (match.Success)
return new Color(
int.Parse(match.Groups[1].Value),
int.Parse(match.Groups[2].Value),
int.Parse(match.Groups[3].Value),
match.Groups.Count == 5 ? int.Parse(match.Groups[4].Value) : 255
);
// Float RGB/RGBA
match = Regex.Match(input, @"\s*([\d]+\.[\d]+)\s*,\s*([\d]+\.[\d]+)\s*,\s*([\d]+\.[\d]+)\s*(?:,\s*([\d]+\.[\d]+))?");
if (match.Success)
return new Color(
float.Parse(match.Groups[1].Value),
float.Parse(match.Groups[2].Value),
float.Parse(match.Groups[3].Value),
match.Groups.Count == 5 ? float.Parse(match.Groups[4].Value) : 1.0f
);
// HTML
match = Regex.Match(input, @"\s*[#]?([A-Fa-f0-9]{2}){3,4}\s*");
if (match.Success)
{
var html = input.TrimStart('#').PadRight(8, 'F');
return new Color(Convert.ToUInt32(html, 16));
}
// Name
if (Enum.TryParse<KnownColor>(input, out var knownColor))
FromKnownColor(knownColor);
// Unsigned Integer
if (uint.TryParse(input, out var packed))
return new Color(packed);
throw new FormatException($"Cannot parse \"{input}\" into Color.");
}
public static bool TryParse(string input, out Color color)
{
try
{
color = Parse(input);
return true;
}
catch (FormatException) { }
catch (ArgumentNullException) { }
catch (OverflowException) { }
color = default(Color);
return false;
}
public void ApplyTone(Tone tone)
{
_vec4.X = Math.Max(0.0f, Math.Min(1.0f, _vec4.X + tone.R));
_vec4.Y = Math.Max(0.0f, Math.Min(1.0f, _vec4.Y + tone.G));
_vec4.Z = Math.Max(0.0f, Math.Min(1.0f, _vec4.Z + tone.B));
if (tone.Gr > TOLERANCE)
{
var mean = (_vec4.X + _vec4.Y + _vec4.Z) / 3.0f;
_vec4.X = (_vec4.X - mean) * tone.Gr;
_vec4.Y = (_vec4.Y - mean) * tone.Gr;
_vec4.Z = (_vec4.Z - mean) * tone.Gr;
}
}
public float GetBrightness() => (Min() + Max()) / 2;
public float GetHue()
{
if (Math.Abs(R - G) < TOLERANCE && Math.Abs(G - B) < TOLERANCE)
return 0;
var hue = 0.0f;
var max = Max();
var min = Min();
var delta = max - min;
if (Math.Abs(R - max) < TOLERANCE)
hue = (G - B) / delta;
else if (Math.Abs(G - max) < TOLERANCE)
hue = 2 + (B - R) / delta;
else if (Math.Abs(B - max) < TOLERANCE)
hue = 4 + (R - G) / delta;
hue *= 60;
if (hue < 0.0f)
hue += 360.0f;
return hue;
}
public float GetSaturation()
{
var max = Max();
var min = Min();
if (Math.Abs(max - min) > TOLERANCE)
{
var l = (max + min) / 2;
return l <= 0.5f ? (max - min) / (max + min) : (max - min) / (2 - max - min);
}
return 0.0f;
}
public Color Lerp(Color color, float amount = 0.5f) => Lerp(this, color, amount);
public void Set(int red, int green, int blue) => Set(new Vector4(red, green, blue, Alpha) / 255.0f);
public void Set(int red, int green, int blue, int alpha) => Set(new Vector4(red, green, blue, alpha) / 255.0f);
public void Set(float red, float green, float blue) => Set(new Vector4(red, green, blue, A));
public void Set(float red, float green, float blue, float alpha) => Set(new Vector4(red, green, blue, alpha));
public void Set(Vector3 vec3) => Set(new Vector4(vec3, A));
public void Set(Vector4 vector)
{
_vec4.X = Math.Max(0.0f, Math.Min(1.0f, vector.X));
_vec4.Y = Math.Max(0.0f, Math.Min(1.0f, vector.Y));
_vec4.Z = Math.Max(0.0f, Math.Min(1.0f, vector.Z));
_vec4.W = Math.Max(0.0f, Math.Min(1.0f, vector.W));
}
public string ToHtml() => "#" + Convert.ToString(PackedValue, 16);
public override string ToString() => $"{Red}, {Green}, {Blue}, {Alpha}";
public Vector3 ToVec3() => new Vector3(R, G, B);
public Vector4 ToVec4() => _vec4;
private float Max() => Math.Max(R, Math.Max(G, B));
private float Min() => Math.Min(R, Math.Min(G, B));
#endregion
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
/// </returns>
public bool Equals(Color other) => _vec4.Equals(other._vec4);
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
/// </returns>
public bool Equals(System.Drawing.Color other) =>
Red == other.R && Green == other.G && Blue == other.B && Alpha == other.A;
/// <summary>
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
/// </summary>
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
if (obj is Color color)
return Equals(color);
return false;
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode() => _vec4.GetHashCode();
/// <summary>
/// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with the data needed to serialize the
/// target object.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to populate with data.</param>
/// <param name="context">
/// The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this
/// serialization.
/// </param>
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Red", Red);
info.AddValue("Green", Green);
info.AddValue("Blue", Blue);
info.AddValue("Alpha", Alpha);
}
public static bool operator ==(Color left, Color right) => Equals(left, right);
public static bool operator !=(Color left, Color right) => !Equals(left, right);
public static Color FromKnownColor(KnownColor knownColor) =>
FromColor(System.Drawing.Color.FromKnownColor(knownColor));
public static Color FromName(string name) => FromColor(System.Drawing.Color.FromName(name));
/// <summary>
/// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return
/// null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the
/// <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute" /> to the class.
/// </summary>
/// <returns>
/// An <see cref="T:System.Xml.Schema.XmlSchema" /> that describes the XML representation of the object that is
/// produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)" /> method
/// and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)" />
/// method.
/// </returns>
public XmlSchema GetSchema() => null;
/// <summary>
/// Generates an object from its XML representation.
/// </summary>
/// <param name="reader">The <see cref="T:System.Xml.XmlReader" /> stream from which the object is deserialized.</param>
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
var color = ColorTranslator.FromHtml(reader.GetAttribute("html"));
_vec4.X = color.R / 255.0f;
_vec4.Y = color.G / 255.0f;
_vec4.Z = color.B / 255.0f;
_vec4.W = color.A / 255.0f;
reader.ReadStartElement();
reader.ReadEndElement();
}
/// <summary>
/// Converts an object into its XML representation.
/// </summary>
/// <param name="writer">The <see cref="T:System.Xml.XmlWriter" /> stream to which the object is serialized.</param>
public void WriteXml(XmlWriter writer) => writer.WriteAttributeString("html", ToHtml());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment