001package squidpony;
002
003import squidpony.panel.IColoredString;
004import squidpony.squidmath.CrossHash;
005import squidpony.squidmath.IRNG;
006import squidpony.squidmath.UnorderedSet;
007
008import java.util.ArrayList;
009
010/**
011 * How to manage colors, making sure that a color is allocated at most once.
012 * 
013 * <p>
014 * If you aren't using squidlib's gdx part, you should use this interface (and
015 * the {@link Skeleton} implementation), because it caches instances.
016 * </p>
017 * 
018 * <p>
019 * If you are using squidlib's gdx part, you should use this interface (and the
020 * {@code SquidColorCenter} implementation) if:
021 * 
022 * <ul>
023 * <li>You don't want to use preallocated instances (if you do, check out
024 * {@code squidpony.squidgrid.gui.Colors})</li>
025 * <li>You don't want to use named colors (if you do, check out
026 * {@code com.badlogic.gdx.graphics.Colors})</li>
027 * <li>You don't like libgdx's Color representation (components as floats
028 * in-between 0 and 1) but prefer components within 0 (inclusive) and 256
029 * (exclusive); and don't mind the overhead of switching the representations. My
030 * personal opinion is that the overhead doesn't matter w.r.t other intensive
031 * operations that we have in roguelikes (path finding).</li>
032 * </ul>
033 * 
034 * @author smelC
035 * 
036 * @param <T>
037 *            The concrete type of colors
038 */
039public interface IColorCenter<T> {
040
041    /**
042     * @param red
043     *            The red component. For screen colors, in-between 0 (inclusive)
044     *            and 256 (exclusive).
045     * @param green
046     *            The green component. For screen colors, in-between 0 (inclusive)
047     *            and 256 (exclusive).
048     * @param blue
049     *            The blue component. For screen colors, in-between 0 (inclusive)
050     *            and 256 (exclusive).
051     * @param opacity
052     *            The alpha component. In-between 0 (inclusive) and 256
053     *            (exclusive). Larger values mean more opacity; 0 is clear.
054     * @return A possibly transparent color.
055     */
056    T get(int red, int green, int blue, int opacity);
057
058        /**
059         * @param red
060         *            The red component. For screen colors, in-between 0 (inclusive)
061     *            and 256 (exclusive).
062         * @param green
063         *            The green component. For screen colors, in-between 0 (inclusive)
064     *            and 256 (exclusive).
065         * @param blue
066         *            The blue component. For screen colors, in-between 0 (inclusive)
067     *            and 256 (exclusive).
068         * @return An opaque color.
069         */
070        T get(int red, int green, int blue);
071
072    /**
073     *
074     * @param hue The hue of the desired color from 0.0 (red, inclusive) towards orange, then
075     *            yellow, and eventually to purple before looping back to almost the same red
076     *            (1.0, exclusive). Values outside this range should be treated as wrapping
077     *            around, so 1.1f and -0.9f would be the same as 0.1f .
078     * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive)
079     *                   to 1.0 (a bright color, inclusive)
080     * @param value the value (essentially lightness) of the color from 0.0 (black,
081     *                   inclusive) to 1.0 (very bright, inclusive).
082     * @param opacity the alpha component as a float; 0.0f is clear, 1.0f is opaque.
083     * @return a possibly transparent color
084     */
085    T getHSV(float hue, float saturation, float value, float opacity);
086
087    /**
088     *
089     * @param hue The hue of the desired color from 0.0 (red, inclusive) towards orange, then
090     *            yellow, and eventually to purple before looping back to almost the same red
091     *            (1.0, exclusive)
092     * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive)
093     *                   to 1.0 (a bright color, exclusive)
094     * @param value the value (essentially lightness) of the color from 0.0 (black,
095         *                   inclusive) to 1.0 (very bright, inclusive).
096     * @return an opaque color
097     */
098    T getHSV(float hue, float saturation, float value);
099
100        /**
101         * @return Opaque white.
102         */
103        T getWhite();
104
105        /**
106         * @return Opaque black.
107         */
108        T getBlack();
109
110        /**
111         * @return The fully transparent color.
112         */
113        T getTransparent();
114
115        /**
116         * @param rng an RNG from SquidLib.
117         * @param opacity
118         *            The alpha component. In-between 0 (inclusive) and 256
119         *            (exclusive). Larger values mean more opacity; 0 is clear.
120         * @return A random color, except for the alpha component.
121         */
122        T getRandom(IRNG rng, int opacity);
123
124        /**
125         * @param c a concrete color
126         * @return The red component. For screen colors, in-between 0 (inclusive) and 256 (exclusive).
127         */
128        int getRed(T c);
129
130        /**
131         * @param c a concrete color
132         * @return The green component. For screen colors, in-between 0 (inclusive) and 256
133         *         (exclusive).
134         */
135        int getGreen(T c);
136
137        /**
138         * @param c a concrete color
139         * @return The blue component. For screen colors, in-between 0 (inclusive) and 256 (exclusive).
140         */
141        int getBlue(T c);
142
143        /**
144         * @param c a concrete color
145         * @return The alpha component. In-between 0 (inclusive) and 256
146         *         (exclusive).
147         */
148        int getAlpha(T c);
149
150    /**
151     *
152     * @param c a concrete color
153     * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and
154     * eventually to purple before looping back to almost the same red (1.0, exclusive)
155     */
156    float getHue(T c);
157
158    /**
159     *
160     * @param c a concrete color
161     * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a
162     * bright color, exclusive)
163     */
164    float getSaturation(T c);
165
166    /**
167     *
168     * @param c a concrete color
169     * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to
170     * 1.0 (very bright, inclusive).
171     */
172    float getValue(T c);
173
174    /**
175     * @param c
176     * @return The color that {@code this} shows when {@code c} is requested. May be {@code c} itself.
177     */
178    T filter(T c);
179
180        /**
181         * @param ics
182         * @return {@code ics} filtered according to {@link #filter(Object)}. May be
183         *         {@code ics} itself if unchanged.
184         */
185        IColoredString<T> filter(IColoredString<T> ics);
186
187        /**
188         * Gets a copy of t and modifies it to make a shade of gray with the same brightness.
189         * The doAlpha parameter causes the alpha to be considered in the calculation of brightness and also changes the
190         * returned alpha of the color.
191         * Not related to reified types or any usage of "reify."
192         * @param t a T to copy; only the copy will be modified
193         * @param doAlpha
194         *            Whether to include (and hereby change) the alpha component.
195         * @return A monochromatic variation of {@code t}.
196         */
197        T greify(/*@Nullable*/ T t, boolean doAlpha);
198
199    /**
200     * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change.
201     * @param start the initial color T
202     * @param end the "target" color T
203     * @param change the degree to change closer to end; a change of 0.0f produces start, 1.0f produces end
204     * @return a new T between start and end
205     */
206    T lerp(T start, T end, float change);
207    /**
208     * Gets a fully-desaturated version of the given color (keeping its brightness, but making it grayscale).
209     * Keeps alpha the same; if you want alpha to be considered (and brightness to be calculated differently), then
210     * you can use greify() in this class instead.
211     * @param color the color T to desaturate (will not be modified)
212     * @return the grayscale version of color
213     */
214    T desaturated(T color);
215
216    /**
217     * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat).
218     * Alpha is left unchanged.
219     * @param color the color T to desaturate
220     * @param degree a float between 0.0f and 1.0f; more makes it less colorful
221     * @return the desaturated (and if a filter is used, also filtered) new color T
222     */
223    T desaturate(T color, float degree);
224
225    /**
226     * Fully saturates color (makes it a vivid color like red or green and less gray) and returns the modified copy.
227     * Leaves alpha unchanged.
228     * @param color the color T to saturate (will not be modified)
229     * @return the saturated version of color
230     */
231    T saturated(T color);
232
233
234    /**
235     * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and
236     * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this does
237     * not necessarily return a specific color, but most implementations will treat a hue of 0 as red.
238     * @param color the color T to saturate
239     * @param degree a float between 0.0f and 1.0f; more makes it more colorful
240     * @return the saturated (and if a filter is used, also filtered) new color
241     */
242    public T saturate(T color, float degree);
243
244        /**
245         * Finds a gradient with 16 steps going from fromColor to toColor,
246         * both included in the gradient.
247         * @param fromColor the color to start with, included in the gradient
248         * @param toColor the color to end on, included in the gradient
249         * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps
250         */
251        public ArrayList<T> gradient(T fromColor, T toColor);
252
253        /**
254         * Finds a gradient with the specified number of steps going from fromColor to toColor,
255         * both included in the gradient.
256         * @param fromColor the color to start with, included in the gradient
257         * @param toColor the color to end on, included in the gradient
258         * @param steps the number of elements to use in the gradient
259         * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps
260         */
261        public ArrayList<T> gradient(T fromColor, T toColor, int steps);
262
263        /**
264         * A skeletal implementation of {@link IColorCenter}.
265         * 
266         * @author smelC
267         * 
268         * @param <T> a concrete color type
269         */
270        abstract class Skeleton<T> implements IColorCenter<T> {
271
272                @SuppressWarnings("unchecked")
273                protected class GranularHasher implements CrossHash.IHasher{
274                        int[] feed = new int[4];
275                        @Override
276                        public int hash(final Object data) {
277                                if(data == null) return 0;
278                                final long y = (data == feed)
279                                                ? getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3])
280                                                : getUniqueIdentifier((T)data);
281                                final int x = (int)(y ^ y >>> 32) * 0x62BD5;
282                                return x ^ ((x << 17) | (x >>> 15)) ^ ((x << 9) | (x >>> 23));
283                        }
284
285                        @Override
286                        public boolean areEqual(final Object left, final Object right) {
287                                if(left == right) return true;
288                                if(left != null && right != null)
289                                {
290                                        if(left == feed)
291                                        {
292                                                return getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]) == getUniqueIdentifier((T)right);
293//                                              final long f = getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]);
294//                                              final long i = getUniqueIdentifier((T)right);
295//                                              System.out.printf("left 0x%016X, right 0x%016X\n", f, i);
296//                                              return f == i;
297                                        }
298                                        else if(right == feed)
299                                        {
300                                                return getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]) == getUniqueIdentifier((T)left);
301//                                              final long f = getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]);
302//                                              final long i = getUniqueIdentifier((T)left);
303//                                              System.out.printf("left 0x%016X, right 0x%016X\n", i, f);
304//                                              return f == i;
305                                        }
306                                        else 
307                                        {
308                                                return (getUniqueIdentifier((T)left) == getUniqueIdentifier((T)right));
309                                        }
310                                }
311                                return false;
312//                              return (left == right) || (left != null && right != null &&
313//                                              ((left == feed && getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]) == getUniqueIdentifier((T)right)) ||
314//                                                              (right == feed && getUniqueIdentifier((T)left) == getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3])) ||
315//                                                              (left != feed && right != feed && getUniqueIdentifier((T)left) == getUniqueIdentifier((T)right))));
316                        }
317                } 
318                protected GranularHasher theHasher = new GranularHasher();
319                private final UnorderedSet<T> cache = new UnorderedSet<>(256, theHasher);
320
321                protected /*Nullable*/ IFilter<T> filter;
322
323                /**
324                 * @param filter
325                 *                      The filter to use, or {@code null} for no filter.
326                 */
327                protected Skeleton(/*Nullable*/ IFilter<T> filter) {
328                        this.filter = filter;
329                }
330
331        /**
332         * It clears the cache. You may need to do this to limit the cache to the colors used in a specific section.
333                 * This is also useful if a Filter changes what colors it should return on a frame-by-frame basis; in that case,
334                 * you can call clearCache() at the start or end of a frame to ensure the next frame gets different colors.
335         */
336        public void clearCache()
337        {
338            cache.clear();
339        }
340
341                /**
342                 * The actual cache is not public, but there are cases where you may want to know how many different colors are
343                 * actually used in a frame or a section of the game. If the cache was emptied (which might be from calling
344                 * {@link #clearCache()}), some colors were requested, then this is called, the returned int should be the
345                 * count of distinct colors this IColorCenter had created and cached; duplicates won't be counted twice.
346                 * @return
347                 */
348                public int cacheSize()
349                {
350                        return cache.size();
351                }
352
353        /**
354         * You may want to copy colors between IColorCenter instances that have different create() methods -- and as
355         * such, will have different values for the same keys in the cache. This allows you to copy the cache from other
356         * into this Skeleton, but using this Skeleton's create() method.
357         * @param other another Skeleton of the same type that will have its cache copied into this Skeleton
358         */
359        public void copyCache(Skeleton<T> other)
360        {
361                cache.addAll(other.cache);
362        }
363
364                /**
365                 * If you're changing the filter, you should likely call
366                 * {@link #clearCache()}.
367                 * 
368                 * @param filter
369                 *            The filter to use, or {@code null} to turn filtering OFF.
370                 * @return {@code this}
371                 */
372                public Skeleton<T> setFilter(IFilter<T> filter) {
373                        this.filter = filter;
374                        return this;
375                }
376                
377        @SuppressWarnings("SuspiciousMethodCalls")
378                @Override
379        public T get(int red, int green, int blue, int opacity) {
380                        theHasher.feed[0] = red;
381                        theHasher.feed[1] = green;
382                        theHasher.feed[2] = blue;
383                        theHasher.feed[3] = opacity;
384                        T t;
385            if (cache.contains(theHasher.feed)) {
386                t = cache.get(theHasher.feed);
387                        }
388                        else
389                        {
390                                /* Miss */
391                t = create(red, green, blue, opacity);
392                                /* Put in cache */
393                cache.add(t);
394            }
395            return t;
396        }
397
398                @Override
399                public T get(int red, int green, int blue) {
400                        return get(red, green, blue, 255);
401                }
402
403        @Override
404        public T getHSV(float hue, float saturation, float value, float opacity) {
405            if ( saturation < 0.0001f )                       //HSV from 0 to 1
406            {
407                return get(Math.round(value * 255), Math.round(value * 255), Math.round(value * 255),
408                        Math.round(opacity * 255));
409            }
410            else
411            {
412                float h = ((hue + 6f) % 1f) * 6f; // allows negative hue to wrap
413                int i = (int)h;
414                float a = value * (1 - saturation);
415                float b = value * (1 - saturation * (h - i));
416                float c = value * (1 - saturation * (1 - (h - i)));
417
418                switch (i)
419                {
420                    case 0: return get(Math.round(value * 255), Math.round(c * 255), Math.round(a * 255),
421                            Math.round(opacity * 255));
422                    case 1: return get(Math.round(b * 255), Math.round(value * 255), Math.round(a * 255),
423                            Math.round(opacity * 255));
424                    case 2: return get(Math.round(a * 255), Math.round(value * 255), Math.round(c * 255),
425                            Math.round(opacity * 255));
426                    case 3: return get(Math.round(a * 255), Math.round(b * 255), Math.round(value * 255),
427                            Math.round(opacity * 255));
428                    case 4: return get(Math.round(c * 255), Math.round(a * 255), Math.round(value * 255),
429                            Math.round(opacity * 255));
430                    default: return get(Math.round(value * 255), Math.round(a * 255), Math.round(b * 255),
431                            Math.round(opacity * 255));
432                }
433            }
434        }
435
436
437        @Override
438        public T getHSV(float hue, float saturation, float value) {
439            return getHSV(hue, saturation, value, 1.0f);
440        }
441
442        @Override
443                public T getWhite() {
444                        return get(255, 255, 255, 255);
445                }
446
447                @Override
448                public T getBlack() {
449                        return get(0, 0, 0, 255);
450                }
451
452                @Override
453                public T getTransparent() {
454                        return get(0, 0, 0, 0);
455                }
456
457                @Override
458                public T getRandom(IRNG rng, int opacity) {
459                        return get(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256), opacity);
460                }
461
462        /**
463         * @param r the red component in 0.0 to 1.0 range, typically
464         * @param g the green component in 0.0 to 1.0 range, typically
465         * @param b the blue component in 0.0 to 1.0 range, typically
466         * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a
467         * bright color, exclusive)
468         */
469        public float getSaturation(float r, float g, float b) {
470            float min = Math.min(Math.min(r, g ), b);    //Min. value of RGB
471            float max = Math.max(Math.max(r, g), b);    //Min. value of RGB
472            float delta = max - min;                     //Delta RGB value
473
474            float saturation;
475
476            if ( delta < 0.0001f )                     //This is a gray, no chroma...
477            {
478                saturation = 0;
479            }
480            else                                    //Chromatic data...
481            {
482                saturation = delta / max;
483            }
484            return saturation;
485        }
486        /**
487         * @param c a concrete color
488         * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a
489         * bright color, exclusive)
490         */
491        @Override
492        public float getSaturation(T c) {
493            return getSaturation(getRed(c) / 255f, getGreen(c) / 255f, getBlue(c) / 255f);
494        }
495
496        /**
497         * @param r the red component in 0.0 to 1.0 range, typically
498         * @param g the green component in 0.0 to 1.0 range, typically
499         * @param b the blue component in 0.0 to 1.0 range, typically
500         * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to
501         * 1.0 (very bright, inclusive).
502         */
503        public float getValue(float r, float g, float b)
504        {
505            return Math.max(Math.max(r, g), b);
506        }
507        /**
508         * @param c a concrete color
509         * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to
510                 * 1.0 (very bright, inclusive).
511                 */
512        @Override
513        public float getValue(T c) {
514            float r = getRed(c) / 255f;                     //RGB from 0 to 255
515            float g = getGreen(c) / 255f;
516            float b = getBlue(c) / 255f;
517
518            return Math.max(Math.max(r, g), b);
519        }
520
521        /**
522         * @param r the red component in 0.0 to 1.0 range, typically
523         * @param g the green component in 0.0 to 1.0 range, typically
524         * @param b the blue component in 0.0 to 1.0 range, typically
525         * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and
526         * eventually to purple before looping back to almost the same red (1.0, exclusive)
527         */
528        public float getHue(float r, float g, float b) {
529            float min = Math.min(Math.min(r, g ), b);    //Min. value of RGB
530            float max = Math.max(Math.max(r, g), b);    //Min. value of RGB
531            float delta = max - min;                     //Delta RGB value
532
533            float hue;
534
535            if ( delta < 0.0001f )                     //This is a gray, no chroma...
536            {
537                hue = 0;                                //HSV results from 0 to 1
538            }
539            else                                    //Chromatic data...
540            {
541                float rDelta = (((max - r) / 6f) + (delta / 2f)) / delta;
542                float gDelta = (((max - g) / 6f) + (delta / 2f)) / delta;
543                float bDelta = (((max - b) / 6f) + (delta / 2f)) / delta;
544
545                if       (r == max) hue = bDelta - gDelta;
546                else if (g == max) hue = (1f / 3f) + rDelta - bDelta;
547                else                 hue = (2f / 3f) + gDelta - rDelta;
548
549                if (hue < 0) hue += 1f;
550                else if (hue > 1) hue -= 1;
551            }
552            return hue;
553        }
554
555        /**
556         * @param c a concrete color
557         * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and
558         * eventually to purple before looping back to almost the same red (1.0, exclusive)
559         */
560        @Override
561        public float getHue(T c) {
562            return getHue(getRed(c) / 255f, getGreen(c) / 255f, getBlue(c) / 255f);
563        }
564
565        @Override
566        public T filter(T c)
567        {
568                return c == null ? null : get(getRed(c), getGreen(c), getBlue(c), getAlpha(c));
569        }
570
571
572                @Override
573                public IColoredString<T> filter(IColoredString<T> ics) {
574                        /*
575                         * It is common not to have a filter or to have the identity one. To
576                         * avoid always copying strings in this case, we first roll over the
577                         * string to see if there'll be a change.
578                         * 
579                         * This is clearly a subjective design choice but my industry
580                         * experience is that minimizing allocations is the thing to do for
581                         * performances, hence I prefer iterating twice to do that.
582                         */
583                        boolean change = false;
584                        for (IColoredString.Bucket<T> bucket : ics) {
585                                final T in = bucket.getColor();
586                                if (in == null)
587                                        continue;
588                                final T out = filter(in);
589                                if (in != out) {
590                                        change = true;
591                                        break;
592                                }
593                        }
594
595                        if (change) {
596                                final IColoredString<T> result = IColoredString.Impl.create();
597                                for (IColoredString.Bucket<T> bucket : ics)
598                                        result.append(bucket.getText(), filter(bucket.getColor()));
599                                return result;
600                        } else
601                                /* Only one allocation: the iterator, yay \o/ */
602                                return ics;
603                }
604                /**
605                 * Gets a copy of t and modifies it to make a shade of gray with the same brightness.
606                 * The doAlpha parameter causes the alpha to be considered in the calculation of brightness and also changes the
607                 * returned alpha of the color.
608                 * Not related to reified types or any usage of "reify."
609                 * @param t a T to copy; only the copy will be modified
610                 * @param doAlpha
611                 *            Whether to include (and hereby change) the alpha component.
612                 * @return A monochromatic variation of {@code t}.
613                 */
614                @Override
615                public T greify(T t, boolean doAlpha) {
616                        if (t == null)
617                                /* Cannot do */
618                                return null;
619                        final int red = getRed(t);
620                        final int green = getGreen(t);
621                        final int blue = getBlue(t);
622                        final int alpha = getAlpha(t);
623                        final int rgb = red + green + blue;
624                        final int mean;
625                        final int newAlpha;
626                        if (doAlpha) {
627                                mean = (rgb + alpha) / 4;
628                                newAlpha = mean;
629                        } else {
630                                mean = rgb / 3;
631                                /* No change */
632                                newAlpha = alpha;
633                        }
634                        return get(mean, mean, mean, newAlpha);
635                }
636
637                /**
638                 * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change.
639         * This implementation tries to work with colors in a way that is as general as possible, using getRed() instead
640         * of some specific detail that depends on how a color is implemented. Other implementations that specialize in
641         * a specific type of color may be able to be more efficient.
642         * @param start the initial color T
643         * @param end the "target" color T
644         * @param change the degree to change closer to end; a change of 0.0f produces start, 1.0f produces end
645         * @return a new T between start and end
646                 */
647                public T lerp(T start, T end, float change)
648                {
649                        if(start == null || end == null)
650                                return null;
651                        final int sr = getRed(start), sg = getGreen(start), sb = getBlue(start), sa = getAlpha(start),
652                                        er = getRed(end), eg = getGreen(end), eb = getBlue(end), ea = getAlpha(end);
653                        return get(
654                                        (int)(sr + change * (er - sr)),
655                                        (int)(sg + change
656                                                        * (eg - sg)),
657                                        (int)(sb + change * (eb - sb)),
658                                        (int)(sa + change * (ea - sa))
659                        );
660                }
661                /**
662                 * Gets a fully-desaturated version of the given color (keeping its brightness, but making it grayscale).
663                 * Keeps alpha the same; if you want alpha to be considered (and brightness to be calculated differently), then
664                 * you can use greify() in this class instead.
665                 * @param color the color T to desaturate (will not be modified)
666                 * @return the grayscale version of color
667                 */
668                public T desaturated(T color)
669                {
670                        int f = (int)Math.min(255, getRed(color) * 0.299f + getGreen(color) * 0.587f + getBlue(color) * 0.114f);
671                        return get(f, f, f, getAlpha(color));
672                }
673
674                /**
675                 * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat).
676                 * Alpha is left unchanged.
677                 * @param color the color T to desaturate
678                 * @param degree a float between 0.0f and 1.0f; more makes it less colorful
679                 * @return the desaturated (and if a filter is used, also filtered) new color T
680                 */
681                public T desaturate(T color, float degree)
682                {
683                        return lerp(color, desaturated(color), degree);
684                }
685
686        /**
687         * Fully saturates color (makes it a vivid color like red or green and less gray) and returns the modified copy.
688         * Leaves alpha unchanged.
689         * @param color the color T to saturate (will not be modified)
690         * @return the saturated version of color
691         */
692        public T saturated(T color)
693        {
694            return getHSV(getHue(color), 1f, getValue(color), getAlpha(color));
695        }
696                /**
697                 * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and
698                 * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this is
699                 * likely to produce a red hue by default (if there's no hue to make vivid, it needs to choose something).
700                 * @param color the color T to saturate
701                 * @param degree a float between 0.0f and 1.0f; more makes it more colorful
702                 * @return the saturated (and if a filter is used, also filtered) new color
703                 */
704                public T saturate(T color, float degree)
705                {
706                        return lerp(color, saturated(color), degree);
707                }
708
709                @Override
710                public ArrayList<T> gradient(T fromColor, T toColor) {
711                        return gradient(fromColor, toColor, 16);
712                }
713
714                @Override
715                public ArrayList<T> gradient(T fromColor, T toColor, int steps) {
716                        ArrayList<T> colors = new ArrayList<>(Math.max(steps, 1));
717                        colors.add(filter(fromColor));
718                        if(steps < 2)
719                                return colors;
720                        for (float i = 1; i < steps; i++) {
721                                colors.add(lerp(fromColor, toColor, i / (steps - 1f)));
722                        }
723                        return colors;
724
725                }
726
727        /**
728         * Create a concrete instance of the color type given as a type parameter. That's the
729         * place to use the {@link #filter}.
730         *
731         * @param red the red component of the desired color
732         * @param green the green component of the desired color
733         * @param blue the blue component of the desired color
734         * @param opacity the alpha component or opacity of the desired color
735         * @return a fresh instance of the concrete color type
736         */
737        protected abstract T create(int red, int green, int blue, int opacity);
738
739                protected long getUniqueIdentifier(int r, int g, int b, int a) {
740                        return ((a & 0xffL) << 48) | ((r & 0xffL) << 32) | ((g & 0xffL) << 16) | (b & 0xffL);
741                }
742                protected long getUniqueIdentifier(T item)
743                {
744                        return ((getAlpha(item) & 0xffL) << 48) | ((getRed(item) & 0xffL) << 32)
745                                        | ((getGreen(item) & 0xffL) << 16) | (getBlue(item) & 0xffL);
746                }
747
748        }
749}