Last active
June 5, 2018 16:48
-
-
Save ForeverZer0/e3db1075a0a0464f644db83a1959154e to your computer and use it in GitHub Desktop.
[C#] RGBA Color structure using a vector
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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