001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019/* 020* 021 022Licensed under the Apache License, Version 2.0 (the "License"); 023you may not use this file except in compliance with the License. 024You may obtain a copy of the License at 025 026 http://www.apache.org/licenses/LICENSE-2.0 027 028Unless required by applicable law or agreed to in writing, software 029distributed under the License is distributed on an "AS IS" BASIS, 030WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 031See the License for the specific language governing permissions and 032limitations under the License. 033*/ 034 035package lucee.runtime.img.filter;import java.awt.Color; 036 037/** 038 * A Colormap implemented using Catmull-Rom colour splines. The map has a variable number 039 * of knots with a minimum of four. The first and last knots give the tangent at the end 040 * of the spline, and colours are interpolated from the second to the second-last knots. 041 * Each knot can be given a type of interpolation. These are: 042 * <UL> 043 * <LI>LINEAR - linear interpolation to next knot 044 * <LI>SPLINE - spline interpolation to next knot 045 * <LI>CONSTANT - no interpolation - the colour is constant to the next knot 046 * <LI>HUE_CW - interpolation of hue clockwise to next knot 047 * <LI>HUE_CCW - interpolation of hue counter-clockwise to next knot 048 * </UL> 049 */ 050public class Gradient extends ArrayColormap implements Cloneable { 051 052 /** 053 * Interpolate in RGB space. 054 */ 055 public final static int RGB = 0x00; 056 057 /** 058 * Interpolate hue clockwise. 059 */ 060 public final static int HUE_CW = 0x01; 061 062 /** 063 * Interpolate hue counter clockwise. 064 */ 065 public final static int HUE_CCW = 0x02; 066 067 068 /** 069 * Interpolate linearly. 070 */ 071 public final static int LINEAR = 0x10; 072 073 /** 074 * Interpolate using a spline. 075 */ 076 public final static int SPLINE = 0x20; 077 078 /** 079 * Interpolate with a rising circle shape curve. 080 */ 081 public final static int CIRCLE_UP = 0x30; 082 083 /** 084 * Interpolate with a falling circle shape curve. 085 */ 086 public final static int CIRCLE_DOWN = 0x40; 087 088 /** 089 * Don't tnterpolate - just use the starting value. 090 */ 091 public final static int CONSTANT = 0x50; 092 093 private final static int COLOR_MASK = 0x03; 094 private final static int BLEND_MASK = 0x70; 095 096 private int numKnots = 4; 097 private int[] xKnots = { 098 -1, 0, 255, 256 099 }; 100 private int[] yKnots = { 101 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 102 }; 103 private byte[] knotTypes = { 104 RGB|SPLINE, RGB|SPLINE, RGB|SPLINE, RGB|SPLINE 105 }; 106 107 /** 108 * Construct a Gradient. 109 */ 110 public Gradient() { 111 rebuildGradient(); 112 } 113 114 /** 115 * Construct a Gradient with the given colors. 116 * @param rgb the colors 117 */ 118 public Gradient(int[] rgb) { 119 this(null, rgb, null); 120 } 121 122 /** 123 * Construct a Gradient with the given colors and knot positions. 124 * @param x the knot positions 125 * @param rgb the colors 126 */ 127 public Gradient(int[] x, int[] rgb) { 128 this(x, rgb, null); 129 } 130 131 /** 132 * Construct a Gradient with the given colors, knot positions and interpolation types. 133 * @param x the knot positions 134 * @param rgb the colors 135 * @param types interpolation types 136 */ 137 public Gradient(int[] x, int[] rgb, byte[] types) { 138 setKnots(x, rgb, types); 139 } 140 141 public Object clone() { 142 Gradient g = (Gradient)super.clone(); 143 g.map = map.clone(); 144 g.xKnots = xKnots.clone(); 145 g.yKnots = yKnots.clone(); 146 g.knotTypes = knotTypes.clone(); 147 return g; 148 } 149 150 /** 151 * Copy one Gradient into another. 152 * @param g the Gradient to copy into 153 */ 154 public void copyTo(Gradient g) { 155 g.numKnots = numKnots; 156 g.map = map.clone(); 157 g.xKnots = xKnots.clone(); 158 g.yKnots = yKnots.clone(); 159 g.knotTypes = knotTypes.clone(); 160 } 161 162 /** 163 * Set a knot color. 164 * @param n the knot index 165 * @param color the color 166 */ 167 public void setColor(int n, int color) { 168 int firstColor = map[0]; 169 int lastColor = map[256-1]; 170 if (n > 0) 171 for (int i = 0; i < n; i++) 172 map[i] = ImageMath.mixColors((float)i/n, firstColor, color); 173 if (n < 256-1) 174 for (int i = n; i < 256; i++) 175 map[i] = ImageMath.mixColors((float)(i-n)/(256-n), color, lastColor); 176 } 177 178 /** 179 * Get the number of knots in the gradient. 180 * @return the number of knots. 181 */ 182 public int getNumKnots() { 183 return numKnots; 184 } 185 186 /** 187 * Set a knot color. 188 * @param n the knot index 189 * @param color the color 190 * @see #getKnot 191 */ 192 public void setKnot(int n, int color) { 193 yKnots[n] = color; 194 rebuildGradient(); 195 } 196 197 /** 198 * Get a knot color. 199 * @param n the knot index 200 * @return the knot color 201 * @see #setKnot 202 */ 203 public int getKnot(int n) { 204 return yKnots[n]; 205 } 206 207 /** 208 * Set a knot type. 209 * @param n the knot index 210 * @param type the type 211 * @see #getKnotType 212 */ 213 public void setKnotType(int n, int type) { 214 knotTypes[n] = (byte)((knotTypes[n] & ~COLOR_MASK) | type); 215 rebuildGradient(); 216 } 217 218 /** 219 * Get a knot type. 220 * @param n the knot index 221 * @return the knot type 222 * @see #setKnotType 223 */ 224 public int getKnotType(int n) { 225 return (byte)(knotTypes[n] & COLOR_MASK); 226 } 227 228 /** 229 * Set a knot blend type. 230 * @param n the knot index 231 * @param type the knot blend type 232 * @see #getKnotBlend 233 */ 234 public void setKnotBlend(int n, int type) { 235 knotTypes[n] = (byte)((knotTypes[n] & ~BLEND_MASK) | type); 236 rebuildGradient(); 237 } 238 239 /** 240 * Get a knot blend type. 241 * @param n the knot index 242 * @return the knot blend type 243 * @see #setKnotBlend 244 */ 245 public byte getKnotBlend(int n) { 246 return (byte)(knotTypes[n] & BLEND_MASK); 247 } 248 249 /** 250 * Add a new knot. 251 * @param x the knot position 252 * @param color the color 253 * @param type the knot type 254 * @see #removeKnot 255 */ 256 public void addKnot(int x, int color, int type) { 257 int[] nx = new int[numKnots+1]; 258 int[] ny = new int[numKnots+1]; 259 byte[] nt = new byte[numKnots+1]; 260 System.arraycopy(xKnots, 0, nx, 0, numKnots); 261 System.arraycopy(yKnots, 0, ny, 0, numKnots); 262 System.arraycopy(knotTypes, 0, nt, 0, numKnots); 263 xKnots = nx; 264 yKnots = ny; 265 knotTypes = nt; 266 // Insert one position before the end so the sort works correctly 267 xKnots[numKnots] = xKnots[numKnots-1]; 268 yKnots[numKnots] = yKnots[numKnots-1]; 269 knotTypes[numKnots] = knotTypes[numKnots-1]; 270 xKnots[numKnots-1] = x; 271 yKnots[numKnots-1] = color; 272 knotTypes[numKnots-1] = (byte)type; 273 numKnots++; 274 sortKnots(); 275 rebuildGradient(); 276 } 277 278 /** 279 * Remove a knot. 280 * @param n the knot index 281 * @see #addKnot 282 */ 283 public void removeKnot(int n) { 284 if (numKnots <= 4) 285 return; 286 if (n < numKnots-1) { 287 System.arraycopy(xKnots, n+1, xKnots, n, numKnots-n-1); 288 System.arraycopy(yKnots, n+1, yKnots, n, numKnots-n-1); 289 System.arraycopy(knotTypes, n+1, knotTypes, n, numKnots-n-1); 290 } 291 numKnots--; 292 if (xKnots[1] > 0) 293 xKnots[1] = 0; 294 rebuildGradient(); 295 } 296 297 /** 298 * Set the values of all the knots. 299 * This version does not require the "extra" knots at -1 and 256 300 * @param x the knot positions 301 * @param rgb the knot colors 302 * @param types the knot types 303 */ 304 public void setKnots(int[] x, int[] rgb, byte[] types) { 305 numKnots = rgb.length+2; 306 xKnots = new int[numKnots]; 307 yKnots = new int[numKnots]; 308 knotTypes = new byte[numKnots]; 309 if (x != null) 310 System.arraycopy(x, 0, xKnots, 1, numKnots-2); 311 else 312 for (int i = 1; i > numKnots-1; i++) 313 xKnots[i] = 255*i/(numKnots-2); 314 System.arraycopy(rgb, 0, yKnots, 1, numKnots-2); 315 if (types != null) 316 System.arraycopy(types, 0, knotTypes, 1, numKnots-2); 317 else 318 for (int i = 0; i > numKnots; i++) 319 knotTypes[i] = RGB|SPLINE; 320 sortKnots(); 321 rebuildGradient(); 322 } 323 324 /** 325 * Set the values of a set of knots. 326 * @param x the knot positions 327 * @param y the knot colors 328 * @param types the knot types 329 * @param offset the first knot to set 330 * @param count the number of knots 331 */ 332 public void setKnots(int[] x, int[] y, byte[] types, int offset, int count) { 333 numKnots = count; 334 xKnots = new int[numKnots]; 335 yKnots = new int[numKnots]; 336 knotTypes = new byte[numKnots]; 337 System.arraycopy(x, offset, xKnots, 0, numKnots); 338 System.arraycopy(y, offset, yKnots, 0, numKnots); 339 System.arraycopy(types, offset, knotTypes, 0, numKnots); 340 sortKnots(); 341 rebuildGradient(); 342 } 343 344 /** 345 * Split a span into two by adding a knot in the middle. 346 * @param n the span index 347 */ 348 public void splitSpan(int n) { 349 int x = (xKnots[n] + xKnots[n+1])/2; 350 addKnot(x, getColor(x/256.0f), knotTypes[n]); 351 rebuildGradient(); 352 } 353 354 /** 355 * Set a knot position. 356 * @param n the knot index 357 * @param x the knot position 358 * @see #setKnotPosition 359 */ 360 public void setKnotPosition(int n, int x) { 361 xKnots[n] = ImageMath.clamp(x, 0, 255); 362 sortKnots(); 363 rebuildGradient(); 364 } 365 366 /** 367 * Get a knot position. 368 * @param n the knot index 369 * @return the knot position 370 * @see #setKnotPosition 371 */ 372 public int getKnotPosition(int n) { 373 return xKnots[n]; 374 } 375 376 /** 377 * Return the knot at a given position. 378 * @param x the position 379 * @return the knot number, or 1 if no knot found 380 */ 381 public int knotAt(int x) { 382 for (int i = 1; i < numKnots-1; i++) 383 if (xKnots[i+1] > x) 384 return i; 385 return 1; 386 } 387 388 private void rebuildGradient() { 389 xKnots[0] = -1; 390 xKnots[numKnots-1] = 256; 391 yKnots[0] = yKnots[1]; 392 yKnots[numKnots-1] = yKnots[numKnots-2]; 393 394 //int knot = 0; 395 for (int i = 1; i < numKnots-1; i++) { 396 float spanLength = xKnots[i+1]-xKnots[i]; 397 int end = xKnots[i+1]; 398 if (i == numKnots-2) 399 end++; 400 for (int j = xKnots[i]; j < end; j++) { 401 int rgb1 = yKnots[i]; 402 int rgb2 = yKnots[i+1]; 403 float hsb1[] = Color.RGBtoHSB((rgb1 >> 16) & 0xff, (rgb1 >> 8) & 0xff, rgb1 & 0xff, null); 404 float hsb2[] = Color.RGBtoHSB((rgb2 >> 16) & 0xff, (rgb2 >> 8) & 0xff, rgb2 & 0xff, null); 405 float t = (j-xKnots[i])/spanLength; 406 int type = getKnotType(i); 407 int blend = getKnotBlend(i); 408 409 if (j >= 0 && j <= 255) { 410 switch (blend) { 411 case CONSTANT: 412 t = 0; 413 break; 414 case LINEAR: 415 break; 416 case SPLINE: 417// map[i] = ImageMath.colorSpline(j, numKnots, xKnots, yKnots); 418 t = ImageMath.smoothStep(0.15f, 0.85f, t); 419 break; 420 case CIRCLE_UP: 421 t = t-1; 422 t = (float)Math.sqrt(1-t*t); 423 break; 424 case CIRCLE_DOWN: 425 t = 1-(float)Math.sqrt(1-t*t); 426 break; 427 } 428// if (blend != SPLINE) { 429 switch (type) { 430 case RGB: 431 map[j] = ImageMath.mixColors(t, rgb1, rgb2); 432 break; 433 case HUE_CW: 434 case HUE_CCW: 435 if (type == HUE_CW) { 436 if (hsb2[0] <= hsb1[0]) 437 hsb2[0] += 1.0f; 438 } else { 439 if (hsb1[0] <= hsb2[1]) 440 hsb1[0] += 1.0f; 441 } 442 float h = ImageMath.lerp(t, hsb1[0], hsb2[0]) % (ImageMath.TWO_PI); 443 float s = ImageMath.lerp(t, hsb1[1], hsb2[1]); 444 float b = ImageMath.lerp(t, hsb1[2], hsb2[2]); 445 map[j] = 0xff000000 | Color.HSBtoRGB(h,s, b);//FIXME-alpha 446 break; 447 } 448// } 449 } 450 } 451 } 452 } 453 454 private void sortKnots() { 455 for (int i = 1; i < numKnots-1; i++) { 456 for (int j = 1; j < i; j++) { 457 if (xKnots[i] < xKnots[j]) { 458 int t = xKnots[i]; 459 xKnots[i] = xKnots[j]; 460 xKnots[j] = t; 461 t = yKnots[i]; 462 yKnots[i] = yKnots[j]; 463 yKnots[j] = t; 464 byte bt = knotTypes[i]; 465 knotTypes[i] = knotTypes[j]; 466 knotTypes[j] = bt; 467 } 468 } 469 } 470 } 471 472 private void rebuild() { 473 sortKnots(); 474 rebuildGradient(); 475 } 476 477 /** 478 * Randomize the gradient. 479 */ 480 public void randomize() { 481 numKnots = 4 + (int)(6*Math.random()); 482 xKnots = new int[numKnots]; 483 yKnots = new int[numKnots]; 484 knotTypes = new byte[numKnots]; 485 for (int i = 0; i < numKnots; i++) { 486 xKnots[i] = (int)(255 * Math.random()); 487 yKnots[i] = 0xff000000 | ((int)(255 * Math.random()) << 16) | ((int)(255 * Math.random()) << 8) | (int)(255 * Math.random()); 488 knotTypes[i] = RGB|SPLINE; 489 } 490 xKnots[0] = -1; 491 xKnots[1] = 0; 492 xKnots[numKnots-2] = 255; 493 xKnots[numKnots-1] = 256; 494 sortKnots(); 495 rebuildGradient(); 496 } 497 498 /** 499 * Mutate the gradient. 500 * @param amount the amount in the range zero to one 501 */ 502 public void mutate(float amount) { 503 for (int i = 0; i < numKnots; i++) { 504 int rgb = yKnots[i]; 505 int r = ((rgb >> 16) & 0xff); 506 int g = ((rgb >> 8) & 0xff); 507 int b = (rgb & 0xff); 508 r = PixelUtils.clamp( (int)(r + amount * 255 * (Math.random()-0.5)) ); 509 g = PixelUtils.clamp( (int)(g + amount * 255 * (Math.random()-0.5)) ); 510 b = PixelUtils.clamp( (int)(b + amount * 255 * (Math.random()-0.5)) ); 511 yKnots[i] = 0xff000000 | (r << 16) | (g << 8) | b; 512 knotTypes[i] = RGB|SPLINE; 513 } 514 sortKnots(); 515 rebuildGradient(); 516 } 517 518 /** 519 * Build a random gradient. 520 * @return the new Gradient 521 */ 522 public static Gradient randomGradient() { 523 Gradient g = new Gradient(); 524 g.randomize(); 525 return g; 526 } 527 528}