001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.graphics.Color;
004import com.badlogic.gdx.math.Interpolation;
005import com.badlogic.gdx.math.MathUtils;
006import com.badlogic.gdx.utils.IntMap;
007import squidpony.IColorCenter;
008import squidpony.IFilter;
009import squidpony.panel.IColoredString;
010import squidpony.squidmath.CoordPacker;
011import squidpony.squidmath.IRNG;
012import squidpony.squidmath.StatefulRNG;
013
014import java.util.ArrayList;
015
016/**
017 * A concrete implementation of {@link IColorCenter} for libgdx's {@link com.badlogic.gdx.graphics.Color}.
018 * Supports filtering any colors that this creates using an {@link IFilter}, such as one from {@link Filters}.
019 * This class largely supersedes the earlier SColorFactory class, and supports similar operations
020 * while also allowing filters to modify the returned colors. SColorFactory has been removed, so code transitioning
021 * to SquidColorCenter may need to change blend() to {@link #lerp(Color, Color, double)}, and setFloor() to
022 * {@link #setGranularity(int)} (with different behavior).
023 * @author smelC
024 * @author Tommy Ettinger
025 * @see SColor Another way to obtain colors by using pre-allocated (and named) instances.
026 */
027public class SquidColorCenter implements IColorCenter<Color> {
028
029    /**
030     * How different requested colors need to be to make a new color; should range from 0 to at most 6.
031     * If this is 0, all requested colors will be looked up (using a cached version if the exact request had been made
032     * before), but if this is greater than 0, then exponentially less colors will be used, using the cache for twice as
033     * many requests at granularity 1 (2 raised to the granularity), four times as many at granularity 2, and so on.
034     * Defaults to 1, which seems to help ensure visually-identical colors are not created more than once.
035     */
036    private int granularity = 1;
037    private int granularityMask = 0xFE;
038
039    /**
040     * Gets the granularity, which is how different requested colors need to be to make a new color; can be from 0 to 6.
041     * If this is 0, all requested colors will be looked up (using a cached version if the exact request had been made
042     * before), but if this is greater than 0, then exponentially less colors will be used, using the cache for twice as
043     * many requests at granularity 1 (2 raised to the granularity), four times as many at granularity 2, and so on.
044     * If no granularity was set, the default is 1.
045     * @return the current granularity, as an int
046     */
047    public int getGranularity() {
048        return granularity;
049    }
050
051    /**
052     * Sets the granularity, which is how different requested colors must be to make a new color; from 0 to at most 6.
053     * If this is 0, all requested colors will be looked up (using a cached version if the exact request had been made
054     * before), but if this is greater than 0, then exponentially less colors will be used, using the cache for twice as
055     * many requests at granularity 1 (2 raised to the granularity), four times as many at granularity 2, and so on.
056     * If no granularity was set, the default is 1.
057     * @param granularity the granularity to use; will be clamped between 0 and 6
058     */
059    public void setGranularity(int granularity) {
060        this.granularity = MathUtils.clamp(granularity, 0, 6);
061        granularityMask = 0xFF << this.granularity & 0xFF;
062    }
063
064    public IFilter<Color> filter;
065    protected IntMap<Color> cache;
066    /**
067     * A fresh filter-less color center.
068     */
069    public SquidColorCenter()
070    {
071        this(null);
072    }
073
074    /**
075         * A fresh filtered color center.
076         * 
077         * @param filterEffect
078         *            The filter to use.
079         */
080    public SquidColorCenter(/*Nullable*/IFilter<Color> filterEffect)
081    {
082        cache = new IntMap<>(256);
083        filter = filterEffect;
084    }
085
086    @Override
087    public Color get(int red, int green, int blue, int opacity) {
088        int abgr = (opacity &= granularityMask & 0xFE) << 24
089                | (blue &= granularityMask) << 16
090                | (green &= granularityMask) << 8
091                | (red &= granularityMask);
092        Color result;
093        if ((result = cache.get(abgr)) != null)
094            return result;
095        if (filter == null) {    /* No filtering */
096            result = new Color(red / 255f, green / 255f, blue / 255f, opacity / 254f);
097        }
098        else {                   /* Some filtering */
099            result = filter.alter(red / 255f, green / 255f, blue / 255f, opacity / 254f);
100        }
101        cache.put(abgr, result);
102        return result;
103    }
104
105    /**
106     * @param red   The red component. For screen colors, in-between 0 (inclusive)
107     *              and 256 (exclusive).
108     * @param green The green component. For screen colors, in-between 0 (inclusive)
109     *              and 256 (exclusive).
110     * @param blue  The blue component. For screen colors, in-between 0 (inclusive)
111     *              and 256 (exclusive).
112     * @return An opaque color.
113     */
114    @Override
115    public Color get(int red, int green, int blue) {
116        return get(red, green, blue, 255);
117    }
118
119    /**
120     * @param hue        The hue of the desired color from 0.0 (red, inclusive) towards orange, then
121     *                   yellow, and eventually to purple before looping back to almost the same red
122     *                   (1.0, exclusive). Values outside this range should be treated as wrapping
123     *                   around, so 1.1f and -0.9f would be the same as 0.1f .
124     * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive)
125     *                   to 1.0 (a bright color, inclusive)
126     * @param value      the value (essentially lightness) of the color from 0.0 (black,
127     *                   inclusive) to 1.0 (very bright, inclusive).
128     * @param opacity    the alpha component as a float; 0.0f is clear, 1.0f is opaque.
129     * @return a possibly transparent color
130     */
131    @Override
132    public Color getHSV(float hue, float saturation, float value, float opacity) {
133        if ( saturation < 0.0001f )                       //HSV from 0 to 1
134        {
135            return get(Math.round(value * 255), Math.round(value * 255), Math.round(value * 255),
136                    Math.round(opacity * 255));
137        }
138        else
139        {
140            float h = ((hue + 6f) % 1f) * 6f; // allows negative hue to wrap
141            int i = (int)h;
142            float a = value * (1 - saturation);
143            float b = value * (1 - saturation * (h - i));
144            float c = value * (1 - saturation * (1 - (h - i)));
145
146            switch (i)
147            {
148                case 0: return get(Math.round(value * 255), Math.round(c * 255), Math.round(a * 255),
149                        Math.round(opacity * 255));
150                case 1: return get(Math.round(b * 255), Math.round(value * 255), Math.round(a * 255),
151                        Math.round(opacity * 255));
152                case 2: return get(Math.round(a * 255), Math.round(value * 255), Math.round(c * 255),
153                        Math.round(opacity * 255));
154                case 3: return get(Math.round(a * 255), Math.round(b * 255), Math.round(value * 255),
155                        Math.round(opacity * 255));
156                case 4: return get(Math.round(c * 255), Math.round(a * 255), Math.round(value * 255),
157                        Math.round(opacity * 255));
158                default: return get(Math.round(value * 255), Math.round(a * 255), Math.round(b * 255),
159                        Math.round(opacity * 255));
160            }
161        }
162    }
163
164    /**
165     * @param hue        The hue of the desired color from 0.0 (red, inclusive) towards orange, then
166     *                   yellow, and eventually to purple before looping back to almost the same red
167     *                   (1.0, exclusive)
168     * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive)
169     *                   to 1.0 (a bright color, exclusive)
170     * @param value      the value (essentially lightness) of the color from 0.0 (black,
171     *                   inclusive) to 1.0 (very bright, inclusive).
172     * @return an opaque color
173     */
174    @Override
175    public Color getHSV(float hue, float saturation, float value) {
176        return getHSV(hue, saturation, value, 1f);
177    }
178
179    /**
180     * @return Opaque white.
181     */
182    @Override
183    public Color getWhite() {
184        return SColor.WHITE;
185    }
186
187    /**
188     * @return Opaque black.
189     */
190    @Override
191    public Color getBlack() {
192        return SColor.BLACK;
193    }
194
195    /**
196     * @return The fully transparent color.
197     */
198    @Override
199    public Color getTransparent() {
200        return SColor.TRANSPARENT;
201    }
202
203    /**
204     * @param rng     any IRNG from SquidLib, such as an RNG, StatefulRNG, or GWTRNG.
205     * @param opacity The alpha component. In-between 0 (inclusive) and 256
206     *                (exclusive). Larger values mean more opacity; 0 is clear.
207     * @return A random color, except for the alpha component.
208     */
209    @Override
210    public Color getRandom(IRNG rng, int opacity) {
211        return get((rng.nextInt() & 0xFFFFFF00) | (opacity & 0xFF));
212    }
213
214    @Override
215    public Color filter(Color c)
216    {
217        if(c == null)
218            return Color.CLEAR;
219        else
220            return filter == null ? c : filter.alter(c.r, c.g, c.b, c.a);
221    }
222
223    /**
224     * @param ics an IColoredString, often produced by {@link GDXMarkup#colorString(CharSequence)}.
225     * @return {@code ics} filtered according to {@link #filter(Color)}. May be
226     * {@code ics} itself if unchanged.
227     */
228    @Override
229    public IColoredString<Color> filter(IColoredString<Color> ics) {
230        /*
231         * It is common not to have a filter or to have the identity one. To
232         * avoid always copying strings in this case, we first roll over the
233         * string to see if there'll be a change.
234         *
235         * This is clearly a subjective design choice but my industry
236         * experience is that minimizing allocations is the thing to do for
237         * performances, hence I prefer iterating twice to do that.
238         */
239        boolean change = false;
240        for (IColoredString.Bucket<Color> bucket : ics) {
241            final Color in = bucket.getColor();
242            if (in == null)
243                continue;
244            final Color out = filter(in);
245            if (in != out) {
246                change = true;
247                break;
248            }
249        }
250
251        if (change) {
252            final IColoredString<Color> result = IColoredString.Impl.create();
253            for (IColoredString.Bucket<Color> bucket : ics)
254                result.append(bucket.getText(), filter(bucket.getColor()));
255            return result;
256        } else
257            /* Only one allocation: the iterator, yay \o/ */
258            return ics;
259    }
260
261    /**
262     * Gets a copy of t and modifies it to make a shade of gray with the same brightness.
263     * The doAlpha parameter causes the alpha to be considered in the calculation of brightness and also changes the
264     * returned alpha of the color, so translucent colors are considered darker and fully clear ones are black (and
265     * still fully clear).
266     * <br>
267     * This uses a perceptual calculation of brightness that matches the luma calculation used in the YCbCr color space.
268     * It does not necessarily match other brightness calculations, such as value as used in HSV.
269     * <br>
270     * Not related to reified types or any usage of "reify."
271     *
272     * @param color   a T to copy; only the copy will be modified
273     * @param doAlpha Whether to include (and hereby change) the alpha component; if false alpha is kept as-is
274     * @return A monochromatic variation of {@code t}.
275     */
276    @Override
277    public Color greify(Color color, boolean doAlpha) {
278        float luma = SColor.luma(color);
279        if(doAlpha) {
280            luma *= color.a;
281            return get(luma, luma, luma, luma);
282        }
283        return get(luma, luma, luma, color.a);
284
285    }
286
287    public Color get(long c)
288    {
289        return get((int)((c >>> 24) & 0xff), (int)((c >>> 16) & 0xff), (int)((c >>> 8) & 0xff), (int)(c & 0xff));
290    }
291    public Color get(float r, float g, float b, float a)
292    {
293        return get((int)(255.9999f * r), (int)(255.9999f * g), (int)(255.9999f * b), (int)(255.9999f * a));
294    }
295    /**
296     * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change.
297     * @param start the initial Color
298     * @param end the "target" color
299     * @param change the degree to change closer to end; a change of 0.0f produces start, 1.0f produces end
300     * @return a new Color
301     */
302    @Override
303    public Color lerp(Color start, Color end, float change)
304    {
305        if(start == null || end == null)
306            return Color.CLEAR;
307        return get(
308                start.r + change * (end.r - start.r),
309                start.g + change * (end.g - start.g),
310                start.b + change * (end.b - start.b),
311                start.a + change * (end.a - start.a)
312        );
313    }
314    /**
315     * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change.
316     * @param start the initial Color
317     * @param end the "target" color
318     * @param change the degree to change closer to end; a change of 0.0 produces start, 1.0 produces end
319     * @return a new Color
320     */
321    public Color lerp(Color start, Color end, double change)
322    {
323        return lerp(start, end, (float)change);
324    }
325        @Override
326        public int getRed(Color c) {
327                return Math.round(c.r * 255f);
328        }
329
330        @Override
331        public int getGreen(Color c) {
332                return Math.round(c.g * 255f);
333        }
334
335        @Override
336        public int getBlue(Color c) {
337                return Math.round(c.b * 255f);
338        }
339
340        @Override
341        public int getAlpha(Color c) {
342                return Math.round(c.a * 255f);
343        }
344
345    /**
346     * @param c a concrete color
347     * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and
348     * eventually to purple before looping back to almost the same red (1.0, exclusive)
349     */
350    @Override
351    public float getHue(Color c) {
352        return SColor.hue(c);
353    }
354
355    /**
356     * @param c a concrete color
357     * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a
358     * bright color, exclusive)
359     */
360    @Override
361    public float getSaturation(Color c) {
362        return SColor.saturation(c);
363    }
364    /**
365     * @param r the red component in 0.0 to 1.0 range, typically
366     * @param g the green component in 0.0 to 1.0 range, typically
367     * @param b the blue component in 0.0 to 1.0 range, typically
368     * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a
369     * bright color, exclusive)
370     */
371    public float getSaturation(float r, float g, float b) {
372        float min = Math.min(Math.min(r, g ), b);    //Min. value of RGB
373        float max = Math.max(Math.max(r, g), b);    //Min. value of RGB
374        float delta = max - min;                     //Delta RGB value
375
376        float saturation;
377
378        if ( delta < 0.0001f )                     //This is a gray, no chroma...
379        {
380            saturation = 0;
381        }
382        else                                    //Chromatic data...
383        {
384            saturation = delta / max;
385        }
386        return saturation;
387    }
388
389    /**
390     * @param r the red component in 0.0 to 1.0 range, typically
391     * @param g the green component in 0.0 to 1.0 range, typically
392     * @param b the blue component in 0.0 to 1.0 range, typically
393     * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to
394     * 1.0 (very bright, inclusive).
395     */
396    public float getValue(float r, float g, float b)
397    {
398        return Math.max(Math.max(r, g), b);
399    }
400
401    /**
402     * @param r the red component in 0.0 to 1.0 range, typically
403     * @param g the green component in 0.0 to 1.0 range, typically
404     * @param b the blue component in 0.0 to 1.0 range, typically
405     * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and
406     * eventually to purple before looping back to almost the same red (1.0, exclusive)
407     */
408    public float getHue(float r, float g, float b) {
409        float min = Math.min(Math.min(r, g ), b);    //Min. value of RGB
410        float max = Math.max(Math.max(r, g), b);    //Min. value of RGB
411        float delta = max - min;                     //Delta RGB value
412
413        float hue;
414
415        if ( delta < 0.0001f )                     //This is a gray, no chroma...
416        {
417            hue = 0;                                //HSV results from 0 to 1
418        }
419        else                                    //Chromatic data...
420        {
421            float rDelta = (((max - r) / 6f) + (delta / 2f)) / delta;
422            float gDelta = (((max - g) / 6f) + (delta / 2f)) / delta;
423            float bDelta = (((max - b) / 6f) + (delta / 2f)) / delta;
424
425            if       (r == max) hue = bDelta - gDelta;
426            else if (g == max) hue = (1f / 3f) + rDelta - bDelta;
427            else                 hue = (2f / 3f) + gDelta - rDelta;
428
429            if (hue < 0) hue += 1f;
430            else if (hue > 1) hue -= 1;
431        }
432        return hue;
433    }
434
435    /**
436     * @param c a concrete color
437     * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to
438     * 1.0 (very bright, inclusive).
439     */
440    @Override
441    public float getValue(Color c) {
442        return SColor.value(c);
443    }
444
445    public static int encode (Color color) {
446        if (color == null)
447            return 0;
448        return color.toIntBits();
449    }
450
451    /**
452     * Gets a modified copy of color as if it is lit with a colored light source.
453     * @param color the color to shine the light on
454     * @param light the color of the light source
455     * @return a copy of the Color color that factors in the lighting of the Color light.
456     */
457    public Color lightWith(Color color, Color light)
458    {
459        return filter(color.cpy().mul(light));
460    }
461
462    /**
463     * Lightens a color by degree and returns the new color (mixed with white).
464     * @param color the color to lighten
465     * @param degree a float between 0.0f and 1.0f; more makes it lighter
466     * @return the lightened (and if a filter is used, also filtered) new color
467     */
468    public Color light(Color color, float degree)
469    {
470        return lerp(color, Color.WHITE, degree);
471    }
472
473    /**
474     * Lightens a color by degree and returns the new color (mixed with white).
475     * @param color the color to lighten
476     * @param degree a double between 0.0 and 1.0; more makes it lighter
477     * @return the lightened (and if a filter is used, also filtered) new color
478     */
479    public Color light(Color color, double degree)
480    {
481        return lerp(color, Color.WHITE, degree);
482    }
483    /**
484     * Lightens a color slightly and returns the new color (10% mix with white).
485     * @param color the color to lighten
486     * @return the lightened (and if a filter is used, also filtered) new color
487     */
488    public Color light(Color color)
489    {
490        return lerp(color, Color.WHITE, 0.1f);
491    }
492    /**
493     * Lightens a color significantly and returns the new color (30% mix with white).
494     * @param color the color to lighten
495     * @return the lightened (and if a filter is used, also filtered) new color
496     */
497    public Color lighter(Color color)
498    {
499        return lerp(color, Color.WHITE, 0.3f);
500    }
501    /**
502     * Lightens a color massively and returns the new color (70% mix with white).
503     * @param color the color to lighten
504     * @return the lightened (and if a filter is used, also filtered) new color
505     */
506    public Color lightest(Color color)
507    {
508        return lerp(color, Color.WHITE, 0.7f);
509    }
510
511    /**
512     * Darkens a color by the specified degree and returns the new color (mixed with black).
513     * @param color the color to darken
514     * @param degree a float between 0.0f and 1.0f; more makes it darker
515     * @return the darkened (and if a filter is used, also filtered) new color
516     */
517    public Color dim(Color color, float degree)
518    {
519        return lerp(color, Color.BLACK, degree);
520    }
521
522    /**
523     * Darkens a color by the specified degree and returns the new color (mixed with black).
524     * @param color the color to darken
525     * @param degree a double between 0.0 and 1.0; more makes it darker
526     * @return the darkened (and if a filter is used, also filtered) new color
527     */
528    public Color dim(Color color, double degree)
529    {
530        return lerp(color, Color.BLACK, degree);
531    }
532    /**
533     * Darkens a color slightly and returns the new color (10% mix with black).
534     * @param color the color to darken
535     * @return the darkened (and if a filter is used, also filtered) new color
536     */
537    public Color dim(Color color)
538    {
539        return lerp(color, Color.BLACK, 0.1f);
540    }
541    /**
542     * Darkens a color significantly and returns the new color (30% mix with black).
543     * @param color the color to darken
544     * @return the darkened (and if a filter is used, also filtered) new color
545     */
546    public Color dimmer(Color color)
547    {
548        return lerp(color, Color.BLACK, 0.3f);
549    }
550    /**
551     * Darkens a color massively and returns the new color (70% mix with black).
552     * @param color the color to darken
553     * @return the darkened (and if a filter is used, also filtered) new color
554     */
555    public Color dimmest(Color color)
556    {
557        return lerp(color, Color.BLACK, 0.7f);
558    }
559
560
561    /**
562     * Gets a fully-desaturated version of the given color (keeping its brightness, but making it grayscale).
563     * @param color the color to desaturate (will not be modified)
564     * @return the grayscale version of color
565     */
566    @Override
567    public Color desaturated(Color color)
568    {
569        float f = color.r * 0.299f + color.g * 0.587f + color.b * 0.114f;
570        return get(f, f, f, color.a);
571    }
572
573    /**
574     * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat).
575     * @param color the color to desaturate
576     * @param degree a float between 0.0f and 1.0f; more makes it less colorful
577     * @return the desaturated (and if a filter is used, also filtered) new color
578     */
579    @Override
580    public Color desaturate(Color color, float degree)
581    {
582        return lerp(color, desaturated(color), degree);
583    }
584
585    /**
586     * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat).
587     * @param color the color to desaturate
588     * @param degree a double between 0.0 and 1.0; more makes it less colorful
589     * @return the desaturated (and if a filter is used, also filtered) new color
590     */
591    public Color desaturate(Color color, double degree)
592    {
593        return lerp(color, desaturated(color), degree);
594    }
595
596    /**
597     * Fully saturates color (makes it a vivid color like red or green and less gray) and returns the modified copy.
598     * Leaves alpha unchanged.
599     *
600     * @param color the color T to saturate (will not be modified)
601     * @return the saturated version of color
602     */
603    @Override
604    public Color saturated(Color color) {
605        return getHSV(getHue(color), 1f, getValue(color), getAlpha(color));
606    }
607
608    /**
609     * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and
610     * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this is
611     * likely to produce a red hue by default (if there's no hue to make vivid, it needs to choose something).
612     * @param color the color to saturate
613     * @param degree a float between 0.0f and 1.0f; more makes it more colorful
614     * @return the saturated (and if a filter is used, also filtered) new color
615     */
616    @Override
617    public Color saturate(Color color, float degree)
618    {
619        return lerp(color, saturated(color), degree);
620    }
621
622    /**
623     * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and
624     * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this is
625     * likely to produce a red hue by default (if there's no hue to make vivid, it needs to choose something).
626     * @param color the color to saturate
627     * @param degree a double between 0.0 and 1.0; more makes it more colorful
628     * @return the saturated (and if a filter is used, also filtered) new color
629     */
630    public Color saturate(Color color, double degree)
631    {
632        return lerp(color, saturated(color), degree);
633    }
634    /**
635     * Gets a fully random color that is only required to be opaque.
636     * @return a random Color
637     */
638    public Color random()
639    {
640        StatefulRNG rng = DefaultResources.getGuiRandom();
641        return get(rng.nextFloat(), rng.nextFloat(), rng.nextFloat(), 1f);
642    }
643
644    /**
645     * Blends a color with a random (opaque) color by a factor of 10% random.
646     * @param color the color to randomize
647     * @return the randomized (and if a filter is used, also filtered) new color
648     */
649    public Color randomize(Color color)
650    {
651        return lerp(color, random(), 0.1f);
652    }
653    /**
654     * Blends a color with a random (opaque) color by a factor of 30% random.
655     * @param color the color to randomize
656     * @return the randomized (and if a filter is used, also filtered) new color
657     */
658    public Color randomizeMore(Color color)
659    {
660        return lerp(color, random(), 0.3f);
661    }
662    /**
663     * Blends a color with a random (opaque) color by a factor of 70% random.
664     * @param color the color to randomize
665     * @return the randomized (and if a filter is used, also filtered) new color
666     */
667    public Color randomizeMost(Color color)
668    {
669        return lerp(color, random(), 0.7f);
670    }
671
672    /**
673     * Blends the colors A and B by a random degree.
674     * @param a a color to mix in
675     * @param b another color to mix in
676     * @return a random blend of a and b.
677     */
678    public Color randomBlend(Color a, Color b)
679    {
680        return lerp(a, b, DefaultResources.getGuiRandom().nextFloat());
681    }
682
683    public Color invert(Color start)
684    {
685        float v = getValue(start);
686        return v > 0.65f
687                ? getHSV((getHue(start) + 0.45f), 1f - getSaturation(start) * 0.85f, v * 0.1f, start.a)
688                : getHSV((getHue(start) + 0.45f), 1f - getSaturation(start) * 1.15f, 1f, start.a);
689    }
690    /**
691     * Finds a 16-step gradient going from fromColor to toColor, both included in the gradient.
692     * @param fromColor the color to start with, included in the gradient
693     * @param toColor the color to end on, included in the gradient
694     * @return a 16-element ArrayList composed of the blending steps from fromColor to toColor
695     */
696    public ArrayList<Color> gradient(Color fromColor, Color toColor)
697    {
698        ArrayList<Color> colors = new ArrayList<>(16);
699        for (int i = 0; i < 16; i++) {
700            colors.add(lerp(fromColor, toColor, i / 15f));
701        }
702        return colors;
703    }
704
705    /**
706     * Finds a gradient with the specified number of steps going from fromColor to toColor,
707     * both included in the gradient.
708     * @param fromColor the color to start with, included in the gradient
709     * @param toColor the color to end on, included in the gradient
710     * @param steps the number of elements to use in the gradient
711     * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps
712     */
713    public ArrayList<Color> gradient(Color fromColor, Color toColor, int steps)
714    {
715        return gradient(fromColor, toColor, steps, Interpolation.linear);
716    }
717
718
719    /**
720     * Finds a gradient with the specified number of steps going from fromColor to midColor, then midColor to (possibly)
721     * fromColor, with both included in the gradient but fromColor only repeated at the end if the number of steps is odd.
722     * @param fromColor the color to start with (and end with, if steps is an odd number), included in the gradient
723     * @param midColor the color to use in the middle of the loop, included in the gradient
724     * @param steps the number of elements to use in the gradient, will be at least 3
725     * @return an ArrayList composed of the blending steps from fromColor to midColor to fromColor again, with length equal to steps
726     */
727    public ArrayList<Color> loopingGradient(Color fromColor, Color midColor, int steps)
728    {
729        return loopingGradient(fromColor, midColor, steps, Interpolation.linear);
730    }
731
732    /**
733     * Finds a gradient with the specified number of steps going from fromColor to toColor, both included in the
734     * gradient. The interpolation argument can be used to make the color stay close to fromColor and/or toColor longer
735     * than it would normally, or shorter if the middle colors are desirable.
736     * @param fromColor the color to start with, included in the gradient
737     * @param toColor the color to end on, included in the gradient
738     * @param steps the number of elements to use in the gradient
739     * @param interpolation a libGDX Interpolation that defines how quickly the color changes during the transition
740     * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps
741     */
742    public ArrayList<Color> gradient(Color fromColor, Color toColor, int steps, Interpolation interpolation)
743    {
744        ArrayList<Color> colors = new ArrayList<>(Math.max(steps, 1));
745        colors.add(filter(fromColor));
746        if(steps < 2)
747            return colors;
748        for (float i = 1; i < steps; i++) {
749            colors.add(lerp(fromColor, toColor, interpolation.apply(i / (steps - 1))));
750        }
751        return colors;
752    }
753
754    /**
755     * Finds a gradient with the specified number of steps going from fromColor to midColor, then midColor to (possibly)
756     * fromColor, with both included in the gradient but fromColor only repeated at the end if the number of steps is
757     * odd. The interpolation argument can be used to make the color linger for a while with colors close to fromColor
758     * or midColor, or to do the opposite and quickly change from one and spend more time in the middle.
759     * @param fromColor the color to start with (and end with, if steps is an odd number), included in the gradient
760     * @param midColor the color to use in the middle of the loop, included in the gradient
761     * @param steps the number of elements to use in the gradient, will be at least 3
762     * @param interpolation a libGDX Interpolation that defines how quickly the color changes at the start and end of
763     *                      each transition, both from fromColor to midColor as well as back to fromColor
764     * @return an ArrayList composed of the blending steps from fromColor to midColor to fromColor again, with length equal to steps
765     */
766    public ArrayList<Color> loopingGradient(Color fromColor, Color midColor, int steps, Interpolation interpolation)
767    {
768        ArrayList<Color> colors = new ArrayList<>(Math.max(3, steps));
769        colors.add(filter(fromColor));
770        for (float i = 1; i < steps * 0.5f; i++) {
771            colors.add(lerp(fromColor, midColor, interpolation.apply(i / (steps * 0.5f))));
772        }
773        for (float i = 0, c = steps * 0.5f; c < steps; i++, c++) {
774            colors.add(lerp(midColor, fromColor, interpolation.apply(i / (steps * 0.5f))));
775        }
776        return colors;
777    }
778
779    /**
780     * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and
781     * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should
782     * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more
783     * likely to see colors that appear for shorter spans on the color wheel, like orange.
784     * Produces fully saturated and max-brightness colors on the rainbow, which is what many people expect for a rainbow.
785     * @param steps the number of different Color elements to generate in the returned ArrayList
786     * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc.
787     */
788    public ArrayList<Color> rainbow(int steps)
789    {
790        return rainbow(1f, 1f, 1f, steps);
791    }
792
793    /**
794     * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and
795     * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should
796     * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more
797     * likely to see colors that appear for shorter spans on the color wheel, like orange.
798     * Uses the given saturation and value for all colors in the rainbow, and only changes hue.
799     * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale
800     * @param value the brightness of the rainbow's colors; 1.0 is brightest
801     * @param steps the number of different Color elements to generate in the returned ArrayList
802     * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc.
803     */
804    public ArrayList<Color> rainbow(float saturation, float value, int steps)
805    {
806        return rainbow(saturation, value, 1f, steps);
807    }
808
809    /**
810     * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and
811     * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should
812     * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more
813     * likely to see colors that appear for shorter spans on the color wheel, like orange.
814     * Uses the given saturation, value, and opacity for all colors in the rainbow, and only changes hue.
815     * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale
816     * @param value the brightness of the rainbow's colors; 1.0 is brightest
817     * @param opacity the alpha value of all colors in the rainbow; 0.0 is fully transparent and 1.0 is opaque
818     * @param steps the number of different Color elements to generate in the returned ArrayList
819     * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc.
820     */
821    public ArrayList<Color> rainbow(float saturation, float value, float opacity, int steps)
822    {
823        steps = Math.max(steps, 1);
824        ArrayList<Color> colors = new ArrayList<>(steps);
825        for (float i = 0; i < 1f - 0.5f / steps; i+= 1.0f / steps) {
826            colors.add(filter(getHSV(i, saturation, value, opacity)));
827        }
828        return colors;
829    }
830
831    /**
832     * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and
833     * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should
834     * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more
835     * likely to see colors that appear for shorter spans on the color wheel, like orange.
836     * Uses the given saturation and value for all colors in the rainbow, and only changes hue.
837     * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale
838     * @param value the brightness of the rainbow's colors; 1.0 is brightest
839     * @param steps the number of different Color elements to generate in the returned ArrayList
840     * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc.
841     */
842    public ArrayList<Color> rainbow(double saturation, double value, int steps)
843    {
844        return rainbow((float)saturation, (float)value, 1f, steps);
845    }
846
847    /**
848     * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and
849     * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should
850     * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more
851     * likely to see colors that appear for shorter spans on the color wheel, like orange.
852     * Uses the given saturation, value, and opacity for all colors in the rainbow, and only changes hue.
853     * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale
854     * @param value the brightness of the rainbow's colors; 1.0 is brightest
855     * @param opacity the alpha value of all colors in the rainbow; 0.0 is fully transparent and 1.0 is opaque
856     * @param steps the number of different Color elements to generate in the returned ArrayList
857     * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc.
858     */
859    public ArrayList<Color> rainbow(double saturation, double value, double opacity, int steps)
860    {
861        return rainbow((float)saturation, (float)value, (float)opacity, steps);
862    }
863    /**
864     * Finds a gradient with the specified number of steps going from fromColor to toColor, both included in the
865     * gradient. This does not typically take a direct path on its way between fromColor and toColor, and is useful to
866     * generate a wide variety of colors that can be confined to a rough amount of maximum difference by choosing values
867     * for fromColor and toColor that are more similar.
868     * <br>
869     * Try using colors for fromColor and toColor that have different r, g, and b values, such as gray and white, then
870     * compare to colors that don't differ on, for example, r, such as bright red and pink. In the first case, red,
871     * green, blue, and many other colors will be generated if there are enough steps; in the second case, red will be
872     * at the same level in all generated colors (very high, so no pure blue or pure green, but purple and yellow are
873     * possible). This should help illustrate how this chooses how far to "zig-zag" off the straight-line path.
874     * @param fromColor the color to start with, included in the gradient
875     * @param toColor the color to end on, included in the gradient
876     * @param steps the number of elements to use in the gradient; ideally no greater than 345 to avoid duplicates
877     * @return an ArrayList composed of the zig-zag steps from fromColor to toColor, with length equal to steps
878     */
879    public ArrayList<Color> zigzagGradient(Color fromColor, Color toColor, int steps)
880    {
881        CoordPacker.init();
882        ArrayList<Color> colors = new ArrayList<>(Math.max(steps, 1));
883        colors.add(filter(fromColor));
884        if(steps < 2)
885            return colors;
886        float dr = toColor.r - fromColor.r, dg = toColor.g - fromColor.g, db = toColor.b - fromColor.b,
887                a = fromColor.a, cr, cg, cb;
888        int decoded;
889        for (float i = 1; i < steps; i++) {
890            // 345 happens to be the distance on our 3D Hilbert curve that corresponds to (7,7,7).
891            decoded = Math.round(345 * (i / (steps - 1)));
892            cr = (CoordPacker.hilbert3X[decoded] / 7f) * dr + fromColor.r;
893            cg = (CoordPacker.hilbert3Y[decoded] / 7f) * dg + fromColor.g;
894            cb = (CoordPacker.hilbert3Z[decoded] / 7f) * db + fromColor.b;
895            colors.add(get(cr, cg, cb, a));
896        }
897        return colors;
898    }
899
900    @Override
901    public String toString() {
902        return "SquidColorCenter{" +
903                "filter=" + (filter == null ? "null" : filter.getClass().getSimpleName()) +
904                ",granularity=" + granularity +
905                '}';
906    }
907    /**
908     * It clears the cache. You may need to do this to limit the cache to the colors used in a specific section.
909     * This is also useful if a Filter changes what colors it should return on a frame-by-frame basis; in that case,
910     * you can call clearCache() at the start or end of a frame to ensure the next frame gets different colors.
911     */
912    public void clearCache()
913    {
914        cache.clear();
915    }
916
917    /**
918     * The actual cache is not public, but there are cases where you may want to know how many different colors are
919     * actually used in a frame or a section of the game. If the cache was emptied (which might be from calling
920     * {@link #clearCache()}), some colors were requested, then this is called, the returned int should be the
921     * count of distinct colors this IColorCenter had created and cached; duplicates won't be counted twice.
922     * @return
923     */
924    public int cacheSize()
925    {
926        return cache.size;
927    }
928
929    /**
930     * You may want to copy colors between IColorCenter instances that have different create() methods -- and as
931     * such, will have different values for the same keys in the cache. This allows you to copy the cache from other
932     * into this Skeleton, but using this Skeleton's create() method.
933     * @param other another Skeleton of the same type that will have its cache copied into this Skeleton
934     */
935    public void copyCache(SquidColorCenter other)
936    {
937        cache.putAll(other.cache);
938    }
939
940}