001package squidpony.squidgrid.gui.gdx; 002 003import com.badlogic.gdx.graphics.Color; 004 005import squidpony.IFilter; 006import squidpony.squidmath.LightRNG; 007 008/** 009 * Implementations of {@link IFilter}, that all are meant to perform different changes 010 * to colors before they are created (they should be passed to SquidColorCenter's constructor, which can use them). 011 * Created by Tommy Ettinger on 10/31/2015. 012 */ 013public class Filters { 014 015 private Filters() { 016 /* You should not build me */ 017 } 018 019 /** 020 * A Filter that does nothing to the colors it is given but pass them along unchanged. 021 */ 022 public static class IdentityFilter implements IFilter<Color> 023 { 024 public IdentityFilter() 025 { 026 /* Nothing to do */ 027 } 028 029 @Override 030 public Color alter(float r, float g, float b, float a) { 031 return new Color(r, g, b, a); 032 } 033 } 034 035 /** 036 * A Filter that converts all colors passed to it to grayscale, like a black and white film. 037 */ 038 public static class GrayscaleFilter implements IFilter<Color> 039 { 040 public GrayscaleFilter() 041 { 042 /* Nothing to do */ 043 } 044 045 @Override 046 public Color alter(float r, float g, float b, float a) { 047 float v = (r + g + b) / 3f; 048 return new Color(v, v, v, a); 049 } 050 } 051 052 /** 053 * A Filter that tracks the highest brightness for any component it was assigned and stores it in state as the first 054 * and only element. 055 */ 056 public static class MaxValueFilter extends Filter<Color> 057 { 058 public MaxValueFilter() 059 { 060 state = new float[]{0}; 061 } 062 063 @Override 064 public Color alter(float r, float g, float b, float a) { 065 state[0] = Math.max(state[0], Math.max(r, Math.max(g, b))); 066 return new Color(r, g, b, a); 067 } 068 069 } 070 071 /** 072 * A Filter that performs a brightness adjustment to make dark areas lighter and light areas not much less bright. 073 */ 074 public static class GammaCorrectFilter extends Filter<Color> { 075 /** 076 * Sets up a GammaCorrectFilter with the desired gamma adjustment. 077 * 078 * @param gamma should be 1.0 or less, and must be greater than 0. Typical values are between 0.4 to 0.8. 079 * @param maxValue the maximum brightness in the colors this will be passed; use MaxValueFilter for this 080 */ 081 public GammaCorrectFilter(float gamma, float maxValue) { 082 state = new float[]{gamma, 1f / (float) Math.pow(maxValue, gamma)}; 083 } 084 085 @Override 086 public Color alter(float r, float g, float b, float a) { 087 return new Color(state[1] * (float) Math.pow(r, state[0]), 088 state[1] * (float) Math.pow(g, state[0]), 089 state[1] * (float) Math.pow(b, state[0]), 090 a); 091 } 092 } 093 /** 094 * A Filter that is constructed with a color and linear-interpolates any color it is told to alter toward the color 095 * it was constructed with. 096 */ 097 public static class LerpFilter extends Filter<Color> { 098 /** 099 * Sets up a LerpFilter with the desired color to linearly interpolate towards. 100 * 101 * @param r the red component to lerp towards 102 * @param g the green component to lerp towards 103 * @param b the blue component to lerp towards 104 * @param a the opacity component to lerp towards 105 * @param amount the amount to lerp by, should be between 0.0 and 1.0 106 */ 107 public LerpFilter(float r, float g, float b, float a, float amount) { 108 state = new float[]{r, g, b, a, amount}; 109 } 110 /** 111 * Sets up a LerpFilter with the desired color to linearly interpolate towards. 112 * 113 * @param color the Color to lerp towards 114 * @param amount the amount to lerp by, should be between 0.0 and 1.0 115 */ 116 public LerpFilter(Color color, float amount) { 117 state = new float[]{color.r, color.g, color.b, color.a, amount}; 118 } 119 120 @Override 121 public Color alter(float r, float g, float b, float a) { 122 return new Color(r, g, b, a).lerp(state[0], state[1], state[2], state[3], state[4]); 123 } 124 } 125 /** 126 * A Filter that is constructed with a group of colors and linear-interpolates any color it is told to alter toward 127 * the color it was constructed with that has the closest hue. 128 */ 129 public static class MultiLerpFilter extends Filter<Color> { 130 private SquidColorCenter globalSCC; 131 /** 132 * Sets up a MultiLerpFilter with the desired colors to linearly interpolate towards; the lengths of each given 133 * array should be identical. 134 * 135 * @param r the red components to lerp towards 136 * @param g the green components to lerp towards 137 * @param b the blue components to lerp towards 138 * @param a the opacity components to lerp towards 139 * @param amount the amounts to lerp by, should each be between 0.0 and 1.0 140 */ 141 public MultiLerpFilter(float[] r, float[] g, float[] b, float[] a, float[] amount) { 142 state = new float[Math.min(r.length, Math.min(g.length, Math.min(b.length, 143 Math.min(a.length, amount.length)))) * 6]; 144 globalSCC = DefaultResources.getSCC(); 145 for (int i = 0; i < state.length / 6; i++) { 146 state[i * 6] = r[i]; 147 state[i * 6 + 1] = g[i]; 148 state[i * 6 + 2] = b[i]; 149 state[i * 6 + 3] = a[i]; 150 state[i * 6 + 4] = amount[i]; 151 state[i * 6 + 5] = globalSCC.getHue(r[i], g[i], b[i]); 152 } 153 }/** 154 * Sets up a MultiLerpFilter with the desired colors to linearly interpolate towards and their amounts. 155 * 156 * @param colors the Colors to lerp towards 157 * @param amount the amounts to lerp by, should each be between 0.0 and 1.0 158 */ 159 public MultiLerpFilter(Color[] colors, float[] amount) { 160 state = new float[Math.min(colors.length, amount.length) * 6]; 161 globalSCC = DefaultResources.getSCC(); 162 for (int i = 0; i < state.length / 6; i++) { 163 state[i * 6] = colors[i].r; 164 state[i * 6 + 1] = colors[i].g; 165 state[i * 6 + 2] = colors[i].b; 166 state[i * 6 + 3] = colors[i].a; 167 state[i * 6 + 4] = amount[i]; 168 state[i * 6 + 5] = globalSCC.getHue(colors[i]); 169 } 170 } 171 172 @Override 173 public Color alter(float r, float g, float b, float a) { 174 float givenH = globalSCC.getHue(r, g, b), givenS = globalSCC.getSaturation(r, g, b), 175 minDiff = 999.0f, temp; 176 if(givenS < 0.05) 177 return new Color(r, g, b, a); 178 int choice = 0; 179 for (int i = 5; i < state.length; i += 6) { 180 temp = state[i] - givenH; 181 temp = (temp >= 0.5f) ? Math.abs(temp - 1f) % 1f : Math.abs(temp); 182 if(temp < minDiff) { 183 minDiff = temp; 184 choice = i / 6; // rounds down 185 } 186 } 187 return new Color(r, g, b, a).lerp(state[choice * 6], state[choice * 6 + 1], state[choice * 6 + 2], 188 state[choice * 6 + 3], state[choice * 6 + 4]); 189 } 190 } 191 192 /** 193 * A Filter that is constructed with a color and makes any color it is told to alter have the same hue as the given 194 * color, have saturation that is somewhere between the given color's and the altered colors, and chiefly is 195 * distinguishable from other colors by value. Useful for sepia effects, which can be created satisfactorily with 196 * {@code new Filters.ColorizeFilter(SColor.CLOVE_BROWN, 0.6f, 0.0f)}. 197 */ 198 public static class ColorizeFilter extends Filter<Color> { 199 private SquidColorCenter globalSCC; 200 /** 201 * Sets up a ColorizeFilter with the desired color to colorize towards. 202 * 203 * @param r the red component to colorize towards 204 * @param g the green component to colorize towards 205 * @param b the blue component to colorize towards 206 */ 207 public ColorizeFilter(float r, float g, float b) { 208 globalSCC = DefaultResources.getSCC(); 209 210 state = new float[]{globalSCC.getHue(r, g, b), globalSCC.getSaturation(r, g, b), 1f, 0f}; 211 } 212 /** 213 * Sets up a ColorizeFilter with the desired color to colorize towards. 214 * 215 * @param color the Color to colorize towards 216 */ 217 public ColorizeFilter(Color color) { 218 globalSCC = DefaultResources.getSCC(); 219 state = new float[]{globalSCC.getHue(color), globalSCC.getSaturation(color), 1f, 0f}; 220 } 221 /** 222 * Sets up a ColorizeFilter with the desired color to colorize towards. 223 * 224 * @param r the red component to colorize towards 225 * @param g the green component to colorize towards 226 * @param b the blue component to colorize towards 227 * @param saturationMultiplier a multiplier to apply to the final color's saturation; may be greater than 1 228 * @param valueModifier a modifier that affects the final brightness value of any color this alters; 229 * typically very small, such as in the -0.2f to 0.2f range 230 */ 231 public ColorizeFilter(float r, float g, float b, float saturationMultiplier, float valueModifier) { 232 globalSCC = DefaultResources.getSCC(); 233 234 state = new float[]{ 235 globalSCC.getHue(r, g, b), 236 globalSCC.getSaturation(r, g, b), 237 saturationMultiplier, 238 valueModifier}; 239 } 240 /** 241 * Sets up a ColorizeFilter with the desired color to colorize towards. 242 * 243 * @param color the Color to colorize towards 244 * @param saturationMultiplier a multiplier to apply to the final color's saturation; may be greater than 1 245 * @param valueModifier a modifier that affects the final brightness value of any color this alters; 246 * typically very small, such as in the -0.2f to 0.2f range 247 */ 248 public ColorizeFilter(Color color, float saturationMultiplier, float valueModifier) { 249 globalSCC = DefaultResources.getSCC(); 250 state = new float[]{ 251 globalSCC.getHue(color), 252 globalSCC.getSaturation(color), 253 saturationMultiplier, 254 valueModifier}; 255 } 256 257 @Override 258 public Color alter(float r, float g, float b, float a) { 259 return globalSCC.getHSV( 260 state[0], 261 Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) + state[1]) * 0.5f * state[2], 1f)), 262 globalSCC.getValue(r, g, b) * (1f - state[3]) + state[3], 263 a); 264 } 265 } 266 /** 267 * A Filter that makes the colors requested from it highly saturated, with the original hue, value and a timer that 268 * increments very slowly altering hue, with hue, value and the timer altering saturation, and the original hue, 269 * saturation, and value all altering value. It should look like a hallucination. 270 * <br> 271 * A short (poorly recorded) video can be seen here http://i.imgur.com/SEw2LXe.gifv ; performance should be smoother 272 * during actual gameplay. 273 */ 274 public static class HallucinateFilter extends Filter<Color> { 275 private SquidColorCenter globalSCC; 276 /** 277 * Sets up a HallucinateFilter with the timer at 0.. 278 */ 279 public HallucinateFilter() { 280 globalSCC = DefaultResources.getSCC(); 281 282 state = new float[]{0f}; 283 } 284 @Override 285 public Color alter(float r, float g, float b, float a) { 286 state[0] += 0.00003f; 287 if(state[0] >= 1.0f) 288 state[0] = 0f; 289 float h = globalSCC.getHue(r, g, b), 290 s = globalSCC.getSaturation(r, g, b), 291 v = globalSCC.getValue(r, g, b); 292 return globalSCC.getHSV( 293 (v * 4f + h + state[0]) % 1.0f, 294 Math.max(0f, Math.min((h + v) * 0.65f + state[0] * 0.4f, 1f)), 295 (h + v + s) * 0.35f + 0.7f, 296 a); 297 } 298 } 299 300 301 /** 302 * A Filter that multiplies the saturation of any color requested from it by a number given during construction. 303 */ 304 public static class SaturationFilter extends Filter<Color> { 305 private SquidColorCenter globalSCC; 306 /** 307 * Sets up a SaturationFilter with the desired saturation multiplier. Using a multiplier of 0f, as you would 308 * expect, makes the image grayscale. Using a multiplier of 0.5 make the image "muted", with no truly bright 309 * colors, while 1.0f makes no change, and and any numbers higher than 1.0f will make the image more saturated, 310 * with the exception of colors that were already grayscale or close to it. This clamps the result, so there's 311 * no need to worry about using too high of a saturation multiplier. 312 * 313 * @param multiplier the amount to multiply each requested color's saturation by; 1.0f means "no change" 314 */ 315 public SaturationFilter(float multiplier) { 316 globalSCC = DefaultResources.getSCC(); 317 318 state = new float[]{multiplier}; 319 } 320 321 @Override 322 public Color alter(float r, float g, float b, float a) { 323 return globalSCC.getHSV( 324 globalSCC.getHue(r, g, b), 325 Math.max(0f, Math.min((globalSCC.getSaturation(r, g, b) * state[0]), 1f)), 326 globalSCC.getValue(r, g, b), 327 a); 328 } 329 } 330 331 /** 332 * A Filter that is constructed with a palette of colors and randomly increases or decreases the red, green, and 333 * blue components of any color it is told to alter. Good for a "glitchy screen" effect. 334 */ 335 public static class WiggleFilter extends Filter<Color> { 336 LightRNG rng; 337 public WiggleFilter() 338 { 339 rng = new LightRNG(); 340 } 341 @Override 342 public Color alter(float r, float g, float b, float a) { 343 return new Color(r - 0.1f + rng.nextInt(5) * 0.05f, 344 g - 0.1f + rng.nextInt(5) * 0.05f, 345 b - 0.1f + rng.nextInt(5) * 0.05f, 346 a); 347 } 348 } 349 350 /** 351 * A Filter that is constructed with a group of colors and forces any color it is told to alter to exactly 352 * the color it was constructed with that has the closest red, green, and blue components. A convenient way to 353 * use this is to pass in one of the color series from SColor, such as RED_SERIES or ACHROMATIC_SERIES. 354 * 355 * Preview using BLUE_GREEN_SERIES foreground, ACHROMATIC_SERIES background: http://i.imgur.com/2HdZpC9.png 356 */ 357 public static class PaletteFilter extends Filter<Color> { 358 /** 359 * Sets up a PaletteFilter with the exact colors to use as individual components; the lengths of each given 360 * array should be identical. 361 * 362 * @param r the red components to use 363 * @param g the green components to use 364 * @param b the blue components to use 365 * @param a the opacity components to use 366 */ 367 public PaletteFilter(float[] r, float[] g, float[] b, float[] a) { 368 state = new float[Math.min(r.length, Math.min(g.length, Math.min(b.length, 369 a.length))) * 4]; 370 for (int i = 0; i < state.length / 4; i++) { 371 state[i * 4] = r[i]; 372 state[i * 4 + 1] = g[i]; 373 state[i * 4 + 2] = b[i]; 374 state[i * 4 + 3] = a[i]; 375 } 376 }/** 377 * Sets up a PaletteFilter with the exact colors to use as Colors. A convenient way to 378 * use this is to pass in one of the color series from SColor, such as RED_SERIES or ACHROMATIC_SERIES. 379 * 380 * @param colors the Colors to use 381 */ 382 public PaletteFilter(Color[] colors) { 383 state = new float[colors.length * 4]; 384 for (int i = 0; i < state.length / 4; i++) { 385 state[i * 4] = colors[i].r; 386 state[i * 4 + 1] = colors[i].g; 387 state[i * 4 + 2] = colors[i].b; 388 state[i * 4 + 3] = colors[i].a; 389 } 390 } 391 392 @Override 393 public Color alter(float r, float g, float b, float a) { 394 float diff = 9999.0f, temp; 395 int choice = 0; 396 for (int i = 0; i < state.length; i += 4) { 397 temp = Math.abs(state[i] - r) + Math.abs(state[i + 1] - g) + Math.abs(state[i + 2] - b); 398 if(temp < diff) { 399 diff = temp; 400 choice = i; 401 } 402 } 403 return new Color(state[choice], state[choice + 1], state[choice + 2], 404 state[choice + 3]); 405 } 406 } 407 408}