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    }