001package squidpony.squidgrid.gui.gdx; 002 003import com.badlogic.gdx.graphics.Color; 004import com.badlogic.gdx.math.MathUtils; 005import squidpony.IFilter; 006import squidpony.squidmath.MathExtras; 007import squidpony.squidmath.ThrustAltRNG; 008 009import java.util.ArrayList; 010import java.util.Collection; 011 012/** 013 * Implementations of {@link IFilter}, that all are meant to perform different changes to colors before they are 014 * created. They should usually be passed to SquidColorCenter's constructor, which can use them. 015 * <br> 016 * Created by Tommy Ettinger on 10/31/2015. 017 */ 018public final class Filters { 019 020 private Filters() { 021 /* You should not build me */ 022 } 023 024 /** 025 * An IFilter that does nothing to the colors it is given but pass them along unchanged. 026 */ 027 public static class IdentityFilter implements IFilter<Color> 028 { 029 public IdentityFilter() 030 { 031 /* Nothing to do */ 032 } 033 034 @Override 035 public Color alter(float r, float g, float b, float a) { 036 return new Color(r, g, b, a); 037 } 038 } 039 public static class ChainFilter implements IFilter<Color> 040 { 041 public ArrayList<IFilter<Color>> chains; 042 public ChainFilter(IFilter<Color> firstFilter, IFilter<Color> secondFilter) 043 { 044 chains = new ArrayList<>(2); 045 chains.add(firstFilter); 046 chains.add(secondFilter); 047 } 048 public ChainFilter(IFilter<Color> firstFilter, IFilter<Color> secondFilter, IFilter<Color> thirdFilter) 049 { 050 chains = new ArrayList<>(2); 051 chains.add(firstFilter); 052 chains.add(secondFilter); 053 chains.add(thirdFilter); 054 } 055 public ChainFilter(Collection<IFilter<Color>> filters) 056 { 057 if(filters == null || filters.isEmpty()) 058 { 059 chains = new ArrayList<>(); 060 chains.add(new IdentityFilter()); 061 } 062 else chains = new ArrayList<>(filters); 063 } 064 @Override 065 public Color alter(float r, float g, float b, float a) { 066 Color c = chains.get(0).alter(r, g, b, a); 067 for (int i = 1; i < chains.size(); i++) { 068 c = chains.get(i).alter(c.r, c.g, c.b, c.a); 069 } 070 return c; 071 } 072 } 073 /** 074 * An IFilter that converts all colors passed to it to grayscale, like a black and white film. 075 */ 076 public static class GrayscaleFilter implements IFilter<Color> 077 { 078 public GrayscaleFilter() 079 { 080 /* Nothing to do */ 081 } 082 083 @Override 084 public Color alter(float r, float g, float b, float a) { 085 float v = (r + g + b) / 3f; 086 return new Color(v, v, v, a); 087 } 088 089 } 090 091 /** 092 * An IFilter that tracks the highest brightness for any component it was assigned and stores it in its {@link #state} 093 * field. You probably want to set state to 0f before filtering a bunch of colors, if this is being used to find 094 * the brightest color on the screen. 095 */ 096 public static class MaxValueFilter implements IFilter<Color> 097 { 098 public float state; 099 public MaxValueFilter() 100 { 101 state = 0f; 102 } 103 104 @Override 105 public Color alter(float r, float g, float b, float a) { 106 state = Math.max(state, Math.max(r, Math.max(g, b))); 107 return new Color(r, g, b, a); 108 } 109 110 } 111 112 /** 113 * An IFilter that performs a brightness adjustment to make dark areas lighter and light areas not much less bright. 114 */ 115 public static class GammaCorrectFilter implements IFilter<Color> { 116 public float gamma; 117 118 public float maxValue; 119 /** 120 * Sets up a GammaCorrectFilter with the desired gamma adjustment. 121 * 122 * @param gamma should be 1.0 or less, and must be greater than 0. Typical values are between 0.4 to 0.8. 123 * @param maxValue the maximum brightness in the colors this will be passed; use MaxValueFilter for this 124 */ 125 public GammaCorrectFilter(float gamma, float maxValue) { 126 this.gamma = gamma; 127 this.maxValue = 1f / (float) Math.pow(maxValue, gamma); 128 } 129 130 @Override 131 public Color alter(float r, float g, float b, float a) { 132 return new Color(maxValue * (float) Math.pow(r, gamma), 133 maxValue * (float) Math.pow(g, gamma), 134 maxValue * (float) Math.pow(b, gamma), 135 a); 136 } 137 } 138 /** 139 * An IFilter that is constructed with a color and linear-interpolates any color it is told to alter toward the color 140 * it was constructed with. 141 */ 142 public static class LerpFilter implements IFilter<Color> { 143 public float r, g, b, a, amount; 144 /** 145 * Sets up a LerpFilter with the desired color to linearly interpolate towards. 146 * 147 * @param r the red component to lerp towards 148 * @param g the green component to lerp towards 149 * @param b the blue component to lerp towards 150 * @param a the opacity component to lerp towards 151 * @param amount the amount to lerp by, should be between 0.0 and 1.0 152 */ 153 public LerpFilter(float r, float g, float b, float a, float amount) 154 { 155 this.r = r; 156 this.g = g; 157 this.b = b; 158 this.a = a; 159 this.amount = amount; 160 } 161 /** 162 * Sets up a LerpFilter with the desired color to linearly interpolate towards. 163 * 164 * @param color the Color to lerp towards 165 * @param amount the amount to lerp by, should be between 0.0 and 1.0 166 */ 167 public LerpFilter(Color color, float amount) { 168 this.r = color.r; 169 this.g = color.g; 170 this.b = color.b; 171 this.a = color.a; 172 this.amount = amount; 173 } 174 175 @Override 176 public Color alter(float r, float g, float b, float a) { 177 return new Color(r, g, b, a).lerp(this.r, this.g, this.b, this.a, this.amount); 178 } 179 180 } 181 /** 182 * An IFilter that is constructed with a group of colors and linear-interpolates any color it is told to alter toward 183 * the color it was constructed with that has the closest hue. 184 */ 185 public static class MultiLerpFilter implements IFilter<Color> { 186 private SquidColorCenter globalSCC; 187 public float[] state; 188 /** 189 * Sets up a MultiLerpFilter with the desired colors to linearly interpolate towards; the lengths of each given 190 * array should be identical. 191 * 192 * @param r the red components to lerp towards 193 * @param g the green components to lerp towards 194 * @param b the blue components to lerp towards 195 * @param a the opacity components to lerp towards 196 * @param amount the amounts to lerp by, should each be between 0.0 and 1.0 197 */ 198 public MultiLerpFilter(float[] r, float[] g, float[] b, float[] a, float[] amount) { 199 state = new float[Math.min(r.length, Math.min(g.length, Math.min(b.length, 200 Math.min(a.length, amount.length)))) * 6]; 201 globalSCC = DefaultResources.getSCC(); 202 for (int i = 0; i < state.length / 6; i++) { 203 state[i * 6] = r[i]; 204 state[i * 6 + 1] = g[i]; 205 state[i * 6 + 2] = b[i]; 206 state[i * 6 + 3] = a[i]; 207 state[i * 6 + 4] = amount[i]; 208 state[i * 6 + 5] = globalSCC.getHue(r[i], g[i], b[i]); 209 } 210 }/** 211 * Sets up a MultiLerpFilter with the desired colors to linearly interpolate towards and their amounts. 212 * 213 * @param colors the Colors to lerp towards 214 * @param amount the amounts to lerp by, should each be between 0.0 and 1.0 215 */ 216 public MultiLerpFilter(Color[] colors, float[] amount) { 217 state = new float[Math.min(colors.length, amount.length) * 6]; 218 globalSCC = DefaultResources.getSCC(); 219 for (int i = 0; i < state.length / 6; i++) { 220 state[i * 6] = colors[i].r; 221 state[i * 6 + 1] = colors[i].g; 222 state[i * 6 + 2] = colors[i].b; 223 state[i * 6 + 3] = colors[i].a; 224 state[i * 6 + 4] = amount[i]; 225 state[i * 6 + 5] = globalSCC.getHue(colors[i]); 226 } 227 } 228 229 @Override 230 public Color alter(float r, float g, float b, float a) { 231 float givenH = globalSCC.getHue(r, g, b), givenS = globalSCC.getSaturation(r, g, b), 232 minDiff = 999.0f, temp; 233 if(givenS < 0.05) 234 return new Color(r, g, b, a); 235 int choice = 0; 236 for (int i = 5; i < state.length; i += 6) { 237 temp = state[i] - givenH; 238 temp = (temp >= 0.5f) ? Math.abs(temp - 1f) % 1f : Math.abs(temp); 239 if(temp < minDiff) { 240 minDiff = temp; 241 choice = i; 242 } 243 } 244 choice /= 6; // rounds down 245 return new Color(r, g, b, a).lerp(state[choice * 6], state[choice * 6 + 1], state[choice * 6 + 2], 246 state[choice * 6 + 3], state[choice * 6 + 4]); 247 } 248 } 249 250 /** 251 * An IFilter that is constructed with a color and makes any color it is told to alter have the same hue as the given 252 * color, have saturation that is somewhere between the given color's and the altered colors, and chiefly is 253 * distinguishable from other colors by value. Useful for sepia effects, which can be created satisfactorily with 254 * {@code new Filters.ColorizeFilter(SColor.CLOVE_BROWN, 0.6f, 0.0f)}. 255 */ 256 public static class ColorizeFilter implements IFilter<Color> { 257 private SquidColorCenter globalSCC; 258 public float targetHue, targetSaturation, saturationMultiplier, valueModifier; 259 /** 260 * Sets up a ColorizeFilter with the desired color to colorize towards. 261 * 262 * @param r the red component to colorize towards 263 * @param g the green component to colorize towards 264 * @param b the blue component to colorize towards 265 */ 266 public ColorizeFilter(float r, float g, float b) { 267 globalSCC = DefaultResources.getSCC(); 268 targetHue = globalSCC.getHue(r, g, b); 269 targetSaturation = globalSCC.getSaturation(r, g, b); 270 saturationMultiplier = 1f; 271 valueModifier = 0f; 272 } 273 /** 274 * Sets up a ColorizeFilter with the desired color to colorize towards. 275 * 276 * @param color the Color to colorize towards 277 */ 278 public ColorizeFilter(Color color) { 279 globalSCC = DefaultResources.getSCC(); 280 targetHue = globalSCC.getHue(color); 281 targetSaturation = globalSCC.getSaturation(color); 282 saturationMultiplier = 1f; 283 valueModifier = 0f; 284 } 285 /** 286 * Sets up a ColorizeFilter with the desired color to colorize towards. 287 * 288 * @param r the red component to colorize towards 289 * @param g the green component to colorize towards 290 * @param b the blue component to colorize towards 291 * @param saturationMultiplier a multiplier to apply to the final color's saturation; may be greater than 1 292 * @param valueModifier a modifier that affects the final brightness value of any color this alters; 293 * typically very small, such as in the -0.2f to 0.2f range 294 */ 295 public ColorizeFilter(float r, float g, float b, float saturationMultiplier, float valueModifier) { 296 globalSCC = DefaultResources.getSCC(); 297 targetHue = globalSCC.getHue(r, g, b); 298 targetSaturation = globalSCC.getSaturation(r, g, b); 299 this.saturationMultiplier = saturationMultiplier; 300 this.valueModifier = valueModifier; 301 } 302 /** 303 * Sets up a ColorizeFilter with the desired color to colorize towards. 304 * 305 * @param color the Color to colorize towards 306 * @param saturationMultiplier a multiplier to apply to the final color's saturation; may be greater than 1 307 * @param valueModifier a modifier that affects the final brightness value of any color this alters; 308 * typically very small, such as in the -0.2f to 0.2f range 309 */ 310 public ColorizeFilter(Color color, float saturationMultiplier, float valueModifier) { 311 globalSCC = DefaultResources.getSCC(); 312 targetHue = globalSCC.getHue(color); 313 targetSaturation = globalSCC.getSaturation(color); 314 this.saturationMultiplier = saturationMultiplier; 315 this.valueModifier = valueModifier; 316 } 317 318 @Override 319 public Color alter(float r, float g, float b, float a) { 320 return globalSCC.getHSV( 321 targetHue, 322 Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) + targetSaturation) * 0.5f * saturationMultiplier, 1f)), 323 globalSCC.getValue(r, g, b) * (1f - valueModifier) + valueModifier, 324 a); 325 } 326 } 327 /** 328 * An IFilter that makes the colors requested from it highly saturated, with the original hue, value and a timer that 329 * increments very slowly altering hue, with hue, value and the timer altering saturation, and the original hue, 330 * saturation, and value all altering value. It should look like a hallucination. 331 * <br> 332 * A short (poorly recorded) video can be seen here http://i.imgur.com/SEw2LXe.gifv ; performance should be smoother 333 * during actual gameplay. 334 */ 335 public static class HallucinateFilter implements IFilter<Color> { 336 private SquidColorCenter globalSCC; 337 /** 338 * Sets up a HallucinateFilter with the timer at 0.. 339 */ 340 public HallucinateFilter() { 341 globalSCC = DefaultResources.getSCC(); 342 } 343 @Override 344 public Color alter(float r, float g, float b, float a) { 345 float t = (System.currentTimeMillis() & 0xfff) * 0x1p-12f, 346 h = globalSCC.getHue(r, g, b), 347 s = globalSCC.getSaturation(r, g, b), 348 v = globalSCC.getValue(r, g, b); 349 return globalSCC.getHSV( 350 (v * 4f + h + t) % 1.0f, 351 Math.max(0f, Math.min((h + v) * 0.65f + t * 0.4f, 1f)), 352 MathExtras.clamp((h + v + s) * 0.35f + 0.7f, 0f, 1f), 353 a); 354 } 355 356 } 357 358 359 /** 360 * An IFilter that multiplies the saturation of any color requested from it by a number given during construction. 361 * When desaturating, you may want to prefer {@link SaturationValueFilter}, because reducing saturation increases 362 * perceived brightness (value), and desaturating an ocean-blue color will make it look like ice if the value 363 * isn't decreased as well. This class is perfectly fine for increasing saturation, in general. 364 */ 365 public static class SaturationFilter implements IFilter<Color> { 366 private SquidColorCenter globalSCC; 367 public float multiplier; 368 /** 369 * Sets up a SaturationFilter with the desired saturation multiplier. Using a multiplier of 0f, as you would 370 * expect, makes the image grayscale. Using a multiplier of 0.5 make the image "muted", with no truly bright 371 * colors, while 1.0f makes no change, and and any numbers higher than 1.0f will make the image more saturated, 372 * with the exception of colors that were already grayscale or close to it. This clamps the result, so there's 373 * no need to worry about using too high of a saturation multiplier. 374 * 375 * @param multiplier the amount to multiply each requested color's saturation by; 1.0f means "no change" 376 */ 377 public SaturationFilter(float multiplier) { 378 globalSCC = DefaultResources.getSCC(); 379 this.multiplier = multiplier; 380 } 381 382 @Override 383 public Color alter(float r, float g, float b, float a) { 384 return globalSCC.getHSV( 385 globalSCC.getHue(r, g, b), 386 Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) * multiplier), 1f)), 387 globalSCC.getValue(r, g, b), 388 a); 389 } 390 391 } 392 393 /** 394 * An IFilter that multiplies the saturation and the value of any color requested from it by different numbers given 395 * during construction. 396 */ 397 public static class SaturationValueFilter implements IFilter<Color> { 398 private SquidColorCenter globalSCC; 399 public float saturationMultiplier, valueMultiplier; 400 /** 401 * Sets up a SaturationValueFilter with the desired saturation and value multipliers. Using a multiplier of 0f for 402 * saturation, as you would expect, makes the image grayscale. Using a multiplier of 0f for value makes all 403 * colors identicaly black; this is probably not a good idea. Using a multiplier of 0.5 for saturation will 404 * make the image "muted", with no truly bright colors, while 1.0f for saturation makes no change, and and any 405 * numbers higher than 1.0f for saturation will make the image more saturated, with the exception of colors that 406 * were already grayscale or close to it. Using 0.5f for value will darken the image significantly, 1.0f for 407 * value will keep it the same for brightness, and higher than 1.0f will lighten it. This clamps the result, so 408 * there's no need to worry about using too high of a saturation or value multiplier. 409 * 410 * @param saturation the amount to multiply each requested color's saturation by; 1.0f means "no change" 411 * @param value the amount to multiply each requested color's value (lightness) by; 1.0f means "no change" 412 */ 413 public SaturationValueFilter(float saturation, float value) { 414 globalSCC = DefaultResources.getSCC(); 415 saturationMultiplier = saturation; 416 valueMultiplier = value; 417 } 418 419 @Override 420 public Color alter(float r, float g, float b, float a) { 421 return globalSCC.getHSV( 422 globalSCC.getHue(r, g, b), 423 Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) * saturationMultiplier), 1f)), 424 Math.max(0f, Math.min((globalSCC.getValue(r, g, b) * valueMultiplier), 1f)), 425 a); 426 } 427 428 } 429 430 /** 431 * An IFilter that is constructed with a palette of colors and randomly increases or decreases the red, green, and 432 * blue components of any color it is told to alter. Good for a "glitchy screen" effect. 433 */ 434 public static class WiggleFilter implements IFilter<Color> { 435 long rngState; 436 public WiggleFilter() 437 { 438 rngState = (long) ((Math.random() - 0.5) * 0x10000000000000L) 439 ^ (long) (((Math.random() - 0.5) * 2.0) * 0x8000000000000000L); 440 } 441 @Override 442 public Color alter(float r, float g, float b, float a) { 443 return new Color(r - 0.1f + ThrustAltRNG.determineFloat(++rngState) * 0.2f, 444 g - 0.1f + ThrustAltRNG.determineFloat(++rngState) * 0.2f, 445 b - 0.1f + ThrustAltRNG.determineFloat(++rngState) * 0.2f, 446 a); 447 } 448 } 449 450 /** 451 * An IFilter that is constructed with a group of colors and forces any color it is told to alter to exactly 452 * the color it was constructed with that has the closest red, green, and blue components. A convenient way to 453 * use this is to pass in one of the color series from SColor, such as {@link SColor#RED_SERIES} or 454 * {@link SColor#ACHROMATIC_SERIES}. This can also be used to enforce usage of a limited color palette such as 455 * one of DawnBringer's popular pixel art palettes, {@link SColor#DAWNBRINGER_16} and 456 * {@link SColor#DAWNBRINGER_32}. Beyond 32 colors, the palettes {@link SColor#VARIED_PALETTE} with 56 colors 457 * and {@link SColor#COLOR_WHEEL_PALETTE} with 198 colors can be used. 458 * 459 * Preview using BLUE_GREEN_SERIES foreground, ACHROMATIC_SERIES background: http://i.imgur.com/2HdZpC9.png 460 */ 461 public static class PaletteFilter implements IFilter<Color> { 462 /** 463 * The array of Color objects this will use as a palette. 464 */ 465 public Color[] colorStore; 466 /** 467 * Sets up a PaletteFilter with the exact colors to use as individual components; the lengths of each given 468 * array should be identical. 469 * 470 * @param r the red components to use 471 * @param g the green components to use 472 * @param b the blue components to use 473 */ 474 public PaletteFilter(float[] r, float[] g, float[] b) { 475 colorStore = new Color[Math.min(r.length, Math.min(g.length, b.length))]; 476// state = new float[colorStore.length * 3]; 477 for (int i = 0; i < colorStore.length; i++) { 478 colorStore[i] = new Color( 479 MathUtils.clamp(r[i], 0f, 1f), 480 MathUtils.clamp(g[i], 0f, 1f), 481 MathUtils.clamp(b[i], 0f, 1f), 482 1f); 483 } 484 }/** 485 * Sets up a PaletteFilter with the exact colors to use as Colors. A convenient way to 486 * use this is to pass in one of the color series from SColor, such as RED_SERIES or ACHROMATIC_SERIES. 487 * The alpha component of each color in the palette is ignored, since when a color is requested for 488 * filtering, its alpha is respected and only its red, green, and blue components are changed. 489 * The {@code colors} array is used verbatim (as a reference, not a copy), so changes to the Color values inside 490 * it will change how this PaletteFilter works (possibly badly). If you expect to edit the array you give as a 491 * parameter to this, it may be optimal to give a temporary copy. 492 * @param colors the Colors to use as an array; will be referenced in the PaletteFilter, so changing items in 493 * this array will change what Colors will be used 494 */ 495 public PaletteFilter(Color[] colors) { 496 colorStore = colors; 497// state = new float[colors.length * 3]; 498// for (int i = 0; i < colors.length; i++) { 499// state[i * 3] = colors[i].r; 500// state[i * 3 + 1] = colors[i].g; 501// state[i * 3 + 2] = colors[i].b; 502// } 503 } 504 505 @Override 506 public Color alter(float r, float g, float b, float a) { 507 int diff = 0x7fffffff, temp; 508 int choice = 0; 509 r = SColor.floatGet(r, g, b, a); 510 for (int i = 0; i < colorStore.length; i++) { 511 temp = SColor.difference2(r, colorStore[i]); 512 if(temp < diff) { 513 diff = temp; 514 choice = i; 515 } 516 } 517 if(a >= 1f) 518 return colorStore[choice]; 519 Color ret = colorStore[choice].cpy(); 520 ret.a = a; 521 return ret; 522 } 523 } 524 /** 525 * An IFilter that alters primarily-red and primarily-green colors so they can be more easily be distinguished by 526 * people with at least some forms of red-green color-blindness (deuteranopia should be handled well, protanopia 527 * very well, and tritanopia may not benefit at all). Causes reds to be darkened and greens to be lightened if the 528 * other of the pair is not present in similar quantities (which is the case for yellows and blues). 529 */ 530 public static class DistinctRedGreenFilter implements IFilter<Color> { 531 /** 532 * Constructs a DistinctRedGreenFilter. This class is a simple wrapper around a function that doesn't need 533 * member variables, so there should be little overhead with this filter. 534 */ 535 public DistinctRedGreenFilter() { 536 } 537 538 @Override 539 public Color alter(float r, float g, float b, float a) { 540 float diff = g - r; 541 if(diff > 0.4f) 542 return new Color(Math.min(1f, r * (0.8f + diff * 0.5f)), Math.min(1f, g * (0.9f + diff * 0.5f)), 543 Math.min(1f, b * (0.8f + diff * 0.5f)), a); 544 else if(diff < -0.3f) 545 return new Color(r * (0.6f - diff), g * (0.7f - diff), 546 b * (0.7f - diff), a); 547 else 548 return new Color(r, g, b, a); 549 } 550 } 551}