001package squidpony.squidgrid.gui.gdx; 002 003import com.badlogic.gdx.graphics.Color; 004import com.badlogic.gdx.math.Interpolation; 005import squidpony.IColorCenter; 006import squidpony.IFilter; 007import squidpony.squidmath.CoordPacker; 008import squidpony.squidmath.StatefulRNG; 009 010import java.util.ArrayList; 011 012/** 013 * A concrete implementation of {@link IColorCenter} for libgdx's {@link com.badlogic.gdx.graphics.Color}. 014 * Supports filtering any colors that this creates using a {@link Filter}, such as one from {@link Filters}. 015 * 016 * @author smelC 017 * @author Tommy Ettinger 018 * @see SColor Another way to obtain colors by using pre-allocated (and named) instances. 019 */ 020public class SquidColorCenter extends IColorCenter.Skeleton<Color> { 021 022 /** 023 * A fresh filter-less color center. 024 */ 025 public SquidColorCenter() 026 { 027 this(null); 028 } 029 030 /** 031 * A fresh filtered color center. 032 * 033 * @param filterEffect 034 * The filter to use. 035 */ 036 public SquidColorCenter(/*Nullable*/IFilter<Color> filterEffect) 037 { 038 super(filterEffect); 039 } 040 @Override 041 protected Color create(int red, int green, int blue, int opacity) { 042 if (filter == null) 043 /* No filtering */ 044 return new Color(red / 255f, green / 255f, blue / 255f, opacity / 255f); 045 else 046 /* Some filtering */ 047 return filter.alter(red / 255f, green / 255f, blue / 255f, opacity / 255f); 048 } 049 @Override 050 public Color filter(Color c) 051 { 052 if(c == null) 053 return Color.CLEAR; 054 else 055 return super.filter(c); 056 } 057 public Color get(long c) 058 { 059 return get((int)((c >> 24) & 0xff), (int)((c >> 16) & 0xff), (int)((c >> 8) & 0xff), (int)(c & 0xff)); 060 } 061 public Color get(float r, float g, float b, float a) 062 { 063 return get(Math.round(255 * r), Math.round(255 * g), Math.round(255 * b), Math.round(255 * a)); 064 } 065 066 /** 067 * Gets the linear interpolation from Color start to Color end, changing by the fraction given by change. 068 * @param start the initial Color 069 * @param end the "target" color 070 * @param change the degree to change closer to end; a change of 0.0f produces start, 1.0f produces end 071 * @return a new Color 072 */ 073 @Override 074 public Color lerp(Color start, Color end, float change) 075 { 076 if(start == null || end == null) 077 return Color.CLEAR; 078 return get( 079 start.r + change * (end.r - start.r), 080 start.g + change * (end.g - start.g), 081 start.b + change * (end.b - start.b), 082 start.a + change * (end.a - start.a) 083 ); 084 } 085 @Override 086 public int getRed(Color c) { 087 return Math.round(c.r * 255f); 088 } 089 090 @Override 091 public int getGreen(Color c) { 092 return Math.round(c.g * 255f); 093 } 094 095 @Override 096 public int getBlue(Color c) { 097 return Math.round(c.b * 255f); 098 } 099 100 @Override 101 public int getAlpha(Color c) { 102 return Math.round(c.a * 255f); 103 } 104 105 public static int encode (Color color) { 106 if (color == null) 107 return 0; 108 return (Math.round(color.r * 255.0f) << 24) 109 | (Math.round(color.g * 255.0f) << 16) 110 | (Math.round(color.b * 255.0f) << 8) 111 | Math.round(color.a * 255.0f); 112 } 113 114 /** 115 * Gets a modified copy of color as if it is lit with a colored light source. 116 * @param color the color to shine the light on 117 * @param light the color of the light source 118 * @return a copy of the Color color that factors in the lighting of the Color light. 119 */ 120 public Color lightWith(Color color, Color light) 121 { 122 return filter(color.cpy().mul(light)); 123 } 124 125 /** 126 * Lightens a color by degree and returns the new color (mixed with white). 127 * @param color the color to lighten 128 * @param degree a float between 0.0f and 1.0f; more makes it lighter 129 * @return the lightened (and if a filter is used, also filtered) new color 130 */ 131 public Color light(Color color, float degree) 132 { 133 return lerp(color, Color.WHITE, degree); 134 } 135 /** 136 * Lightens a color slightly and returns the new color (10% mix with white). 137 * @param color the color to lighten 138 * @return the lightened (and if a filter is used, also filtered) new color 139 */ 140 public Color light(Color color) 141 { 142 return lerp(color, Color.WHITE, 0.1f); 143 } 144 /** 145 * Lightens a color significantly and returns the new color (30% mix with white). 146 * @param color the color to lighten 147 * @return the lightened (and if a filter is used, also filtered) new color 148 */ 149 public Color lighter(Color color) 150 { 151 return lerp(color, Color.WHITE, 0.3f); 152 } 153 /** 154 * Lightens a color massively and returns the new color (70% mix with white). 155 * @param color the color to lighten 156 * @return the lightened (and if a filter is used, also filtered) new color 157 */ 158 public Color lightest(Color color) 159 { 160 return lerp(color, Color.WHITE, 0.7f); 161 } 162 163 /** 164 * Darkens a color by the specified degree and returns the new color (mixed with black). 165 * @param color the color to darken 166 * @param degree a float between 0.0f and 1.0f; more makes it darker 167 * @return the darkened (and if a filter is used, also filtered) new color 168 */ 169 public Color dim(Color color, float degree) 170 { 171 return lerp(color, Color.BLACK, degree); 172 } 173 /** 174 * Darkens a color slightly and returns the new color (10% mix with black). 175 * @param color the color to darken 176 * @return the darkened (and if a filter is used, also filtered) new color 177 */ 178 public Color dim(Color color) 179 { 180 return lerp(color, Color.BLACK, 0.1f); 181 } 182 /** 183 * Darkens a color significantly and returns the new color (30% mix with black). 184 * @param color the color to darken 185 * @return the darkened (and if a filter is used, also filtered) new color 186 */ 187 public Color dimmer(Color color) 188 { 189 return lerp(color, Color.BLACK, 0.3f); 190 } 191 /** 192 * Darkens a color massively and returns the new color (70% mix with black). 193 * @param color the color to darken 194 * @return the darkened (and if a filter is used, also filtered) new color 195 */ 196 public Color dimmest(Color color) 197 { 198 return lerp(color, Color.BLACK, 0.7f); 199 } 200 201 202 /** 203 * Gets a fully-desaturated version of the given color (keeping its brightness, but making it grayscale). 204 * @param color the color to desaturate (will not be modified) 205 * @return the grayscale version of color 206 */ 207 @Override 208 public Color desaturated(Color color) 209 { 210 float f = color.r * 0.299f + color.g * 0.587f + color.b * 0.114f; 211 return get(f, f, f, color.a); 212 } 213 214 /** 215 * Brings a color closer to grayscale by the specified degree and returns the new color (desaturated somewhat). 216 * @param color the color to desaturate 217 * @param degree a float between 0.0f and 1.0f; more makes it less colorful 218 * @return the desaturated (and if a filter is used, also filtered) new color 219 */ 220 @Override 221 public Color desaturate(Color color, float degree) 222 { 223 return lerp(color, desaturated(color), degree); 224 } 225 226 /** 227 * Fully saturates color (makes it a vivid color like red or green and less gray) and returns the modified copy. 228 * Leaves alpha unchanged. 229 * 230 * @param color the color T to saturate (will not be modified) 231 * @return the saturated version of color 232 */ 233 @Override 234 public Color saturated(Color color) { 235 return getHSV(getHue(color), 1f, getValue(color), getAlpha(color)); 236 } 237 238 /** 239 * Saturates color (makes it closer to a vivid color like red or green and less gray) by the specified degree and 240 * returns the new color (saturated somewhat). If this is called on a color that is very close to gray, this is 241 * likely to produce a red hue by default (if there's no hue to make vivid, it needs to choose something). 242 * @param color the color to saturate 243 * @param degree a float between 0.0f and 1.0f; more makes it more colorful 244 * @return the saturated (and if a filter is used, also filtered) new color 245 */ 246 @Override 247 public Color saturate(Color color, float degree) 248 { 249 return lerp(color, saturated(color), degree); 250 } 251 /** 252 * Gets a fully random color that is only required to be opaque. 253 * @return a random Color 254 */ 255 public Color random() 256 { 257 StatefulRNG rng = DefaultResources.getGuiRandom(); 258 return get(rng.nextFloat(), rng.nextFloat(), rng.nextFloat(), 1f); 259 } 260 261 /** 262 * Blends a color with a random (opaque) color by a factor of 10% random. 263 * @param color the color to randomize 264 * @return the randomized (and if a filter is used, also filtered) new color 265 */ 266 public Color randomize(Color color) 267 { 268 return lerp(color, random(), 0.1f); 269 } 270 /** 271 * Blends a color with a random (opaque) color by a factor of 30% random. 272 * @param color the color to randomize 273 * @return the randomized (and if a filter is used, also filtered) new color 274 */ 275 public Color randomizeMore(Color color) 276 { 277 return lerp(color, random(), 0.3f); 278 } 279 /** 280 * Blends a color with a random (opaque) color by a factor of 70% random. 281 * @param color the color to randomize 282 * @return the randomized (and if a filter is used, also filtered) new color 283 */ 284 public Color randomizeMost(Color color) 285 { 286 return lerp(color, random(), 0.7f); 287 } 288 289 /** 290 * Blends the colors A and B by a random degree. 291 * @param a a color to mix in 292 * @param b another color to mix in 293 * @return a random blend of a and b. 294 */ 295 public Color randomBlend(Color a, Color b) 296 { 297 return lerp(a, b, DefaultResources.getGuiRandom().nextFloat()); 298 } 299 300 /** 301 * Finds a 16-step gradient going from fromColor to toColor, both included in the gradient. 302 * @param fromColor the color to start with, included in the gradient 303 * @param toColor the color to end on, included in the gradient 304 * @return a 16-element ArrayList composed of the blending steps from fromColor to toColor 305 */ 306 public ArrayList<Color> gradient(Color fromColor, Color toColor) 307 { 308 ArrayList<Color> colors = new ArrayList<>(16); 309 for (int i = 0; i < 16; i++) { 310 colors.add(lerp(fromColor, toColor, i / 15f)); 311 } 312 return colors; 313 } 314 315 /** 316 * Finds a gradient with the specified number of steps going from fromColor to toColor, 317 * both included in the gradient. 318 * @param fromColor the color to start with, included in the gradient 319 * @param toColor the color to end on, included in the gradient 320 * @param steps the number of elements to use in the gradient 321 * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps 322 */ 323 public ArrayList<Color> gradient(Color fromColor, Color toColor, int steps) 324 { 325 return gradient(fromColor, toColor, steps, Interpolation.linear); 326 } 327 328 329 /** 330 * Finds a gradient with the specified number of steps going from fromColor to midColor, then midColor to (possibly) 331 * fromColor, with both included in the gradient but fromColor only repeated at the end if the number of steps is odd. 332 * @param fromColor the color to start with (and end with, if steps is an odd number), included in the gradient 333 * @param midColor the color to use in the middle of the loop, included in the gradient 334 * @param steps the number of elements to use in the gradient, will be at least 3 335 * @return an ArrayList composed of the blending steps from fromColor to midColor to fromColor again, with length equal to steps 336 */ 337 public ArrayList<Color> loopingGradient(Color fromColor, Color midColor, int steps) 338 { 339 return loopingGradient(fromColor, midColor, steps, Interpolation.linear); 340 } 341 342 /** 343 * Finds a gradient with the specified number of steps going from fromColor to toColor, both included in the 344 * gradient. The interpolation argument can be used to make the color stay close to fromColor and/or toColor longer 345 * than it would normally, or shorter if the middle colors are desirable. 346 * @param fromColor the color to start with, included in the gradient 347 * @param toColor the color to end on, included in the gradient 348 * @param steps the number of elements to use in the gradient 349 * @param interpolation a libGDX Interpolation that defines how quickly the color changes during the transition 350 * @return an ArrayList composed of the blending steps from fromColor to toColor, with length equal to steps 351 */ 352 public ArrayList<Color> gradient(Color fromColor, Color toColor, int steps, Interpolation interpolation) 353 { 354 ArrayList<Color> colors = new ArrayList<>((steps > 1) ? steps : 1); 355 colors.add(filter(fromColor)); 356 if(steps < 2) 357 return colors; 358 for (float i = 1; i < steps; i++) { 359 colors.add(lerp(fromColor, toColor, interpolation.apply(i / (steps - 1)))); 360 } 361 return colors; 362 } 363 364 /** 365 * Finds a gradient with the specified number of steps going from fromColor to midColor, then midColor to (possibly) 366 * fromColor, with both included in the gradient but fromColor only repeated at the end if the number of steps is 367 * odd. The interpolation argument can be used to make the color linger for a while with colors close to fromColor 368 * or midColor, or to do the opposite and quickly change from one and spend more time in the middle. 369 * @param fromColor the color to start with (and end with, if steps is an odd number), included in the gradient 370 * @param midColor the color to use in the middle of the loop, included in the gradient 371 * @param steps the number of elements to use in the gradient, will be at least 3 372 * @param interpolation a libGDX Interpolation that defines how quickly the color changes at the start and end of 373 * each transition, both from fromColor to midColor as well as back to fromColor 374 * @return an ArrayList composed of the blending steps from fromColor to midColor to fromColor again, with length equal to steps 375 */ 376 public ArrayList<Color> loopingGradient(Color fromColor, Color midColor, int steps, Interpolation interpolation) 377 { 378 ArrayList<Color> colors = new ArrayList<>((steps > 3) ? steps : 3); 379 colors.add(filter(fromColor)); 380 for (float i = 1; i < steps / 2; i++) { 381 colors.add(lerp(fromColor, midColor, interpolation.apply(i / (steps / 2)))); 382 } 383 for (float i = 0, c = steps / 2; c < steps; i++, c++) { 384 colors.add(lerp(midColor, fromColor, interpolation.apply(i / (steps / 2)))); 385 } 386 return colors; 387 } 388 389 /** 390 * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and 391 * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should 392 * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more 393 * likely to see colors that appear for shorter spans on the color wheel, like orange. 394 * Produces fully saturated and max-brightness colors on the rainbow, which is what many people expect for a rainbow. 395 * @param steps the number of different Color elements to generate in the returned ArrayList 396 * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc. 397 */ 398 public ArrayList<Color> rainbow(int steps) 399 { 400 return rainbow(1.0f, 1.0f, steps); 401 } 402 403 /** 404 * Generates a hue-shifted rainbow of colors, starting at red and going through orange, yellow, green, blue, and 405 * purple before getting close to red at the end again. If the given number of steps is less than 6 or so, you should 406 * expect to see only some of those colors; if steps is larger (36 may be reasonable for gradients), you are more 407 * likely to see colors that appear for shorter spans on the color wheel, like orange. 408 * Uses the given saturation and value for all colors in the rainbow, and only changes hue. 409 * @param saturation the saturation of the rainbow's colors; 1.0 is boldest and 0.0 is grayscale 410 * @param value the brightness of the rainbow's colors; 1.0 is brightest 411 * @param steps the number of different Color elements to generate in the returned ArrayList 412 * @return an ArrayList of Color where each element goes around the color wheel, starting at red, then orange, etc. 413 */ 414 public ArrayList<Color> rainbow(float saturation, float value, int steps) 415 { 416 steps = (steps > 1) ? steps : 1; 417 ArrayList<Color> colors = new ArrayList<>(steps); 418 for (float i = 0; i < 1f - 0.5f / steps; i+= 1.0f / steps) { 419 colors.add(filter(getHSV(i, saturation, value))); 420 } 421 return colors; 422 } 423 424 /** 425 * Finds a gradient with the specified number of steps going from fromColor to toColor, both included in the 426 * gradient. This does not typically take a direct path on its way between fromColor and toColor, and is useful to 427 * generate a wide variety of colors that can be confined to a rough amount of maximum difference by choosing values 428 * for fromColor and toColor that are more similar. 429 * <br> 430 * Try using colors for fromColor and toColor that have different r, g, and b values, such as gray and white, then 431 * compare to colors that don't differ on, for example, r, such as bright red and pink. In the first case, red, 432 * green, blue, and many other colors will be generated if there are enough steps; in the second case, red will be 433 * at the same level in all generated colors (very high, so no pure blue or pure green, but purple and yellow are 434 * possible). This should help illustrate how this chooses how far to "zig-zag" off the straight-line path. 435 * @param fromColor the color to start with, included in the gradient 436 * @param toColor the color to end on, included in the gradient 437 * @param steps the number of elements to use in the gradient; ideally no greater than 345 to avoid duplicates 438 * @return an ArrayList composed of the zig-zag steps from fromColor to toColor, with length equal to steps 439 */ 440 public ArrayList<Color> zigzagGradient(Color fromColor, Color toColor, int steps) 441 { 442 ArrayList<Color> colors = new ArrayList<>((steps > 1) ? steps : 1); 443 colors.add(filter(fromColor)); 444 if(steps < 2) 445 return colors; 446 float dr = toColor.r - fromColor.r, dg = toColor.g - fromColor.g, db = toColor.b - fromColor.b, 447 a = fromColor.a, cr, cg, cb; 448 int decoded; 449 for (float i = 1; i < steps; i++) { 450 // 345 happens to be the distance on our 3D Hilbert curve that corresponds to (7,7,7). 451 decoded = Math.round(345 * (i / (steps - 1))); 452 cr = (CoordPacker.hilbert3X[decoded] / 7f) * dr + fromColor.r; 453 cg = (CoordPacker.hilbert3Y[decoded] / 7f) * dg + fromColor.g; 454 cb = (CoordPacker.hilbert3Z[decoded] / 7f) * db + fromColor.b; 455 colors.add(get(cr, cg, cb, a)); 456 } 457 return colors; 458 } 459 460 @Override 461 public String toString() { 462 return "SquidColorCenter{" + 463 "filter=" + (filter == null ? "null" : filter.getClass().getSimpleName()) + 464 '}'; 465 } 466}