001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.graphics.Color;
004import com.badlogic.gdx.math.Interpolation;
005import squidpony.IColorCenter;
006import squidpony.IFilter;
007import squidpony.squidmath.CoordPacker;
008import squidpony.squidmath.StatefulRNG;
009
010import java.util.ArrayList;
011
012/**
013 * A concrete implementation of {@link IColorCenter} for libgdx's {@link com.badlogic.gdx.graphics.Color}.
014 * Supports filtering any colors that this creates using a {@link Filter}, such as one from {@link Filters}.
015 *
016 * @author smelC
017 * @author Tommy Ettinger
018 * @see SColor Another way to obtain colors by using pre-allocated (and named) instances.
019 */
020public class SquidColorCenter extends IColorCenter.Skeleton<Color> {
021
022    /**
023     * A fresh filter-less color center.
024     */
025    public SquidColorCenter()
026    {
027        this(null);
028    }
029
030    /**
031         * A fresh filtered color center.
032         * 
033         * @param filterEffect
034         *            The filter to use.
035         */
036    public SquidColorCenter(/*Nullable*/IFilter<Color> filterEffect)
037    {
038        super(filterEffect);
039    }
040        @Override
041        protected Color create(int red, int green, int blue, int opacity) {
042                if (filter == null)
043                        /* No filtering */
044                        return new Color(red / 255f, green / 255f, blue / 255f, opacity / 255f);
045                else
046                        /* Some filtering */
047                        return filter.alter(red / 255f, green / 255f, blue / 255f, opacity / 255f);
048        }
049    @Override
050        public Color filter(Color c)
051    {
052        if(c == null)
053            return Color.CLEAR;
054        else
055                return super.filter(c);
056    }
057    public Color get(long c)
058    {
059        return get((int)((c >> 24) & 0xff), (int)((c >> 16) & 0xff), (int)((c >> 8) & 0xff), (int)(c & 0xff));
060    }
061    public Color get(float r, float g, float b, float a)
062    {
063        return get(Math.round(255 * r), Math.round(255 * g), Math.round(255 * b), Math.round(255 * a));
064    }
065
066    /**
067     * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change.
068     * @param start the initial Color
069     * @param end the "target" color
070     * @param change the degree to change closer to end; a change of 0.0f produces start, 1.0f produces end
071     * @return a new Color
072     */
073    @Override
074    public Color lerp(Color start, Color end, float change)
075    {
076        if(start == null || end == null)
077            return Color.CLEAR;
078        return get(
079                start.r + change * (end.r - start.r),
080                start.g + change * (end.g - start.g),
081                start.b + change * (end.b - start.b),
082                start.a + change * (end.a - start.a)
083        );
084    }
085        @Override
086        public int getRed(Color c) {
087                return Math.round(c.r * 255f);
088        }
089
090        @Override
091        public int getGreen(Color c) {
092                return Math.round(c.g * 255f);
093        }
094
095        @Override
096        public int getBlue(Color c) {
097                return Math.round(c.b * 255f);
098        }
099
100        @Override
101        public int getAlpha(Color c) {
102                return Math.round(c.a * 255f);
103        }
104
105    public static int encode (Color color) {
106        if (color == null)
107            return 0;
108        return (Math.round(color.r * 255.0f) << 24)
109                | (Math.round(color.g * 255.0f) << 16)
110                | (Math.round(color.b * 255.0f) << 8)
111                | Math.round(color.a * 255.0f);
112    }
113
114    /**
115     * Gets a modified copy of color as if it is lit with a colored light source.
116     * @param color the color to shine the light on
117     * @param light the color of the light source
118     * @return a copy of the Color color that factors in the lighting of the Color light.
119     */
120    public Color lightWith(Color color, Color light)
121    {
122        return filter(color.cpy().mul(light));
123    }
124
125    /**
126     * Lightens a color by degree and returns the new color (mixed with white).
127     * @param color the color to lighten
128     * @param degree a float between 0.0f and 1.0f; more makes it lighter
129     * @return the lightened (and if a filter is used, also filtered) new color
130     */
131    public Color light(Color color, float degree)
132    {
133        return lerp(color, Color.WHITE, degree);
134    }
135    /**
136     * Lightens a color slightly and returns the new color (10% mix with white).
137     * @param color the color to lighten
138     * @return the lightened (and if a filter is used, also filtered) new color
139     */
140    public Color light(Color color)
141    {
142        return lerp(color, Color.WHITE, 0.1f);
143    }
144    /**
145     * Lightens a color significantly and returns the new color (30% mix with white).
146     * @param color the color to lighten
147     * @return the lightened (and if a filter is used, also filtered) new color
148     */
149    public Color lighter(Color color)
150    {
151        return lerp(color, Color.WHITE, 0.3f);
152    }
153    /**
154     * Lightens a color massively and returns the new color (70% mix with white).
155     * @param color the color to lighten
156     * @return the lightened (and if a filter is used, also filtered) new color
157     */
158    public Color lightest(Color color)
159    {
160        return lerp(color, Color.WHITE, 0.7f);
161    }
162
163    /**
164     * Darkens a color by the specified degree and returns the new color (mixed with black).
165     * @param color the color to darken
166     * @param degree a float between 0.0f and 1.0f; more makes it darker
167     * @return the darkened (and if a filter is used, also filtered) new color
168     */
169    public Color dim(Color color, float degree)
170    {
171        return lerp(color, Color.BLACK, degree);
172    }
173    /**
174     * Darkens a color slightly and returns the new color (10% mix with black).
175     * @param color the color to darken
176     * @return the darkened (and if a filter is used, also filtered) new color
177     */
178    public Color dim(Color color)
179    {
180        return lerp(color, Color.BLACK, 0.1f);
181    }
182    /**
183     * Darkens a color significantly and returns the new color (30% mix with black).
184     * @param color the color to darken
185     * @return the darkened (and if a filter is used, also filtered) new color
186     */
187    public Color dimmer(Color color)
188    {
189        return lerp(color, Color.BLACK, 0.3f);
190    }
191    /**
192     * Darkens a color massively and returns the new color (70% mix with black).
193     * @param color the color to darken
194     * @return the darkened (and if a filter is used, also filtered) new color
195     */
196    public Color dimmest(Color color)
197    {
198        return lerp(color, Color.BLACK, 0.7f);
199    }
200
201
202    /**
203     * Gets a fully-desaturated version of the given color (keeping its brightness, but making it grayscale).
204     * @param color the color to desaturate (will not be modified)
205     * @return the grayscale version of color
206     */
207    @Override
208    public Color desaturated(Color color)
209    {
210        float f = color.r * 0.299f + color.g * 0.587f + color.b * 0.114f;
211        return get(f, f, f, color.a);
212    }
213
214    /**
215     * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat).
216     * @param color the color to desaturate
217     * @param degree a float between 0.0f and 1.0f; more makes it less colorful
218     * @return the desaturated (and if a filter is used, also filtered) new color
219     */
220    @Override
221    public Color desaturate(Color color, float degree)
222    {
223        return lerp(color, desaturated(color), degree);
224    }
225
226    /**
227     * Fully saturates color (makes it a vivid color like red or green and less gray) and returns the modified copy.
228     * Leaves alpha unchanged.
229     *
230     * @param color the color T to saturate (will not be modified)
231     * @return the saturated version of color
232     */
233    @Override
234    public Color saturated(Color color) {
235        return getHSV(getHue(color), 1f, getValue(color), getAlpha(color));
236    }
237
238    /**
239     * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and
240     * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this is
241     * likely to produce a red hue by default (if there's no hue to make vivid, it needs to choose something).
242     * @param color the color to saturate
243     * @param degree a float between 0.0f and 1.0f; more makes it more colorful
244     * @return the saturated (and if a filter is used, also filtered) new color
245     */
246    @Override
247    public Color saturate(Color color, float degree)
248    {
249        return lerp(color, saturated(color), degree);
250    }
251    /**
252     * Gets a fully random color that is only required to be opaque.
253     * @return a random Color
254     */
255    public Color random()
256    {
257        StatefulRNG rng = DefaultResources.getGuiRandom();
258        return get(rng.nextFloat(), rng.nextFloat(), rng.nextFloat(), 1f);
259    }
260
261    /**
262     * Blends a color with a random (opaque) color by a factor of 10% random.
263     * @param color the color to randomize
264     * @return the randomized (and if a filter is used, also filtered) new color
265     */
266    public Color randomize(Color color)
267    {
268        return lerp(color, random(), 0.1f);
269    }
270    /**
271     * Blends a color with a random (opaque) color by a factor of 30% random.
272     * @param color the color to randomize
273     * @return the randomized (and if a filter is used, also filtered) new color
274     */
275    public Color randomizeMore(Color color)
276    {
277        return lerp(color, random(), 0.3f);
278    }
279    /**
280     * Blends a color with a random (opaque) color by a factor of 70% random.
281     * @param color the color to randomize
282     * @return the randomized (and if a filter is used, also filtered) new color
283     */
284    public Color randomizeMost(Color color)
285    {
286        return lerp(color, random(), 0.7f);
287    }
288
289    /**
290     * Blends the colors A and B by a random degree.
291     * @param a a color to mix in
292     * @param b another color to mix in
293     * @return a random blend of a and b.
294     */
295    public Color randomBlend(Color a, Color b)
296    {
297        return lerp(a, b, DefaultResources.getGuiRandom().nextFloat());
298    }
299
300    /**
301     * Finds a 16-step gradient going from fromColor to toColor, both included in the gradient.
302     * @param fromColor the color to start with, included in the gradient
303     * @param toColor the color to end on, included in the gradient
304     * @return a 16-element ArrayList composed of the blending steps from fromColor to toColor
305     */
306    public ArrayList<Color> gradient(Color fromColor, Color toColor)
307    {
308        ArrayList<Color> colors = new ArrayList<>(16);
309        for (int i = 0; i < 16; i++) {
310            colors.add(lerp(fromColor, toColor, i / 15f));
311        }
312        return colors;
313    }
314
315    /**
316     * Finds a gradient with the specified number of steps going from fromColor to toColor,
317     * both included in the gradient.
318     * @param fromColor the color to start with, included in the gradient
319     * @param toColor the color to end on, included in the gradient
320     * @param steps the number of elements to use in the gradient
321     * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps
322     */
323    public ArrayList<Color> gradient(Color fromColor, Color toColor, int steps)
324    {
325        return gradient(fromColor, toColor, steps, Interpolation.linear);
326    }
327
328
329    /**
330     * Finds a gradient with the specified number of steps going from fromColor to midColor, then midColor to (possibly)
331     * fromColor, with both included in the gradient but fromColor only repeated at the end if the number of steps is odd.
332     * @param fromColor the color to start with (and end with, if steps is an odd number), included in the gradient
333     * @param midColor the color to use in the middle of the loop, included in the gradient
334     * @param steps the number of elements to use in the gradient, will be at least 3
335     * @return an ArrayList composed of the blending steps from fromColor to midColor to fromColor again, with length equal to steps
336     */
337    public ArrayList<Color> loopingGradient(Color fromColor, Color midColor, int steps)
338    {
339        return loopingGradient(fromColor, midColor, steps, Interpolation.linear);
340    }
341
342    /**
343     * Finds a gradient with the specified number of steps going from fromColor to toColor, both included in the
344     * gradient. The interpolation argument can be used to make the color stay close to fromColor and/or toColor longer
345     * than it would normally, or shorter if the middle colors are desirable.
346     * @param fromColor the color to start with, included in the gradient
347     * @param toColor the color to end on, included in the gradient
348     * @param steps the number of elements to use in the gradient
349     * @param interpolation a libGDX Interpolation that defines how quickly the color changes during the transition
350     * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps
351     */
352    public ArrayList<Color> gradient(Color fromColor, Color toColor, int steps, Interpolation interpolation)
353    {
354        ArrayList<Color> colors = new ArrayList<>((steps > 1) ? steps : 1);
355        colors.add(filter(fromColor));
356        if(steps < 2)
357            return colors;
358        for (float i = 1; i < steps; i++) {
359            colors.add(lerp(fromColor, toColor, interpolation.apply(i / (steps - 1))));
360        }
361        return colors;
362    }
363
364    /**
365     * Finds a gradient with the specified number of steps going from fromColor to midColor, then midColor to (possibly)
366     * fromColor, with both included in the gradient but fromColor only repeated at the end if the number of steps is
367     * odd. The interpolation argument can be used to make the color linger for a while with colors close to fromColor
368     * or midColor, or to do the opposite and quickly change from one and spend more time in the middle.
369     * @param fromColor the color to start with (and end with, if steps is an odd number), included in the gradient
370     * @param midColor the color to use in the middle of the loop, included in the gradient
371     * @param steps the number of elements to use in the gradient, will be at least 3
372     * @param interpolation a libGDX Interpolation that defines how quickly the color changes at the start and end of
373     *                      each transition, both from fromColor to midColor as well as back to fromColor
374     * @return an ArrayList composed of the blending steps from fromColor to midColor to fromColor again, with length equal to steps
375     */
376    public ArrayList<Color> loopingGradient(Color fromColor, Color midColor, int steps, Interpolation interpolation)
377    {
378        ArrayList<Color> colors = new ArrayList<>((steps > 3) ? steps : 3);
379        colors.add(filter(fromColor));
380        for (float i = 1; i < steps / 2; i++) {
381            colors.add(lerp(fromColor, midColor, interpolation.apply(i / (steps / 2))));
382        }
383        for (float i = 0, c = steps / 2; c < steps; i++, c++) {
384            colors.add(lerp(midColor, fromColor, interpolation.apply(i / (steps / 2))));
385        }
386        return colors;
387    }
388
389    /**
390     * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and
391     * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should
392     * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more
393     * likely to see colors that appear for shorter spans on the color wheel, like orange.
394     * Produces fully saturated and max-brightness colors on the rainbow, which is what many people expect for a rainbow.
395     * @param steps the number of different Color elements to generate in the returned ArrayList
396     * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc.
397     */
398    public ArrayList<Color> rainbow(int steps)
399    {
400        return rainbow(1.0f, 1.0f, steps);
401    }
402
403    /**
404     * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and
405     * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should
406     * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more
407     * likely to see colors that appear for shorter spans on the color wheel, like orange.
408     * Uses the given saturation and value for all colors in the rainbow, and only changes hue.
409     * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale
410     * @param value the brightness of the rainbow's colors; 1.0 is brightest
411     * @param steps the number of different Color elements to generate in the returned ArrayList
412     * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc.
413     */
414    public ArrayList<Color> rainbow(float saturation, float value, int steps)
415    {
416        steps = (steps > 1) ? steps : 1;
417        ArrayList<Color> colors = new ArrayList<>(steps);
418        for (float i = 0; i < 1f - 0.5f / steps; i+= 1.0f / steps) {
419            colors.add(filter(getHSV(i, saturation, value)));
420        }
421        return colors;
422    }
423
424    /**
425     * Finds a gradient with the specified number of steps going from fromColor to toColor, both included in the
426     * gradient. This does not typically take a direct path on its way between fromColor and toColor, and is useful to
427     * generate a wide variety of colors that can be confined to a rough amount of maximum difference by choosing values
428     * for fromColor and toColor that are more similar.
429     * <br>
430     * Try using colors for fromColor and toColor that have different r, g, and b values, such as gray and white, then
431     * compare to colors that don't differ on, for example, r, such as bright red and pink. In the first case, red,
432     * green, blue, and many other colors will be generated if there are enough steps; in the second case, red will be
433     * at the same level in all generated colors (very high, so no pure blue or pure green, but purple and yellow are
434     * possible). This should help illustrate how this chooses how far to "zig-zag" off the straight-line path.
435     * @param fromColor the color to start with, included in the gradient
436     * @param toColor the color to end on, included in the gradient
437     * @param steps the number of elements to use in the gradient; ideally no greater than 345 to avoid duplicates
438     * @return an ArrayList composed of the zig-zag steps from fromColor to toColor, with length equal to steps
439     */
440    public ArrayList<Color> zigzagGradient(Color fromColor, Color toColor, int steps)
441    {
442        ArrayList<Color> colors = new ArrayList<>((steps > 1) ? steps : 1);
443        colors.add(filter(fromColor));
444        if(steps < 2)
445            return colors;
446        float dr = toColor.r - fromColor.r, dg = toColor.g - fromColor.g, db = toColor.b - fromColor.b,
447                a = fromColor.a, cr, cg, cb;
448        int decoded;
449        for (float i = 1; i < steps; i++) {
450            // 345 happens to be the distance on our 3D Hilbert curve that corresponds to (7,7,7).
451            decoded = Math.round(345 * (i / (steps - 1)));
452            cr = (CoordPacker.hilbert3X[decoded] / 7f) * dr + fromColor.r;
453            cg = (CoordPacker.hilbert3Y[decoded] / 7f) * dg + fromColor.g;
454            cb = (CoordPacker.hilbert3Z[decoded] / 7f) * db + fromColor.b;
455            colors.add(get(cr, cg, cb, a));
456        }
457        return colors;
458    }
459
460    @Override
461    public String toString() {
462        return "SquidColorCenter{" +
463                "filter=" + (filter == null ? "null" : filter.getClass().getSimpleName()) +
464                '}';
465    }
466}