You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

576 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Xml;
namespace Otter {
/// <summary>
/// Class that represents a color with red, green, blue, and alpha channels.
/// </summary>
public class Color {
#region Static Methods
/// <summary>
/// Interpolate from one Color to another.
/// </summary>
/// <param name="from">The start Color.</param>
/// <param name="to">The end Color.</param>
/// <param name="amount">The amount of completion on the lerp. (0 - 1)</param>
/// <returns>The interpolated Color.</returns>
public static Color Lerp(Color from, Color to, float amount) {
if (amount <= 0) return new Color(from);
if (amount >= 1) return new Color(to);
var c = new Color(from);
c.R = from.R + (to.R - from.R) * amount;
c.G = from.G + (to.G - from.G) * amount;
c.B = from.B + (to.B - from.B) * amount;
c.A = from.A + (to.A - from.A) * amount;
return c;
}
/// <summary>
/// Interpolate through a set of Colors.
/// </summary>
/// <param name="amount">The amount of completion on the lerp. (0 - 1)</param>
/// <param name="colors">The Colors to interpolate through.</param>
/// <returns>The interpolated Color.</returns>
public static Color Lerp(float amount, params Color[] colors) {
if (amount <= 0) return colors[0];
if (amount >= 1) return colors[colors.Length - 1];
int fromIndex = (int)Util.ScaleClamp(amount, 0, 1, 0, colors.Length - 1);
int toIndex = fromIndex + 1;
float length = 1f / (colors.Length - 1);
float lerp = Util.ScaleClamp(amount % length, 0, length, 0, 1);
// This is a fix for odd numbered color amounts. When fromIndex was
// odd, lerp would evaluate to 1 when it should be 0.
if (lerp >= 0.9999f && fromIndex % 2 == 1) {
lerp = 0;
}
return Lerp(colors[fromIndex], colors[toIndex], lerp);
}
/// <summary>
/// Return a new color made by mixing multiple colors.
/// Mixes the colors evenly.
/// </summary>
/// <param name="colors">The colors to be mixed.</param>
/// <returns>A new color of all the colors mixed together.</returns>
public static Color Mix(params Color[] colors) {
float r = 0, g = 0, b = 0, a = 0;
foreach (var c in colors) {
r += c.R;
g += c.G;
b += c.B;
a += c.A;
}
r /= colors.Length;
g /= colors.Length;
b /= colors.Length;
a /= colors.Length;
return new Color(r, g, b, a);
}
/// <summary>
/// Create a new color from HSV values.
/// </summary>
/// <param name="h">Hue, 0 to 360.</param>
/// <param name="s">Saturation, 0 to 1.</param>
/// <param name="v">Value, 0 to 1.</param>
/// <param name="a">Alpha, 0 to 1.</param>
/// <returns>A new RGBA color.</returns>
public static Color FromHSV(float h, float s, float v, float a) {
h = h < 0 ? 0 : (h > 1 ? 1 : h);
s = s < 0 ? 0 : (s > 1 ? 1 : s);
v = v < 0 ? 0 : (v > 1 ? 1 : v);
h *= 360;
int hi = (int)(h / 60) % 6;
float f = (h / 60) - (int)(h / 60);
float p = (v * (1 - s));
float q = (v * (1 - f * s));
float t = (v * (1 - (1 - f) * s));
float r, g, b;
switch (hi) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
default: r = g = b = 0; break;
}
return new Color(r, g, b, a);
}
/// <summary>
/// Store a custom Color. Actually stores a new copy of that Color.
/// </summary>
/// <param name="color">The Color to store.</param>
/// <param name="name">The name of the Color.</param>
public static void AddCustom(Color color, Enum name) {
customColors.Add(Util.EnumValueToString(name), new Color(color));
}
/// <summary>
/// Store a custom Color. Actually stores a new copy of that Color.
/// </summary>
/// <param name="color">The Color to store.</param>
/// <param name="name">The name of the Color.</param>
public static void AddCustom(string color, Enum name) {
AddCustom(new Color(color), name);
}
/// <summary>
/// Store a custom Color. Actually stores a new copy of that Color.
/// </summary>
/// <param name="color">The Color to store.</param>
/// <param name="name">The name of the Color.</param>
public static void AddCustom(UInt32 color, Enum name) {
AddCustom(new Color(color), name);
}
/// <summary>
/// Store a custom Color. Actually stores a new copy of that Color.
/// </summary>
/// <param name="color">The Color to store.</param>
/// <param name="name">The name of the Color.</param>
public static void AddCustom(Color color, string name) {
customColors.Add(name, new Color(color));
}
/// <summary>
/// Store a custom Color. Actually stores a new copy of that Color.
/// </summary>
/// <param name="color">The Color to store.</param>
/// <param name="name">The name of the Color.</param>
public static void AddCustom(string color, string name) {
AddCustom(new Color(color), name);
}
/// <summary>
/// Store a custom Color. Actually stores a new copy of that Color.
/// </summary>
/// <param name="color">The Color to store.</param>
/// <param name="name">The name of the Color.</param>
public static void AddCustom(UInt32 color, string name) {
AddCustom(new Color(color), name);
}
/// <summary>
/// Get a stored custom Color. Returns a new copy of it.
/// </summary>
/// <param name="name">The name of the Color stored.</param>
/// <returns>A new copy of the custom Color.</returns>
public static Color Custom(Enum name) {
return customColors[Util.EnumValueToString(name)].Copy();
}
public static Color Custom(string name) {
return customColors[name].Copy();
}
/// <summary>
/// Create a shade of gray based on a value 0 to 1.
/// </summary>
/// <param name="rgb">The level of gray. 0 is black, 1 is white.</param>
/// <returns>A color of RGB equal to the value input for rgb.</returns>
public static Color Shade(float rgb) {
return new Color(rgb, rgb, rgb);
}
/// <summary>
/// Create a shade of gray based on a value 0 to 1.
/// </summary>
/// <param name="rgb">The level of gray. 0 is black, 1 is white.</param>
/// <param name="a">The alpha of the returned Color.</param>
/// <returns>A color of RGB equal to the value input for rgb with alpha a.</returns>
public static Color Shade(float rgb, float a) {
return new Color(rgb, rgb, rgb, a);
}
/// <summary>
/// Create a Color using byte values 0 - 255.
/// </summary>
/// <param name="r">Red</param>
/// <param name="g">Green</param>
/// <param name="b">Blue</param>
/// <param name="a">Alpha</param>
/// <returns>A new Color.</returns>
public static Color FromBytes(byte r, byte g, byte b, byte a = 255) {
var color = new Color();
color.ByteR = r;
color.ByteG = g;
color.ByteB = b;
color.ByteA = a;
return color;
}
#endregion
#region Static Properties
public static Color White {
get { return new Color(1f, 1f, 1f); }
}
public static Color Black {
get { return new Color(0f, 0f, 0f); }
}
public static Color Red {
get { return new Color(1f, 0f, 0f); }
}
public static Color Green {
get { return new Color(0f, 1f, 0f); }
}
public static Color Blue {
get { return new Color(0f, 0f, 1f); }
}
public static Color Cyan {
get { return new Color(0f, 1f, 1f); }
}
public static Color Magenta {
get { return new Color(1f, 0f, 1f); }
}
public static Color Yellow {
get { return new Color(1f, 1f, 0f); }
}
public static Color Orange {
get { return new Color(1f, 0.5f, 0); }
}
public static Color Gold {
get { return new Color("FFCC00"); }
}
public static Color None {
get { return new Color(0f, 0f, 0f, 0f); }
}
public static Color Grey {
get { return new Color("999999"); }
}
public static Color Gray {
get { return Color.Grey; }
}
public static Color Random {
get { return Rand.Color; }
}
public static Color RandomAlpha {
get { return Rand.ColorAlpha; }
}
#endregion
#region Static Fields
static Dictionary<string, Color> customColors = new Dictionary<string, Color>();
#endregion
#region Private Fields
float r, g, b, a;
#endregion
#region Public Properties
/// <summary>
/// Red
/// </summary>
public float R {
get { return r; }
set {
r = Util.Clamp(value, 0, 1);
if (Graphic != null) { Graphic.NeedsUpdate = true; }
}
}
/// <summary>
/// Green
/// </summary>
public float G {
get { return g; }
set {
g = Util.Clamp(value, 0, 1);
if (Graphic != null) { Graphic.NeedsUpdate = true; }
}
}
/// <summary>
/// Blue
/// </summary>
public float B {
get { return b; }
set {
b = Util.Clamp(value, 0, 1);
if (Graphic != null) { Graphic.NeedsUpdate = true; }
}
}
/// <summary>
/// Alpha
/// </summary>
public float A {
get { return a; }
set {
a = Util.Clamp(value, 0, 1);
if (Graphic != null) { Graphic.NeedsUpdate = true; }
}
}
/// <summary>
/// The bytes for Red.
/// </summary>
public byte ByteR {
set {
R = Convert.ToSingle(value) / 255f;
}
get {
return (byte)(R * 255);
}
}
/// <summary>
/// The bytes for Green.
/// </summary>
public byte ByteG {
set {
G = Convert.ToSingle(value) / 255f;
}
get {
return (byte)(G * 255);
}
}
/// <summary>
/// The bytes for Blue.
/// </summary>
public byte ByteB {
set {
B = Convert.ToSingle(value) / 255f;
}
get {
return (byte)(B * 255);
}
}
/// <summary>
/// The bytes for Alpha.
/// </summary>
public byte ByteA {
set {
A = Convert.ToSingle(value) / 255f;
}
get {
return (byte)(A * 255);
}
}
#endregion
#region Constructors
/// <summary>
/// Create a new color.
/// </summary>
/// <param name="r">Red, 0 to 1.</param>
/// <param name="g">Green, 0 to 1.</param>
/// <param name="b">Blue, 0 to 1.</param>
/// <param name="a">Alpha, 0 to 1.</param>
public Color(float r = 1f, float g = 1f, float b = 1f, float a = 1f) {
R = r;
G = g;
B = b;
A = a;
}
/// <summary>
/// Create a color by copying the RGBA from another color.
/// </summary>
/// <param name="copy">The color to copy.</param>
public Color(Color copy) : this(copy.R, copy.G, copy.B, copy.A) { }
/// <summary>
/// Create a new color from an XML element.
/// </summary>
/// <param name="e">An XmlElement that contains attributes R, G, B, and A, from 0 to 255.</param>
public Color(XmlElement e) {
A = float.Parse(e.Attributes["A"].Value) / 255;
R = float.Parse(e.Attributes["R"].Value) / 255;
G = float.Parse(e.Attributes["G"].Value) / 255;
B = float.Parse(e.Attributes["B"].Value) / 255;
}
/// <summary>
/// Return a new color containing the channels from this color.
/// </summary>
/// <returns></returns>
public Color Copy() {
return new Color(R, G, B, A);
}
/// <summary>
/// Create a new color from a string. Formats are "RGB", "RGBA", "RRGGBB", and "RRGGBBAA".
/// </summary>
/// <param name="hex">A string with a hex representation of each channel.</param>
public Color(string hex) {
int red = 255, green = 255, blue = 255, alpha = 255;
if (hex.Length == 6) {
red = Convert.ToInt32(hex.Substring(0, 2), 16);
green = Convert.ToInt32(hex.Substring(2, 2), 16);
blue = Convert.ToInt32(hex.Substring(4, 2), 16);
}
else if (hex.Length == 3) {
red = Convert.ToInt32(hex.Substring(0, 1) + hex.Substring(0, 1), 16);
green = Convert.ToInt32(hex.Substring(1, 1) + hex.Substring(1, 1), 16);
blue = Convert.ToInt32(hex.Substring(2, 1) + hex.Substring(2, 1), 16);
}
else if (hex.Length == 8) {
red = Convert.ToInt32(hex.Substring(0, 2), 16);
green = Convert.ToInt32(hex.Substring(2, 2), 16);
blue = Convert.ToInt32(hex.Substring(4, 2), 16);
alpha = Convert.ToInt32(hex.Substring(6, 2), 16);
}
else if (hex.Length == 4) {
red = Convert.ToInt32(hex.Substring(0, 1) + hex.Substring(0, 1), 16);
green = Convert.ToInt32(hex.Substring(1, 1) + hex.Substring(1, 1), 16);
blue = Convert.ToInt32(hex.Substring(2, 1) + hex.Substring(2, 1), 16);
alpha = Convert.ToInt32(hex.Substring(3, 1) + hex.Substring(2, 1), 16);
}
R = Util.ScaleClamp(red, 0, 255, 0, 1);
G = Util.ScaleClamp(green, 0, 255, 0, 1);
B = Util.ScaleClamp(blue, 0, 255, 0, 1);
A = Util.ScaleClamp(alpha, 0, 255, 0, 1);
}
/// <summary>
/// Create a new color from a hex number. Formats are 0xRGB, 0xRRGGBB, 0xRGBA, 0xRRGGBBAA.
/// </summary>
/// <param name="hex">A hex number representing a color.</param>
public Color(UInt32 hex) : this(hex.ToString("X6")) { }
#endregion
#region Public Methods
public override string ToString() {
return "Color [R: " + R.ToString("0.00") + " G: " + G.ToString("0.00") + " B: " + B.ToString("0.00") + " A: " + A.ToString("0.00") + "]";
}
/// <summary>
/// Get a hex string of the Color.
/// </summary>
public string ColorString {
get {
return ByteR.ToString("X2") + ByteG.ToString("X2") + ByteB.ToString("X2") + ByteA.ToString("X2");
}
}
public void SetColor(float r, float g, float b, float a) {
R = r;
G = g;
B = b;
A = a;
}
public void SetColor(float r, float g, float b) {
SetColor(r, g, b, A);
}
public void SetColor(Color color) {
SetColor(color.R, color.G, color.B, color.A);
}
#endregion
#region Operators
public static Color operator *(Color value1, Color value2) {
value1 = new Color(value1); // Make new color otherwise this doesn't work properly.
value1.R *= value2.R;
value1.G *= value2.G;
value1.B *= value2.B;
value1.A *= value2.A;
return value1;
}
public static Color operator *(Color value1, float value2) {
value1 = new Color(value1);
value1.R *= value2;
value1.G *= value2;
value1.B *= value2;
return value1;
}
public static Color operator +(Color value1, Color value2) {
value1 = new Color(value1);
value1.R += value2.R;
value1.G += value2.G;
value1.B += value2.B;
value1.A += value2.A;
return value1;
}
public static Color operator -(Color value1, Color value2) {
value1 = new Color(value1);
value1.R -= value2.R;
value1.G -= value2.G;
value1.B -= value2.B;
value1.A -= value2.A;
return value1;
}
public static Color operator /(Color value1, Color value2) {
value1 = new Color(value1);
value1.R /= value2.R;
value1.G /= value2.G;
value1.B /= value2.B;
value1.A /= value2.A;
return value1;
}
#endregion
#region Internal
internal Color(SFML.Graphics.Color copy) {
ByteR = copy.R;
ByteG = copy.G;
ByteB = copy.B;
ByteA = copy.A;
}
internal SFML.Graphics.Color SFMLColor {
get { return new SFML.Graphics.Color(ByteR, ByteG, ByteB, ByteA); }
}
internal Graphic Graphic; // Keep track of the graphic so it can be flagged for an update when the color changes
#endregion
}
}