001package squidpony; 002 003import squidpony.panel.IColoredString; 004import squidpony.squidmath.CrossHash; 005import squidpony.squidmath.IRNG; 006import squidpony.squidmath.UnorderedSet; 007 008import java.util.ArrayList; 009 010/** 011 * How to manage colors, making sure that a color is allocated at most once. 012 * 013 * <p> 014 * If you aren't using squidlib's gdx part, you should use this interface (and 015 * the {@link Skeleton} implementation), because it caches instances. 016 * </p> 017 * 018 * <p> 019 * If you are using squidlib's gdx part, you should use this interface (and the 020 * {@code SquidColorCenter} implementation) if: 021 * 022 * <ul> 023 * <li>You don't want to use preallocated instances (if you do, check out 024 * {@code squidpony.squidgrid.gui.Colors})</li> 025 * <li>You don't want to use named colors (if you do, check out 026 * {@code com.badlogic.gdx.graphics.Colors})</li> 027 * <li>You don't like libgdx's Color representation (components as floats 028 * in-between 0 and 1) but prefer components within 0 (inclusive) and 256 029 * (exclusive); and don't mind the overhead of switching the representations. My 030 * personal opinion is that the overhead doesn't matter w.r.t other intensive 031 * operations that we have in roguelikes (path finding).</li> 032 * </ul> 033 * 034 * @author smelC 035 * 036 * @param <T> 037 * The concrete type of colors 038 */ 039public interface IColorCenter<T> { 040 041 /** 042 * @param red 043 * The red component. For screen colors, in-between 0 (inclusive) 044 * and 256 (exclusive). 045 * @param green 046 * The green component. For screen colors, in-between 0 (inclusive) 047 * and 256 (exclusive). 048 * @param blue 049 * The blue component. For screen colors, in-between 0 (inclusive) 050 * and 256 (exclusive). 051 * @param opacity 052 * The alpha component. In-between 0 (inclusive) and 256 053 * (exclusive). Larger values mean more opacity; 0 is clear. 054 * @return A possibly transparent color. 055 */ 056 T get(int red, int green, int blue, int opacity); 057 058 /** 059 * @param red 060 * The red component. For screen colors, in-between 0 (inclusive) 061 * and 256 (exclusive). 062 * @param green 063 * The green component. For screen colors, in-between 0 (inclusive) 064 * and 256 (exclusive). 065 * @param blue 066 * The blue component. For screen colors, in-between 0 (inclusive) 067 * and 256 (exclusive). 068 * @return An opaque color. 069 */ 070 T get(int red, int green, int blue); 071 072 /** 073 * 074 * @param hue The hue of the desired color from 0.0 (red, inclusive) towards orange, then 075 * yellow, and eventually to purple before looping back to almost the same red 076 * (1.0, exclusive). Values outside this range should be treated as wrapping 077 * around, so 1.1f and -0.9f would be the same as 0.1f . 078 * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive) 079 * to 1.0 (a bright color, inclusive) 080 * @param value the value (essentially lightness) of the color from 0.0 (black, 081 * inclusive) to 1.0 (very bright, inclusive). 082 * @param opacity the alpha component as a float; 0.0f is clear, 1.0f is opaque. 083 * @return a possibly transparent color 084 */ 085 T getHSV(float hue, float saturation, float value, float opacity); 086 087 /** 088 * 089 * @param hue The hue of the desired color from 0.0 (red, inclusive) towards orange, then 090 * yellow, and eventually to purple before looping back to almost the same red 091 * (1.0, exclusive) 092 * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive) 093 * to 1.0 (a bright color, exclusive) 094 * @param value the value (essentially lightness) of the color from 0.0 (black, 095 * inclusive) to 1.0 (very bright, inclusive). 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(IRNG 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 (very bright, inclusive). 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 * Finds a gradient with 16 steps going from fromColor to toColor, 246 * both included in the gradient. 247 * @param fromColor the color to start with, included in the gradient 248 * @param toColor the color to end on, included in the gradient 249 * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps 250 */ 251 public ArrayList<T> gradient(T fromColor, T toColor); 252 253 /** 254 * Finds a gradient with the specified number of steps going from fromColor to toColor, 255 * both included in the gradient. 256 * @param fromColor the color to start with, included in the gradient 257 * @param toColor the color to end on, included in the gradient 258 * @param steps the number of elements to use in the gradient 259 * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps 260 */ 261 public ArrayList<T> gradient(T fromColor, T toColor, int steps); 262 263 /** 264 * A skeletal implementation of {@link IColorCenter}. 265 * 266 * @author smelC 267 * 268 * @param <T> a concrete color type 269 */ 270 abstract class Skeleton<T> implements IColorCenter<T> { 271 272 @SuppressWarnings("unchecked") 273 protected class GranularHasher implements CrossHash.IHasher{ 274 int[] feed = new int[4]; 275 @Override 276 public int hash(final Object data) { 277 if(data == null) return 0; 278 final long y = (data == feed) 279 ? getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]) 280 : getUniqueIdentifier((T)data); 281 final int x = (int)(y ^ y >>> 32) * 0x62BD5; 282 return x ^ ((x << 17) | (x >>> 15)) ^ ((x << 9) | (x >>> 23)); 283 } 284 285 @Override 286 public boolean areEqual(final Object left, final Object right) { 287 if(left == right) return true; 288 if(left != null && right != null) 289 { 290 if(left == feed) 291 { 292 return getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]) == getUniqueIdentifier((T)right); 293// final long f = getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]); 294// final long i = getUniqueIdentifier((T)right); 295// System.out.printf("left 0x%016X, right 0x%016X\n", f, i); 296// return f == i; 297 } 298 else if(right == feed) 299 { 300 return getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]) == getUniqueIdentifier((T)left); 301// final long f = getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]); 302// final long i = getUniqueIdentifier((T)left); 303// System.out.printf("left 0x%016X, right 0x%016X\n", i, f); 304// return f == i; 305 } 306 else 307 { 308 return (getUniqueIdentifier((T)left) == getUniqueIdentifier((T)right)); 309 } 310 } 311 return false; 312// return (left == right) || (left != null && right != null && 313// ((left == feed && getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3]) == getUniqueIdentifier((T)right)) || 314// (right == feed && getUniqueIdentifier((T)left) == getUniqueIdentifier(feed[0], feed[1], feed[2], feed[3])) || 315// (left != feed && right != feed && getUniqueIdentifier((T)left) == getUniqueIdentifier((T)right)))); 316 } 317 } 318 protected GranularHasher theHasher = new GranularHasher(); 319 private final UnorderedSet<T> cache = new UnorderedSet<>(256, theHasher); 320 321 protected /*Nullable*/ IFilter<T> filter; 322 323 /** 324 * @param filter 325 * The filter to use, or {@code null} for no filter. 326 */ 327 protected Skeleton(/*Nullable*/ IFilter<T> filter) { 328 this.filter = filter; 329 } 330 331 /** 332 * It clears the cache. You may need to do this to limit the cache to the colors used in a specific section. 333 * This is also useful if a Filter changes what colors it should return on a frame-by-frame basis; in that case, 334 * you can call clearCache() at the start or end of a frame to ensure the next frame gets different colors. 335 */ 336 public void clearCache() 337 { 338 cache.clear(); 339 } 340 341 /** 342 * The actual cache is not public, but there are cases where you may want to know how many different colors are 343 * actually used in a frame or a section of the game. If the cache was emptied (which might be from calling 344 * {@link #clearCache()}), some colors were requested, then this is called, the returned int should be the 345 * count of distinct colors this IColorCenter had created and cached; duplicates won't be counted twice. 346 * @return 347 */ 348 public int cacheSize() 349 { 350 return cache.size(); 351 } 352 353 /** 354 * You may want to copy colors between IColorCenter instances that have different create() methods -- and as 355 * such, will have different values for the same keys in the cache. This allows you to copy the cache from other 356 * into this Skeleton, but using this Skeleton's create() method. 357 * @param other another Skeleton of the same type that will have its cache copied into this Skeleton 358 */ 359 public void copyCache(Skeleton<T> other) 360 { 361 cache.addAll(other.cache); 362 } 363 364 /** 365 * If you're changing the filter, you should likely call 366 * {@link #clearCache()}. 367 * 368 * @param filter 369 * The filter to use, or {@code null} to turn filtering OFF. 370 * @return {@code this} 371 */ 372 public Skeleton<T> setFilter(IFilter<T> filter) { 373 this.filter = filter; 374 return this; 375 } 376 377 @SuppressWarnings("SuspiciousMethodCalls") 378 @Override 379 public T get(int red, int green, int blue, int opacity) { 380 theHasher.feed[0] = red; 381 theHasher.feed[1] = green; 382 theHasher.feed[2] = blue; 383 theHasher.feed[3] = opacity; 384 T t; 385 if (cache.contains(theHasher.feed)) { 386 t = cache.get(theHasher.feed); 387 } 388 else 389 { 390 /* Miss */ 391 t = create(red, green, blue, opacity); 392 /* Put in cache */ 393 cache.add(t); 394 } 395 return t; 396 } 397 398 @Override 399 public T get(int red, int green, int blue) { 400 return get(red, green, blue, 255); 401 } 402 403 @Override 404 public T getHSV(float hue, float saturation, float value, float opacity) { 405 if ( saturation < 0.0001f ) //HSV from 0 to 1 406 { 407 return get(Math.round(value * 255), Math.round(value * 255), Math.round(value * 255), 408 Math.round(opacity * 255)); 409 } 410 else 411 { 412 float h = ((hue + 6f) % 1f) * 6f; // allows negative hue to wrap 413 int i = (int)h; 414 float a = value * (1 - saturation); 415 float b = value * (1 - saturation * (h - i)); 416 float c = value * (1 - saturation * (1 - (h - i))); 417 418 switch (i) 419 { 420 case 0: return get(Math.round(value * 255), Math.round(c * 255), Math.round(a * 255), 421 Math.round(opacity * 255)); 422 case 1: return get(Math.round(b * 255), Math.round(value * 255), Math.round(a * 255), 423 Math.round(opacity * 255)); 424 case 2: return get(Math.round(a * 255), Math.round(value * 255), Math.round(c * 255), 425 Math.round(opacity * 255)); 426 case 3: return get(Math.round(a * 255), Math.round(b * 255), Math.round(value * 255), 427 Math.round(opacity * 255)); 428 case 4: return get(Math.round(c * 255), Math.round(a * 255), Math.round(value * 255), 429 Math.round(opacity * 255)); 430 default: return get(Math.round(value * 255), Math.round(a * 255), Math.round(b * 255), 431 Math.round(opacity * 255)); 432 } 433 } 434 } 435 436 437 @Override 438 public T getHSV(float hue, float saturation, float value) { 439 return getHSV(hue, saturation, value, 1.0f); 440 } 441 442 @Override 443 public T getWhite() { 444 return get(255, 255, 255, 255); 445 } 446 447 @Override 448 public T getBlack() { 449 return get(0, 0, 0, 255); 450 } 451 452 @Override 453 public T getTransparent() { 454 return get(0, 0, 0, 0); 455 } 456 457 @Override 458 public T getRandom(IRNG rng, int opacity) { 459 return get(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256), opacity); 460 } 461 462 /** 463 * @param r the red component in 0.0 to 1.0 range, typically 464 * @param g the green component in 0.0 to 1.0 range, typically 465 * @param b the blue component in 0.0 to 1.0 range, typically 466 * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a 467 * bright color, exclusive) 468 */ 469 public float getSaturation(float r, float g, float b) { 470 float min = Math.min(Math.min(r, g ), b); //Min. value of RGB 471 float max = Math.max(Math.max(r, g), b); //Min. value of RGB 472 float delta = max - min; //Delta RGB value 473 474 float saturation; 475 476 if ( delta < 0.0001f ) //This is a gray, no chroma... 477 { 478 saturation = 0; 479 } 480 else //Chromatic data... 481 { 482 saturation = delta / max; 483 } 484 return saturation; 485 } 486 /** 487 * @param c a concrete color 488 * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a 489 * bright color, exclusive) 490 */ 491 @Override 492 public float getSaturation(T c) { 493 return getSaturation(getRed(c) / 255f, getGreen(c) / 255f, getBlue(c) / 255f); 494 } 495 496 /** 497 * @param r the red component in 0.0 to 1.0 range, typically 498 * @param g the green component in 0.0 to 1.0 range, typically 499 * @param b the blue component in 0.0 to 1.0 range, typically 500 * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to 501 * 1.0 (very bright, inclusive). 502 */ 503 public float getValue(float r, float g, float b) 504 { 505 return Math.max(Math.max(r, g), b); 506 } 507 /** 508 * @param c a concrete color 509 * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to 510 * 1.0 (very bright, inclusive). 511 */ 512 @Override 513 public float getValue(T c) { 514 float r = getRed(c) / 255f; //RGB from 0 to 255 515 float g = getGreen(c) / 255f; 516 float b = getBlue(c) / 255f; 517 518 return Math.max(Math.max(r, g), b); 519 } 520 521 /** 522 * @param r the red component in 0.0 to 1.0 range, typically 523 * @param g the green component in 0.0 to 1.0 range, typically 524 * @param b the blue component in 0.0 to 1.0 range, typically 525 * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and 526 * eventually to purple before looping back to almost the same red (1.0, exclusive) 527 */ 528 public float getHue(float r, float g, float b) { 529 float min = Math.min(Math.min(r, g ), b); //Min. value of RGB 530 float max = Math.max(Math.max(r, g), b); //Min. value of RGB 531 float delta = max - min; //Delta RGB value 532 533 float hue; 534 535 if ( delta < 0.0001f ) //This is a gray, no chroma... 536 { 537 hue = 0; //HSV results from 0 to 1 538 } 539 else //Chromatic data... 540 { 541 float rDelta = (((max - r) / 6f) + (delta / 2f)) / delta; 542 float gDelta = (((max - g) / 6f) + (delta / 2f)) / delta; 543 float bDelta = (((max - b) / 6f) + (delta / 2f)) / delta; 544 545 if (r == max) hue = bDelta - gDelta; 546 else if (g == max) hue = (1f / 3f) + rDelta - bDelta; 547 else hue = (2f / 3f) + gDelta - rDelta; 548 549 if (hue < 0) hue += 1f; 550 else if (hue > 1) hue -= 1; 551 } 552 return hue; 553 } 554 555 /** 556 * @param c a concrete color 557 * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and 558 * eventually to purple before looping back to almost the same red (1.0, exclusive) 559 */ 560 @Override 561 public float getHue(T c) { 562 return getHue(getRed(c) / 255f, getGreen(c) / 255f, getBlue(c) / 255f); 563 } 564 565 @Override 566 public T filter(T c) 567 { 568 return c == null ? null : get(getRed(c), getGreen(c), getBlue(c), getAlpha(c)); 569 } 570 571 572 @Override 573 public IColoredString<T> filter(IColoredString<T> ics) { 574 /* 575 * It is common not to have a filter or to have the identity one. To 576 * avoid always copying strings in this case, we first roll over the 577 * string to see if there'll be a change. 578 * 579 * This is clearly a subjective design choice but my industry 580 * experience is that minimizing allocations is the thing to do for 581 * performances, hence I prefer iterating twice to do that. 582 */ 583 boolean change = false; 584 for (IColoredString.Bucket<T> bucket : ics) { 585 final T in = bucket.getColor(); 586 if (in == null) 587 continue; 588 final T out = filter(in); 589 if (in != out) { 590 change = true; 591 break; 592 } 593 } 594 595 if (change) { 596 final IColoredString<T> result = IColoredString.Impl.create(); 597 for (IColoredString.Bucket<T> bucket : ics) 598 result.append(bucket.getText(), filter(bucket.getColor())); 599 return result; 600 } else 601 /* Only one allocation: the iterator, yay \o/ */ 602 return ics; 603 } 604 /** 605 * Gets a copy of t and modifies it to make a shade of gray with the same brightness. 606 * The doAlpha parameter causes the alpha to be considered in the calculation of brightness and also changes the 607 * returned alpha of the color. 608 * Not related to reified types or any usage of "reify." 609 * @param t a T to copy; only the copy will be modified 610 * @param doAlpha 611 * Whether to include (and hereby change) the alpha component. 612 * @return A monochromatic variation of {@code t}. 613 */ 614 @Override 615 public T greify(T t, boolean doAlpha) { 616 if (t == null) 617 /* Cannot do */ 618 return null; 619 final int red = getRed(t); 620 final int green = getGreen(t); 621 final int blue = getBlue(t); 622 final int alpha = getAlpha(t); 623 final int rgb = red + green + blue; 624 final int mean; 625 final int newAlpha; 626 if (doAlpha) { 627 mean = (rgb + alpha) / 4; 628 newAlpha = mean; 629 } else { 630 mean = rgb / 3; 631 /* No change */ 632 newAlpha = alpha; 633 } 634 return get(mean, mean, mean, newAlpha); 635 } 636 637 /** 638 * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change. 639 * This implementation tries to work with colors in a way that is as general as possible, using getRed() instead 640 * of some specific detail that depends on how a color is implemented. Other implementations that specialize in 641 * a specific type of color may be able to be more efficient. 642 * @param start the initial color T 643 * @param end the "target" color T 644 * @param change the degree to change closer to end; a change of 0.0f produces start, 1.0f produces end 645 * @return a new T between start and end 646 */ 647 public T lerp(T start, T end, float change) 648 { 649 if(start == null || end == null) 650 return null; 651 final int sr = getRed(start), sg = getGreen(start), sb = getBlue(start), sa = getAlpha(start), 652 er = getRed(end), eg = getGreen(end), eb = getBlue(end), ea = getAlpha(end); 653 return get( 654 (int)(sr + change * (er - sr)), 655 (int)(sg + change 656 * (eg - sg)), 657 (int)(sb + change * (eb - sb)), 658 (int)(sa + change * (ea - sa)) 659 ); 660 } 661 /** 662 * Gets a fully-desaturated version of the given color (keeping its brightness, but making it grayscale). 663 * Keeps alpha the same; if you want alpha to be considered (and brightness to be calculated differently), then 664 * you can use greify() in this class instead. 665 * @param color the color T to desaturate (will not be modified) 666 * @return the grayscale version of color 667 */ 668 public T desaturated(T color) 669 { 670 int f = (int)Math.min(255, getRed(color) * 0.299f + getGreen(color) * 0.587f + getBlue(color) * 0.114f); 671 return get(f, f, f, getAlpha(color)); 672 } 673 674 /** 675 * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat). 676 * Alpha is left unchanged. 677 * @param color the color T to desaturate 678 * @param degree a float between 0.0f and 1.0f; more makes it less colorful 679 * @return the desaturated (and if a filter is used, also filtered) new color T 680 */ 681 public T desaturate(T color, float degree) 682 { 683 return lerp(color, desaturated(color), degree); 684 } 685 686 /** 687 * Fully saturates color (makes it a vivid color like red or green and less gray) and returns the modified copy. 688 * Leaves alpha unchanged. 689 * @param color the color T to saturate (will not be modified) 690 * @return the saturated version of color 691 */ 692 public T saturated(T color) 693 { 694 return getHSV(getHue(color), 1f, getValue(color), getAlpha(color)); 695 } 696 /** 697 * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and 698 * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this is 699 * likely to produce a red hue by default (if there's no hue to make vivid, it needs to choose something). 700 * @param color the color T to saturate 701 * @param degree a float between 0.0f and 1.0f; more makes it more colorful 702 * @return the saturated (and if a filter is used, also filtered) new color 703 */ 704 public T saturate(T color, float degree) 705 { 706 return lerp(color, saturated(color), degree); 707 } 708 709 @Override 710 public ArrayList<T> gradient(T fromColor, T toColor) { 711 return gradient(fromColor, toColor, 16); 712 } 713 714 @Override 715 public ArrayList<T> gradient(T fromColor, T toColor, int steps) { 716 ArrayList<T> colors = new ArrayList<>(Math.max(steps, 1)); 717 colors.add(filter(fromColor)); 718 if(steps < 2) 719 return colors; 720 for (float i = 1; i < steps; i++) { 721 colors.add(lerp(fromColor, toColor, i / (steps - 1f))); 722 } 723 return colors; 724 725 } 726 727 /** 728 * Create a concrete instance of the color type given as a type parameter. That's the 729 * place to use the {@link #filter}. 730 * 731 * @param red the red component of the desired color 732 * @param green the green component of the desired color 733 * @param blue the blue component of the desired color 734 * @param opacity the alpha component or opacity of the desired color 735 * @return a fresh instance of the concrete color type 736 */ 737 protected abstract T create(int red, int green, int blue, int opacity); 738 739 protected long getUniqueIdentifier(int r, int g, int b, int a) { 740 return ((a & 0xffL) << 48) | ((r & 0xffL) << 32) | ((g & 0xffL) << 16) | (b & 0xffL); 741 } 742 protected long getUniqueIdentifier(T item) 743 { 744 return ((getAlpha(item) & 0xffL) << 48) | ((getRed(item) & 0xffL) << 32) 745 | ((getGreen(item) & 0xffL) << 16) | (getBlue(item) & 0xffL); 746 } 747 748 } 749}