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