001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.graphics.Color;
004
005import squidpony.IFilter;
006import squidpony.squidmath.LightRNG;
007
008/**
009 * Implementations of {@link IFilter}, that all are meant to perform different changes
010 * to colors before they are created (they should be passed to SquidColorCenter's constructor, which can use them).
011 * Created by Tommy Ettinger on 10/31/2015.
012 */
013public class Filters {
014
015        private Filters() {
016                /* You should not build me */
017        }
018
019    /**
020     * A Filter that does nothing to the colors it is given but pass them along unchanged.
021     */
022    public static class IdentityFilter implements IFilter<Color>
023    {
024        public IdentityFilter()
025        {
026                /* Nothing to do */
027        }
028
029        @Override
030        public Color alter(float r, float g, float b, float a) {
031            return new Color(r, g, b, a);
032        }
033    }
034
035    /**
036     * A Filter that converts all colors passed to it to grayscale, like a black and white film.
037     */
038    public static class GrayscaleFilter implements IFilter<Color>
039    {
040        public GrayscaleFilter()
041        {
042                /* Nothing to do */
043        }
044
045        @Override
046        public Color alter(float r, float g, float b, float a) {
047            float v = (r + g + b) / 3f;
048            return new Color(v, v, v, a);
049        }
050    }
051
052    /**
053     * A Filter that tracks the highest brightness for any component it was assigned and stores it in state as the first
054     * and only element.
055     */
056    public static class MaxValueFilter extends Filter<Color>
057    {
058        public MaxValueFilter()
059        {
060            state = new float[]{0};
061        }
062
063        @Override
064        public Color alter(float r, float g, float b, float a) {
065            state[0] = Math.max(state[0], Math.max(r, Math.max(g, b)));
066            return new Color(r, g, b, a);
067        }
068
069    }
070
071    /**
072     * A Filter that performs a brightness adjustment to make dark areas lighter and light areas not much less bright.
073     */
074    public static class GammaCorrectFilter extends Filter<Color> {
075        /**
076         * Sets up a GammaCorrectFilter with the desired gamma adjustment.
077         *
078         * @param gamma    should be 1.0 or less, and must be greater than 0. Typical values are between 0.4 to 0.8.
079         * @param maxValue the maximum brightness in the colors this will be passed; use MaxValueFilter for this
080         */
081        public GammaCorrectFilter(float gamma, float maxValue) {
082            state = new float[]{gamma, 1f / (float) Math.pow(maxValue, gamma)};
083        }
084
085        @Override
086        public Color alter(float r, float g, float b, float a) {
087            return new Color(state[1] * (float) Math.pow(r, state[0]),
088                    state[1] * (float) Math.pow(g, state[0]),
089                    state[1] * (float) Math.pow(b, state[0]),
090                    a);
091        }
092    }
093    /**
094     * A Filter that is constructed with a color and linear-interpolates any color it is told to alter toward the color
095     * it was constructed with.
096     */
097    public static class LerpFilter extends Filter<Color> {
098        /**
099         * Sets up a LerpFilter with the desired color to linearly interpolate towards.
100         *
101         * @param r the red component to lerp towards
102         * @param g the green component to lerp towards
103         * @param b the blue component to lerp towards
104         * @param a the opacity component to lerp towards
105         * @param amount the amount to lerp by, should be between 0.0 and 1.0
106         */
107        public LerpFilter(float r, float g, float b, float a, float amount) {
108            state = new float[]{r, g, b, a, amount};
109        }
110        /**
111         * Sets up a LerpFilter with the desired color to linearly interpolate towards.
112         *
113         * @param color the Color to lerp towards
114         * @param amount the amount to lerp by, should be between 0.0 and 1.0
115         */
116        public LerpFilter(Color color, float amount) {
117            state = new float[]{color.r, color.g, color.b, color.a, amount};
118        }
119
120        @Override
121        public Color alter(float r, float g, float b, float a) {
122            return new Color(r, g, b, a).lerp(state[0], state[1], state[2], state[3], state[4]);
123        }
124    }
125    /**
126     * A Filter that is constructed with a group of colors and linear-interpolates any color it is told to alter toward
127     * the color it was constructed with that has the closest hue.
128     */
129    public static class MultiLerpFilter extends Filter<Color> {
130        private SquidColorCenter globalSCC;
131        /**
132         * Sets up a MultiLerpFilter with the desired colors to linearly interpolate towards; the lengths of each given
133         * array should be identical.
134         *
135         * @param r the red components to lerp towards
136         * @param g the green components to lerp towards
137         * @param b the blue components to lerp towards
138         * @param a the opacity components to lerp towards
139         * @param amount the amounts to lerp by, should each be between 0.0 and 1.0
140         */
141        public MultiLerpFilter(float[] r, float[] g, float[] b, float[] a, float[] amount) {
142            state = new float[Math.min(r.length, Math.min(g.length, Math.min(b.length,
143                    Math.min(a.length, amount.length)))) * 6];
144            globalSCC = DefaultResources.getSCC();
145            for (int i = 0; i < state.length / 6; i++) {
146                state[i * 6] = r[i];
147                state[i * 6 + 1] = g[i];
148                state[i * 6 + 2] = b[i];
149                state[i * 6 + 3] = a[i];
150                state[i * 6 + 4] = amount[i];
151                state[i * 6 + 5] = globalSCC.getHue(r[i], g[i], b[i]);
152            }
153        }/**
154         * Sets up a MultiLerpFilter with the desired colors to linearly interpolate towards and their amounts.
155         *
156         * @param colors the Colors to lerp towards
157         * @param amount the amounts to lerp by, should each be between 0.0 and 1.0
158         */
159        public MultiLerpFilter(Color[] colors, float[] amount) {
160            state = new float[Math.min(colors.length, amount.length) * 6];
161            globalSCC = DefaultResources.getSCC();
162            for (int i = 0; i < state.length / 6; i++) {
163                state[i * 6] = colors[i].r;
164                state[i * 6 + 1] = colors[i].g;
165                state[i * 6 + 2] = colors[i].b;
166                state[i * 6 + 3] = colors[i].a;
167                state[i * 6 + 4] = amount[i];
168                state[i * 6 + 5] = globalSCC.getHue(colors[i]);
169            }
170        }
171
172        @Override
173        public Color alter(float r, float g, float b, float a) {
174            float givenH = globalSCC.getHue(r, g, b), givenS = globalSCC.getSaturation(r, g, b),
175                    minDiff = 999.0f, temp;
176            if(givenS < 0.05)
177                return new Color(r, g, b, a);
178            int choice = 0;
179            for (int i = 5; i < state.length; i += 6) {
180                temp = state[i] - givenH;
181                temp = (temp >= 0.5f) ? Math.abs(temp - 1f) % 1f : Math.abs(temp);
182                if(temp < minDiff) {
183                    minDiff = temp;
184                    choice = i / 6; // rounds down
185                }
186            }
187            return new Color(r, g, b, a).lerp(state[choice * 6], state[choice * 6 + 1], state[choice * 6 + 2],
188                    state[choice * 6 + 3], state[choice * 6 + 4]);
189        }
190    }
191
192    /**
193     * A Filter that is constructed with a color and makes any color it is told to alter have the same hue as the given
194     * color, have saturation that is somewhere between the given color's and the altered colors, and chiefly is
195     * distinguishable from other colors by value. Useful for sepia effects, which can be created satisfactorily with
196     * {@code new Filters.ColorizeFilter(SColor.CLOVE_BROWN, 0.6f, 0.0f)}.
197     */
198    public static class ColorizeFilter extends Filter<Color> {
199        private SquidColorCenter globalSCC;
200        /**
201         * Sets up a ColorizeFilter with the desired color to colorize towards.
202         *
203         * @param r the red component to colorize towards
204         * @param g the green component to colorize towards
205         * @param b the blue component to colorize towards
206         */
207        public ColorizeFilter(float r, float g, float b) {
208            globalSCC = DefaultResources.getSCC();
209
210            state = new float[]{globalSCC.getHue(r, g, b), globalSCC.getSaturation(r, g, b), 1f, 0f};
211        }
212        /**
213         * Sets up a ColorizeFilter with the desired color to colorize towards.
214         *
215         * @param color the Color to colorize towards
216         */
217        public ColorizeFilter(Color color) {
218            globalSCC = DefaultResources.getSCC();
219            state = new float[]{globalSCC.getHue(color), globalSCC.getSaturation(color), 1f, 0f};
220        }
221        /**
222         * Sets up a ColorizeFilter with the desired color to colorize towards.
223         *
224         * @param r the red component to colorize towards
225         * @param g the green component to colorize towards
226         * @param b the blue component to colorize towards
227         * @param saturationMultiplier a multiplier to apply to the final color's saturation; may be greater than 1
228         * @param valueModifier a modifier that affects the final brightness value of any color this alters;
229         *                      typically very small, such as in the -0.2f to 0.2f range
230         */
231        public ColorizeFilter(float r, float g, float b, float saturationMultiplier, float valueModifier) {
232            globalSCC = DefaultResources.getSCC();
233
234            state = new float[]{
235                    globalSCC.getHue(r, g, b),
236                    globalSCC.getSaturation(r, g, b),
237                    saturationMultiplier,
238                    valueModifier};
239        }
240        /**
241         * Sets up a ColorizeFilter with the desired color to colorize towards.
242         *
243         * @param color the Color to colorize towards
244         * @param saturationMultiplier a multiplier to apply to the final color's saturation; may be greater than 1
245         * @param valueModifier a modifier that affects the final brightness value of any color this alters;
246         *                      typically very small, such as in the -0.2f to 0.2f range
247         */
248        public ColorizeFilter(Color color, float saturationMultiplier, float valueModifier) {
249            globalSCC = DefaultResources.getSCC();
250            state = new float[]{
251                    globalSCC.getHue(color),
252                    globalSCC.getSaturation(color),
253                    saturationMultiplier,
254                    valueModifier};
255        }
256
257        @Override
258        public Color alter(float r, float g, float b, float a) {
259            return globalSCC.getHSV(
260                    state[0],
261                    Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) + state[1]) * 0.5f * state[2], 1f)),
262                    globalSCC.getValue(r, g, b) * (1f - state[3]) + state[3],
263                    a);
264        }
265    }
266    /**
267     * A Filter that makes the colors requested from it highly saturated, with the original hue, value and a timer that
268     * increments very slowly altering hue, with hue, value and the timer altering saturation, and the original hue,
269     * saturation, and value all altering value. It should look like a hallucination.
270     * <br>
271     * A short (poorly recorded) video can be seen here http://i.imgur.com/SEw2LXe.gifv ; performance should be smoother
272     * during actual gameplay.
273     */
274    public static class HallucinateFilter extends Filter<Color> {
275        private SquidColorCenter globalSCC;
276        /**
277         * Sets up a HallucinateFilter with the timer at 0..
278         */
279        public HallucinateFilter() {
280            globalSCC = DefaultResources.getSCC();
281
282            state = new float[]{0f};
283        }
284        @Override
285        public Color alter(float r, float g, float b, float a) {
286            state[0] += 0.00003f;
287            if(state[0] >= 1.0f)
288                state[0] = 0f;
289            float h = globalSCC.getHue(r, g, b),
290                    s = globalSCC.getSaturation(r, g, b),
291                    v = globalSCC.getValue(r, g, b);
292            return globalSCC.getHSV(
293                    (v * 4f + h + state[0]) % 1.0f,
294                    Math.max(0f, Math.min((h + v) * 0.65f + state[0] * 0.4f, 1f)),
295                    (h + v + s) * 0.35f + 0.7f,
296                    a);
297        }
298    }
299
300
301    /**
302     * A Filter that multiplies the saturation of any color requested from it by a number given during construction.
303     */
304    public static class SaturationFilter extends Filter<Color> {
305        private SquidColorCenter globalSCC;
306        /**
307         * Sets up a SaturationFilter with the desired saturation multiplier. Using a multiplier of 0f, as you would
308         * expect, makes the image grayscale. Using a multiplier of 0.5 make the image "muted", with no truly bright
309         * colors, while 1.0f makes no change, and and any numbers higher than 1.0f will make the image more saturated,
310         * with the exception of colors that were already grayscale or close to it. This clamps the result, so there's
311         * no need to worry about using too high of a saturation multiplier.
312         *
313         * @param multiplier the amount to multiply each requested color's saturation by; 1.0f means "no change"
314         */
315        public SaturationFilter(float multiplier) {
316            globalSCC = DefaultResources.getSCC();
317
318            state = new float[]{multiplier};
319        }
320
321        @Override
322        public Color alter(float r, float g, float b, float a) {
323            return globalSCC.getHSV(
324                    globalSCC.getHue(r, g, b),
325                    Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) * state[0]), 1f)),
326                    globalSCC.getValue(r, g, b),
327                    a);
328        }
329    }
330
331    /**
332     * A Filter that is constructed with a palette of colors and randomly increases or decreases the red, green, and
333     * blue components of any color it is told to alter. Good for a "glitchy screen" effect.
334     */
335    public static class WiggleFilter extends Filter<Color> {
336        LightRNG rng;
337        public WiggleFilter()
338        {
339            rng = new LightRNG();
340        }
341        @Override
342        public Color alter(float r, float g, float b, float a) {
343            return new Color(r - 0.1f + rng.nextInt(5) * 0.05f,
344                    g - 0.1f + rng.nextInt(5) * 0.05f,
345                    b - 0.1f + rng.nextInt(5) * 0.05f,
346                    a);
347        }
348    }
349
350    /**
351     * A Filter that is constructed with a group of colors and forces any color it is told to alter to exactly
352     * the color it was constructed with that has the closest red, green, and blue components. A convenient way to
353     * use this is to pass in one of the color series from SColor, such as RED_SERIES or ACHROMATIC_SERIES.
354     *
355     * Preview using BLUE_GREEN_SERIES foreground, ACHROMATIC_SERIES background: http://i.imgur.com/2HdZpC9.png
356     */
357    public static class PaletteFilter extends Filter<Color> {
358        /**
359         * Sets up a PaletteFilter with the exact colors to use as individual components; the lengths of each given
360         * array should be identical.
361         *
362         * @param r the red components to use
363         * @param g the green components to use
364         * @param b the blue components to use
365         * @param a the opacity components to use
366         */
367        public PaletteFilter(float[] r, float[] g, float[] b, float[] a) {
368            state = new float[Math.min(r.length, Math.min(g.length, Math.min(b.length,
369                    a.length))) * 4];
370            for (int i = 0; i < state.length / 4; i++) {
371                state[i * 4] = r[i];
372                state[i * 4 + 1] = g[i];
373                state[i * 4 + 2] = b[i];
374                state[i * 4 + 3] = a[i];
375            }
376        }/**
377         * Sets up a PaletteFilter with the exact colors to use as Colors. A convenient way to
378         * use this is to pass in one of the color series from SColor, such as RED_SERIES or ACHROMATIC_SERIES.
379         *
380         * @param colors the Colors to use
381         */
382        public PaletteFilter(Color[] colors) {
383            state = new float[colors.length * 4];
384            for (int i = 0; i < state.length / 4; i++) {
385                state[i * 4] = colors[i].r;
386                state[i * 4 + 1] = colors[i].g;
387                state[i * 4 + 2] = colors[i].b;
388                state[i * 4 + 3] = colors[i].a;
389            }
390        }
391
392        @Override
393        public Color alter(float r, float g, float b, float a) {
394            float diff = 9999.0f, temp;
395            int choice = 0;
396            for (int i = 0; i < state.length; i += 4) {
397                temp = Math.abs(state[i] - r) + Math.abs(state[i + 1] - g) + Math.abs(state[i + 2] - b);
398                if(temp < diff) {
399                    diff = temp;
400                    choice = i;
401                }
402            }
403            return new Color(state[choice], state[choice + 1], state[choice + 2],
404                    state[choice + 3]);
405        }
406    }
407
408}