001package squidpony;
002
003import squidpony.panel.IColoredString;
004import squidpony.squidmath.RNG;
005
006import java.util.HashMap;
007import java.util.Map;
008
009/**
010 * How to manage colors, making sure that a color is allocated at most once.
011 * 
012 * <p>
013 * If you aren't using squidlib's gdx part, you should use this interface (and
014 * the {@link Skeleton} implementation), because it caches instances.
015 * </p>
016 * 
017 * <p>
018 * If you are using squidlib's gdx part, you should use this interface (and the
019 * {@code SquidColorCenter} implementation) if:
020 * 
021 * <ul>
022 * <li>You don't want to use preallocated instances (if you do, check out
023 * {@code squidpony.squidgrid.gui.Colors})</li>
024 * <li>You don't want to use named colors (if you do, check out
025 * {@code com.badlogic.gdx.graphics.Colors})</li>
026 * <li>You don't like libgdx's Color representation (components as floats
027 * in-between 0 and 1) but prefer components within 0 (inclusive) and 256
028 * (exclusive); and don't mind the overhead of switching the representations. My
029 * personal opinion is that the overhead doesn't matter w.r.t other intensive
030 * operations that we have in roguelikes (path finding).</li>
031 * </ul>
032 * 
033 * @author smelC
034 * 
035 * @param <T>
036 *            The concrete type of colors
037 */
038public interface IColorCenter<T> {
039
040        /**
041         * @param red
042         *            The red component. For screen colors, in-between 0 (inclusive)
043     *            and 256 (exclusive).
044         * @param green
045         *            The green component. For screen colors, in-between 0 (inclusive)
046     *            and 256 (exclusive).
047         * @param blue
048         *            The blue component. For screen colors, in-between 0 (inclusive)
049     *            and 256 (exclusive).
050         * @param opacity
051         *            The alpha component. In-between 0 (inclusive) and 256
052         *            (exclusive). Larger values mean more opacity; 0 is clear.
053         * @return A possibly transparent color.
054         */
055        T get(int red, int green, int blue, int opacity);
056
057        /**
058         * @param red
059         *            The red component. For screen colors, in-between 0 (inclusive)
060     *            and 256 (exclusive).
061         * @param green
062         *            The green component. For screen colors, in-between 0 (inclusive)
063     *            and 256 (exclusive).
064         * @param blue
065         *            The blue component. For screen colors, in-between 0 (inclusive)
066     *            and 256 (exclusive).
067         * @return An opaque color.
068         */
069        T get(int red, int green, int blue);
070
071    /**
072     *
073     * @param hue The hue of the desired color from 0.0 (red, inclusive) towards orange, then
074     *            yellow, and eventually to purple before looping back to almost the same red
075     *            (1.0, exclusive)
076     * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive)
077     *                   to 1.0 (a bright color, exclusive)
078     * @param value the value (essentially lightness) of the color from 0.0 (black,
079     *                   inclusive) to 1.0 (inclusive) for screen colors or arbitrarily high
080     *                   for HDR colors.
081     * @param opacity the alpha component as a float; 0.0f is clear, 1.0f is opaque.
082     * @return a possibly transparent color
083     */
084    T getHSV(float hue, float saturation, float value, float opacity);
085
086    /**
087     *
088     * @param hue The hue of the desired color from 0.0 (red, inclusive) towards orange, then
089     *            yellow, and eventually to purple before looping back to almost the same red
090     *            (1.0, exclusive)
091     * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive)
092     *                   to 1.0 (a bright color, exclusive)
093     * @param value the value (essentially lightness) of the color from 0.0 (black,
094     *                   inclusive) to 1.0 (inclusive) for screen colors or arbitrarily high
095     *                   for HDR colors.
096     * @return an opaque color
097     */
098    T getHSV(float hue, float saturation, float value);
099
100        /**
101         * @return Opaque white.
102         */
103        T getWhite();
104
105        /**
106         * @return Opaque black.
107         */
108        T getBlack();
109
110        /**
111         * @return The fully transparent color.
112         */
113        T getTransparent();
114
115        /**
116         * @param rng an RNG from SquidLib.
117         * @param opacity
118         *            The alpha component. In-between 0 (inclusive) and 256
119         *            (exclusive). Larger values mean more opacity; 0 is clear.
120         * @return A random color, except for the alpha component.
121         */
122        T getRandom(RNG rng, int opacity);
123
124        /**
125         * @param c a concrete color
126         * @return The red component. For screen colors, in-between 0 (inclusive) and 256 (exclusive).
127         */
128        int getRed(T c);
129
130        /**
131         * @param c a concrete color
132         * @return The green component. For screen colors, in-between 0 (inclusive) and 256
133         *         (exclusive).
134         */
135        int getGreen(T c);
136
137        /**
138         * @param c a concrete color
139         * @return The blue component. For screen colors, in-between 0 (inclusive) and 256 (exclusive).
140         */
141        int getBlue(T c);
142
143        /**
144         * @param c a concrete color
145         * @return The alpha component. In-between 0 (inclusive) and 256
146         *         (exclusive).
147         */
148        int getAlpha(T c);
149
150    /**
151     *
152     * @param c a concrete color
153     * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and
154     * eventually to purple before looping back to almost the same red (1.0, exclusive)
155     */
156    float getHue(T c);
157
158    /**
159     *
160     * @param c a concrete color
161     * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a
162     * bright color, exclusive)
163     */
164    float getSaturation(T c);
165
166    /**
167     *
168     * @param c a concrete color
169     * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to
170     * 1.0 (inclusive) for screen colors or arbitrarily high for HDR colors.
171     */
172    float getValue(T c);
173
174    /**
175     * @param c
176     * @return The color that {@code this} shows when {@code c} is requested. May be {@code c} itself.
177     */
178    T filter(T c);
179
180        /**
181         * @param ics
182         * @return {@code ics} filtered according to {@link #filter(Object)}. May be
183         *         {@code ics} itself if unchanged.
184         */
185        IColoredString<T> filter(IColoredString<T> ics);
186
187        /**
188         * Gets a copy of t and modifies it to make a shade of gray with the same brightness.
189         * The doAlpha parameter causes the alpha to be considered in the calculation of brightness and also changes the
190         * returned alpha of the color.
191         * Not related to reified types or any usage of "reify."
192         * @param t a T to copy; only the copy will be modified
193         * @param doAlpha
194         *            Whether to include (and hereby change) the alpha component.
195         * @return A monochromatic variation of {@code t}.
196         */
197        T greify(/*@Nullable*/ T t, boolean doAlpha);
198
199    /**
200     * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change.
201     * @param start the initial color T
202     * @param end the "target" color T
203     * @param change the degree to change closer to end; a change of 0.0f produces start, 1.0f produces end
204     * @return a new T between start and end
205     */
206    T lerp(T start, T end, float change);
207    /**
208     * Gets a fully-desaturated version of the given color (keeping its brightness, but making it grayscale).
209     * Keeps alpha the same; if you want alpha to be considered (and brightness to be calculated differently), then
210     * you can use greify() in this class instead.
211     * @param color the color T to desaturate (will not be modified)
212     * @return the grayscale version of color
213     */
214    T desaturated(T color);
215
216    /**
217     * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat).
218     * Alpha is left unchanged.
219     * @param color the color T to desaturate
220     * @param degree a float between 0.0f and 1.0f; more makes it less colorful
221     * @return the desaturated (and if a filter is used, also filtered) new color T
222     */
223    T desaturate(T color, float degree);
224
225    /**
226     * Fully saturates color (makes it a vivid color like red or green and less gray) and returns the modified copy.
227     * Leaves alpha unchanged.
228     * @param color the color T to saturate (will not be modified)
229     * @return the saturated version of color
230     */
231    T saturated(T color);
232
233
234    /**
235     * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and
236     * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this does
237     * not necessarily return a specific color, but most implementations will treat a hue of 0 as red.
238     * @param color the color T to saturate
239     * @param degree a float between 0.0f and 1.0f; more makes it more colorful
240     * @return the saturated (and if a filter is used, also filtered) new color
241     */
242    public T saturate(T color, float degree);
243
244    /**
245         * A skeletal implementation of {@link IColorCenter}.
246         * 
247         * @author smelC
248         * 
249         * @param <T> a concrete color type
250         */
251        abstract class Skeleton<T> implements IColorCenter<T> {
252
253                private final Map<Long, T> cache = new HashMap<>(256);
254
255                protected /*Nullable*/ IFilter<T> filter;
256
257                /**
258                 * @param filter
259                 *                      The filter to use, or {@code null} for no filter.
260                 */
261                protected Skeleton(/*Nullable*/ IFilter<T> filter) {
262                        this.filter = filter;
263                }
264
265        /**
266         * It clears the cache. You may need to do this to limit the cache to the colors used in a specific section.
267                 * This is also useful if a Filter changes what colors it should return on a frame-by-frame basis; in that case,
268                 * you can call clearCache() at the start or end of a frame to ensure the next frame gets different colors.
269         */
270        public void clearCache()
271        {
272            cache.clear();
273        }
274
275        /**
276         * You may want to copy colors between IColorCenter instances that have different create() methods -- and as
277         * such, will have different values for the same keys in the cache. This allows you to copy the cache from other
278         * into this Skeleton, but using this Skeleton's create() method.
279         * @param other another Skeleton of the same type that will have its cache copied into this Skeleton
280         */
281        public void copyCache(Skeleton<T> other)
282        {
283            for (Map.Entry<Long, T> k : other.cache.entrySet())
284            {
285                cache.put(k.getKey(), create(getRed(k.getValue()), getGreen(k.getValue()), getBlue(k.getValue()),
286                        getAlpha(k.getValue())));
287            }
288        }
289
290                /**
291                 * If you're changing the filter, you should likely call
292                 * {@link #clearCache()}.
293                 * 
294                 * @param filter
295                 *            The filter to use, or {@code null} to turn filtering OFF.
296                 * @return {@code this}
297                 */
298                public Skeleton<T> setFilter(IFilter<T> filter) {
299                        this.filter = filter;
300                        return this;
301                }
302
303                @Override
304                public T get(int red, int green, int blue, int opacity) {
305                        final Long value = getUniqueIdentifier((short)red, (short)green, (short)blue, (short)opacity);
306                        T t = cache.get(value);
307                        if (t == null) {
308                                /* Miss */
309                                t = create(red, green, blue, opacity);
310                                /* Put in cache */
311                                cache.put(value, t);
312                        }
313                        return t;
314                }
315
316                @Override
317                public T get(int red, int green, int blue) {
318                        return get(red, green, blue, 255);
319                }
320
321        @Override
322        public T getHSV(float hue, float saturation, float value, float opacity) {
323            if ( saturation < 0.0001 )                       //HSV from 0 to 1
324            {
325                return get(Math.round(value * 255), Math.round(value * 255), Math.round(value * 255),
326                        Math.round(opacity * 255));
327            }
328            else
329            {
330                float h = hue * 6f;
331                if ( h >= 6 ) h = 0;      //H must be < 1
332                int i = (int)h;             //Or ... var_i = floor( var_h )
333                float a = value * ( 1 - saturation );
334                float b = value * ( 1 - saturation * ( h - i ) );
335                float c = value * ( 1 - saturation * ( 1 - ( h - i ) ) );
336
337                switch (i)
338                {
339                    case 0: return get(Math.round(value * 255), Math.round(c * 255), Math.round(a * 255),
340                            Math.round(opacity * 255));
341                    case 1: return get(Math.round(b * 255), Math.round(value * 255), Math.round(a * 255),
342                            Math.round(opacity * 255));
343                    case 2: return get(Math.round(a * 255), Math.round(value * 255), Math.round(c * 255),
344                            Math.round(opacity * 255));
345                    case 3: return get(Math.round(a * 255), Math.round(b * 255), Math.round(value * 255),
346                            Math.round(opacity * 255));
347                    case 4: return get(Math.round(c * 255), Math.round(a * 255), Math.round(value * 255),
348                            Math.round(opacity * 255));
349                    default: return get(Math.round(value * 255), Math.round(a * 255), Math.round(b * 255),
350                            Math.round(opacity * 255));
351                }
352            }
353        }
354
355        @Override
356        public T getHSV(float hue, float saturation, float value) {
357            return getHSV(hue, saturation, value, 1.0f);
358        }
359
360        @Override
361                public T getWhite() {
362                        return get(255, 255, 255, 255);
363                }
364
365                @Override
366                public T getBlack() {
367                        return get(0, 0, 0, 255);
368                }
369
370                @Override
371                public T getTransparent() {
372                        return get(0, 0, 0, 0);
373                }
374
375                @Override
376                public T getRandom(RNG rng, int opacity) {
377                        return get(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256), opacity);
378                }
379
380        /**
381         * @param r the red component in 0.0 to 1.0 range, typically
382         * @param g the green component in 0.0 to 1.0 range, typically
383         * @param b the blue component in 0.0 to 1.0 range, typically
384         * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a
385         * bright color, exclusive)
386         */
387        public float getSaturation(float r, float g, float b) {
388            float min = Math.min(Math.min(r, g ), b);    //Min. value of RGB
389            float max = Math.max(Math.max(r, g), b);    //Min. value of RGB
390            float delta = max - min;                     //Delta RGB value
391
392            float saturation;
393
394            if ( delta < 0.0001f )                     //This is a gray, no chroma...
395            {
396                saturation = 0;
397            }
398            else                                    //Chromatic data...
399            {
400                saturation = delta / max;
401            }
402            return saturation;
403        }
404        /**
405         * @param c a concrete color
406         * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a
407         * bright color, exclusive)
408         */
409        @Override
410        public float getSaturation(T c) {
411            return getSaturation(getRed(c) / 255f, getGreen(c) / 255f, getBlue(c) / 255f);
412        }
413
414        /**
415         * @param r the red component in 0.0 to 1.0 range, typically
416         * @param g the green component in 0.0 to 1.0 range, typically
417         * @param b the blue component in 0.0 to 1.0 range, typically
418         * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to
419         * 1.0 (inclusive) for screen colors or arbitrarily high for HDR colors.
420         */
421        public float getValue(float r, float g, float b)
422        {
423            return Math.max(Math.max(r, g), b);
424        }
425        /**
426         * @param c a concrete color
427         * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to
428         * 1.0 (inclusive) for screen colors or arbitrarily high for HDR colors.
429         */
430        @Override
431        public float getValue(T c) {
432            float r = getRed(c) / 255f;                     //RGB from 0 to 255
433            float g = getGreen(c) / 255f;
434            float b = getBlue(c) / 255f;
435
436            return Math.max(Math.max(r, g), b);
437        }
438
439        /**
440         * @param r the red component in 0.0 to 1.0 range, typically
441         * @param g the green component in 0.0 to 1.0 range, typically
442         * @param b the blue component in 0.0 to 1.0 range, typically
443         * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and
444         * eventually to purple before looping back to almost the same red (1.0, exclusive)
445         */
446        public float getHue(float r, float g, float b) {
447            float min = Math.min(Math.min(r, g ), b);    //Min. value of RGB
448            float max = Math.max(Math.max(r, g), b);    //Min. value of RGB
449            float delta = max - min;                     //Delta RGB value
450
451            float hue;
452
453            if ( delta < 0.0001f )                     //This is a gray, no chroma...
454            {
455                hue = 0;                                //HSV results from 0 to 1
456            }
457            else                                    //Chromatic data...
458            {
459                float rDelta = ( ( ( max - r ) / 6f ) + ( delta / 2f ) ) / delta;
460                float gDelta = ( ( ( max - g ) / 6f ) + ( delta / 2f ) ) / delta;
461                float bDelta = ( ( ( max - b ) / 6f ) + ( delta / 2f ) ) / delta;
462
463                if       ( r == max ) hue = bDelta - gDelta;
464                else if ( g == max ) hue = ( 1f / 3f ) + rDelta - bDelta;
465                else                 hue = ( 2f / 3f ) + gDelta - rDelta;
466
467                if ( hue < 0 ) hue += 1f;
468                else if ( hue > 1 ) hue -= 1;
469            }
470            return hue;
471        }
472
473        /**
474         * @param c a concrete color
475         * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and
476         * eventually to purple before looping back to almost the same red (1.0, exclusive)
477         */
478        @Override
479        public float getHue(T c) {
480            return getHue(getRed(c) / 255f, getGreen(c) / 255f, getBlue(c) / 255f);
481        }
482
483        @Override
484                public T filter(T c)
485        {
486                return get(getRed(c), getGreen(c), getBlue(c), getAlpha(c));
487        }
488
489                @Override
490                public IColoredString<T> filter(IColoredString<T> ics) {
491                        /*
492                         * It is common not to have a filter or to have the identity one. To
493                         * avoid always copying strings in this case, we first roll over the
494                         * string to see if there'll be a change.
495                         * 
496                         * This is clearly a subjective design choice but my industry
497                         * experience is that minimizing allocations is the thing to do for
498                         * performances, hence I prefer iterating twice to do that.
499                         */
500                        boolean change = false;
501                        for (IColoredString.Bucket<T> bucket : ics) {
502                                final T in = bucket.getColor();
503                                if (in == null)
504                                        continue;
505                                final T out = filter(in);
506                                if (in != out) {
507                                        change = true;
508                                        break;
509                                }
510                        }
511
512                        if (change) {
513                                final IColoredString<T> result = IColoredString.Impl.create();
514                                for (IColoredString.Bucket<T> bucket : ics)
515                                        result.append(bucket.getText(), filter(bucket.getColor()));
516                                return result;
517                        } else
518                                /* Only one allocation: the iterator, yay \o/ */
519                                return ics;
520                }
521                /**
522                 * Gets a copy of t and modifies it to make a shade of gray with the same brightness.
523                 * The doAlpha parameter causes the alpha to be considered in the calculation of brightness and also changes the
524                 * returned alpha of the color.
525                 * Not related to reified types or any usage of "reify."
526                 * @param t a T to copy; only the copy will be modified
527                 * @param doAlpha
528                 *            Whether to include (and hereby change) the alpha component.
529                 * @return A monochromatic variation of {@code t}.
530                 */
531                @Override
532                public T greify(T t, boolean doAlpha) {
533                        if (t == null)
534                                /* Cannot do */
535                                return null;
536                        final int red = getRed(t);
537                        final int green = getGreen(t);
538                        final int blue = getBlue(t);
539                        final int alpha = getAlpha(t);
540                        final int rgb = red + green + blue;
541                        final int mean;
542                        final int newAlpha;
543                        if (doAlpha) {
544                                mean = (rgb + alpha) / 4;
545                                newAlpha = mean;
546                        } else {
547                                mean = rgb / 3;
548                                /* No change */
549                                newAlpha = alpha;
550                        }
551                        return get(mean, mean, mean, newAlpha);
552                }
553
554                /**
555                 * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change.
556         * This implementation tries to work with colors in a way that is as general as possible, using getRed() instead
557         * of some specific detail that depends on how a color is implemented. Other implementations that specialize in
558         * a specific type of color may be able to be more efficient.
559         * @param start the initial color T
560         * @param end the "target" color T
561         * @param change the degree to change closer to end; a change of 0.0f produces start, 1.0f produces end
562         * @return a new T between start and end
563                 */
564                public T lerp(T start, T end, float change)
565                {
566                        if(start == null || end == null)
567                                return null;
568                        final int sr = getRed(start), sg = getGreen(start), sb = getBlue(start), sa = getAlpha(start),
569                                        er = getRed(end), eg = getGreen(end), eb = getBlue(end), ea = getAlpha(end);
570                        return get(
571                                        (int)(sr + change * (er - sr)),
572                                        (int)(sg + change
573                                                        * (eg - sg)),
574                                        (int)(sb + change * (eb - sb)),
575                                        (int)(sa + change * (ea - sa))
576                        );
577                }
578                /**
579                 * Gets a fully-desaturated version of the given color (keeping its brightness, but making it grayscale).
580                 * Keeps alpha the same; if you want alpha to be considered (and brightness to be calculated differently), then
581                 * you can use greify() in this class instead.
582                 * @param color the color T to desaturate (will not be modified)
583                 * @return the grayscale version of color
584                 */
585                public T desaturated(T color)
586                {
587                        int f = (int)Math.min(255, getRed(color) * 0.299f + getGreen(color) * 0.587f + getBlue(color) * 0.114f);
588                        return get(f, f, f, getAlpha(color));
589                }
590
591                /**
592                 * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat).
593                 * Alpha is left unchanged.
594                 * @param color the color T to desaturate
595                 * @param degree a float between 0.0f and 1.0f; more makes it less colorful
596                 * @return the desaturated (and if a filter is used, also filtered) new color T
597                 */
598                public T desaturate(T color, float degree)
599                {
600                        return lerp(color, desaturated(color), degree);
601                }
602
603        /**
604         * Fully saturates color (makes it a vivid color like red or green and less gray) and returns the modified copy.
605         * Leaves alpha unchanged.
606         * @param color the color T to saturate (will not be modified)
607         * @return the saturated version of color
608         */
609        public T saturated(T color)
610        {
611            return getHSV(getHue(color), 1f, getValue(color), getAlpha(color));
612        }
613                /**
614                 * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and
615                 * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this is
616                 * likely to produce a red hue by default (if there's no hue to make vivid, it needs to choose something).
617                 * @param color the color T to saturate
618                 * @param degree a float between 0.0f and 1.0f; more makes it more colorful
619                 * @return the saturated (and if a filter is used, also filtered) new color
620                 */
621                public T saturate(T color, float degree)
622                {
623                        return lerp(color, saturated(color), degree);
624                }
625
626
627                /**
628                 * Create a concrete instance of the color type given as a type parameter. That's the
629                 * place to use the {@link #filter}.
630                 * 
631                 * @param red the red component of the desired color
632                 * @param green the green component of the desired color
633                 * @param blue the blue component of the desired color
634                 * @param opacity the alpha component or opacity of the desired color
635                 * @return a fresh instance of the concrete color type
636                 */
637                protected abstract T create(int red, int green, int blue, int opacity);
638
639                private long getUniqueIdentifier(short r, short g, short b, short a) {
640                        return ((a & 0xffL) << 48) | ((r & 0xffffL) << 32) | ((g & 0xffffL) << 16) | (b & 0xffffL);
641                }
642
643        }
644}