001package squidpony.squidgrid.gui.gdx; 002 003import com.badlogic.gdx.graphics.Color; 004import com.badlogic.gdx.math.Interpolation; 005import com.badlogic.gdx.math.MathUtils; 006import com.badlogic.gdx.utils.IntMap; 007import squidpony.IColorCenter; 008import squidpony.IFilter; 009import squidpony.panel.IColoredString; 010import squidpony.squidmath.CoordPacker; 011import squidpony.squidmath.IRNG; 012import squidpony.squidmath.StatefulRNG; 013 014import java.util.ArrayList; 015 016/** 017 * A concrete implementation of {@link IColorCenter} for libgdx's {@link com.badlogic.gdx.graphics.Color}. 018 * Supports filtering any colors that this creates using an {@link IFilter}, such as one from {@link Filters}. 019 * This class largely supersedes the earlier SColorFactory class, and supports similar operations 020 * while also allowing filters to modify the returned colors. SColorFactory has been removed, so code transitioning 021 * to SquidColorCenter may need to change blend() to {@link #lerp(Color, Color, double)}, and setFloor() to 022 * {@link #setGranularity(int)} (with different behavior). 023 * @author smelC 024 * @author Tommy Ettinger 025 * @see SColor Another way to obtain colors by using pre-allocated (and named) instances. 026 */ 027public class SquidColorCenter implements IColorCenter<Color> { 028 029 /** 030 * How different requested colors need to be to make a new color; should range from 0 to at most 6. 031 * If this is 0, all requested colors will be looked up (using a cached version if the exact request had been made 032 * before), but if this is greater than 0, then exponentially less colors will be used, using the cache for twice as 033 * many requests at granularity 1 (2 raised to the granularity), four times as many at granularity 2, and so on. 034 * Defaults to 1, which seems to help ensure visually-identical colors are not created more than once. 035 */ 036 private int granularity = 1; 037 private int granularityMask = 0xFE; 038 039 /** 040 * Gets the granularity, which is how different requested colors need to be to make a new color; can be from 0 to 6. 041 * If this is 0, all requested colors will be looked up (using a cached version if the exact request had been made 042 * before), but if this is greater than 0, then exponentially less colors will be used, using the cache for twice as 043 * many requests at granularity 1 (2 raised to the granularity), four times as many at granularity 2, and so on. 044 * If no granularity was set, the default is 1. 045 * @return the current granularity, as an int 046 */ 047 public int getGranularity() { 048 return granularity; 049 } 050 051 /** 052 * Sets the granularity, which is how different requested colors must be to make a new color; from 0 to at most 6. 053 * If this is 0, all requested colors will be looked up (using a cached version if the exact request had been made 054 * before), but if this is greater than 0, then exponentially less colors will be used, using the cache for twice as 055 * many requests at granularity 1 (2 raised to the granularity), four times as many at granularity 2, and so on. 056 * If no granularity was set, the default is 1. 057 * @param granularity the granularity to use; will be clamped between 0 and 6 058 */ 059 public void setGranularity(int granularity) { 060 this.granularity = MathUtils.clamp(granularity, 0, 6); 061 granularityMask = 0xFF << this.granularity & 0xFF; 062 } 063 064 public IFilter<Color> filter; 065 protected IntMap<Color> cache; 066 /** 067 * A fresh filter-less color center. 068 */ 069 public SquidColorCenter() 070 { 071 this(null); 072 } 073 074 /** 075 * A fresh filtered color center. 076 * 077 * @param filterEffect 078 * The filter to use. 079 */ 080 public SquidColorCenter(/*Nullable*/IFilter<Color> filterEffect) 081 { 082 cache = new IntMap<>(256); 083 filter = filterEffect; 084 } 085 086 @Override 087 public Color get(int red, int green, int blue, int opacity) { 088 int abgr = (opacity &= granularityMask & 0xFE) << 24 089 | (blue &= granularityMask) << 16 090 | (green &= granularityMask) << 8 091 | (red &= granularityMask); 092 Color result; 093 if ((result = cache.get(abgr)) != null) 094 return result; 095 if (filter == null) { /* No filtering */ 096 result = new Color(red / 255f, green / 255f, blue / 255f, opacity / 254f); 097 } 098 else { /* Some filtering */ 099 result = filter.alter(red / 255f, green / 255f, blue / 255f, opacity / 254f); 100 } 101 cache.put(abgr, result); 102 return result; 103 } 104 105 /** 106 * @param red The red component. For screen colors, in-between 0 (inclusive) 107 * and 256 (exclusive). 108 * @param green The green component. For screen colors, in-between 0 (inclusive) 109 * and 256 (exclusive). 110 * @param blue The blue component. For screen colors, in-between 0 (inclusive) 111 * and 256 (exclusive). 112 * @return An opaque color. 113 */ 114 @Override 115 public Color get(int red, int green, int blue) { 116 return get(red, green, blue, 255); 117 } 118 119 /** 120 * @param hue The hue of the desired color from 0.0 (red, inclusive) towards orange, then 121 * yellow, and eventually to purple before looping back to almost the same red 122 * (1.0, exclusive). Values outside this range should be treated as wrapping 123 * around, so 1.1f and -0.9f would be the same as 0.1f . 124 * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive) 125 * to 1.0 (a bright color, inclusive) 126 * @param value the value (essentially lightness) of the color from 0.0 (black, 127 * inclusive) to 1.0 (very bright, inclusive). 128 * @param opacity the alpha component as a float; 0.0f is clear, 1.0f is opaque. 129 * @return a possibly transparent color 130 */ 131 @Override 132 public Color getHSV(float hue, float saturation, float value, float opacity) { 133 if ( saturation < 0.0001f ) //HSV from 0 to 1 134 { 135 return get(Math.round(value * 255), Math.round(value * 255), Math.round(value * 255), 136 Math.round(opacity * 255)); 137 } 138 else 139 { 140 float h = ((hue + 6f) % 1f) * 6f; // allows negative hue to wrap 141 int i = (int)h; 142 float a = value * (1 - saturation); 143 float b = value * (1 - saturation * (h - i)); 144 float c = value * (1 - saturation * (1 - (h - i))); 145 146 switch (i) 147 { 148 case 0: return get(Math.round(value * 255), Math.round(c * 255), Math.round(a * 255), 149 Math.round(opacity * 255)); 150 case 1: return get(Math.round(b * 255), Math.round(value * 255), Math.round(a * 255), 151 Math.round(opacity * 255)); 152 case 2: return get(Math.round(a * 255), Math.round(value * 255), Math.round(c * 255), 153 Math.round(opacity * 255)); 154 case 3: return get(Math.round(a * 255), Math.round(b * 255), Math.round(value * 255), 155 Math.round(opacity * 255)); 156 case 4: return get(Math.round(c * 255), Math.round(a * 255), Math.round(value * 255), 157 Math.round(opacity * 255)); 158 default: return get(Math.round(value * 255), Math.round(a * 255), Math.round(b * 255), 159 Math.round(opacity * 255)); 160 } 161 } 162 } 163 164 /** 165 * @param hue The hue of the desired color from 0.0 (red, inclusive) towards orange, then 166 * yellow, and eventually to purple before looping back to almost the same red 167 * (1.0, exclusive) 168 * @param saturation the saturation of the color from 0.0 (a grayscale color; inclusive) 169 * to 1.0 (a bright color, exclusive) 170 * @param value the value (essentially lightness) of the color from 0.0 (black, 171 * inclusive) to 1.0 (very bright, inclusive). 172 * @return an opaque color 173 */ 174 @Override 175 public Color getHSV(float hue, float saturation, float value) { 176 return getHSV(hue, saturation, value, 1f); 177 } 178 179 /** 180 * @return Opaque white. 181 */ 182 @Override 183 public Color getWhite() { 184 return SColor.WHITE; 185 } 186 187 /** 188 * @return Opaque black. 189 */ 190 @Override 191 public Color getBlack() { 192 return SColor.BLACK; 193 } 194 195 /** 196 * @return The fully transparent color. 197 */ 198 @Override 199 public Color getTransparent() { 200 return SColor.TRANSPARENT; 201 } 202 203 /** 204 * @param rng any IRNG from SquidLib, such as an RNG, StatefulRNG, or GWTRNG. 205 * @param opacity The alpha component. In-between 0 (inclusive) and 256 206 * (exclusive). Larger values mean more opacity; 0 is clear. 207 * @return A random color, except for the alpha component. 208 */ 209 @Override 210 public Color getRandom(IRNG rng, int opacity) { 211 return get((rng.nextInt() & 0xFFFFFF00) | (opacity & 0xFF)); 212 } 213 214 @Override 215 public Color filter(Color c) 216 { 217 if(c == null) 218 return Color.CLEAR; 219 else 220 return filter == null ? c : filter.alter(c.r, c.g, c.b, c.a); 221 } 222 223 /** 224 * @param ics an IColoredString, often produced by {@link GDXMarkup#colorString(CharSequence)}. 225 * @return {@code ics} filtered according to {@link #filter(Color)}. May be 226 * {@code ics} itself if unchanged. 227 */ 228 @Override 229 public IColoredString<Color> filter(IColoredString<Color> ics) { 230 /* 231 * It is common not to have a filter or to have the identity one. To 232 * avoid always copying strings in this case, we first roll over the 233 * string to see if there'll be a change. 234 * 235 * This is clearly a subjective design choice but my industry 236 * experience is that minimizing allocations is the thing to do for 237 * performances, hence I prefer iterating twice to do that. 238 */ 239 boolean change = false; 240 for (IColoredString.Bucket<Color> bucket : ics) { 241 final Color in = bucket.getColor(); 242 if (in == null) 243 continue; 244 final Color out = filter(in); 245 if (in != out) { 246 change = true; 247 break; 248 } 249 } 250 251 if (change) { 252 final IColoredString<Color> result = IColoredString.Impl.create(); 253 for (IColoredString.Bucket<Color> bucket : ics) 254 result.append(bucket.getText(), filter(bucket.getColor())); 255 return result; 256 } else 257 /* Only one allocation: the iterator, yay \o/ */ 258 return ics; 259 } 260 261 /** 262 * Gets a copy of t and modifies it to make a shade of gray with the same brightness. 263 * The doAlpha parameter causes the alpha to be considered in the calculation of brightness and also changes the 264 * returned alpha of the color, so translucent colors are considered darker and fully clear ones are black (and 265 * still fully clear). 266 * <br> 267 * This uses a perceptual calculation of brightness that matches the luma calculation used in the YCbCr color space. 268 * It does not necessarily match other brightness calculations, such as value as used in HSV. 269 * <br> 270 * Not related to reified types or any usage of "reify." 271 * 272 * @param color a T to copy; only the copy will be modified 273 * @param doAlpha Whether to include (and hereby change) the alpha component; if false alpha is kept as-is 274 * @return A monochromatic variation of {@code t}. 275 */ 276 @Override 277 public Color greify(Color color, boolean doAlpha) { 278 float luma = SColor.luma(color); 279 if(doAlpha) { 280 luma *= color.a; 281 return get(luma, luma, luma, luma); 282 } 283 return get(luma, luma, luma, color.a); 284 285 } 286 287 public Color get(long c) 288 { 289 return get((int)((c >>> 24) & 0xff), (int)((c >>> 16) & 0xff), (int)((c >>> 8) & 0xff), (int)(c & 0xff)); 290 } 291 public Color get(float r, float g, float b, float a) 292 { 293 return get((int)(255.9999f * r), (int)(255.9999f * g), (int)(255.9999f * b), (int)(255.9999f * a)); 294 } 295 /** 296 * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change. 297 * @param start the initial Color 298 * @param end the "target" color 299 * @param change the degree to change closer to end; a change of 0.0f produces start, 1.0f produces end 300 * @return a new Color 301 */ 302 @Override 303 public Color lerp(Color start, Color end, float change) 304 { 305 if(start == null || end == null) 306 return Color.CLEAR; 307 return get( 308 start.r + change * (end.r - start.r), 309 start.g + change * (end.g - start.g), 310 start.b + change * (end.b - start.b), 311 start.a + change * (end.a - start.a) 312 ); 313 } 314 /** 315 * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change. 316 * @param start the initial Color 317 * @param end the "target" color 318 * @param change the degree to change closer to end; a change of 0.0 produces start, 1.0 produces end 319 * @return a new Color 320 */ 321 public Color lerp(Color start, Color end, double change) 322 { 323 return lerp(start, end, (float)change); 324 } 325 @Override 326 public int getRed(Color c) { 327 return Math.round(c.r * 255f); 328 } 329 330 @Override 331 public int getGreen(Color c) { 332 return Math.round(c.g * 255f); 333 } 334 335 @Override 336 public int getBlue(Color c) { 337 return Math.round(c.b * 255f); 338 } 339 340 @Override 341 public int getAlpha(Color c) { 342 return Math.round(c.a * 255f); 343 } 344 345 /** 346 * @param c a concrete color 347 * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and 348 * eventually to purple before looping back to almost the same red (1.0, exclusive) 349 */ 350 @Override 351 public float getHue(Color c) { 352 return SColor.hue(c); 353 } 354 355 /** 356 * @param c a concrete color 357 * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a 358 * bright color, exclusive) 359 */ 360 @Override 361 public float getSaturation(Color c) { 362 return SColor.saturation(c); 363 } 364 /** 365 * @param r the red component in 0.0 to 1.0 range, typically 366 * @param g the green component in 0.0 to 1.0 range, typically 367 * @param b the blue component in 0.0 to 1.0 range, typically 368 * @return the saturation of the color from 0.0 (a grayscale color; inclusive) to 1.0 (a 369 * bright color, exclusive) 370 */ 371 public float getSaturation(float r, float g, float b) { 372 float min = Math.min(Math.min(r, g ), b); //Min. value of RGB 373 float max = Math.max(Math.max(r, g), b); //Min. value of RGB 374 float delta = max - min; //Delta RGB value 375 376 float saturation; 377 378 if ( delta < 0.0001f ) //This is a gray, no chroma... 379 { 380 saturation = 0; 381 } 382 else //Chromatic data... 383 { 384 saturation = delta / max; 385 } 386 return saturation; 387 } 388 389 /** 390 * @param r the red component in 0.0 to 1.0 range, typically 391 * @param g the green component in 0.0 to 1.0 range, typically 392 * @param b the blue component in 0.0 to 1.0 range, typically 393 * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to 394 * 1.0 (very bright, inclusive). 395 */ 396 public float getValue(float r, float g, float b) 397 { 398 return Math.max(Math.max(r, g), b); 399 } 400 401 /** 402 * @param r the red component in 0.0 to 1.0 range, typically 403 * @param g the green component in 0.0 to 1.0 range, typically 404 * @param b the blue component in 0.0 to 1.0 range, typically 405 * @return The hue of the color from 0.0 (red, inclusive) towards orange, then yellow, and 406 * eventually to purple before looping back to almost the same red (1.0, exclusive) 407 */ 408 public float getHue(float r, float g, float b) { 409 float min = Math.min(Math.min(r, g ), b); //Min. value of RGB 410 float max = Math.max(Math.max(r, g), b); //Min. value of RGB 411 float delta = max - min; //Delta RGB value 412 413 float hue; 414 415 if ( delta < 0.0001f ) //This is a gray, no chroma... 416 { 417 hue = 0; //HSV results from 0 to 1 418 } 419 else //Chromatic data... 420 { 421 float rDelta = (((max - r) / 6f) + (delta / 2f)) / delta; 422 float gDelta = (((max - g) / 6f) + (delta / 2f)) / delta; 423 float bDelta = (((max - b) / 6f) + (delta / 2f)) / delta; 424 425 if (r == max) hue = bDelta - gDelta; 426 else if (g == max) hue = (1f / 3f) + rDelta - bDelta; 427 else hue = (2f / 3f) + gDelta - rDelta; 428 429 if (hue < 0) hue += 1f; 430 else if (hue > 1) hue -= 1; 431 } 432 return hue; 433 } 434 435 /** 436 * @param c a concrete color 437 * @return the value (essentially lightness) of the color from 0.0 (black, inclusive) to 438 * 1.0 (very bright, inclusive). 439 */ 440 @Override 441 public float getValue(Color c) { 442 return SColor.value(c); 443 } 444 445 public static int encode (Color color) { 446 if (color == null) 447 return 0; 448 return color.toIntBits(); 449 } 450 451 /** 452 * Gets a modified copy of color as if it is lit with a colored light source. 453 * @param color the color to shine the light on 454 * @param light the color of the light source 455 * @return a copy of the Color color that factors in the lighting of the Color light. 456 */ 457 public Color lightWith(Color color, Color light) 458 { 459 return filter(color.cpy().mul(light)); 460 } 461 462 /** 463 * Lightens a color by degree and returns the new color (mixed with white). 464 * @param color the color to lighten 465 * @param degree a float between 0.0f and 1.0f; more makes it lighter 466 * @return the lightened (and if a filter is used, also filtered) new color 467 */ 468 public Color light(Color color, float degree) 469 { 470 return lerp(color, Color.WHITE, degree); 471 } 472 473 /** 474 * Lightens a color by degree and returns the new color (mixed with white). 475 * @param color the color to lighten 476 * @param degree a double between 0.0 and 1.0; more makes it lighter 477 * @return the lightened (and if a filter is used, also filtered) new color 478 */ 479 public Color light(Color color, double degree) 480 { 481 return lerp(color, Color.WHITE, degree); 482 } 483 /** 484 * Lightens a color slightly and returns the new color (10% mix with white). 485 * @param color the color to lighten 486 * @return the lightened (and if a filter is used, also filtered) new color 487 */ 488 public Color light(Color color) 489 { 490 return lerp(color, Color.WHITE, 0.1f); 491 } 492 /** 493 * Lightens a color significantly and returns the new color (30% mix with white). 494 * @param color the color to lighten 495 * @return the lightened (and if a filter is used, also filtered) new color 496 */ 497 public Color lighter(Color color) 498 { 499 return lerp(color, Color.WHITE, 0.3f); 500 } 501 /** 502 * Lightens a color massively and returns the new color (70% mix with white). 503 * @param color the color to lighten 504 * @return the lightened (and if a filter is used, also filtered) new color 505 */ 506 public Color lightest(Color color) 507 { 508 return lerp(color, Color.WHITE, 0.7f); 509 } 510 511 /** 512 * Darkens a color by the specified degree and returns the new color (mixed with black). 513 * @param color the color to darken 514 * @param degree a float between 0.0f and 1.0f; more makes it darker 515 * @return the darkened (and if a filter is used, also filtered) new color 516 */ 517 public Color dim(Color color, float degree) 518 { 519 return lerp(color, Color.BLACK, degree); 520 } 521 522 /** 523 * Darkens a color by the specified degree and returns the new color (mixed with black). 524 * @param color the color to darken 525 * @param degree a double between 0.0 and 1.0; more makes it darker 526 * @return the darkened (and if a filter is used, also filtered) new color 527 */ 528 public Color dim(Color color, double degree) 529 { 530 return lerp(color, Color.BLACK, degree); 531 } 532 /** 533 * Darkens a color slightly and returns the new color (10% mix with black). 534 * @param color the color to darken 535 * @return the darkened (and if a filter is used, also filtered) new color 536 */ 537 public Color dim(Color color) 538 { 539 return lerp(color, Color.BLACK, 0.1f); 540 } 541 /** 542 * Darkens a color significantly and returns the new color (30% mix with black). 543 * @param color the color to darken 544 * @return the darkened (and if a filter is used, also filtered) new color 545 */ 546 public Color dimmer(Color color) 547 { 548 return lerp(color, Color.BLACK, 0.3f); 549 } 550 /** 551 * Darkens a color massively and returns the new color (70% mix with black). 552 * @param color the color to darken 553 * @return the darkened (and if a filter is used, also filtered) new color 554 */ 555 public Color dimmest(Color color) 556 { 557 return lerp(color, Color.BLACK, 0.7f); 558 } 559 560 561 /** 562 * Gets a fully-desaturated version of the given color (keeping its brightness, but making it grayscale). 563 * @param color the color to desaturate (will not be modified) 564 * @return the grayscale version of color 565 */ 566 @Override 567 public Color desaturated(Color color) 568 { 569 float f = color.r * 0.299f + color.g * 0.587f + color.b * 0.114f; 570 return get(f, f, f, color.a); 571 } 572 573 /** 574 * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat). 575 * @param color the color to desaturate 576 * @param degree a float between 0.0f and 1.0f; more makes it less colorful 577 * @return the desaturated (and if a filter is used, also filtered) new color 578 */ 579 @Override 580 public Color desaturate(Color color, float degree) 581 { 582 return lerp(color, desaturated(color), degree); 583 } 584 585 /** 586 * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat). 587 * @param color the color to desaturate 588 * @param degree a double between 0.0 and 1.0; more makes it less colorful 589 * @return the desaturated (and if a filter is used, also filtered) new color 590 */ 591 public Color desaturate(Color color, double degree) 592 { 593 return lerp(color, desaturated(color), degree); 594 } 595 596 /** 597 * Fully saturates color (makes it a vivid color like red or green and less gray) and returns the modified copy. 598 * Leaves alpha unchanged. 599 * 600 * @param color the color T to saturate (will not be modified) 601 * @return the saturated version of color 602 */ 603 @Override 604 public Color saturated(Color color) { 605 return getHSV(getHue(color), 1f, getValue(color), getAlpha(color)); 606 } 607 608 /** 609 * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and 610 * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this is 611 * likely to produce a red hue by default (if there's no hue to make vivid, it needs to choose something). 612 * @param color the color to saturate 613 * @param degree a float between 0.0f and 1.0f; more makes it more colorful 614 * @return the saturated (and if a filter is used, also filtered) new color 615 */ 616 @Override 617 public Color saturate(Color color, float degree) 618 { 619 return lerp(color, saturated(color), degree); 620 } 621 622 /** 623 * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and 624 * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this is 625 * likely to produce a red hue by default (if there's no hue to make vivid, it needs to choose something). 626 * @param color the color to saturate 627 * @param degree a double between 0.0 and 1.0; more makes it more colorful 628 * @return the saturated (and if a filter is used, also filtered) new color 629 */ 630 public Color saturate(Color color, double degree) 631 { 632 return lerp(color, saturated(color), degree); 633 } 634 /** 635 * Gets a fully random color that is only required to be opaque. 636 * @return a random Color 637 */ 638 public Color random() 639 { 640 StatefulRNG rng = DefaultResources.getGuiRandom(); 641 return get(rng.nextFloat(), rng.nextFloat(), rng.nextFloat(), 1f); 642 } 643 644 /** 645 * Blends a color with a random (opaque) color by a factor of 10% random. 646 * @param color the color to randomize 647 * @return the randomized (and if a filter is used, also filtered) new color 648 */ 649 public Color randomize(Color color) 650 { 651 return lerp(color, random(), 0.1f); 652 } 653 /** 654 * Blends a color with a random (opaque) color by a factor of 30% random. 655 * @param color the color to randomize 656 * @return the randomized (and if a filter is used, also filtered) new color 657 */ 658 public Color randomizeMore(Color color) 659 { 660 return lerp(color, random(), 0.3f); 661 } 662 /** 663 * Blends a color with a random (opaque) color by a factor of 70% random. 664 * @param color the color to randomize 665 * @return the randomized (and if a filter is used, also filtered) new color 666 */ 667 public Color randomizeMost(Color color) 668 { 669 return lerp(color, random(), 0.7f); 670 } 671 672 /** 673 * Blends the colors A and B by a random degree. 674 * @param a a color to mix in 675 * @param b another color to mix in 676 * @return a random blend of a and b. 677 */ 678 public Color randomBlend(Color a, Color b) 679 { 680 return lerp(a, b, DefaultResources.getGuiRandom().nextFloat()); 681 } 682 683 public Color invert(Color start) 684 { 685 float v = getValue(start); 686 return v > 0.65f 687 ? getHSV((getHue(start) + 0.45f), 1f - getSaturation(start) * 0.85f, v * 0.1f, start.a) 688 : getHSV((getHue(start) + 0.45f), 1f - getSaturation(start) * 1.15f, 1f, start.a); 689 } 690 /** 691 * Finds a 16-step gradient going from fromColor to toColor, both included in the gradient. 692 * @param fromColor the color to start with, included in the gradient 693 * @param toColor the color to end on, included in the gradient 694 * @return a 16-element ArrayList composed of the blending steps from fromColor to toColor 695 */ 696 public ArrayList<Color> gradient(Color fromColor, Color toColor) 697 { 698 ArrayList<Color> colors = new ArrayList<>(16); 699 for (int i = 0; i < 16; i++) { 700 colors.add(lerp(fromColor, toColor, i / 15f)); 701 } 702 return colors; 703 } 704 705 /** 706 * Finds a gradient with the specified number of steps going from fromColor to toColor, 707 * both included in the gradient. 708 * @param fromColor the color to start with, included in the gradient 709 * @param toColor the color to end on, included in the gradient 710 * @param steps the number of elements to use in the gradient 711 * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps 712 */ 713 public ArrayList<Color> gradient(Color fromColor, Color toColor, int steps) 714 { 715 return gradient(fromColor, toColor, steps, Interpolation.linear); 716 } 717 718 719 /** 720 * Finds a gradient with the specified number of steps going from fromColor to midColor, then midColor to (possibly) 721 * fromColor, with both included in the gradient but fromColor only repeated at the end if the number of steps is odd. 722 * @param fromColor the color to start with (and end with, if steps is an odd number), included in the gradient 723 * @param midColor the color to use in the middle of the loop, included in the gradient 724 * @param steps the number of elements to use in the gradient, will be at least 3 725 * @return an ArrayList composed of the blending steps from fromColor to midColor to fromColor again, with length equal to steps 726 */ 727 public ArrayList<Color> loopingGradient(Color fromColor, Color midColor, int steps) 728 { 729 return loopingGradient(fromColor, midColor, steps, Interpolation.linear); 730 } 731 732 /** 733 * Finds a gradient with the specified number of steps going from fromColor to toColor, both included in the 734 * gradient. The interpolation argument can be used to make the color stay close to fromColor and/or toColor longer 735 * than it would normally, or shorter if the middle colors are desirable. 736 * @param fromColor the color to start with, included in the gradient 737 * @param toColor the color to end on, included in the gradient 738 * @param steps the number of elements to use in the gradient 739 * @param interpolation a libGDX Interpolation that defines how quickly the color changes during the transition 740 * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps 741 */ 742 public ArrayList<Color> gradient(Color fromColor, Color toColor, int steps, Interpolation interpolation) 743 { 744 ArrayList<Color> colors = new ArrayList<>(Math.max(steps, 1)); 745 colors.add(filter(fromColor)); 746 if(steps < 2) 747 return colors; 748 for (float i = 1; i < steps; i++) { 749 colors.add(lerp(fromColor, toColor, interpolation.apply(i / (steps - 1)))); 750 } 751 return colors; 752 } 753 754 /** 755 * Finds a gradient with the specified number of steps going from fromColor to midColor, then midColor to (possibly) 756 * fromColor, with both included in the gradient but fromColor only repeated at the end if the number of steps is 757 * odd. The interpolation argument can be used to make the color linger for a while with colors close to fromColor 758 * or midColor, or to do the opposite and quickly change from one and spend more time in the middle. 759 * @param fromColor the color to start with (and end with, if steps is an odd number), included in the gradient 760 * @param midColor the color to use in the middle of the loop, included in the gradient 761 * @param steps the number of elements to use in the gradient, will be at least 3 762 * @param interpolation a libGDX Interpolation that defines how quickly the color changes at the start and end of 763 * each transition, both from fromColor to midColor as well as back to fromColor 764 * @return an ArrayList composed of the blending steps from fromColor to midColor to fromColor again, with length equal to steps 765 */ 766 public ArrayList<Color> loopingGradient(Color fromColor, Color midColor, int steps, Interpolation interpolation) 767 { 768 ArrayList<Color> colors = new ArrayList<>(Math.max(3, steps)); 769 colors.add(filter(fromColor)); 770 for (float i = 1; i < steps * 0.5f; i++) { 771 colors.add(lerp(fromColor, midColor, interpolation.apply(i / (steps * 0.5f)))); 772 } 773 for (float i = 0, c = steps * 0.5f; c < steps; i++, c++) { 774 colors.add(lerp(midColor, fromColor, interpolation.apply(i / (steps * 0.5f)))); 775 } 776 return colors; 777 } 778 779 /** 780 * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and 781 * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should 782 * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more 783 * likely to see colors that appear for shorter spans on the color wheel, like orange. 784 * Produces fully saturated and max-brightness colors on the rainbow, which is what many people expect for a rainbow. 785 * @param steps the number of different Color elements to generate in the returned ArrayList 786 * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc. 787 */ 788 public ArrayList<Color> rainbow(int steps) 789 { 790 return rainbow(1f, 1f, 1f, steps); 791 } 792 793 /** 794 * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and 795 * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should 796 * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more 797 * likely to see colors that appear for shorter spans on the color wheel, like orange. 798 * Uses the given saturation and value for all colors in the rainbow, and only changes hue. 799 * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale 800 * @param value the brightness of the rainbow's colors; 1.0 is brightest 801 * @param steps the number of different Color elements to generate in the returned ArrayList 802 * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc. 803 */ 804 public ArrayList<Color> rainbow(float saturation, float value, int steps) 805 { 806 return rainbow(saturation, value, 1f, steps); 807 } 808 809 /** 810 * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and 811 * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should 812 * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more 813 * likely to see colors that appear for shorter spans on the color wheel, like orange. 814 * Uses the given saturation, value, and opacity for all colors in the rainbow, and only changes hue. 815 * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale 816 * @param value the brightness of the rainbow's colors; 1.0 is brightest 817 * @param opacity the alpha value of all colors in the rainbow; 0.0 is fully transparent and 1.0 is opaque 818 * @param steps the number of different Color elements to generate in the returned ArrayList 819 * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc. 820 */ 821 public ArrayList<Color> rainbow(float saturation, float value, float opacity, int steps) 822 { 823 steps = Math.max(steps, 1); 824 ArrayList<Color> colors = new ArrayList<>(steps); 825 for (float i = 0; i < 1f - 0.5f / steps; i+= 1.0f / steps) { 826 colors.add(filter(getHSV(i, saturation, value, opacity))); 827 } 828 return colors; 829 } 830 831 /** 832 * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and 833 * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should 834 * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more 835 * likely to see colors that appear for shorter spans on the color wheel, like orange. 836 * Uses the given saturation and value for all colors in the rainbow, and only changes hue. 837 * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale 838 * @param value the brightness of the rainbow's colors; 1.0 is brightest 839 * @param steps the number of different Color elements to generate in the returned ArrayList 840 * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc. 841 */ 842 public ArrayList<Color> rainbow(double saturation, double value, int steps) 843 { 844 return rainbow((float)saturation, (float)value, 1f, steps); 845 } 846 847 /** 848 * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and 849 * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should 850 * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more 851 * likely to see colors that appear for shorter spans on the color wheel, like orange. 852 * Uses the given saturation, value, and opacity for all colors in the rainbow, and only changes hue. 853 * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale 854 * @param value the brightness of the rainbow's colors; 1.0 is brightest 855 * @param opacity the alpha value of all colors in the rainbow; 0.0 is fully transparent and 1.0 is opaque 856 * @param steps the number of different Color elements to generate in the returned ArrayList 857 * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc. 858 */ 859 public ArrayList<Color> rainbow(double saturation, double value, double opacity, int steps) 860 { 861 return rainbow((float)saturation, (float)value, (float)opacity, steps); 862 } 863 /** 864 * Finds a gradient with the specified number of steps going from fromColor to toColor, both included in the 865 * gradient. This does not typically take a direct path on its way between fromColor and toColor, and is useful to 866 * generate a wide variety of colors that can be confined to a rough amount of maximum difference by choosing values 867 * for fromColor and toColor that are more similar. 868 * <br> 869 * Try using colors for fromColor and toColor that have different r, g, and b values, such as gray and white, then 870 * compare to colors that don't differ on, for example, r, such as bright red and pink. In the first case, red, 871 * green, blue, and many other colors will be generated if there are enough steps; in the second case, red will be 872 * at the same level in all generated colors (very high, so no pure blue or pure green, but purple and yellow are 873 * possible). This should help illustrate how this chooses how far to "zig-zag" off the straight-line path. 874 * @param fromColor the color to start with, included in the gradient 875 * @param toColor the color to end on, included in the gradient 876 * @param steps the number of elements to use in the gradient; ideally no greater than 345 to avoid duplicates 877 * @return an ArrayList composed of the zig-zag steps from fromColor to toColor, with length equal to steps 878 */ 879 public ArrayList<Color> zigzagGradient(Color fromColor, Color toColor, int steps) 880 { 881 CoordPacker.init(); 882 ArrayList<Color> colors = new ArrayList<>(Math.max(steps, 1)); 883 colors.add(filter(fromColor)); 884 if(steps < 2) 885 return colors; 886 float dr = toColor.r - fromColor.r, dg = toColor.g - fromColor.g, db = toColor.b - fromColor.b, 887 a = fromColor.a, cr, cg, cb; 888 int decoded; 889 for (float i = 1; i < steps; i++) { 890 // 345 happens to be the distance on our 3D Hilbert curve that corresponds to (7,7,7). 891 decoded = Math.round(345 * (i / (steps - 1))); 892 cr = (CoordPacker.hilbert3X[decoded] / 7f) * dr + fromColor.r; 893 cg = (CoordPacker.hilbert3Y[decoded] / 7f) * dg + fromColor.g; 894 cb = (CoordPacker.hilbert3Z[decoded] / 7f) * db + fromColor.b; 895 colors.add(get(cr, cg, cb, a)); 896 } 897 return colors; 898 } 899 900 @Override 901 public String toString() { 902 return "SquidColorCenter{" + 903 "filter=" + (filter == null ? "null" : filter.getClass().getSimpleName()) + 904 ",granularity=" + granularity + 905 '}'; 906 } 907 /** 908 * It clears the cache. You may need to do this to limit the cache to the colors used in a specific section. 909 * This is also useful if a Filter changes what colors it should return on a frame-by-frame basis; in that case, 910 * you can call clearCache() at the start or end of a frame to ensure the next frame gets different colors. 911 */ 912 public void clearCache() 913 { 914 cache.clear(); 915 } 916 917 /** 918 * The actual cache is not public, but there are cases where you may want to know how many different colors are 919 * actually used in a frame or a section of the game. If the cache was emptied (which might be from calling 920 * {@link #clearCache()}), some colors were requested, then this is called, the returned int should be the 921 * count of distinct colors this IColorCenter had created and cached; duplicates won't be counted twice. 922 * @return 923 */ 924 public int cacheSize() 925 { 926 return cache.size; 927 } 928 929 /** 930 * You may want to copy colors between IColorCenter instances that have different create() methods -- and as 931 * such, will have different values for the same keys in the cache. This allows you to copy the cache from other 932 * into this Skeleton, but using this Skeleton's create() method. 933 * @param other another Skeleton of the same type that will have its cache copied into this Skeleton 934 */ 935 public void copyCache(SquidColorCenter other) 936 { 937 cache.putAll(other.cache); 938 } 939 940}