001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.graphics.Color;
004import com.badlogic.gdx.math.MathUtils;
005import squidpony.squidmath.NumberTools;
006import com.github.tommyettinger.anim8.PaletteReducer;
007
008import static squidpony.squidgrid.gui.gdx.SColor.floatGet;
009import static squidpony.squidgrid.gui.gdx.SColor.lerpFloatColorsBlended;
010
011/**
012 * Pre-made FloatFilter classes that you can use to filter colors without producing extra Color objects.
013 * <br>
014 * Created by Tommy Ettinger on 7/22/2018.
015 */
016public final class FloatFilters {
017    private FloatFilters() {
018        // don't build me!
019    }
020
021    /**
022     * Wraps the functionality of {@link SColor#toEditedFloat(float, float, float, float, float)} so it can be called as
023     * a FloatFilter, adding values to hue, saturation, and value (clamping saturation and value and wrapping hue).
024     * Hue is in the 0.0 to 1.0 range, as SquidLib handles it, instead of libGDX's 0 to 360 range.
025     */
026    public static class HSVFilter extends FloatFilter {
027        public float hueAddend, saturationAddend, valueAddend;
028
029        public HSVFilter(float saturation, float value) {
030            this(0f, saturation, value);
031        }
032
033        public HSVFilter(float hueAdd, float saturationAdd, float valueAdd) {
034            hueAddend = hueAdd;
035            saturationAddend = saturationAdd;
036            valueAddend = valueAdd;
037        }
038
039        /**
040         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
041         *
042         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
043         * @return a packed float color, as produced by {@link Color#toFloatBits()}
044         */
045        @Override
046        public float alter(float color) {
047            return SColor.toEditedFloat(color, hueAddend, saturationAddend, valueAddend, 0f);
048        }
049    }
050
051    /**
052     * Changes all colors this alters to have the same hue as, and a closer saturation to, a target color given in the
053     * constructor, as well as optionally multiplying saturation of the result and/or adding lightness/value. A good
054     * example usage of this is to make a sepia-tone effect with
055     * {@code new FloatFilters.ColorizeFilter(SColor.CLOVE_BROWN, 0.6f, 0.0f)}.
056     */
057    public static class ColorizeFilter extends FloatFilter {
058        public float targetCb, targetCr, lumaAddend;
059
060        public ColorizeFilter(float color) {
061            this(color, 1f, 0f);
062        }
063
064        public ColorizeFilter(Color color) {
065            this(color.toFloatBits(), 1f, 0f);
066        }
067
068        public ColorizeFilter(Color color, float chromaMul, float lumaAdd) {
069            this(color.toFloatBits(), chromaMul, lumaAdd);
070        }
071
072        public ColorizeFilter(float color, float chromaMul, float lumaAdd) {
073            targetCb = SColor.chromaBOfFloat(color) * chromaMul;
074            targetCr = SColor.chromaROfFloat(color) * chromaMul;
075            lumaAddend = lumaAdd;
076        }
077
078        /**
079         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
080         *
081         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
082         * @return a packed float color, as produced by {@link Color#toFloatBits()}
083         */
084        @Override
085        public float alter(float color) {
086            final int bits = NumberTools.floatToIntBits(color);
087            return SColor.floatGetYCbCr((bits & 0x000000ff) * (0x1.010102p-8f  * 0.299f) +
088                    (bits & 0x0000ff00) * (0x1.010102p-16f * 0.587f) +
089                    (bits & 0x00ff0000) * (0x1.010102p-24f * 0.114f),
090                    targetCb, targetCr,
091                    ((bits & 0xfe000000) >>> 24) * 0x1.020408p-8f);
092        }
093    }
094
095    /**
096     * A FloatFilter that alters primarily-red and primarily-green colors so they can be more easily be distinguished by
097     * people with at least some forms of red-green color-blindness (deuteranopia should be handled well, protanopia
098     * very well, and tritanopia may not benefit at all). Causes reds to be darkened and greens to be lightened if the
099     * other of the pair is not present in similar quantities (which is the case for yellows and blues).
100     */
101    public static class DistinctRedGreenFilter extends FloatFilter {
102        /**
103         * Constructs a DistinctRedGreenFilter. This class is a simple wrapper around a function that doesn't need
104         * member variables, so there should be little overhead with this filter.
105         */
106        public DistinctRedGreenFilter() {
107        }
108
109        /**
110         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
111         *
112         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
113         * @return a packed float color, as produced by {@link Color#toFloatBits()}
114         */
115        @Override
116        public float alter(float color) {
117//            final int bits = NumberTools.floatToIntBits(color),
118//                    r = bits & 0xFF, g = bits >>> 8 & 0xFF, b = bits >>> 16 & 0xFF, a = bits >>> 24,
119//                    diff = g - r;
120//            if (diff > 101)
121//                return floatGet(Math.min(1f, 0x1.010102p-16f * r * (203 + (diff >> 1))),
122//                        Math.min(1f, 0x1.010102p-16f * g * (228 + (diff >> 1))),
123//                        Math.min(1f, 0x1.010102p-16f * b * (203 + (diff >> 1))),
124//                        0x1.010102p-8f * a);
125//            else if (diff < -75)
126//                return floatGet(Math.min(1f, 0x1.010102p-16f * r * (152 - diff)),
127//                        Math.min(1f, 0x1.010102p-16f * g * (177 - diff)),
128//                        Math.min(1f, 0x1.010102p-16f * b * (177 - diff)),
129//                        0x1.010102p-8f * a);
130//            else
131//                return color;
132            final int bits = NumberTools.floatToIntBits(color);
133            final float opacity = (bits >>> 25) * 0.007874016f;
134            float luma = (
135                    (bits & 0x000000ff) * (0x1.010102p-8f * 0.299f) +
136                            (bits & 0x0000ff00) * (0x1.010102p-16f * 0.587f) +
137                            (bits & 0x00ff0000) * (0x1.010102p-24f * 0.114f));
138            float chromaB = (
139                    (bits & 0x000000ff) * (0x1.010102p-8f * -0.168736f) +
140                            (bits & 0x0000ff00) * (0x1.010102p-16f * -0.331264f) +
141                            (bits & 0x00ff0000) * (0x1.010102p-24f * 0.5f));
142            float chromaR = (
143                    (bits & 0x000000ff) * (0x1.010102p-8f * 0.5f) +
144                            (bits & 0x0000ff00) * (0x1.010102p-16f * -0.418688f) +
145                            (bits & 0x00ff0000) * (0x1.010102p-24f * -0.081312f));
146            if(chromaB < -0.05f)
147            {
148                float theta = NumberTools.atan2(chromaR, chromaB);
149                float dist = (float) Math.sqrt(chromaB * chromaB + chromaR * chromaR);
150                if(theta >= 0f)
151                {
152                    theta *= 0.5f;
153                    luma += theta * 0.15f;
154                    theta += 0.7853981633974483f;
155                }
156                else
157                {
158                    theta *= 0.4f;
159                    luma += theta * 0.225f;
160                    theta -= 0.9424778335276408f;
161                }
162                chromaR = MathUtils.sin(theta) * dist;
163                chromaB = MathUtils.cos(theta) * dist;
164            }
165            return floatGet(MathUtils.clamp(luma + chromaR * 1.402f, 0f, 1f),
166                    MathUtils.clamp(luma - chromaB * 0.344136f - chromaR * 0.714136f, 0f, 1f),
167                    MathUtils.clamp(luma + chromaB * 1.772f, 0f, 1f),
168                    opacity);
169
170        }
171    }
172
173    /**
174     * A FloatFilter that makes no changes to the colors given to it; useful as a default for when no filter is wanted.
175     */
176    public static class IdentityFilter extends FloatFilter {
177        /**
178         * Takes a packed float color and returns it un-edited.
179         *
180         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
181         * @return a packed float color, as produced by {@link Color#toFloatBits()}
182         */
183        @Override
184        public float alter(float color) {
185            return color;
186        }
187    }
188
189    /**
190     * A static constant of the one possible IdentityFilter, to avoid needing to make duplicates.
191     * IdentityFilter makes no changes to the colors given to it.
192     */
193    public static final IdentityFilter identityFilter = new IdentityFilter();
194
195    /**
196     * A FloatFilter that makes all colors given to it grayscale, using only their luma as calculated by
197     * {@link SColor#lumaOfFloat(float)} as the lightness (it does also preserve alpha transparency).
198     */
199    public static class GrayscaleFilter extends FloatFilter {
200        /**
201         * Takes a packed float color and produces a grayscale packed float color that this FloatFilter edited.
202         * Uses the luma calculation from {@link SColor#lumaOfFloat(float)} instead of the value calculation from
203         * {@link SColor#valueOfFloat(float)}; luma tends to be more visually-accurate on modern monitors.
204         *
205         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
206         * @return a packed float color, as produced by {@link Color#toFloatBits()}
207         */
208        @Override
209        public float alter(float color) {
210            final int bits = NumberTools.floatToIntBits(color);
211            color = (bits & 0x000000ff) * (0x1.010102p-8f * 0.299f) +
212                    (bits & 0x0000ff00) * (0x1.010102p-16f * 0.587f) +
213                    (bits & 0x00ff0000) * (0x1.010102p-24f * 0.114f);
214            return floatGet(color, color, color, (bits >>> 25) * 0.007874016f);
215        }
216    }
217    
218    public static final GrayscaleFilter grayscaleFilter = new GrayscaleFilter();
219
220    /**
221     * Like {@link HSVFilter}, but edits its input colors in YCbCr color space, and multiplies rather than adds.
222     * Y is luma, and affects how bright the color is (luma 1 is white, luma 0 is black). Cb is Chroma(blue) amd Cr is
223     * Chroma(red), two inter-related channels that determine the hue and vividness of a specific color. When Cb and Cr
224     * are both 0, the color is grayscale. When Cb is 0.5 and Cr is -0.5, the color is blue unless Y is very high or
225     * low. When Cb is -0.5 and Cr is 0.5, the color is red with the same caveats re: Y. When Cb and Cr are both -0.5,
226     * the color is green (same caveats), and when both are 0.5, the color is purple. When Y is 0.5, Cb and Cr form a
227     * graph like this:
228     * <br>
229     * <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/YCbCr-CbCr_Y50.png/240px-YCbCr-CbCr_Y50.png" />
230     * <br>
231     * Valid values for Cb and Cr are from -0.5 to 0.5 at the widest part of the range (it shrinks as Y approaches 0 or
232     * 1), but there aren't really invalid values here because this filter will clamp results with higher or lower
233     * channel values than a color can have. Each of yMul, cbMul, and crMul can have any float value, but yMul should be
234     * positive (unless you want this to only produce solid black). Similarly, cbMul and crMul will not produce
235     * meaningful results if they are very large (either positive or negative); it's recommended to use values between
236     * 0.0 and 1.0 for both if you want to desaturate colors or values somewhat greater than 1.0 to oversaturate them.
237     */
238    public static class YCbCrFilter extends FloatFilter {
239        public float yMul, cbMul, crMul;
240
241        public YCbCrFilter(float yMul) {
242            this(yMul, 1f, 1f);
243        }
244
245        public YCbCrFilter(float yMul, float cbMul, float crMul) {
246            this.yMul = yMul;
247            this.cbMul = cbMul;
248            this.crMul = crMul;
249        }
250
251        /**
252         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
253         *
254         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
255         * @return a packed float color, as produced by {@link Color#toFloatBits()}
256         */
257        @Override
258        public float alter(float color) {
259            final int bits = NumberTools.floatToIntBits(color);
260            final float opacity = (bits >>> 25) * 0.007874016f;
261            float luma = yMul * (
262                    (bits & 0x000000ff) * (0x1.010102p-8f * 0.299f) +
263                            (bits & 0x0000ff00) * (0x1.010102p-16f * 0.587f) +
264                            (bits & 0x00ff0000) * (0x1.010102p-24f * 0.114f));
265            final float chromaB = cbMul * (
266                    (bits & 0x000000ff) * (0x1.010102p-8f * -0.168736f) +
267                            (bits & 0x0000ff00) * (0x1.010102p-16f * -0.331264f) +
268                            (bits & 0x00ff0000) * (0x1.010102p-24f * 0.5f));
269            final float chromaR = crMul * (
270                    (bits & 0x000000ff) * (0x1.010102p-8f * 0.5f) +
271                            (bits & 0x0000ff00) * (0x1.010102p-16f * -0.418688f) +
272                            (bits & 0x00ff0000) * (0x1.010102p-24f * -0.081312f));
273            return floatGet(MathUtils.clamp(luma + chromaR * 1.402f, 0f, 1f),
274                    MathUtils.clamp(luma - chromaB * 0.344136f - chromaR * 0.714136f, 0f, 1f),
275                    MathUtils.clamp(luma + chromaB * 1.772f, 0f, 1f),
276                    opacity);
277        }
278    }
279
280    /**
281     * Like {@link YCbCrFilter}, but edits its input colors in YCoCg color space, or like {@link HSVFilter} except it
282     * doesn't add, it multiplies. Most of the time you should prefer {@link YCbCrFilter} as long as it isn't a
283     * performance bottleneck; this method is faster but less accurate. Y is luminance, ranging from 0 (dark) to 1
284     * (light), and affects how bright the color is, but isn't very accurate perceptually. Co is Chrominance(orange) and
285     * Cg is Chrominance(green) (both range from -0.5 to 0.5), two inter-related channels that determine the hue and
286     * vividness of a specific color. When Co and Cg are both 0, the color is grayscale. When Co is 0.5 and Cg is -0.5,
287     * the color is red unless Y is very high or low. When Co is -0.5 and Cg is 0.5, the color is cyan with the same
288     * caveats re: Y. When Co and Cg are both -0.5, the color is blue (same caveats), and when both are 0.5, the color
289     * is yellow.
290     * <br>
291     * Valid values for Co and Cg are from -0.5 to 0.5 at the widest part of the range (it shrinks as Y approaches 0 or
292     * 1), but there aren't really invalid values here because this filter will clamp results with higher or lower
293     * channel values than a color can have. Each of yMul, coMul, and cgMul can have any float value, but yMul should be
294     * positive (unless you want this to only produce solid black). Similarly, coMul and cgMul will not produce
295     * meaningful results if they are very large (either positive or negative); it's recommended to use values between
296     * 0.0 and 1.0 for both if you want to desaturate colors or values somewhat greater than 1.0 to oversaturate them.
297     */
298    public static class YCoCgFilter extends FloatFilter {
299        public float yMul, coMul, cgMul;
300
301        public YCoCgFilter(float luminanceMul, float orangeMul, float greenMul) {
302            this.yMul = luminanceMul;
303            this.coMul = orangeMul;
304            this.cgMul = greenMul;
305        }
306
307        /**
308         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
309         *
310         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
311         * @return a packed float color, as produced by {@link Color#toFloatBits()}
312         */
313        @Override
314        public float alter(float color) {
315            final int bits = NumberTools.floatToIntBits(color);
316            final float opacity = (bits >>> 25) * 0.007874016f;
317            final float y = yMul * (((bits & 0x000000ff) + ((bits & 0x0000ff00) >>> 7) + ((bits & 0x00ff0000) >>> 16)) * 0x1.010102p-10f);
318            final float co = coMul * (((bits & 0x000000ff) - ((bits & 0x00ff0000) >>> 16)) * 0x1.010102p-9f);
319            final float cg = cgMul * ((((bits & 0x0000ff00) >>> 7) - (bits & 0x000000ff) - ((bits & 0x00ff0000) >>> 16)) * 0x1.010102p-10f);
320
321            final float t = y - cg;
322            return floatGet(MathUtils.clamp(t + co, 0f, 1f),
323                    MathUtils.clamp(y + cg, 0f, 1f),
324                    MathUtils.clamp(t - co, 0f, 1f),
325                    opacity);
326        }
327    }
328
329    /**
330     * Like {@link YCbCrFilter} or  {@link YCoCgFilter}, but edits its input colors in YCwCm color space, which is very
331     * similar to YCoCg but has chroma/chrominance components that are useful aesthetically on their own. You may often
332     * prefer {@link YCbCrFilter} because it calculates lightness (luma) more precisely, but the Cb (blue-ness) and Cr
333     * (red-ness) components are less useful for some purposes individually. Y is luminance, ranging from 0 (dark) to 1
334     * (light), and affects how bright the color is, but isn't very accurate perceptually. Cw is Chroma(warm) and
335     * Cm is Chroma(mild) (both range from -1.0 to 1.0), two inter-related channels that determine the hue and vividness
336     * of a specific color. When Cw and Cm are both 0, the color is grayscale. When Cw is 1 and Cm is -1, the color
337     * is red or like red. When Cw is -1 and Cm is 1, the color is green or like green. When Cw and Cm are both -1, the
338     * color is blue or like blue, and when both are 1, the color is roughly yellow or brown (depending on Y).
339     * <br>
340     * Valid values for Cw and Cm are from -1.0 to 1.0, but there aren't really invalid values here because this filter
341     * will clamp results with higher or lower channel values than a color can have. Each of yMul, cwMul, and cmMul can
342     * have any float value, but yMul should be positive (unless you want this to only produce solid black). Similarly,
343     * cwMul and cmMul will not produce meaningful results if they are very large (either positive or negative); it's
344     * recommended to use values between 0.0 and 1.0 for both if you want to desaturate colors or values somewhat
345     * greater than 1.0 to oversaturate them. Unlike {@link YCbCrFilter} and {@link YCoCgFilter}, you can benefit from
346     * setting cwMul independently of the other chroma component, which can be used to emphasize warm vs. cool colors if
347     * cwMul is greater than 1.0, or to de-emphasize that comparison if it is between 0.0 and 1.0. A similar option is
348     * possible for cmMul, but it isn't as clear of an artistic convention; a high cmMul will separate green-and-yellow
349     * colors further from red-purple-and-blue colors. Also unlike the other YCC filters, this allows an additive change
350     * to Y, Cw, and Cm applied after the multiplicative change but before converting to RGB and clamping. This can be
351     * used to make all colors warmer or cooler (such as for volcano or frozen scenes) by adding or subtracting from Cw,
352     * for instance. It can also lighten or darken all colors by changing luma.
353     */
354    public static class YCwCmFilter extends FloatFilter {
355        public float yMul, cwMul, cmMul, yAdd, cwAdd, cmAdd;
356
357        public YCwCmFilter(float yMul, float cwMul, float cmMul) {
358            this(yMul, cwMul, cmMul, 0f, 0f, 0f);
359        }
360        public YCwCmFilter(float yMul, float cwMul, float cmMul, float yAdd, float cwAdd, float cmAdd) {
361            this.yMul = yMul;
362            this.cwMul = cwMul;
363            this.cmMul = cmMul;
364            this.yAdd = yAdd;
365            this.cwAdd = cwAdd;
366            this.cmAdd = cmAdd;
367        }
368
369        /**
370         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
371         *
372         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
373         * @return a packed float color, as produced by {@link Color#toFloatBits()}
374         */
375        @Override
376        public float alter(float color) {
377            final int bits = NumberTools.floatToIntBits(color);
378            final float opacity = (bits >>> 25) * 0.007874016f;
379            final float luma = yAdd + yMul * ((bits & 0xFF) * 0x3p-11f + (bits >>> 8 & 0xFF) * 0x1p-9f + (bits >>> 16 & 0xFF) * 0x1p-11f);
380            final float warm = (cwAdd + cwMul * (((bits & 0xFF) - (bits >>> 16 & 0xff)) * 0x1.010102p-8f));
381            final float mild = 0.5f * (cmAdd + cmMul * (((bits >>> 8 & 0xff) - (bits >>> 16 & 0xff)) * 0x1.010102p-8f));
382
383            return floatGet(MathUtils.clamp(luma + warm * 0.625f - mild, 0f, 1f),
384                    MathUtils.clamp(luma + mild - warm * 0.375f, 0f, 1f),
385                    MathUtils.clamp(luma - warm * 0.375f - mild, 0f, 1f),
386                    opacity);
387        }
388    }
389
390    /**
391     * A FloatFilter that chains together one or more FloatFilters one after the next, passing the float output of one
392     * as input to the next until the chain has all been called.
393     */
394    public static class ChainFilter extends FloatFilter {
395        public FloatFilter[] filters;
396
397        /**
398         * Takes a vararg or array of FloatFilter objects and produces a ChainFilter that will call all of them in order
399         * on any color given to this to alter.
400         * @param filters an array or vararg of FloatFilter objects; none can be null
401         */
402        public ChainFilter(FloatFilter... filters)
403        {
404            if(filters == null || filters.length == 0)
405                this.filters = new FloatFilter[]{new IdentityFilter()};
406            this.filters = filters;
407        }
408
409        /**
410         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
411         *
412         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
413         * @return a packed float color, as produced by {@link Color#toFloatBits()}
414         */
415        @Override
416        public float alter(float color) {
417            for (int i = 0; i < filters.length; i++) {
418                color = filters[i].alter(color);
419            }
420            return color;
421        }
422    }
423
424    /**
425     * A FloatFilter that linearly interpolates (lerps) any color it is given toward a specified color by a specified
426     * amount. Uses {@link SColor#lerpFloatColorsBlended(float, float, float)} to mix a requested color with the target
427     * color, and this means the alpha of the target color affects the amount of change instead of the resulting alpha. 
428     */
429    public static class LerpFilter extends FloatFilter {
430        public float target, amount;
431
432        /**
433         * Builds a LerpFilter with a Color (which will be converted to a packed float color) and an amount as a float.
434         * The amount is how much the target color will affect input colors, from 0f to 1f. If the target color has an
435         * alpha component that is less than 1, then amount is effectively multiplied by that alpha.
436         * @param target a libGDX color; must not be null
437         * @param amount a float that determines how much target will affect an input color; will be clamped between 0f and 1f
438         */
439        public LerpFilter(Color target, float amount) {
440            this.target = target.toFloatBits();
441            this.amount = MathUtils.clamp(amount, 0f, 1f);
442        }
443
444        /**
445         * Builds a LerpFilter with a packed float color and an amount as a float.
446         * The amount is how much the target color will affect input colors, from 0f to 1f. If the target color has an
447         * alpha component that is less than 1, then amount is effectively multiplied by that alpha.
448         * @param target a packed float color; must not be null
449         * @param amount a float that determines how much target will affect an input color; will be clamped between 0f and 1f
450         */
451
452        public LerpFilter(float target, float amount)
453        {
454            this.target = target;
455            this.amount = MathUtils.clamp(amount, 0f, 1f);
456        }
457
458        /**
459         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
460         *
461         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
462         * @return a packed float color, as produced by {@link Color#toFloatBits()}
463         */
464        @Override
465        public float alter(float color) {
466            return lerpFloatColorsBlended(color, target, amount);
467        }
468    }
469
470    /**
471     * A FloatFilter that linearly interpolates (lerps) any color it is given toward the most-similar of a group of
472     * given colors. Uses {@link SColor#lerpFloatColorsBlended(float, float, float)} to mix a requested color with the
473     * chosen target color, and this means the alpha of the target color affects the amount of change instead of the
474     * resulting alpha. Changing the alpha of the colors this is given can be done easily with
475     * {@link SColor#translucentColor(float, float)}, and this allows you to specify varying amounts to mix by.
476     */
477    public static class MultiLerpFilter extends FloatFilter {
478        public float[] targets;
479        public float amount;
480        /**
481         * Builds a MultiLerpFilter with an array of Color objects (which will be converted to an array of packed float
482         * colors) and an amount as a float. The amount is how much the target colors will affect input colors, from 0f
483         * to 1f. If a target color has an alpha component that is less than 1, then amount is effectively multiplied by
484         * that alpha. If you want to edit the alpha without duplicating Color objects, you can use
485         * {@link SColor#translucentColor(Color, float)} to make a float array to pass to
486         *{@link #MultiLerpFilter(float, float...)}.
487         * @param amount a float that determines how much target will affect an input color; will be clamped between 0f and 1f
488         * @param targets an array of libGDX Color objects; must not be null or empty
489         */
490        public MultiLerpFilter(float amount, Color[] targets) {
491            this.targets = new float[targets.length];
492            for (int i = 0; i < targets.length; i++) {
493                this.targets[i] = targets[i].toFloatBits();
494            }
495            this.amount = MathUtils.clamp(amount, 0f, 1f);
496        }
497
498        /**
499         * Builds a MultiLerpFilter with an array of packed float colors and an amount as a float.
500         * The amount is how much the target color will affect input colors, from 0f to 1f. If the target color has an
501         * alpha component that is less than 1, then amount is effectively multiplied by that alpha.
502         * @param amount a float that determines how much target will affect an input color; will be clamped between 0f and 1f
503         * @param targets an array or vararg of packed float colors; must not be null or empty
504         */
505
506        public MultiLerpFilter(float amount, float... targets)
507        {
508            this.targets = targets;
509            this.amount = MathUtils.clamp(amount, 0f, 1f);
510        }
511
512        /**
513         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
514         *
515         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
516         * @return a packed float color, as produced by {@link Color#toFloatBits()}
517         */
518        @Override
519        public float alter(float color) {
520            int choice = 0, diff = SColor.difference2(targets[0], color);
521            for (int i = 1; i < targets.length; i++) {
522                if(diff != (diff = Math.min(SColor.difference2(targets[i], color), diff)))
523                    choice = i;
524            }
525            return lerpFloatColorsBlended(color, targets[choice], amount);
526        }
527    }
528
529    /**
530     * A FloatFilter that limits the colors it can return to a fixed palette, and won't return any colors that are
531     * missing from that palette (although it can always return fully-transparent). {@link PaletteReducerFilter} is also
532     * an option; it uses more memory but is faster to look up colors in larger palettes (it has a maximum size of 256
533     * colors, though, which this class doesn't).
534     */
535    public static class PaletteFilter extends FloatFilter {
536        public float[] targets;
537        /**
538         * Builds a PaletteFilter with an array of Color objects that this will choose from. The array will be converted
539         * to an array of packed float colors, and not referenced directly.
540         * @param targets an array of libGDX Color objects; must not be null or empty
541         */
542        public PaletteFilter(final Color[] targets) {
543            this.targets = new float[targets.length];
544            for (int i = 0; i < targets.length; i++) {
545                this.targets[i] = targets[i].toFloatBits();
546            }
547        }
548
549        /**
550         * Builds a PaletteFilter with an array of packed float colors that this will choose from. The array will be
551         * referenced directly, not copied, so if you change the contents of targets, it will be reflected here.
552         * @param targets an array or vararg of packed float colors; must not be null or empty
553         */
554
555        public PaletteFilter(final float... targets)
556        {
557            this.targets = targets;
558        }
559
560        /**
561         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
562         *
563         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
564         * @return a packed float color, as produced by {@link Color#toFloatBits()}
565         */
566        @Override
567        public float alter(float color) {
568            if(color >= 0f) //if color is halfway-transparent or closer to transparent...
569                return 0f; // return fully transparent
570            int choice = 0, diff = SColor.difference2(targets[0], color);
571            for (int i = 1; i < targets.length; i++) {
572                if(diff != (diff = Math.min(SColor.difference2(targets[i], color), diff)))
573                    choice = i;
574            }
575            return targets[choice];
576        }
577    }
578
579    /**
580     * A FloatFilter that limits the colors it can return to a fixed palette as determined by a {@link PaletteReducer},
581     * and won't return any colors that are missing from that palette (although it can always return fully-transparent).
582     * This is like {@link PaletteFilter} but trades off memory usage (it uses about 33KB to store a large-ish lookup
583     * table) to improve speed on large palettes. This can't use a palette larger than 256 colors (including transparent
584     * almost always).
585     */
586    public static class PaletteReducerFilter extends FloatFilter {
587        public PaletteReducer reducer;
588
589        /**
590         * Builds a PaletteReducerFilter that will use the 256-color (including transparent) DawnBringer Aurora palette.
591         */
592        public PaletteReducerFilter()
593        {
594            reducer = new PaletteReducer();
595        }
596        
597        /**
598         * Builds a PaletteReducerFilter with the given PaletteReducer, which will be referenced without copying. You
599         * can call {@link PaletteReducer#exact(Color[])} or other similar methods before this filter is used to set up
600         * the palette, and this is often done before the PaletteReducer is passed here.
601         * @param palette a PaletteReducer that should have the desired palette set up before this is used
602         */
603        public PaletteReducerFilter(final PaletteReducer palette) {
604            reducer = palette;
605        }
606
607        /**
608         * Builds a PaletteReducerFilter with an array or vararg of libGDX colors (at most 256 colors, and often
609         * starting with a transparent color) that this will choose from. The array will have its contents read but will
610         * not be held onto, so later changes won't affect it.
611         * @param targets an array or vararg of libGDX colors; must not be null
612         */
613
614        public PaletteReducerFilter(final Color... targets)
615        {
616            reducer = new PaletteReducer(targets);
617        }
618
619        /**
620         * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited.
621         *
622         * @param color a packed float color, as produced by {@link Color#toFloatBits()}
623         * @return a packed float color, as produced by {@link Color#toFloatBits()}
624         */
625        @Override
626        public float alter(float color) {
627            return reducer.reduceFloat(color);
628        }
629    }
630
631}