001 package org.bukkit;
002
003 import java.util.Map;
004
005 import org.apache.commons.lang.Validate;
006 import org.bukkit.configuration.serialization.ConfigurationSerializable;
007 import org.bukkit.configuration.serialization.SerializableAs;
008
009 import com.google.common.collect.ImmutableMap;
010
011 /**
012 * A container for a color palette. This class is immutable; the set methods
013 * return a new color. The color names listed as fields are HTML4 standards,
014 * but subject to change.
015 */
016 @SerializableAs("Color")
017 public final class Color implements ConfigurationSerializable {
018 private static final int BIT_MASK = 0xff;
019
020 /**
021 * White, or (0xFF,0xFF,0xFF) in (R,G,B)
022 */
023 public static final Color WHITE = fromRGB(0xFFFFFF);
024
025 /**
026 * Silver, or (0xC0,0xC0,0xC0) in (R,G,B)
027 */
028 public static final Color SILVER = fromRGB(0xC0C0C0);
029
030 /**
031 * Gray, or (0x80,0x80,0x80) in (R,G,B)
032 */
033 public static final Color GRAY = fromRGB(0x808080);
034
035 /**
036 * Black, or (0x00,0x00,0x00) in (R,G,B)
037 */
038 public static final Color BLACK = fromRGB(0x000000);
039
040 /**
041 * Red, or (0xFF,0x00,0x00) in (R,G,B)
042 */
043 public static final Color RED = fromRGB(0xFF0000);
044
045 /**
046 * Maroon, or (0x80,0x00,0x00) in (R,G,B)
047 */
048 public static final Color MAROON = fromRGB(0x800000);
049
050 /**
051 * Yellow, or (0xFF,0xFF,0x00) in (R,G,B)
052 */
053 public static final Color YELLOW = fromRGB(0xFFFF00);
054
055 /**
056 * Olive, or (0x80,0x80,0x00) in (R,G,B)
057 */
058 public static final Color OLIVE = fromRGB(0x808000);
059
060 /**
061 * Lime, or (0x00,0xFF,0x00) in (R,G,B)
062 */
063 public static final Color LIME = fromRGB(0x00FF00);
064
065 /**
066 * Green, or (0x00,0x80,0x00) in (R,G,B)
067 */
068 public static final Color GREEN = fromRGB(0x008000);
069
070 /**
071 * Aqua, or (0x00,0xFF,0xFF) in (R,G,B)
072 */
073 public static final Color AQUA = fromRGB(0x00FFFF);
074
075 /**
076 * Teal, or (0x00,0x80,0x80) in (R,G,B)
077 */
078 public static final Color TEAL = fromRGB(0x008080);
079
080 /**
081 * Blue, or (0x00,0x00,0xFF) in (R,G,B)
082 */
083 public static final Color BLUE = fromRGB(0x0000FF);
084
085 /**
086 * Navy, or (0x00,0x00,0x80) in (R,G,B)
087 */
088 public static final Color NAVY = fromRGB(0x000080);
089
090 /**
091 * Fuchsia, or (0xFF,0x00,0xFF) in (R,G,B)
092 */
093 public static final Color FUCHSIA = fromRGB(0xFF00FF);
094
095 /**
096 * Purple, or (0x80,0x00,0x80) in (R,G,B)
097 */
098 public static final Color PURPLE = fromRGB(0x800080);
099
100 /**
101 * Orange, or (0xFF,0xA5,0x00) in (R,G,B)
102 */
103 public static final Color ORANGE = fromRGB(0xFFA500);
104
105 private final byte red;
106 private final byte green;
107 private final byte blue;
108
109 /**
110 * Creates a new Color object from a red, green, and blue
111 *
112 * @param red integer from 0-255
113 * @param green integer from 0-255
114 * @param blue integer from 0-255
115 * @return a new Color object for the red, green, blue
116 * @throws IllegalArgumentException if any value is strictly >255 or <0
117 */
118 public static Color fromRGB(int red, int green, int blue) throws IllegalArgumentException {
119 return new Color(red, green, blue);
120 }
121
122 /**
123 * Creates a new Color object from a blue, green, and red
124 *
125 * @param blue integer from 0-255
126 * @param green integer from 0-255
127 * @param red integer from 0-255
128 * @return a new Color object for the red, green, blue
129 * @throws IllegalArgumentException if any value is strictly >255 or <0
130 */
131 public static Color fromBGR(int blue, int green, int red) throws IllegalArgumentException {
132 return new Color(red, green, blue);
133 }
134
135 /**
136 * Creates a new color object from an integer that contains the red,
137 * green, and blue bytes in the lowest order 24 bits.
138 *
139 * @param rgb the integer storing the red, green, and blue values
140 * @return a new color object for specified values
141 * @throws IllegalArgumentException if any data is in the highest order 8
142 * bits
143 */
144 public static Color fromRGB(int rgb) throws IllegalArgumentException {
145 Validate.isTrue((rgb >> 24) == 0, "Extrenuous data in: ", rgb);
146 return fromRGB(rgb >> 16 & BIT_MASK, rgb >> 8 & BIT_MASK, rgb >> 0 & BIT_MASK);
147 }
148
149 /**
150 * Creates a new color object from an integer that contains the blue,
151 * green, and red bytes in the lowest order 24 bits.
152 *
153 * @param bgr the integer storing the blue, green, and red values
154 * @return a new color object for specified values
155 * @throws IllegalArgumentException if any data is in the highest order 8
156 * bits
157 */
158 public static Color fromBGR(int bgr) throws IllegalArgumentException {
159 Validate.isTrue((bgr >> 24) == 0, "Extrenuous data in: ", bgr);
160 return fromBGR(bgr >> 16 & BIT_MASK, bgr >> 8 & BIT_MASK, bgr >> 0 & BIT_MASK);
161 }
162
163 private Color(int red, int green, int blue) {
164 Validate.isTrue(red >= 0 && red <= BIT_MASK, "Red is not between 0-255: ", red);
165 Validate.isTrue(green >= 0 && green <= BIT_MASK, "Green is not between 0-255: ", green);
166 Validate.isTrue(blue >= 0 && blue <= BIT_MASK, "Blue is not between 0-255: ", blue);
167
168 this.red = (byte) red;
169 this.green = (byte) green;
170 this.blue = (byte) blue;
171 }
172
173 /**
174 * Gets the red component
175 *
176 * @return red component, from 0 to 255
177 */
178 public int getRed() {
179 return BIT_MASK & red;
180 }
181
182 /**
183 * Creates a new Color object with specified component
184 *
185 * @param red the red component, from 0 to 255
186 * @return a new color object with the red component
187 */
188 public Color setRed(int red) {
189 return fromRGB(red, getGreen(), getBlue());
190 }
191
192 /**
193 * Gets the green component
194 *
195 * @return green component, from 0 to 255
196 */
197 public int getGreen() {
198 return BIT_MASK & green;
199 }
200
201 /**
202 * Creates a new Color object with specified component
203 *
204 * @param green the red component, from 0 to 255
205 * @return a new color object with the red component
206 */
207 public Color setGreen(int green) {
208 return fromRGB(getRed(), green, getBlue());
209 }
210
211 /**
212 * Gets the blue component
213 *
214 * @return blue component, from 0 to 255
215 */
216 public int getBlue() {
217 return BIT_MASK & blue;
218 }
219
220 /**
221 * Creates a new Color object with specified component
222 *
223 * @param blue the red component, from 0 to 255
224 * @return a new color object with the red component
225 */
226 public Color setBlue(int blue) {
227 return fromRGB(getRed(), getGreen(), blue);
228 }
229
230 /**
231 *
232 * @return An integer representation of this color, as 0xRRGGBB
233 */
234 public int asRGB() {
235 return getRed() << 16 | getGreen() << 8 | getBlue() << 0;
236 }
237
238 /**
239 *
240 * @return An integer representation of this color, as 0xBBGGRR
241 */
242 public int asBGR() {
243 return getBlue() << 16 | getGreen() << 8 | getRed() << 0;
244 }
245
246 /**
247 * Creates a new color with its RGB components changed as if it was dyed
248 * with the colors passed in, replicating vanilla workbench dyeing
249 *
250 * @param colors The DyeColors to dye with
251 * @return A new color with the changed rgb components
252 */
253 // TODO: Javadoc what this method does, not what it mimics. API != Implementation
254 public Color mixDyes(DyeColor... colors) {
255 Validate.noNullElements(colors, "Colors cannot be null");
256
257 Color[] toPass = new Color[colors.length];
258 for (int i = 0; i < colors.length; i++) {
259 toPass[i] = colors[i].getColor();
260 }
261
262 return mixColors(toPass);
263 }
264
265 /**
266 * Creates a new color with its RGB components changed as if it was dyed
267 * with the colors passed in, replicating vanilla workbench dyeing
268 *
269 * @param colors The colors to dye with
270 * @return A new color with the changed rgb components
271 */
272 // TODO: Javadoc what this method does, not what it mimics. API != Implementation
273 public Color mixColors(Color... colors) {
274 Validate.noNullElements(colors, "Colors cannot be null");
275
276 int totalRed = this.getRed();
277 int totalGreen = this.getGreen();
278 int totalBlue = this.getBlue();
279 int totalMax = Math.max(Math.max(totalRed, totalGreen), totalBlue);
280 for (Color color : colors) {
281 totalRed += color.getRed();
282 totalGreen += color.getGreen();
283 totalBlue += color.getBlue();
284 totalMax += Math.max(Math.max(color.getRed(), color.getGreen()), color.getBlue());
285 }
286
287 float averageRed = totalRed / (colors.length + 1);
288 float averageGreen = totalGreen / (colors.length + 1);
289 float averageBlue = totalBlue / (colors.length + 1);
290 float averageMax = totalMax / (colors.length + 1);
291
292 float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue);
293 float gainFactor = averageMax / maximumOfAverages;
294
295 return Color.fromRGB((int) (averageRed * gainFactor), (int) (averageGreen * gainFactor), (int) (averageBlue * gainFactor));
296 }
297
298 @Override
299 public boolean equals(Object o) {
300 if (!(o instanceof Color)) {
301 return false;
302 }
303 final Color that = (Color) o;
304 return this.blue == that.blue && this.green == that.green && this.red == that.red;
305 }
306
307 @Override
308 public int hashCode() {
309 return asRGB() ^ Color.class.hashCode();
310 }
311
312 public Map<String, Object> serialize() {
313 return ImmutableMap.<String, Object>of(
314 "RED", getRed(),
315 "BLUE", getBlue(),
316 "GREEN", getGreen()
317 );
318 }
319
320 @SuppressWarnings("javadoc")
321 public static Color deserialize(Map<String, Object> map) {
322 return fromRGB(
323 asInt("RED", map),
324 asInt("GREEN", map),
325 asInt("BLUE", map)
326 );
327 }
328
329 private static int asInt(String string, Map<String, Object> map) {
330 Object value = map.get(string);
331 if (value == null) {
332 throw new IllegalArgumentException(string + " not in map " + map);
333 }
334 if (!(value instanceof Number)) {
335 throw new IllegalArgumentException(string + '(' + value + ") is not a number");
336 }
337 return ((Number) value).intValue();
338 }
339
340 @Override
341 public String toString() {
342 return "Color:[rgb0x" + Integer.toHexString(getRed()).toUpperCase() + Integer.toHexString(getGreen()).toUpperCase() + Integer.toHexString(getBlue()).toUpperCase() + "]";
343 }
344 }