001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.graphics.Color;
004import com.badlogic.gdx.math.MathUtils;
005import squidpony.IFilter;
006import squidpony.squidmath.MathExtras;
007import squidpony.squidmath.ThrustAltRNG;
008
009import java.util.ArrayList;
010import java.util.Collection;
011
012/**
013 * Implementations of {@link IFilter}, that all are meant to perform different changes to colors before they are
014 * created. They should usually be passed to SquidColorCenter's constructor, which can use them.
015 * <br>
016 * Created by Tommy Ettinger on 10/31/2015.
017 */
018public final class Filters {
019
020        private Filters() {
021                /* You should not build me */
022        }
023
024    /**
025     * An IFilter that does nothing to the colors it is given but pass them along unchanged.
026     */
027    public static class IdentityFilter implements IFilter<Color>
028    {
029        public IdentityFilter()
030        {
031                /* Nothing to do */
032        }
033
034        @Override
035        public Color alter(float r, float g, float b, float a) {
036            return new Color(r, g, b, a);
037        }
038    }
039    public static class ChainFilter implements IFilter<Color>
040    {
041        public ArrayList<IFilter<Color>> chains;
042        public ChainFilter(IFilter<Color> firstFilter, IFilter<Color> secondFilter)
043        {
044            chains = new ArrayList<>(2);
045            chains.add(firstFilter);
046            chains.add(secondFilter);
047        }
048        public ChainFilter(IFilter<Color> firstFilter, IFilter<Color> secondFilter, IFilter<Color> thirdFilter)
049        {
050            chains = new ArrayList<>(2);
051            chains.add(firstFilter);
052            chains.add(secondFilter);
053            chains.add(thirdFilter);
054        }
055        public ChainFilter(Collection<IFilter<Color>> filters)
056        {
057            if(filters == null || filters.isEmpty())
058            {
059                chains = new ArrayList<>();
060                chains.add(new IdentityFilter());
061            }
062            else chains = new ArrayList<>(filters);
063        }
064        @Override
065        public Color alter(float r, float g, float b, float a) {
066            Color c = chains.get(0).alter(r, g, b, a);
067            for (int i = 1; i < chains.size(); i++) {
068                c = chains.get(i).alter(c.r, c.g, c.b, c.a);
069            }
070            return c;
071        }
072    }
073    /**
074     * An IFilter that converts all colors passed to it to grayscale, like a black and white film.
075     */
076    public static class GrayscaleFilter implements IFilter<Color>
077    {
078        public GrayscaleFilter()
079        {
080                /* Nothing to do */
081        }
082
083        @Override
084        public Color alter(float r, float g, float b, float a) {
085            float v = (r + g + b) / 3f;
086            return new Color(v, v, v, a);
087        }
088
089    }
090
091    /**
092     * An IFilter that tracks the highest brightness for any component it was assigned and stores it in its {@link #state}
093     * field. You probably want to set state to 0f before filtering a bunch of colors, if this is being used to find
094     * the brightest color on the screen.
095     */
096    public static class MaxValueFilter implements IFilter<Color>
097    {
098        public float state;
099        public MaxValueFilter()
100        {
101            state = 0f;
102        }
103
104        @Override
105        public Color alter(float r, float g, float b, float a) {
106            state = Math.max(state, Math.max(r, Math.max(g, b)));
107            return new Color(r, g, b, a);
108        }
109
110    }
111
112    /**
113     * An IFilter that performs a brightness adjustment to make dark areas lighter and light areas not much less bright.
114     */
115    public static class GammaCorrectFilter implements IFilter<Color> {
116        public float gamma;
117        
118        public float maxValue;
119        /**
120         * Sets up a GammaCorrectFilter with the desired gamma adjustment.
121         *
122         * @param gamma    should be 1.0 or less, and must be greater than 0. Typical values are between 0.4 to 0.8.
123         * @param maxValue the maximum brightness in the colors this will be passed; use MaxValueFilter for this
124         */
125        public GammaCorrectFilter(float gamma, float maxValue) {
126            this.gamma = gamma;
127            this.maxValue = 1f / (float) Math.pow(maxValue, gamma);
128        }
129
130        @Override
131        public Color alter(float r, float g, float b, float a) {
132            return new Color(maxValue * (float) Math.pow(r, gamma),
133                    maxValue * (float) Math.pow(g, gamma),
134                    maxValue * (float) Math.pow(b, gamma),
135                    a);
136        }
137    }
138    /**
139     * An IFilter that is constructed with a color and linear-interpolates any color it is told to alter toward the color
140     * it was constructed with.
141     */
142    public static class LerpFilter implements IFilter<Color> {
143        public float r, g, b, a, amount;
144        /**
145         * Sets up a LerpFilter with the desired color to linearly interpolate towards.
146         *
147         * @param r the red component to lerp towards
148         * @param g the green component to lerp towards
149         * @param b the blue component to lerp towards
150         * @param a the opacity component to lerp towards
151         * @param amount the amount to lerp by, should be between 0.0 and 1.0
152         */
153        public LerpFilter(float r, float g, float b, float a, float amount)
154        {
155            this.r = r;
156            this.g = g;
157            this.b = b;
158            this.a = a;
159            this.amount = amount;
160        }
161        /**
162         * Sets up a LerpFilter with the desired color to linearly interpolate towards.
163         *
164         * @param color the Color to lerp towards
165         * @param amount the amount to lerp by, should be between 0.0 and 1.0
166         */
167        public LerpFilter(Color color, float amount) {
168            this.r = color.r;
169            this.g = color.g;
170            this.b = color.b;
171            this.a = color.a;
172            this.amount = amount;
173        }
174
175        @Override
176        public Color alter(float r, float g, float b, float a) {
177            return new Color(r, g, b, a).lerp(this.r, this.g, this.b, this.a, this.amount);
178        }
179
180    }
181    /**
182     * An IFilter that is constructed with a group of colors and linear-interpolates any color it is told to alter toward
183     * the color it was constructed with that has the closest hue.
184     */
185    public static class MultiLerpFilter implements IFilter<Color> {
186        private SquidColorCenter globalSCC;
187        public float[] state;
188        /**
189         * Sets up a MultiLerpFilter with the desired colors to linearly interpolate towards; the lengths of each given
190         * array should be identical.
191         *
192         * @param r the red components to lerp towards
193         * @param g the green components to lerp towards
194         * @param b the blue components to lerp towards
195         * @param a the opacity components to lerp towards
196         * @param amount the amounts to lerp by, should each be between 0.0 and 1.0
197         */
198        public MultiLerpFilter(float[] r, float[] g, float[] b, float[] a, float[] amount) {
199            state = new float[Math.min(r.length, Math.min(g.length, Math.min(b.length,
200                    Math.min(a.length, amount.length)))) * 6];
201            globalSCC = DefaultResources.getSCC();
202            for (int i = 0; i < state.length / 6; i++) {
203                state[i * 6] = r[i];
204                state[i * 6 + 1] = g[i];
205                state[i * 6 + 2] = b[i];
206                state[i * 6 + 3] = a[i];
207                state[i * 6 + 4] = amount[i];
208                state[i * 6 + 5] = globalSCC.getHue(r[i], g[i], b[i]);
209            }
210        }/**
211         * Sets up a MultiLerpFilter with the desired colors to linearly interpolate towards and their amounts.
212         *
213         * @param colors the Colors to lerp towards
214         * @param amount the amounts to lerp by, should each be between 0.0 and 1.0
215         */
216        public MultiLerpFilter(Color[] colors, float[] amount) {
217            state = new float[Math.min(colors.length, amount.length) * 6];
218            globalSCC = DefaultResources.getSCC();
219            for (int i = 0; i < state.length / 6; i++) {
220                state[i * 6] = colors[i].r;
221                state[i * 6 + 1] = colors[i].g;
222                state[i * 6 + 2] = colors[i].b;
223                state[i * 6 + 3] = colors[i].a;
224                state[i * 6 + 4] = amount[i];
225                state[i * 6 + 5] = globalSCC.getHue(colors[i]);
226            }
227        }
228
229        @Override
230        public Color alter(float r, float g, float b, float a) {
231            float givenH = globalSCC.getHue(r, g, b), givenS = globalSCC.getSaturation(r, g, b),
232                    minDiff = 999.0f, temp;
233            if(givenS < 0.05)
234                return new Color(r, g, b, a);
235            int choice = 0;
236            for (int i = 5; i < state.length; i += 6) {
237                temp = state[i] - givenH;
238                temp = (temp >= 0.5f) ? Math.abs(temp - 1f) % 1f : Math.abs(temp);
239                if(temp < minDiff) {
240                    minDiff = temp;
241                    choice = i;
242                }
243            }
244            choice /= 6;  // rounds down
245            return new Color(r, g, b, a).lerp(state[choice * 6], state[choice * 6 + 1], state[choice * 6 + 2],
246                    state[choice * 6 + 3], state[choice * 6 + 4]);
247        }
248    }
249
250    /**
251     * An IFilter that is constructed with a color and makes any color it is told to alter have the same hue as the given
252     * color, have saturation that is somewhere between the given color's and the altered colors, and chiefly is
253     * distinguishable from other colors by value. Useful for sepia effects, which can be created satisfactorily with
254     * {@code new Filters.ColorizeFilter(SColor.CLOVE_BROWN, 0.6f, 0.0f)}.
255     */
256    public static class ColorizeFilter implements IFilter<Color> {
257        private SquidColorCenter globalSCC;
258        public float targetHue, targetSaturation, saturationMultiplier, valueModifier;
259        /**
260         * Sets up a ColorizeFilter with the desired color to colorize towards.
261         *
262         * @param r the red component to colorize towards
263         * @param g the green component to colorize towards
264         * @param b the blue component to colorize towards
265         */
266        public ColorizeFilter(float r, float g, float b) {
267            globalSCC = DefaultResources.getSCC();
268            targetHue = globalSCC.getHue(r, g, b);
269            targetSaturation = globalSCC.getSaturation(r, g, b);
270            saturationMultiplier = 1f;
271            valueModifier = 0f;
272        }
273        /**
274         * Sets up a ColorizeFilter with the desired color to colorize towards.
275         *
276         * @param color the Color to colorize towards
277         */
278        public ColorizeFilter(Color color) {
279            globalSCC = DefaultResources.getSCC();
280            targetHue = globalSCC.getHue(color);
281            targetSaturation = globalSCC.getSaturation(color);
282            saturationMultiplier = 1f;
283            valueModifier = 0f;
284        }
285        /**
286         * Sets up a ColorizeFilter with the desired color to colorize towards.
287         *
288         * @param r the red component to colorize towards
289         * @param g the green component to colorize towards
290         * @param b the blue component to colorize towards
291         * @param saturationMultiplier a multiplier to apply to the final color's saturation; may be greater than 1
292         * @param valueModifier a modifier that affects the final brightness value of any color this alters;
293         *                      typically very small, such as in the -0.2f to 0.2f range
294         */
295        public ColorizeFilter(float r, float g, float b, float saturationMultiplier, float valueModifier) {
296            globalSCC = DefaultResources.getSCC();
297            targetHue = globalSCC.getHue(r, g, b);
298            targetSaturation = globalSCC.getSaturation(r, g, b);
299            this.saturationMultiplier = saturationMultiplier;
300            this.valueModifier = valueModifier;
301        }
302        /**
303         * Sets up a ColorizeFilter with the desired color to colorize towards.
304         *
305         * @param color the Color to colorize towards
306         * @param saturationMultiplier a multiplier to apply to the final color's saturation; may be greater than 1
307         * @param valueModifier a modifier that affects the final brightness value of any color this alters;
308         *                      typically very small, such as in the -0.2f to 0.2f range
309         */
310        public ColorizeFilter(Color color, float saturationMultiplier, float valueModifier) {
311            globalSCC = DefaultResources.getSCC();
312            targetHue = globalSCC.getHue(color);
313            targetSaturation = globalSCC.getSaturation(color);
314            this.saturationMultiplier = saturationMultiplier;
315            this.valueModifier = valueModifier;
316        }
317
318        @Override
319        public Color alter(float r, float g, float b, float a) {
320            return globalSCC.getHSV(
321                    targetHue,
322                    Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) + targetSaturation) * 0.5f * saturationMultiplier, 1f)),
323                    globalSCC.getValue(r, g, b) * (1f - valueModifier) + valueModifier,
324                    a);
325        }
326    }
327    /**
328     * An IFilter that makes the colors requested from it highly saturated, with the original hue, value and a timer that
329     * increments very slowly altering hue, with hue, value and the timer altering saturation, and the original hue,
330     * saturation, and value all altering value. It should look like a hallucination.
331     * <br>
332     * A short (poorly recorded) video can be seen here http://i.imgur.com/SEw2LXe.gifv ; performance should be smoother
333     * during actual gameplay.
334     */
335    public static class HallucinateFilter implements IFilter<Color> {
336        private SquidColorCenter globalSCC;
337        /**
338         * Sets up a HallucinateFilter with the timer at 0..
339         */
340        public HallucinateFilter() {
341            globalSCC = DefaultResources.getSCC();
342        }
343        @Override
344        public Color alter(float r, float g, float b, float a) {
345            float t = (System.currentTimeMillis() & 0xfff) * 0x1p-12f,
346                    h = globalSCC.getHue(r, g, b),
347                    s = globalSCC.getSaturation(r, g, b),
348                    v = globalSCC.getValue(r, g, b);
349            return globalSCC.getHSV(
350                    (v * 4f + h + t) % 1.0f,
351                    Math.max(0f, Math.min((h + v) * 0.65f + t * 0.4f, 1f)),
352                    MathExtras.clamp((h + v + s) * 0.35f + 0.7f, 0f, 1f),
353                    a);
354        }
355
356    }
357
358
359    /**
360     * An IFilter that multiplies the saturation of any color requested from it by a number given during construction.
361     * When desaturating, you may want to prefer {@link SaturationValueFilter}, because reducing saturation increases
362     * perceived brightness (value), and desaturating an ocean-blue color will make it look like ice if the value
363     * isn't decreased as well. This class is perfectly fine for increasing saturation, in general.
364     */
365    public static class SaturationFilter implements IFilter<Color> {
366        private SquidColorCenter globalSCC;
367        public float multiplier;
368        /**
369         * Sets up a SaturationFilter with the desired saturation multiplier. Using a multiplier of 0f, as you would
370         * expect, makes the image grayscale. Using a multiplier of 0.5 make the image "muted", with no truly bright
371         * colors, while 1.0f makes no change, and and any numbers higher than 1.0f will make the image more saturated,
372         * with the exception of colors that were already grayscale or close to it. This clamps the result, so there's
373         * no need to worry about using too high of a saturation multiplier.
374         *
375         * @param multiplier the amount to multiply each requested color's saturation by; 1.0f means "no change"
376         */
377        public SaturationFilter(float multiplier) {
378            globalSCC = DefaultResources.getSCC();
379            this.multiplier = multiplier;
380        }
381
382        @Override
383        public Color alter(float r, float g, float b, float a) {
384            return globalSCC.getHSV(
385                    globalSCC.getHue(r, g, b),
386                    Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) * multiplier), 1f)),
387                    globalSCC.getValue(r, g, b),
388                    a);
389        }
390
391    }
392
393    /**
394     * An IFilter that multiplies the saturation and the value of any color requested from it by different numbers given
395     * during construction.
396     */
397    public static class SaturationValueFilter implements IFilter<Color> {
398        private SquidColorCenter globalSCC;
399        public float saturationMultiplier, valueMultiplier;
400        /**
401         * Sets up a SaturationValueFilter with the desired saturation and value multipliers. Using a multiplier of 0f for
402         * saturation, as you would expect, makes the image grayscale. Using a multiplier of 0f for value makes all
403         * colors identicaly black; this is probably not a good idea. Using a multiplier of 0.5 for saturation will
404         * make the image "muted", with no truly bright colors, while 1.0f for saturation makes no change, and and any
405         * numbers higher than 1.0f for saturation will make the image more saturated, with the exception of colors that
406         * were already grayscale or close to it. Using 0.5f for value will darken the image significantly, 1.0f for
407         * value will keep it the same for brightness, and higher than 1.0f will lighten it. This clamps the result, so
408         * there's no need to worry about using too high of a saturation or value multiplier.
409         *
410         * @param saturation the amount to multiply each requested color's saturation by; 1.0f means "no change"
411         * @param value      the amount to multiply each requested color's value (lightness) by; 1.0f means "no change"
412         */
413        public SaturationValueFilter(float saturation, float value) {
414            globalSCC = DefaultResources.getSCC();
415            saturationMultiplier = saturation;
416            valueMultiplier = value;
417        }
418
419        @Override
420        public Color alter(float r, float g, float b, float a) {
421            return globalSCC.getHSV(
422                    globalSCC.getHue(r, g, b),
423                    Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) * saturationMultiplier), 1f)),
424                    Math.max(0f, Math.min((globalSCC.getValue(r, g, b) * valueMultiplier), 1f)),
425                    a);
426        }
427
428    }
429
430    /**
431     * An IFilter that is constructed with a palette of colors and randomly increases or decreases the red, green, and
432     * blue components of any color it is told to alter. Good for a "glitchy screen" effect.
433     */
434    public static class WiggleFilter implements IFilter<Color> {
435        long rngState;
436        public WiggleFilter()
437        {
438            rngState = (long) ((Math.random() - 0.5) * 0x10000000000000L)
439                    ^ (long) (((Math.random() - 0.5) * 2.0) * 0x8000000000000000L);
440        }
441        @Override
442        public Color alter(float r, float g, float b, float a) {
443            return new Color(r - 0.1f + ThrustAltRNG.determineFloat(++rngState) * 0.2f,
444                    g - 0.1f + ThrustAltRNG.determineFloat(++rngState) * 0.2f,
445                    b - 0.1f + ThrustAltRNG.determineFloat(++rngState) * 0.2f,
446                    a);
447        }
448    }
449
450    /**
451     * An IFilter that is constructed with a group of colors and forces any color it is told to alter to exactly
452     * the color it was constructed with that has the closest red, green, and blue components. A convenient way to
453     * use this is to pass in one of the color series from SColor, such as {@link SColor#RED_SERIES} or
454     * {@link SColor#ACHROMATIC_SERIES}. This can also be used to enforce usage of a limited color palette such as
455     * one of DawnBringer's popular pixel art palettes, {@link SColor#DAWNBRINGER_16} and
456     * {@link SColor#DAWNBRINGER_32}. Beyond 32 colors, the palettes {@link SColor#VARIED_PALETTE} with 56 colors
457     * and {@link SColor#COLOR_WHEEL_PALETTE} with 198 colors can be used.
458     *
459     * Preview using BLUE_GREEN_SERIES foreground, ACHROMATIC_SERIES background: http://i.imgur.com/2HdZpC9.png
460     */
461    public static class PaletteFilter implements IFilter<Color> {
462        /**
463         * The array of Color objects this will use as a palette.
464         */
465        public Color[] colorStore;
466        /**
467         * Sets up a PaletteFilter with the exact colors to use as individual components; the lengths of each given
468         * array should be identical.
469         *
470         * @param r the red components to use
471         * @param g the green components to use
472         * @param b the blue components to use
473         */
474        public PaletteFilter(float[] r, float[] g, float[] b) {
475            colorStore = new Color[Math.min(r.length, Math.min(g.length, b.length))];
476//            state = new float[colorStore.length * 3];
477            for (int i = 0; i < colorStore.length; i++) {
478                colorStore[i] = new Color(
479                MathUtils.clamp(r[i], 0f, 1f),
480                MathUtils.clamp(g[i], 0f, 1f),
481                MathUtils.clamp(b[i], 0f, 1f),
482                        1f);
483            }
484        }/**
485         * Sets up a PaletteFilter with the exact colors to use as Colors. A convenient way to
486         * use this is to pass in one of the color series from SColor, such as RED_SERIES or ACHROMATIC_SERIES.
487         * The alpha component of each color in the palette is ignored, since when a color is requested for
488         * filtering, its alpha is respected and only its red, green, and blue components are changed.
489         * The {@code colors} array is used verbatim (as a reference, not a copy), so changes to the Color values inside
490         * it will change how this PaletteFilter works (possibly badly). If you expect to edit the array you give as a
491         * parameter to this, it may be optimal to give a temporary copy.
492         * @param colors the Colors to use as an array; will be referenced in the PaletteFilter, so changing items in
493         *               this array will change what Colors will be used
494         */
495        public PaletteFilter(Color[] colors) {
496            colorStore = colors;
497//            state = new float[colors.length * 3];
498//            for (int i = 0; i < colors.length; i++) {
499//                state[i * 3] = colors[i].r;
500//                state[i * 3 + 1] = colors[i].g;
501//                state[i * 3 + 2] = colors[i].b;
502//            }
503        }
504
505        @Override
506        public Color alter(float r, float g, float b, float a) {
507            int diff = 0x7fffffff, temp;
508            int choice = 0;
509            r = SColor.floatGet(r, g, b, a);
510            for (int i = 0; i < colorStore.length; i++) {
511                temp = SColor.difference2(r, colorStore[i]);
512                if(temp < diff) {
513                    diff = temp;
514                    choice = i;
515                }
516            }
517            if(a >= 1f)
518                return colorStore[choice];
519            Color ret = colorStore[choice].cpy();
520            ret.a = a;
521            return ret;
522        }
523    }
524    /**
525     * An IFilter that alters primarily-red and primarily-green colors so they can be more easily be distinguished by
526     * people with at least some forms of red-green color-blindness (deuteranopia should be handled well, protanopia
527     * very well, and tritanopia may not benefit at all). Causes reds to be darkened and greens to be lightened if the
528     * other of the pair is not present in similar quantities (which is the case for yellows and blues).
529     */
530    public static class DistinctRedGreenFilter implements IFilter<Color> {
531        /**
532         * Constructs a DistinctRedGreenFilter. This class is a simple wrapper around a function that doesn't need
533         * member variables, so there should be little overhead with this filter.
534         */
535        public DistinctRedGreenFilter() {
536        }
537
538        @Override
539        public Color alter(float r, float g, float b, float a) {
540            float diff = g - r;
541            if(diff > 0.4f)
542                return new Color(Math.min(1f, r * (0.8f + diff * 0.5f)), Math.min(1f, g * (0.9f + diff * 0.5f)),
543                        Math.min(1f, b * (0.8f + diff * 0.5f)), a);
544            else if(diff < -0.3f)
545                return new Color(r * (0.6f - diff), g * (0.7f - diff),
546                        b * (0.7f - diff), a);
547            else
548                return new Color(r, g, b, a);
549        }
550    }
551}