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.*; 020import java.awt.image.*; 021import java.util.*; 022import com.jhlabs.math.*; 023 024/** 025 * A filter which produces an image with a cellular texture. 026 */ 027public class CellularFilter extends WholeImageFilter implements Function2D, Cloneable { 028 029 protected float scale = 32; 030 protected float stretch = 1.0f; 031 protected float angle = 0.0f; 032 public float amount = 1.0f; 033 public float turbulence = 1.0f; 034 public float gain = 0.5f; 035 public float bias = 0.5f; 036 public float distancePower = 2; 037 public boolean useColor = false; 038 protected Colormap colormap = new Gradient(); 039 protected float[] coefficients = { 1, 0, 0, 0 }; 040 protected float angleCoefficient; 041 protected Random random = new Random(); 042 protected float m00 = 1.0f; 043 protected float m01 = 0.0f; 044 protected float m10 = 0.0f; 045 protected float m11 = 1.0f; 046 protected Point[] results = null; 047 protected float randomness = 0; 048 protected int gridType = HEXAGONAL; 049 private float min; 050 private float max; 051 private static byte[] probabilities; 052 private float gradientCoefficient; 053 054 public final static int RANDOM = 0; 055 public final static int SQUARE = 1; 056 public final static int HEXAGONAL = 2; 057 public final static int OCTAGONAL = 3; 058 public final static int TRIANGULAR = 4; 059 060 public CellularFilter() { 061 results = new Point[3]; 062 for (int j = 0; j < results.length; j++) 063 results[j] = new Point(); 064 if (probabilities == null) { 065 probabilities = new byte[8192]; 066 float factorial = 1; 067 float total = 0; 068 float mean = 2.5f; 069 for (int i = 0; i < 10; i++) { 070 if (i > 1) 071 factorial *= i; 072 float probability = (float)Math.pow(mean, i) * (float)Math.exp(-mean) / factorial; 073 int start = (int)(total * 8192); 074 total += probability; 075 int end = (int)(total * 8192); 076 for (int j = start; j < end; j++) 077 probabilities[j] = (byte)i; 078 } 079 } 080 } 081 082 /** 083 * Specifies the scale of the texture. 084 * @param scale the scale of the texture. 085 * @min-value 1 086 * @max-value 300+ 087 * @see #getScale 088 */ 089 public void setScale(float scale) { 090 this.scale = scale; 091 } 092 093 /** 094 * Returns the scale of the texture. 095 * @return the scale of the texture. 096 * @see #setScale 097 */ 098 public float getScale() { 099 return scale; 100 } 101 102 /** 103 * Specifies the stretch factor of the texture. 104 * @param stretch the stretch factor of the texture. 105 * @min-value 1 106 * @max-value 50+ 107 * @see #getStretch 108 */ 109 public void setStretch(float stretch) { 110 this.stretch = stretch; 111 } 112 113 /** 114 * Returns the stretch factor of the texture. 115 * @return the stretch factor of the texture. 116 * @see #setStretch 117 */ 118 public float getStretch() { 119 return stretch; 120 } 121 122 /** 123 * Specifies the angle of the texture. 124 * @param angle the angle of the texture. 125 * @angle 126 * @see #getAngle 127 */ 128 public void setAngle(float angle) { 129 this.angle = angle; 130 float cos = (float)Math.cos(angle); 131 float sin = (float)Math.sin(angle); 132 m00 = cos; 133 m01 = sin; 134 m10 = -sin; 135 m11 = cos; 136 } 137 138 /** 139 * Returns the angle of the texture. 140 * @return the angle of the texture. 141 * @see #setAngle 142 */ 143 public float getAngle() { 144 return angle; 145 } 146 147 public void setCoefficient(int i, float v) { 148 coefficients[i] = v; 149 } 150 151 public float getCoefficient(int i) { 152 return coefficients[i]; 153 } 154 155 public void setAngleCoefficient(float angleCoefficient) { 156 this.angleCoefficient = angleCoefficient; 157 } 158 159 public float getAngleCoefficient() { 160 return angleCoefficient; 161 } 162 163 public void setGradientCoefficient(float gradientCoefficient) { 164 this.gradientCoefficient = gradientCoefficient; 165 } 166 167 public float getGradientCoefficient() { 168 return gradientCoefficient; 169 } 170 171 public void setF1( float v ) { 172 coefficients[0] = v; 173 } 174 175 public float getF1() { 176 return coefficients[0]; 177 } 178 179 public void setF2( float v ) { 180 coefficients[1] = v; 181 } 182 183 public float getF2() { 184 return coefficients[1]; 185 } 186 187 public void setF3( float v ) { 188 coefficients[2] = v; 189 } 190 191 public float getF3() { 192 return coefficients[2]; 193 } 194 195 public void setF4( float v ) { 196 coefficients[3] = v; 197 } 198 199 public float getF4() { 200 return coefficients[3]; 201 } 202 203 /** 204 * Set the colormap to be used for the filter. 205 * @param colormap the colormap 206 * @see #getColormap 207 */ 208 public void setColormap(Colormap colormap) { 209 this.colormap = colormap; 210 } 211 212 /** 213 * Get the colormap to be used for the filter. 214 * @return the colormap 215 * @see #setColormap 216 */ 217 public Colormap getColormap() { 218 return colormap; 219 } 220 221 public void setRandomness(float randomness) { 222 this.randomness = randomness; 223 } 224 225 public float getRandomness() { 226 return randomness; 227 } 228 229 public void setGridType(int gridType) { 230 this.gridType = gridType; 231 } 232 233 public int getGridType() { 234 return gridType; 235 } 236 237 public void setDistancePower(float distancePower) { 238 this.distancePower = distancePower; 239 } 240 241 public float getDistancePower() { 242 return distancePower; 243 } 244 245 /** 246 * Specifies the turbulence of the texture. 247 * @param turbulence the turbulence of the texture. 248 * @min-value 0 249 * @max-value 1 250 * @see #getTurbulence 251 */ 252 public void setTurbulence(float turbulence) { 253 this.turbulence = turbulence; 254 } 255 256 /** 257 * Returns the turbulence of the effect. 258 * @return the turbulence of the effect. 259 * @see #setTurbulence 260 */ 261 public float getTurbulence() { 262 return turbulence; 263 } 264 265 /** 266 * Set the amount of effect. 267 * @param amount the amount 268 * @min-value 0 269 * @max-value 1 270 * @see #getAmount 271 */ 272 public void setAmount(float amount) { 273 this.amount = amount; 274 } 275 276 /** 277 * Get the amount of texture. 278 * @return the amount 279 * @see #setAmount 280 */ 281 public float getAmount() { 282 return amount; 283 } 284 285 public class Point { 286 public int index; 287 public float x, y; 288 public float dx, dy; 289 public float cubeX, cubeY; 290 public float distance; 291 } 292 293 private float checkCube(float x, float y, int cubeX, int cubeY, Point[] results) { 294 int numPoints; 295 random.setSeed(571*cubeX + 23*cubeY); 296 switch (gridType) { 297 case RANDOM: 298 default: 299 numPoints = probabilities[random.nextInt() & 0x1fff]; 300 break; 301 case SQUARE: 302 numPoints = 1; 303 break; 304 case HEXAGONAL: 305 numPoints = 1; 306 break; 307 case OCTAGONAL: 308 numPoints = 2; 309 break; 310 case TRIANGULAR: 311 numPoints = 2; 312 break; 313 } 314 for (int i = 0; i < numPoints; i++) { 315 float px = 0, py = 0; 316 float weight = 1.0f; 317 switch (gridType) { 318 case RANDOM: 319 px = random.nextFloat(); 320 py = random.nextFloat(); 321 break; 322 case SQUARE: 323 px = py = 0.5f; 324 if (randomness != 0) { 325 px += randomness * (random.nextFloat()-0.5); 326 py += randomness * (random.nextFloat()-0.5); 327 } 328 break; 329 case HEXAGONAL: 330 if ((cubeX & 1) == 0) { 331 px = 0.75f; py = 0; 332 } else { 333 px = 0.75f; py = 0.5f; 334 } 335 if (randomness != 0) { 336 px += randomness * Noise.noise2(271*(cubeX+px), 271*(cubeY+py)); 337 py += randomness * Noise.noise2(271*(cubeX+px)+89, 271*(cubeY+py)+137); 338 } 339 break; 340 case OCTAGONAL: 341 switch (i) { 342 case 0: px = 0.207f; py = 0.207f; break; 343 case 1: px = 0.707f; py = 0.707f; weight = 1.6f; break; 344 } 345 if (randomness != 0) { 346 px += randomness * Noise.noise2(271*(cubeX+px), 271*(cubeY+py)); 347 py += randomness * Noise.noise2(271*(cubeX+px)+89, 271*(cubeY+py)+137); 348 } 349 break; 350 case TRIANGULAR: 351 if ((cubeY & 1) == 0) { 352 if (i == 0) { 353 px = 0.25f; py = 0.35f; 354 } else { 355 px = 0.75f; py = 0.65f; 356 } 357 } else { 358 if (i == 0) { 359 px = 0.75f; py = 0.35f; 360 } else { 361 px = 0.25f; py = 0.65f; 362 } 363 } 364 if (randomness != 0) { 365 px += randomness * Noise.noise2(271*(cubeX+px), 271*(cubeY+py)); 366 py += randomness * Noise.noise2(271*(cubeX+px)+89, 271*(cubeY+py)+137); 367 } 368 break; 369 } 370 float dx = (float)Math.abs(x-px); 371 float dy = (float)Math.abs(y-py); 372 float d; 373 dx *= weight; 374 dy *= weight; 375 if (distancePower == 1.0f) 376 d = dx + dy; 377 else if (distancePower == 2.0f) 378 d = (float)Math.sqrt(dx*dx + dy*dy); 379 else 380 d = (float)Math.pow((float)Math.pow(dx, distancePower) + (float)Math.pow(dy, distancePower), 1/distancePower); 381 382 // Insertion sort the long way round to speed it up a bit 383 if (d < results[0].distance) { 384 Point p = results[2]; 385 results[2] = results[1]; 386 results[1] = results[0]; 387 results[0] = p; 388 p.distance = d; 389 p.dx = dx; 390 p.dy = dy; 391 p.x = cubeX+px; 392 p.y = cubeY+py; 393 } else if (d < results[1].distance) { 394 Point p = results[2]; 395 results[2] = results[1]; 396 results[1] = p; 397 p.distance = d; 398 p.dx = dx; 399 p.dy = dy; 400 p.x = cubeX+px; 401 p.y = cubeY+py; 402 } else if (d < results[2].distance) { 403 Point p = results[2]; 404 p.distance = d; 405 p.dx = dx; 406 p.dy = dy; 407 p.x = cubeX+px; 408 p.y = cubeY+py; 409 } 410 } 411 return results[2].distance; 412 } 413 414 public float evaluate(float x, float y) { 415 for (int j = 0; j < results.length; j++) 416 results[j].distance = Float.POSITIVE_INFINITY; 417 418 int ix = (int)x; 419 int iy = (int)y; 420 float fx = x-ix; 421 float fy = y-iy; 422 423 float d = checkCube(fx, fy, ix, iy, results); 424 if (d > fy) 425 d = checkCube(fx, fy+1, ix, iy-1, results); 426 if (d > 1-fy) 427 d = checkCube(fx, fy-1, ix, iy+1, results); 428 if (d > fx) { 429 checkCube(fx+1, fy, ix-1, iy, results); 430 if (d > fy) 431 d = checkCube(fx+1, fy+1, ix-1, iy-1, results); 432 if (d > 1-fy) 433 d = checkCube(fx+1, fy-1, ix-1, iy+1, results); 434 } 435 if (d > 1-fx) { 436 d = checkCube(fx-1, fy, ix+1, iy, results); 437 if (d > fy) 438 d = checkCube(fx-1, fy+1, ix+1, iy-1, results); 439 if (d > 1-fy) 440 d = checkCube(fx-1, fy-1, ix+1, iy+1, results); 441 } 442 443 float t = 0; 444 for (int i = 0; i < 3; i++) 445 t += coefficients[i] * results[i].distance; 446 if (angleCoefficient != 0) { 447 float angle = (float)Math.atan2(y-results[0].y, x-results[0].x); 448 if (angle < 0) 449 angle += 2*(float)Math.PI; 450 angle /= 4*(float)Math.PI; 451 t += angleCoefficient * angle; 452 } 453 if (gradientCoefficient != 0) { 454 float a = 1/(results[0].dy+results[0].dx); 455 t += gradientCoefficient * a; 456 } 457 return t; 458 } 459 460 public float turbulence2(float x, float y, float freq) { 461 float t = 0.0f; 462 463 for (float f = 1.0f; f <= freq; f *= 2) 464 t += evaluate(f*x, f*y) / f; 465 return t; 466 } 467 468 public int getPixel(int x, int y, int[] inPixels, int width, int height) { 469 float nx = m00*x + m01*y; 470 float ny = m10*x + m11*y; 471 nx /= scale; 472 ny /= scale * stretch; 473 nx += 1000; 474 ny += 1000; // Reduce artifacts around 0,0 475 float f = turbulence == 1.0f ? evaluate(nx, ny) : turbulence2(nx, ny, turbulence); 476 // Normalize to 0..1 477// f = (f-min)/(max-min); 478 f *= 2; 479 f *= amount; 480 int a = 0xff000000; 481 int v; 482 if (colormap != null) { 483 v = colormap.getColor(f); 484 if (useColor) { 485 int srcx = ImageMath.clamp((int)((results[0].x-1000)*scale), 0, width-1); 486 int srcy = ImageMath.clamp((int)((results[0].y-1000)*scale), 0, height-1); 487 v = inPixels[srcy * width + srcx]; 488 f = (results[1].distance - results[0].distance) / (results[1].distance + results[0].distance); 489 f = ImageMath.smoothStep(coefficients[1], coefficients[0], f); 490 v = ImageMath.mixColors(f, 0xff000000, v); 491 } 492 return v; 493 } else { 494 v = PixelUtils.clamp((int)(f*255)); 495 int r = v << 16; 496 int g = v << 8; 497 int b = v; 498 return a|r|g|b; 499 } 500 } 501 502 protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { 503// float[] minmax = Noise.findRange(this, null); 504// min = minmax[0]; 505// max = minmax[1]; 506 507 int index = 0; 508 int[] outPixels = new int[width * height]; 509 510 for (int y = 0; y < height; y++) { 511 for (int x = 0; x < width; x++) { 512 outPixels[index++] = getPixel(x, y, inPixels, width, height); 513 } 514 } 515 return outPixels; 516 } 517 518 public Object clone() { 519 CellularFilter f = (CellularFilter)super.clone(); 520 f.coefficients = (float[])coefficients.clone(); 521 f.results = (Point[])results.clone(); 522 f.random = new Random(); 523// if (colormap != null) 524// f.colormap = (Colormap)colormap.clone(); 525 return f; 526 } 527 528 public String toString() { 529 return "Texture/Cellular..."; 530 } 531 532}