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.Rectangle; 036import java.awt.image.BufferedImage; 037import java.util.Random; 038 039import lucee.runtime.engine.ThreadLocalPageContext; 040import lucee.runtime.exp.FunctionException; 041import lucee.runtime.exp.PageException; 042import lucee.runtime.img.ImageUtil; 043import lucee.runtime.img.math.Noise; 044import lucee.runtime.type.KeyImpl; 045import lucee.runtime.type.Struct; 046import lucee.runtime.type.util.CollectionUtil; 047/** 048 * A filter which simulates underwater caustics. This can be animated to get a bottom-of-the-swimming-pool effect. 049 */ 050public class CausticsFilter extends WholeImageFilter implements DynFiltering { 051 052 private float scale = 32; 053 //private float angle = 0.0f; 054 private int brightness = 10; 055 private float amount = 1.0f; 056 private float turbulence = 1.0f; 057 private float dispersion = 0.0f; 058 private float time = 0.0f; 059 private int samples = 2; 060 private int bgColor = 0xff799fff; 061 062 private float s, c; 063 064 public CausticsFilter() { 065 } 066 067 /** 068 * Specifies the scale of the texture. 069 * @param scale the scale of the texture. 070 * @min-value 1 071 * @max-value 300+ 072 * @see #getScale 073 */ 074 public void setScale(float scale) { 075 this.scale = scale; 076 } 077 078 /** 079 * Returns the scale of the texture. 080 * @return the scale of the texture. 081 * @see #setScale 082 */ 083 public float getScale() { 084 return scale; 085 } 086 087 /** 088 * Set the brightness. 089 * @param brightness the brightness. 090 * @min-value 0 091 * @max-value 1 092 * @see #getBrightness 093 */ 094 public void setBrightness(int brightness) { 095 this.brightness = brightness; 096 } 097 098 /** 099 * Get the brightness. 100 * @return the brightness. 101 * @see #setBrightness 102 */ 103 public int getBrightness() { 104 return brightness; 105 } 106 107 /** 108 * Specifies the turbulence of the texture. 109 * @param turbulence the turbulence of the texture. 110 * @min-value 0 111 * @max-value 1 112 * @see #getTurbulence 113 */ 114 public void setTurbulence(float turbulence) { 115 this.turbulence = turbulence; 116 } 117 118 /** 119 * Returns the turbulence of the effect. 120 * @return the turbulence of the effect. 121 * @see #setTurbulence 122 */ 123 public float getTurbulence() { 124 return turbulence; 125 } 126 127 /** 128 * Set the amount of effect. 129 * @param amount the amount 130 * @min-value 0 131 * @max-value 1 132 * @see #getAmount 133 */ 134 public void setAmount(float amount) { 135 this.amount = amount; 136 } 137 138 /** 139 * Get the amount of effect. 140 * @return the amount 141 * @see #setAmount 142 */ 143 public float getAmount() { 144 return amount; 145 } 146 147 /** 148 * Set the dispersion. 149 * @param dispersion the dispersion 150 * @min-value 0 151 * @max-value 1 152 * @see #getDispersion 153 */ 154 public void setDispersion(float dispersion) { 155 this.dispersion = dispersion; 156 } 157 158 /** 159 * Get the dispersion. 160 * @return the dispersion 161 * @see #setDispersion 162 */ 163 public float getDispersion() { 164 return dispersion; 165 } 166 167 /** 168 * Set the time. Use this to animate the effect. 169 * @param time the time 170 * @see #getTime 171 */ 172 public void setTime(float time) { 173 this.time = time; 174 } 175 176 /** 177 * Set the time. 178 * @return the time 179 * @see #setTime 180 */ 181 public float getTime() { 182 return time; 183 } 184 185 /** 186 * Set the number of samples per pixel. More samples means better quality, but slower rendering. 187 * @param samples the number of samples 188 * @see #getSamples 189 */ 190 public void setSamples(int samples) { 191 this.samples = samples; 192 } 193 194 /** 195 * Get the number of samples per pixel. 196 * @return the number of samples 197 * @see #setSamples 198 */ 199 public int getSamples() { 200 return samples; 201 } 202 203 /** 204 * Set the background color. 205 * @param c the color 206 * @see #getBgColor 207 */ 208 public void setBgColor(int c) { 209 bgColor = c; 210 } 211 212 /** 213 * Get the background color. 214 * @return the color 215 * @see #setBgColor 216 */ 217 public int getBgColor() { 218 return bgColor; 219 } 220 221 protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { 222 Random random = new Random(0); 223 224 s = (float)Math.sin(0.1); 225 c = (float)Math.cos(0.1); 226 227 //int srcWidth = originalSpace.width; 228 //int srcHeight = originalSpace.height; 229 int outWidth = transformedSpace.width; 230 int outHeight = transformedSpace.height; 231 int index = 0; 232 int[] pixels = new int[outWidth * outHeight]; 233 234 for (int y = 0; y < outHeight; y++) { 235 for (int x = 0; x < outWidth; x++) { 236 pixels[index++] = bgColor; 237 } 238 } 239 240 int v = brightness/samples; 241 if (v == 0) 242 v = 1; 243 244 float rs = 1.0f/scale; 245 float d = 0.95f; 246 index = 0; 247 for (int y = 0; y < outHeight; y++) { 248 for (int x = 0; x < outWidth; x++) { 249 for (int s = 0; s < samples; s++) { 250 float sx = x+random.nextFloat(); 251 float sy = y+random.nextFloat(); 252 float nx = sx*rs; 253 float ny = sy*rs; 254 float xDisplacement, yDisplacement; 255 float focus = 0.1f+amount; 256 xDisplacement = evaluate(nx-d, ny) - evaluate(nx+d, ny); 257 yDisplacement = evaluate(nx, ny+d) - evaluate(nx, ny-d); 258 259 if (dispersion > 0) { 260 for (int c = 0; c < 3; c++) { 261 float ca = (1+c*dispersion); 262 float srcX = sx + scale*focus * xDisplacement*ca; 263 float srcY = sy + scale*focus * yDisplacement*ca; 264 265 if (srcX < 0 || srcX >= outWidth-1 || srcY < 0 || srcY >= outHeight-1) { 266 } else { 267 int i = ((int)srcY)*outWidth+(int)srcX; 268 int rgb = pixels[i]; 269 int r = (rgb >> 16) & 0xff; 270 int g = (rgb >> 8) & 0xff; 271 int b = rgb & 0xff; 272 if (c == 2) 273 r += v; 274 else if (c == 1) 275 g += v; 276 else 277 b += v; 278 if (r > 255) 279 r = 255; 280 if (g > 255) 281 g = 255; 282 if (b > 255) 283 b = 255; 284 pixels[i] = 0xff000000 | (r << 16) | (g << 8) | b; 285 } 286 } 287 } else { 288 float srcX = sx + scale*focus * xDisplacement; 289 float srcY = sy + scale*focus * yDisplacement; 290 291 if (srcX < 0 || srcX >= outWidth-1 || srcY < 0 || srcY >= outHeight-1) { 292 } else { 293 int i = ((int)srcY)*outWidth+(int)srcX; 294 int rgb = pixels[i]; 295 int r = (rgb >> 16) & 0xff; 296 int g = (rgb >> 8) & 0xff; 297 int b = rgb & 0xff; 298 r += v; 299 g += v; 300 b += v; 301 if (r > 255) 302 r = 255; 303 if (g > 255) 304 g = 255; 305 if (b > 255) 306 b = 255; 307 pixels[i] = 0xff000000 | (r << 16) | (g << 8) | b; 308 } 309 } 310 } 311 } 312 } 313 return pixels; 314 } 315 316 /*private static int add(int rgb, float brightness) { 317 int r = (rgb >> 16) & 0xff; 318 int g = (rgb >> 8) & 0xff; 319 int b = rgb & 0xff; 320 r += brightness; 321 g += brightness; 322 b += brightness; 323 if (r > 255) 324 r = 255; 325 if (g > 255) 326 g = 255; 327 if (b > 255) 328 b = 255; 329 return 0xff000000 | (r << 16) | (g << 8) | b; 330 }*/ 331 332 /*private static int add(int rgb, float brightness, int c) { 333 int r = (rgb >> 16) & 0xff; 334 int g = (rgb >> 8) & 0xff; 335 int b = rgb & 0xff; 336 if (c == 2) 337 r += brightness; 338 else if (c == 1) 339 g += brightness; 340 else 341 b += brightness; 342 if (r > 255) 343 r = 255; 344 if (g > 255) 345 g = 255; 346 if (b > 255) 347 b = 255; 348 return 0xff000000 | (r << 16) | (g << 8) | b; 349 }*/ 350 351 private static float turbulence2(float x, float y, float time, float octaves) { 352 float value = 0.0f; 353 float remainder; 354 float lacunarity = 2.0f; 355 float f = 1.0f; 356 int i; 357 358 // to prevent "cascading" effects 359 x += 371; 360 y += 529; 361 362 for (i = 0; i < (int)octaves; i++) { 363 value += Noise.noise3(x, y, time) / f; 364 x *= lacunarity; 365 y *= lacunarity; 366 f *= 2; 367 } 368 369 remainder = octaves - (int)octaves; 370 if (remainder != 0) 371 value += remainder * Noise.noise3(x, y, time) / f; 372 373 return value; 374 } 375 376 private float evaluate(float x, float y) { 377 float xt = s*x + c*time; 378 float tt = c*x - c*time; 379 float f = turbulence == 0.0 ? Noise.noise3(xt, y, tt) : turbulence2(xt, y, tt, turbulence); 380 return f; 381 } 382 383 public String toString() { 384 return "Texture/Caustics..."; 385 } 386 387 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 388 Object o; 389 if((o=parameters.removeEL(KeyImpl.init("Amount")))!=null)setAmount(ImageFilterUtil.toFloatValue(o,"Amount")); 390 if((o=parameters.removeEL(KeyImpl.init("Brightness")))!=null)setBrightness(ImageFilterUtil.toIntValue(o,"Brightness")); 391 if((o=parameters.removeEL(KeyImpl.init("Turbulence")))!=null)setTurbulence(ImageFilterUtil.toFloatValue(o,"Turbulence")); 392 if((o=parameters.removeEL(KeyImpl.init("Dispersion")))!=null)setDispersion(ImageFilterUtil.toFloatValue(o,"Dispersion")); 393 if((o=parameters.removeEL(KeyImpl.init("BgColor")))!=null)setBgColor(ImageFilterUtil.toColorRGB(o,"BgColor")); 394 if((o=parameters.removeEL(KeyImpl.init("Time")))!=null)setTime(ImageFilterUtil.toFloatValue(o,"Time")); 395 if((o=parameters.removeEL(KeyImpl.init("Scale")))!=null)setScale(ImageFilterUtil.toFloatValue(o,"Scale")); 396 if((o=parameters.removeEL(KeyImpl.init("Samples")))!=null)setSamples(ImageFilterUtil.toIntValue(o,"Samples")); 397 398 // check for arguments not supported 399 if(parameters.size()>0) { 400 throw new FunctionException(ThreadLocalPageContext.get(), "ImageFilter", 3, "parameters", "the parameter"+(parameters.size()>1?"s":"")+" ["+CollectionUtil.getKeyList(parameters,", ")+"] "+(parameters.size()>1?"are":"is")+" not allowed, only the following parameters are supported [Amount, Brightness, Turbulence, Dispersion, BgColor, Time, Scale, Samples]"); 401 } 402 403 return filter(src, dst); 404 } 405}