1 /*
2  * Copyright (c) 2017-2019 sel-project
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  *
22  */
23 /**
24  * Copyright: Copyright (c) 2017-2019 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/selery/source/selery/util/color.d, selery/util/color.d)
28  */
29 module selery.util.color;
30 
31 import std.conv : to;
32 
33 /**
34  * Container for an rgba colour.
35  * Example:
36  * ---
37  * Color c = new Color(255, 0, 0);
38  * c.blue = 255;
39  * assert(c == new Color(255, 0, 255));
40  * ---
41  */
42 class Color {
43 
44 	public ubyte r, g, b, a;
45 
46 	alias red = this.r;
47 	alias green = this.g;
48 	alias blue = this.b;
49 	alias alpha = this.a;
50 
51 	/**
52 	 * Constructs a colour using rgb(a) values (in range 0..255).
53 	 * By default alpha is 0% transparent (255).
54 	 */
55 	public pure nothrow @safe @nogc this(ubyte r, ubyte g, ubyte b, ubyte a=0xFF) {
56 		this.r = r;
57 		this.g = g;
58 		this.b = b;
59 		this.a = a;
60 	}
61 
62 	/**
63 	 * Creates the average colour of the given one.
64 	 * Example:
65 	 * ---
66 	 * assert(new Color([new Color(200, 0, 0), new Color(0, 200, 0)]) == new Color(100, 100, 0));
67 	 * ---
68 	 */
69 	public @safe this(Color[] colors) {
70 		uint r, g, b, a;
71 		foreach(Color color ; colors) {
72 			r += color.r;
73 			g += color.g;
74 			b += color.b;
75 			a += color.a;
76 		}
77 		this.r = to!ubyte(r / colors.length);
78 		this.g = to!ubyte(g / colors.length);
79 		this.b = to!ubyte(b / colors.length);
80 		this.a = to!ubyte(a / colors.length);
81 	}
82 
83 	/**
84 	 * Encodes the colour as an unsigned integer to
85 	 * be used in the network operations or to be saved.
86 	 */
87 	public pure nothrow @property @safe @nogc uint rgb() {
88 		return (this.r << 16) | (this.g << 8) | this.b;
89 	}
90 
91 	/// ditto
92 	public pure nothrow @property @safe @nogc uint rgba() {
93 		return (this.rgb << 8) | a;
94 	}
95 
96 	/// ditto
97 	public pure nothrow @property @safe @nogc uint argb() {
98 		return (a << 24) | this.rgb;
99 	}
100 
101 	/**
102 	 * Checks if the colour is completely transparent (with
103 	 * the alpha channel equals to 0).
104 	 * Example:
105 	 * ---
106 	 * assert(!new Color(0, 0, 0, 1).transparent);
107 	 * assert(Colors.TRANSPARENT.transparent);
108 	 * ---
109 	 */
110 	public pure nothrow @property @safe @nogc bool transparent() {
111 		return this.alpha == 0;
112 	}
113 
114 	/**
115 	 * Sets the colour as transparent (with alpha = 0) or
116 	 * opaque (with alpha = 255).
117 	 */
118 	public pure nothrow @property @safe @nogc bool transparent(bool transparent) {
119 		return (this.alpha = (transparent ? 0 : 255)) == 0;
120 	}
121 
122 	/**
123 	 * Compares two colours.
124 	 * Returns: true if red, green, blue an alpha are equals, false otherwise
125 	 */
126 	public override bool opEquals(Object o) {
127 		if(cast(Color)o) {
128 			Color c = cast(Color)o;
129 			return this.r == c.r && this.g == c.g && this.b == c.b && this.a == c.a;
130 		}
131 		return false;
132 	}
133 
134 	/**
135 	 * Converts and hexadecimal representation of a colour into
136 	 * a Color object.
137 	 * Returns: a Color object or null if the string's length is invalid
138 	 * Throws:
139 	 * 		ConvException if one of the string is not an hexadecimal number
140 	 * Example:
141 	 * ---
142 	 * assert(Color.fromString("00CC00").green == 204);
143 	 * assert(Color.fromString("123456").rgb == 0x123456);
144 	 * assert(Color.fromString("01F") == Color.fromString("0011FF"));
145 	 * ---
146 	 */
147 	public static pure @safe Color fromString(string c) {
148 		if(c.length == 6) {
149 			return new Color(to!ubyte(c[0..2], 16), to!ubyte(c[2..4], 16), to!ubyte(c[4..6], 16));
150 		} else if(c.length == 3) {
151 			return fromString([c[0], c[0], c[1], c[1], c[2], c[2]].idup);
152 		} else {
153 			return null;
154 		}
155 	}
156 
157 	/**
158 	 * Converts an rgb-encoded integer to a Color.
159 	 * Example:
160 	 * ---
161 	 * assert(Color.fromRGB(0x0000FF).blue == 255);
162 	 * assert(Color.fromRGB(0x111111).green == 17);
163 	 * ---
164 	 */
165 	public static pure nothrow @safe Color fromRGB(uint c) {
166 		return new Color((c >> 16) & 255, (c >> 8) & 255, c & 255);
167 	}
168 
169 }
170 
171 /**
172  * Interface for object that have colours.
173  * Example:
174  * ---
175  * if(cast(Colorable)object) {
176  *    object.to!Colorable.color = Color(1, 100, 5);
177  * }
178  * ---
179  */
180 interface Colorable {
181 
182 	/**
183 	 * Gets the current colour.
184 	 * Example:
185 	 * ---
186 	 * if(object.color is null || object.color.transparent) {
187 	 *    writeln("There's no colour!");
188 	 * }
189 	 * ---
190 	 */
191 	public @property @safe Color color();
192 
193 	/**
194 	 * Sets the colour for this object.
195 	 * Example:
196 	 * ---
197 	 * if(object.color is null) {
198 	 *    object.color = Color.fromString("003311");
199 	 * }
200 	 * ---
201 	 */
202 	public @property @safe Color color(Color color);
203 
204 }
205 
206 /**
207  * Useful collection of colours to be used for dying, choosing
208  * block's colours and colouring maps.
209  * Example:
210  * ---
211  * leatherHelmet.color = Colors.cocoa;
212  * 
213  * world[0, 64, 0] = Blocks.wool[Colors.Wool.brown];
214  * ---
215  */
216 final class Colors {
217 
218 	@disable this();
219 
220 	public static const(Color) transparent = new Color(0, 0, 0, 0);
221 
222 	public static const(Color) black = Color.fromRGB(0x191919);
223 	public static const(Color) red = Color.fromRGB(0x993333);
224 	public static const(Color) green = Color.fromRGB(0x667F33);
225 	public static const(Color) cocoa = Color.fromRGB(0x664C33);
226 	public static const(Color) lapis = Color.fromRGB(0x334CB2);
227 	public static const(Color) purple = Color.fromRGB(0x7F3FB2);
228 	public static const(Color) cyan = Color.fromRGB(0x4C7F99);
229 	public static const(Color) lightGray = Color.fromRGB(0x999999);
230 	public static const(Color) gray = Color.fromRGB(0x4C4C4C);
231 	public static const(Color) pink = Color.fromRGB(0xF27FA5);
232 	public static const(Color) lime = Color.fromRGB(0x7FCC19);
233 	public static const(Color) yellow = Color.fromRGB(0xE5E533);
234 	public static const(Color) blue = Color.fromRGB(0x6699D8);
235 	public static const(Color) magenta = Color.fromRGB(0xB24CD8);
236 	public static const(Color) orange = Color.fromRGB(0xD87F33);
237 	public static const(Color) white = Color.fromRGB(0xFFFFFF);
238 
239 	enum Wool : ubyte {
240 
241 		white = 0,
242 		orange = 1,
243 		magenta = 2,
244 		lightBlue = 3,
245 		yellow = 4,
246 		lime = 5,
247 		pink = 6,
248 		gray = 7,
249 		lightGray = 8,
250 		cyan = 9,
251 		purple = 10,
252 		blue = 11,
253 		brown = 12,
254 		green = 13,
255 		red = 14,
256 		black = 15
257 
258 	}
259 
260 	alias Carpet = Wool;
261 
262 	alias Sheep = Wool;
263 
264 	alias StainedClay = Wool;
265 
266 	alias StainedGlass = Wool;
267 
268 }