001package squidpony.squidgrid.gui.gdx; 002 003import com.badlogic.gdx.graphics.Color; 004import com.badlogic.gdx.math.MathUtils; 005import squidpony.squidmath.NumberTools; 006import com.github.tommyettinger.anim8.PaletteReducer; 007 008import static squidpony.squidgrid.gui.gdx.SColor.floatGet; 009import static squidpony.squidgrid.gui.gdx.SColor.lerpFloatColorsBlended; 010 011/** 012 * Pre-made FloatFilter classes that you can use to filter colors without producing extra Color objects. 013 * <br> 014 * Created by Tommy Ettinger on 7/22/2018. 015 */ 016public final class FloatFilters { 017 private FloatFilters() { 018 // don't build me! 019 } 020 021 /** 022 * Wraps the functionality of {@link SColor#toEditedFloat(float, float, float, float, float)} so it can be called as 023 * a FloatFilter, adding values to hue, saturation, and value (clamping saturation and value and wrapping hue). 024 * Hue is in the 0.0 to 1.0 range, as SquidLib handles it, instead of libGDX's 0 to 360 range. 025 */ 026 public static class HSVFilter extends FloatFilter { 027 public float hueAddend, saturationAddend, valueAddend; 028 029 public HSVFilter(float saturation, float value) { 030 this(0f, saturation, value); 031 } 032 033 public HSVFilter(float hueAdd, float saturationAdd, float valueAdd) { 034 hueAddend = hueAdd; 035 saturationAddend = saturationAdd; 036 valueAddend = valueAdd; 037 } 038 039 /** 040 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 041 * 042 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 043 * @return a packed float color, as produced by {@link Color#toFloatBits()} 044 */ 045 @Override 046 public float alter(float color) { 047 return SColor.toEditedFloat(color, hueAddend, saturationAddend, valueAddend, 0f); 048 } 049 } 050 051 /** 052 * Changes all colors this alters to have the same hue as, and a closer saturation to, a target color given in the 053 * constructor, as well as optionally multiplying saturation of the result and/or adding lightness/value. A good 054 * example usage of this is to make a sepia-tone effect with 055 * {@code new FloatFilters.ColorizeFilter(SColor.CLOVE_BROWN, 0.6f, 0.0f)}. 056 */ 057 public static class ColorizeFilter extends FloatFilter { 058 public float targetCb, targetCr, lumaAddend; 059 060 public ColorizeFilter(float color) { 061 this(color, 1f, 0f); 062 } 063 064 public ColorizeFilter(Color color) { 065 this(color.toFloatBits(), 1f, 0f); 066 } 067 068 public ColorizeFilter(Color color, float chromaMul, float lumaAdd) { 069 this(color.toFloatBits(), chromaMul, lumaAdd); 070 } 071 072 public ColorizeFilter(float color, float chromaMul, float lumaAdd) { 073 targetCb = SColor.chromaBOfFloat(color) * chromaMul; 074 targetCr = SColor.chromaROfFloat(color) * chromaMul; 075 lumaAddend = lumaAdd; 076 } 077 078 /** 079 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 080 * 081 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 082 * @return a packed float color, as produced by {@link Color#toFloatBits()} 083 */ 084 @Override 085 public float alter(float color) { 086 final int bits = NumberTools.floatToIntBits(color); 087 return SColor.floatGetYCbCr((bits & 0x000000ff) * (0x1.010102p-8f * 0.299f) + 088 (bits & 0x0000ff00) * (0x1.010102p-16f * 0.587f) + 089 (bits & 0x00ff0000) * (0x1.010102p-24f * 0.114f), 090 targetCb, targetCr, 091 ((bits & 0xfe000000) >>> 24) * 0x1.020408p-8f); 092 } 093 } 094 095 /** 096 * A FloatFilter that alters primarily-red and primarily-green colors so they can be more easily be distinguished by 097 * people with at least some forms of red-green color-blindness (deuteranopia should be handled well, protanopia 098 * very well, and tritanopia may not benefit at all). Causes reds to be darkened and greens to be lightened if the 099 * other of the pair is not present in similar quantities (which is the case for yellows and blues). 100 */ 101 public static class DistinctRedGreenFilter extends FloatFilter { 102 /** 103 * Constructs a DistinctRedGreenFilter. This class is a simple wrapper around a function that doesn't need 104 * member variables, so there should be little overhead with this filter. 105 */ 106 public DistinctRedGreenFilter() { 107 } 108 109 /** 110 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 111 * 112 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 113 * @return a packed float color, as produced by {@link Color#toFloatBits()} 114 */ 115 @Override 116 public float alter(float color) { 117// final int bits = NumberTools.floatToIntBits(color), 118// r = bits & 0xFF, g = bits >>> 8 & 0xFF, b = bits >>> 16 & 0xFF, a = bits >>> 24, 119// diff = g - r; 120// if (diff > 101) 121// return floatGet(Math.min(1f, 0x1.010102p-16f * r * (203 + (diff >> 1))), 122// Math.min(1f, 0x1.010102p-16f * g * (228 + (diff >> 1))), 123// Math.min(1f, 0x1.010102p-16f * b * (203 + (diff >> 1))), 124// 0x1.010102p-8f * a); 125// else if (diff < -75) 126// return floatGet(Math.min(1f, 0x1.010102p-16f * r * (152 - diff)), 127// Math.min(1f, 0x1.010102p-16f * g * (177 - diff)), 128// Math.min(1f, 0x1.010102p-16f * b * (177 - diff)), 129// 0x1.010102p-8f * a); 130// else 131// return color; 132 final int bits = NumberTools.floatToIntBits(color); 133 final float opacity = (bits >>> 25) * 0.007874016f; 134 float luma = ( 135 (bits & 0x000000ff) * (0x1.010102p-8f * 0.299f) + 136 (bits & 0x0000ff00) * (0x1.010102p-16f * 0.587f) + 137 (bits & 0x00ff0000) * (0x1.010102p-24f * 0.114f)); 138 float chromaB = ( 139 (bits & 0x000000ff) * (0x1.010102p-8f * -0.168736f) + 140 (bits & 0x0000ff00) * (0x1.010102p-16f * -0.331264f) + 141 (bits & 0x00ff0000) * (0x1.010102p-24f * 0.5f)); 142 float chromaR = ( 143 (bits & 0x000000ff) * (0x1.010102p-8f * 0.5f) + 144 (bits & 0x0000ff00) * (0x1.010102p-16f * -0.418688f) + 145 (bits & 0x00ff0000) * (0x1.010102p-24f * -0.081312f)); 146 if(chromaB < -0.05f) 147 { 148 float theta = NumberTools.atan2(chromaR, chromaB); 149 float dist = (float) Math.sqrt(chromaB * chromaB + chromaR * chromaR); 150 if(theta >= 0f) 151 { 152 theta *= 0.5f; 153 luma += theta * 0.15f; 154 theta += 0.7853981633974483f; 155 } 156 else 157 { 158 theta *= 0.4f; 159 luma += theta * 0.225f; 160 theta -= 0.9424778335276408f; 161 } 162 chromaR = MathUtils.sin(theta) * dist; 163 chromaB = MathUtils.cos(theta) * dist; 164 } 165 return floatGet(MathUtils.clamp(luma + chromaR * 1.402f, 0f, 1f), 166 MathUtils.clamp(luma - chromaB * 0.344136f - chromaR * 0.714136f, 0f, 1f), 167 MathUtils.clamp(luma + chromaB * 1.772f, 0f, 1f), 168 opacity); 169 170 } 171 } 172 173 /** 174 * A FloatFilter that makes no changes to the colors given to it; useful as a default for when no filter is wanted. 175 */ 176 public static class IdentityFilter extends FloatFilter { 177 /** 178 * Takes a packed float color and returns it un-edited. 179 * 180 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 181 * @return a packed float color, as produced by {@link Color#toFloatBits()} 182 */ 183 @Override 184 public float alter(float color) { 185 return color; 186 } 187 } 188 189 /** 190 * A static constant of the one possible IdentityFilter, to avoid needing to make duplicates. 191 * IdentityFilter makes no changes to the colors given to it. 192 */ 193 public static final IdentityFilter identityFilter = new IdentityFilter(); 194 195 /** 196 * A FloatFilter that makes all colors given to it grayscale, using only their luma as calculated by 197 * {@link SColor#lumaOfFloat(float)} as the lightness (it does also preserve alpha transparency). 198 */ 199 public static class GrayscaleFilter extends FloatFilter { 200 /** 201 * Takes a packed float color and produces a grayscale packed float color that this FloatFilter edited. 202 * Uses the luma calculation from {@link SColor#lumaOfFloat(float)} instead of the value calculation from 203 * {@link SColor#valueOfFloat(float)}; luma tends to be more visually-accurate on modern monitors. 204 * 205 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 206 * @return a packed float color, as produced by {@link Color#toFloatBits()} 207 */ 208 @Override 209 public float alter(float color) { 210 final int bits = NumberTools.floatToIntBits(color); 211 color = (bits & 0x000000ff) * (0x1.010102p-8f * 0.299f) + 212 (bits & 0x0000ff00) * (0x1.010102p-16f * 0.587f) + 213 (bits & 0x00ff0000) * (0x1.010102p-24f * 0.114f); 214 return floatGet(color, color, color, (bits >>> 25) * 0.007874016f); 215 } 216 } 217 218 public static final GrayscaleFilter grayscaleFilter = new GrayscaleFilter(); 219 220 /** 221 * Like {@link HSVFilter}, but edits its input colors in YCbCr color space, and multiplies rather than adds. 222 * Y is luma, and affects how bright the color is (luma 1 is white, luma 0 is black). Cb is Chroma(blue) amd Cr is 223 * Chroma(red), two inter-related channels that determine the hue and vividness of a specific color. When Cb and Cr 224 * are both 0, the color is grayscale. When Cb is 0.5 and Cr is -0.5, the color is blue unless Y is very high or 225 * low. When Cb is -0.5 and Cr is 0.5, the color is red with the same caveats re: Y. When Cb and Cr are both -0.5, 226 * the color is green (same caveats), and when both are 0.5, the color is purple. When Y is 0.5, Cb and Cr form a 227 * graph like this: 228 * <br> 229 * <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/YCbCr-CbCr_Y50.png/240px-YCbCr-CbCr_Y50.png" /> 230 * <br> 231 * Valid values for Cb and Cr are from -0.5 to 0.5 at the widest part of the range (it shrinks as Y approaches 0 or 232 * 1), but there aren't really invalid values here because this filter will clamp results with higher or lower 233 * channel values than a color can have. Each of yMul, cbMul, and crMul can have any float value, but yMul should be 234 * positive (unless you want this to only produce solid black). Similarly, cbMul and crMul will not produce 235 * meaningful results if they are very large (either positive or negative); it's recommended to use values between 236 * 0.0 and 1.0 for both if you want to desaturate colors or values somewhat greater than 1.0 to oversaturate them. 237 */ 238 public static class YCbCrFilter extends FloatFilter { 239 public float yMul, cbMul, crMul; 240 241 public YCbCrFilter(float yMul) { 242 this(yMul, 1f, 1f); 243 } 244 245 public YCbCrFilter(float yMul, float cbMul, float crMul) { 246 this.yMul = yMul; 247 this.cbMul = cbMul; 248 this.crMul = crMul; 249 } 250 251 /** 252 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 253 * 254 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 255 * @return a packed float color, as produced by {@link Color#toFloatBits()} 256 */ 257 @Override 258 public float alter(float color) { 259 final int bits = NumberTools.floatToIntBits(color); 260 final float opacity = (bits >>> 25) * 0.007874016f; 261 float luma = yMul * ( 262 (bits & 0x000000ff) * (0x1.010102p-8f * 0.299f) + 263 (bits & 0x0000ff00) * (0x1.010102p-16f * 0.587f) + 264 (bits & 0x00ff0000) * (0x1.010102p-24f * 0.114f)); 265 final float chromaB = cbMul * ( 266 (bits & 0x000000ff) * (0x1.010102p-8f * -0.168736f) + 267 (bits & 0x0000ff00) * (0x1.010102p-16f * -0.331264f) + 268 (bits & 0x00ff0000) * (0x1.010102p-24f * 0.5f)); 269 final float chromaR = crMul * ( 270 (bits & 0x000000ff) * (0x1.010102p-8f * 0.5f) + 271 (bits & 0x0000ff00) * (0x1.010102p-16f * -0.418688f) + 272 (bits & 0x00ff0000) * (0x1.010102p-24f * -0.081312f)); 273 return floatGet(MathUtils.clamp(luma + chromaR * 1.402f, 0f, 1f), 274 MathUtils.clamp(luma - chromaB * 0.344136f - chromaR * 0.714136f, 0f, 1f), 275 MathUtils.clamp(luma + chromaB * 1.772f, 0f, 1f), 276 opacity); 277 } 278 } 279 280 /** 281 * Like {@link YCbCrFilter}, but edits its input colors in YCoCg color space, or like {@link HSVFilter} except it 282 * doesn't add, it multiplies. Most of the time you should prefer {@link YCbCrFilter} as long as it isn't a 283 * performance bottleneck; this method is faster but less accurate. Y is luminance, ranging from 0 (dark) to 1 284 * (light), and affects how bright the color is, but isn't very accurate perceptually. Co is Chrominance(orange) and 285 * Cg is Chrominance(green) (both range from -0.5 to 0.5), two inter-related channels that determine the hue and 286 * vividness of a specific color. When Co and Cg are both 0, the color is grayscale. When Co is 0.5 and Cg is -0.5, 287 * the color is red unless Y is very high or low. When Co is -0.5 and Cg is 0.5, the color is cyan with the same 288 * caveats re: Y. When Co and Cg are both -0.5, the color is blue (same caveats), and when both are 0.5, the color 289 * is yellow. 290 * <br> 291 * Valid values for Co and Cg are from -0.5 to 0.5 at the widest part of the range (it shrinks as Y approaches 0 or 292 * 1), but there aren't really invalid values here because this filter will clamp results with higher or lower 293 * channel values than a color can have. Each of yMul, coMul, and cgMul can have any float value, but yMul should be 294 * positive (unless you want this to only produce solid black). Similarly, coMul and cgMul will not produce 295 * meaningful results if they are very large (either positive or negative); it's recommended to use values between 296 * 0.0 and 1.0 for both if you want to desaturate colors or values somewhat greater than 1.0 to oversaturate them. 297 */ 298 public static class YCoCgFilter extends FloatFilter { 299 public float yMul, coMul, cgMul; 300 301 public YCoCgFilter(float luminanceMul, float orangeMul, float greenMul) { 302 this.yMul = luminanceMul; 303 this.coMul = orangeMul; 304 this.cgMul = greenMul; 305 } 306 307 /** 308 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 309 * 310 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 311 * @return a packed float color, as produced by {@link Color#toFloatBits()} 312 */ 313 @Override 314 public float alter(float color) { 315 final int bits = NumberTools.floatToIntBits(color); 316 final float opacity = (bits >>> 25) * 0.007874016f; 317 final float y = yMul * (((bits & 0x000000ff) + ((bits & 0x0000ff00) >>> 7) + ((bits & 0x00ff0000) >>> 16)) * 0x1.010102p-10f); 318 final float co = coMul * (((bits & 0x000000ff) - ((bits & 0x00ff0000) >>> 16)) * 0x1.010102p-9f); 319 final float cg = cgMul * ((((bits & 0x0000ff00) >>> 7) - (bits & 0x000000ff) - ((bits & 0x00ff0000) >>> 16)) * 0x1.010102p-10f); 320 321 final float t = y - cg; 322 return floatGet(MathUtils.clamp(t + co, 0f, 1f), 323 MathUtils.clamp(y + cg, 0f, 1f), 324 MathUtils.clamp(t - co, 0f, 1f), 325 opacity); 326 } 327 } 328 329 /** 330 * Like {@link YCbCrFilter} or {@link YCoCgFilter}, but edits its input colors in YCwCm color space, which is very 331 * similar to YCoCg but has chroma/chrominance components that are useful aesthetically on their own. You may often 332 * prefer {@link YCbCrFilter} because it calculates lightness (luma) more precisely, but the Cb (blue-ness) and Cr 333 * (red-ness) components are less useful for some purposes individually. Y is luminance, ranging from 0 (dark) to 1 334 * (light), and affects how bright the color is, but isn't very accurate perceptually. Cw is Chroma(warm) and 335 * Cm is Chroma(mild) (both range from -1.0 to 1.0), two inter-related channels that determine the hue and vividness 336 * of a specific color. When Cw and Cm are both 0, the color is grayscale. When Cw is 1 and Cm is -1, the color 337 * is red or like red. When Cw is -1 and Cm is 1, the color is green or like green. When Cw and Cm are both -1, the 338 * color is blue or like blue, and when both are 1, the color is roughly yellow or brown (depending on Y). 339 * <br> 340 * Valid values for Cw and Cm are from -1.0 to 1.0, but there aren't really invalid values here because this filter 341 * will clamp results with higher or lower channel values than a color can have. Each of yMul, cwMul, and cmMul can 342 * have any float value, but yMul should be positive (unless you want this to only produce solid black). Similarly, 343 * cwMul and cmMul will not produce meaningful results if they are very large (either positive or negative); it's 344 * recommended to use values between 0.0 and 1.0 for both if you want to desaturate colors or values somewhat 345 * greater than 1.0 to oversaturate them. Unlike {@link YCbCrFilter} and {@link YCoCgFilter}, you can benefit from 346 * setting cwMul independently of the other chroma component, which can be used to emphasize warm vs. cool colors if 347 * cwMul is greater than 1.0, or to de-emphasize that comparison if it is between 0.0 and 1.0. A similar option is 348 * possible for cmMul, but it isn't as clear of an artistic convention; a high cmMul will separate green-and-yellow 349 * colors further from red-purple-and-blue colors. Also unlike the other YCC filters, this allows an additive change 350 * to Y, Cw, and Cm applied after the multiplicative change but before converting to RGB and clamping. This can be 351 * used to make all colors warmer or cooler (such as for volcano or frozen scenes) by adding or subtracting from Cw, 352 * for instance. It can also lighten or darken all colors by changing luma. 353 */ 354 public static class YCwCmFilter extends FloatFilter { 355 public float yMul, cwMul, cmMul, yAdd, cwAdd, cmAdd; 356 357 public YCwCmFilter(float yMul, float cwMul, float cmMul) { 358 this(yMul, cwMul, cmMul, 0f, 0f, 0f); 359 } 360 public YCwCmFilter(float yMul, float cwMul, float cmMul, float yAdd, float cwAdd, float cmAdd) { 361 this.yMul = yMul; 362 this.cwMul = cwMul; 363 this.cmMul = cmMul; 364 this.yAdd = yAdd; 365 this.cwAdd = cwAdd; 366 this.cmAdd = cmAdd; 367 } 368 369 /** 370 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 371 * 372 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 373 * @return a packed float color, as produced by {@link Color#toFloatBits()} 374 */ 375 @Override 376 public float alter(float color) { 377 final int bits = NumberTools.floatToIntBits(color); 378 final float opacity = (bits >>> 25) * 0.007874016f; 379 final float luma = yAdd + yMul * ((bits & 0xFF) * 0x3p-11f + (bits >>> 8 & 0xFF) * 0x1p-9f + (bits >>> 16 & 0xFF) * 0x1p-11f); 380 final float warm = (cwAdd + cwMul * (((bits & 0xFF) - (bits >>> 16 & 0xff)) * 0x1.010102p-8f)); 381 final float mild = 0.5f * (cmAdd + cmMul * (((bits >>> 8 & 0xff) - (bits >>> 16 & 0xff)) * 0x1.010102p-8f)); 382 383 return floatGet(MathUtils.clamp(luma + warm * 0.625f - mild, 0f, 1f), 384 MathUtils.clamp(luma + mild - warm * 0.375f, 0f, 1f), 385 MathUtils.clamp(luma - warm * 0.375f - mild, 0f, 1f), 386 opacity); 387 } 388 } 389 390 /** 391 * A FloatFilter that chains together one or more FloatFilters one after the next, passing the float output of one 392 * as input to the next until the chain has all been called. 393 */ 394 public static class ChainFilter extends FloatFilter { 395 public FloatFilter[] filters; 396 397 /** 398 * Takes a vararg or array of FloatFilter objects and produces a ChainFilter that will call all of them in order 399 * on any color given to this to alter. 400 * @param filters an array or vararg of FloatFilter objects; none can be null 401 */ 402 public ChainFilter(FloatFilter... filters) 403 { 404 if(filters == null || filters.length == 0) 405 this.filters = new FloatFilter[]{new IdentityFilter()}; 406 this.filters = filters; 407 } 408 409 /** 410 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 411 * 412 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 413 * @return a packed float color, as produced by {@link Color#toFloatBits()} 414 */ 415 @Override 416 public float alter(float color) { 417 for (int i = 0; i < filters.length; i++) { 418 color = filters[i].alter(color); 419 } 420 return color; 421 } 422 } 423 424 /** 425 * A FloatFilter that linearly interpolates (lerps) any color it is given toward a specified color by a specified 426 * amount. Uses {@link SColor#lerpFloatColorsBlended(float, float, float)} to mix a requested color with the target 427 * color, and this means the alpha of the target color affects the amount of change instead of the resulting alpha. 428 */ 429 public static class LerpFilter extends FloatFilter { 430 public float target, amount; 431 432 /** 433 * Builds a LerpFilter with a Color (which will be converted to a packed float color) and an amount as a float. 434 * The amount is how much the target color will affect input colors, from 0f to 1f. If the target color has an 435 * alpha component that is less than 1, then amount is effectively multiplied by that alpha. 436 * @param target a libGDX color; must not be null 437 * @param amount a float that determines how much target will affect an input color; will be clamped between 0f and 1f 438 */ 439 public LerpFilter(Color target, float amount) { 440 this.target = target.toFloatBits(); 441 this.amount = MathUtils.clamp(amount, 0f, 1f); 442 } 443 444 /** 445 * Builds a LerpFilter with a packed float color and an amount as a float. 446 * The amount is how much the target color will affect input colors, from 0f to 1f. If the target color has an 447 * alpha component that is less than 1, then amount is effectively multiplied by that alpha. 448 * @param target a packed float color; must not be null 449 * @param amount a float that determines how much target will affect an input color; will be clamped between 0f and 1f 450 */ 451 452 public LerpFilter(float target, float amount) 453 { 454 this.target = target; 455 this.amount = MathUtils.clamp(amount, 0f, 1f); 456 } 457 458 /** 459 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 460 * 461 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 462 * @return a packed float color, as produced by {@link Color#toFloatBits()} 463 */ 464 @Override 465 public float alter(float color) { 466 return lerpFloatColorsBlended(color, target, amount); 467 } 468 } 469 470 /** 471 * A FloatFilter that linearly interpolates (lerps) any color it is given toward the most-similar of a group of 472 * given colors. Uses {@link SColor#lerpFloatColorsBlended(float, float, float)} to mix a requested color with the 473 * chosen target color, and this means the alpha of the target color affects the amount of change instead of the 474 * resulting alpha. Changing the alpha of the colors this is given can be done easily with 475 * {@link SColor#translucentColor(float, float)}, and this allows you to specify varying amounts to mix by. 476 */ 477 public static class MultiLerpFilter extends FloatFilter { 478 public float[] targets; 479 public float amount; 480 /** 481 * Builds a MultiLerpFilter with an array of Color objects (which will be converted to an array of packed float 482 * colors) and an amount as a float. The amount is how much the target colors will affect input colors, from 0f 483 * to 1f. If a target color has an alpha component that is less than 1, then amount is effectively multiplied by 484 * that alpha. If you want to edit the alpha without duplicating Color objects, you can use 485 * {@link SColor#translucentColor(Color, float)} to make a float array to pass to 486 *{@link #MultiLerpFilter(float, float...)}. 487 * @param amount a float that determines how much target will affect an input color; will be clamped between 0f and 1f 488 * @param targets an array of libGDX Color objects; must not be null or empty 489 */ 490 public MultiLerpFilter(float amount, Color[] targets) { 491 this.targets = new float[targets.length]; 492 for (int i = 0; i < targets.length; i++) { 493 this.targets[i] = targets[i].toFloatBits(); 494 } 495 this.amount = MathUtils.clamp(amount, 0f, 1f); 496 } 497 498 /** 499 * Builds a MultiLerpFilter with an array of packed float colors and an amount as a float. 500 * The amount is how much the target color will affect input colors, from 0f to 1f. If the target color has an 501 * alpha component that is less than 1, then amount is effectively multiplied by that alpha. 502 * @param amount a float that determines how much target will affect an input color; will be clamped between 0f and 1f 503 * @param targets an array or vararg of packed float colors; must not be null or empty 504 */ 505 506 public MultiLerpFilter(float amount, float... targets) 507 { 508 this.targets = targets; 509 this.amount = MathUtils.clamp(amount, 0f, 1f); 510 } 511 512 /** 513 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 514 * 515 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 516 * @return a packed float color, as produced by {@link Color#toFloatBits()} 517 */ 518 @Override 519 public float alter(float color) { 520 int choice = 0, diff = SColor.difference2(targets[0], color); 521 for (int i = 1; i < targets.length; i++) { 522 if(diff != (diff = Math.min(SColor.difference2(targets[i], color), diff))) 523 choice = i; 524 } 525 return lerpFloatColorsBlended(color, targets[choice], amount); 526 } 527 } 528 529 /** 530 * A FloatFilter that limits the colors it can return to a fixed palette, and won't return any colors that are 531 * missing from that palette (although it can always return fully-transparent). {@link PaletteReducerFilter} is also 532 * an option; it uses more memory but is faster to look up colors in larger palettes (it has a maximum size of 256 533 * colors, though, which this class doesn't). 534 */ 535 public static class PaletteFilter extends FloatFilter { 536 public float[] targets; 537 /** 538 * Builds a PaletteFilter with an array of Color objects that this will choose from. The array will be converted 539 * to an array of packed float colors, and not referenced directly. 540 * @param targets an array of libGDX Color objects; must not be null or empty 541 */ 542 public PaletteFilter(final Color[] targets) { 543 this.targets = new float[targets.length]; 544 for (int i = 0; i < targets.length; i++) { 545 this.targets[i] = targets[i].toFloatBits(); 546 } 547 } 548 549 /** 550 * Builds a PaletteFilter with an array of packed float colors that this will choose from. The array will be 551 * referenced directly, not copied, so if you change the contents of targets, it will be reflected here. 552 * @param targets an array or vararg of packed float colors; must not be null or empty 553 */ 554 555 public PaletteFilter(final float... targets) 556 { 557 this.targets = targets; 558 } 559 560 /** 561 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 562 * 563 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 564 * @return a packed float color, as produced by {@link Color#toFloatBits()} 565 */ 566 @Override 567 public float alter(float color) { 568 if(color >= 0f) //if color is halfway-transparent or closer to transparent... 569 return 0f; // return fully transparent 570 int choice = 0, diff = SColor.difference2(targets[0], color); 571 for (int i = 1; i < targets.length; i++) { 572 if(diff != (diff = Math.min(SColor.difference2(targets[i], color), diff))) 573 choice = i; 574 } 575 return targets[choice]; 576 } 577 } 578 579 /** 580 * A FloatFilter that limits the colors it can return to a fixed palette as determined by a {@link PaletteReducer}, 581 * and won't return any colors that are missing from that palette (although it can always return fully-transparent). 582 * This is like {@link PaletteFilter} but trades off memory usage (it uses about 33KB to store a large-ish lookup 583 * table) to improve speed on large palettes. This can't use a palette larger than 256 colors (including transparent 584 * almost always). 585 */ 586 public static class PaletteReducerFilter extends FloatFilter { 587 public PaletteReducer reducer; 588 589 /** 590 * Builds a PaletteReducerFilter that will use the 256-color (including transparent) DawnBringer Aurora palette. 591 */ 592 public PaletteReducerFilter() 593 { 594 reducer = new PaletteReducer(); 595 } 596 597 /** 598 * Builds a PaletteReducerFilter with the given PaletteReducer, which will be referenced without copying. You 599 * can call {@link PaletteReducer#exact(Color[])} or other similar methods before this filter is used to set up 600 * the palette, and this is often done before the PaletteReducer is passed here. 601 * @param palette a PaletteReducer that should have the desired palette set up before this is used 602 */ 603 public PaletteReducerFilter(final PaletteReducer palette) { 604 reducer = palette; 605 } 606 607 /** 608 * Builds a PaletteReducerFilter with an array or vararg of libGDX colors (at most 256 colors, and often 609 * starting with a transparent color) that this will choose from. The array will have its contents read but will 610 * not be held onto, so later changes won't affect it. 611 * @param targets an array or vararg of libGDX colors; must not be null 612 */ 613 614 public PaletteReducerFilter(final Color... targets) 615 { 616 reducer = new PaletteReducer(targets); 617 } 618 619 /** 620 * Takes a packed float color and produces a potentially-different packed float color that this FloatFilter edited. 621 * 622 * @param color a packed float color, as produced by {@link Color#toFloatBits()} 623 * @return a packed float color, as produced by {@link Color#toFloatBits()} 624 */ 625 @Override 626 public float alter(float color) { 627 return reducer.reduceFloat(color); 628 } 629 } 630 631}