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.Graphics2D; 036import java.awt.Toolkit; 037import java.awt.image.BufferedImage; 038import java.util.Random; 039 040import lucee.runtime.engine.ThreadLocalPageContext; 041import lucee.runtime.exp.FunctionException; 042import lucee.runtime.exp.PageException; 043import lucee.runtime.img.ImageUtil; 044import lucee.runtime.img.math.Noise; 045import lucee.runtime.type.KeyImpl; 046import lucee.runtime.type.Struct; 047import lucee.runtime.type.util.CollectionUtil; 048 049public class SkyFilter extends PointFilter implements DynFiltering { 050 051 private float scale = 0.1f; 052 private float stretch = 1.0f; 053 private float angle = 0.0f; 054 private float amount = 1.0f; 055 private float H = 1.0f; 056 private float octaves = 8.0f; 057 private float lacunarity = 2.0f; 058 private float gain = 1.0f; 059 private float bias = 0.6f; 060 private int operation; 061 //private float min; 062 //private float max; 063 //private boolean ridged; 064 //private FBM fBm; 065 protected Random random = new Random(); 066 //private Function2D basis; 067 068 private float cloudCover = 0.5f; 069 private float cloudSharpness = 0.5f; 070 private float time = 0.3f; 071 private float glow = 0.5f; 072 private float glowFalloff = 0.5f; 073 private float haziness = 0.96f; 074 private float t = 0.0f; 075 //private float sunRadius = 10f; 076 private int sunColor = 0xffffffff; 077 private float sunR, sunG, sunB; 078 private float sunAzimuth = 0.5f; 079 private float sunElevation = 0.5f; 080 private float windSpeed = 0.0f; 081 082 private float cameraAzimuth = 0.0f; 083 private float cameraElevation = 0.0f; 084 private float fov = 1.0f; 085 086 private float[] exponents; 087 private float[] tan; 088 private BufferedImage skyColors; 089 //private int[] skyPixels; 090 091 private final static float r255 = 1.0f/255.0f; 092 093 private float width, height; 094 095 public SkyFilter() { 096 if ( skyColors == null ) { 097 skyColors = ImageUtils.createImage( Toolkit.getDefaultToolkit().getImage( getClass().getResource("SkyColors.png") ).getSource() ); 098 } 099 } 100 101 public void setAmount(float amount) { 102 this.amount = amount; 103 } 104 105 public float getAmount() { 106 return amount; 107 } 108 109 public void setOperation(int operation) { 110 this.operation = operation; 111 } 112 113 public int getOperation() { 114 return operation; 115 } 116 117 public void setScale(float scale) { 118 this.scale = scale; 119 } 120 121 public float getScale() { 122 return scale; 123 } 124 125 public void setStretch(float stretch) { 126 this.stretch = stretch; 127 } 128 129 public float getStretch() { 130 return stretch; 131 } 132 133 public void setT(float t) { 134 this.t = t; 135 } 136 137 public float getT() { 138 return t; 139 } 140 141 public void setFOV(float fov) { 142 this.fov = fov; 143 } 144 145 public float getFOV() { 146 return fov; 147 } 148 149 public void setCloudCover(float cloudCover) { 150 this.cloudCover = cloudCover; 151 } 152 153 public float getCloudCover() { 154 return cloudCover; 155 } 156 157 public void setCloudSharpness(float cloudSharpness) { 158 this.cloudSharpness = cloudSharpness; 159 } 160 161 public float getCloudSharpness() { 162 return cloudSharpness; 163 } 164 165 public void setTime(float time) { 166 this.time = time; 167 } 168 169 public float getTime() { 170 return time; 171 } 172 173 public void setGlow(float glow) { 174 this.glow = glow; 175 } 176 177 public float getGlow() { 178 return glow; 179 } 180 181 public void setGlowFalloff(float glowFalloff) { 182 this.glowFalloff = glowFalloff; 183 } 184 185 public float getGlowFalloff() { 186 return glowFalloff; 187 } 188 189 public void setAngle(float angle) { 190 this.angle = angle; 191 } 192 193 public float getAngle() { 194 return angle; 195 } 196 197 public void setOctaves(float octaves) { 198 this.octaves = octaves; 199 } 200 201 public float getOctaves() { 202 return octaves; 203 } 204 205 public void setH(float H) { 206 this.H = H; 207 } 208 209 public float getH() { 210 return H; 211 } 212 213 public void setLacunarity(float lacunarity) { 214 this.lacunarity = lacunarity; 215 } 216 217 public float getLacunarity() { 218 return lacunarity; 219 } 220 221 public void setGain(float gain) { 222 this.gain = gain; 223 } 224 225 public float getGain() { 226 return gain; 227 } 228 229 public void setBias(float bias) { 230 this.bias = bias; 231 } 232 233 public float getBias() { 234 return bias; 235 } 236 237 public void setHaziness(float haziness) { 238 this.haziness = haziness; 239 } 240 241 public float getHaziness() { 242 return haziness; 243 } 244 245 public void setSunElevation(float sunElevation) { 246 this.sunElevation = sunElevation; 247 } 248 249 public float getSunElevation() { 250 return sunElevation; 251 } 252 253 public void setSunAzimuth(float sunAzimuth) { 254 this.sunAzimuth = sunAzimuth; 255 } 256 257 public float getSunAzimuth() { 258 return sunAzimuth; 259 } 260 261 public void setSunColor(int sunColor) { 262 this.sunColor = sunColor; 263 } 264 265 public int getSunColor() { 266 return sunColor; 267 } 268 269 public void setCameraElevation(float cameraElevation) { 270 this.cameraElevation = cameraElevation; 271 } 272 273 public float getCameraElevation() { 274 return cameraElevation; 275 } 276 277 public void setCameraAzimuth(float cameraAzimuth) { 278 this.cameraAzimuth = cameraAzimuth; 279 } 280 281 public float getCameraAzimuth() { 282 return cameraAzimuth; 283 } 284 285 public void setWindSpeed(float windSpeed) { 286 this.windSpeed = windSpeed; 287 } 288 289 public float getWindSpeed() { 290 return windSpeed; 291 } 292 293float mn, mx; 294 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 295long start = System.currentTimeMillis(); 296 sunR = ((sunColor >> 16) & 0xff) * r255; 297 sunG = ((sunColor >> 8) & 0xff) * r255; 298 sunB = (sunColor & 0xff) * r255; 299 300mn = 10000; 301mx = -10000; 302 exponents = new float[(int)octaves+1]; 303 //float frequency = 1.0f; 304 for (int i = 0; i <= (int)octaves; i++) { 305 exponents[i] = (float)Math.pow(2, -i); 306 //frequency *= lacunarity; 307 } 308 309 //min = -1; 310 //max = 1; 311 312//min = -1.2f; 313//max = 1.2f; 314 315 width = src.getWidth(); 316 height = src.getHeight(); 317 318 int h = src.getHeight(); 319 tan = new float[h]; 320 for (int i = 0; i < h; i++) 321 tan[i] = (float)Math.tan( fov * i/h * Math.PI * 0.5 ); 322 323 if ( dst == null ) 324 dst = createCompatibleDestImage( src, null ); 325 int t = (int)(63*time); 326// skyPixels = getRGB( skyColors, t, 0, 1, 64, skyPixels ); 327 Graphics2D g = dst.createGraphics(); 328 g.drawImage( skyColors, 0, 0, dst.getWidth(), dst.getHeight(), t, 0, t+1, 64, null ); 329 g.dispose(); 330 super.filter( dst, dst ); 331// g.drawRenderedImage( clouds, null ); 332// g.dispose(); 333long finish = System.currentTimeMillis(); 334System.out.println(mn+" "+mx+" "+(finish-start)*0.001f); 335 exponents = null; 336 tan = null; 337 return dst; 338 } 339 340 public float evaluate(float x, float y) { 341 float value = 0.0f; 342 float remainder; 343 int i; 344 345 // to prevent "cascading" effects 346 x += 371; 347 y += 529; 348 349 for (i = 0; i < (int)octaves; i++) { 350 value += Noise.noise3(x, y, t) * exponents[i]; 351 x *= lacunarity; 352 y *= lacunarity; 353 } 354 355 remainder = octaves - (int)octaves; 356 if (remainder != 0) 357 value += remainder * Noise.noise3(x, y, t) * exponents[i]; 358 359 return value; 360 } 361 362 public int filterRGB(int x, int y, int rgb) { 363 364// Curvature 365float fx = x / width; 366//y += 20*Math.sin( fx*Math.PI*0.5 ); 367 float fy = y / height; 368 float haze = (float)Math.pow( haziness, 100*fy*fy ); 369// int argb = skyPixels[(int)fy]; 370 float r = ((rgb >> 16) & 0xff) * r255; 371 float g = ((rgb >> 8) & 0xff) * r255; 372 float b = (rgb & 0xff) * r255; 373 374 float cx = width*0.5f; 375 float nx = x-cx; 376 float ny = y; 377// FOV 378//ny = (float)Math.tan( fov * fy * Math.PI * 0.5 ); 379ny = tan[y]; 380nx = (fx-0.5f) * (1+ny); 381ny += t*windSpeed;// Wind towards the camera 382 383// float xscale = scale/(1+y*bias*0.1f); 384 nx /= scale; 385 ny /= scale * stretch; 386 float f = evaluate(nx, ny); 387//float fg = f;//FIXME-bump map 388 // Normalize to 0..1 389// f = (f-min)/(max-min); 390 391 f = (f+1.23f)/2.46f; 392 393// f *= amount; 394 //int a = rgb & 0xff000000; 395 int v; 396 397 // Work out cloud cover 398 float c = f - cloudCover; 399 if (c < 0) 400 c = 0; 401 402 float cloudAlpha = 1 - (float)Math.pow(cloudSharpness, c); 403//cloudAlpha *= amount; 404//if ( cloudAlpha > 1 ) 405// cloudAlpha = 1; 406mn = Math.min(mn, cloudAlpha); 407mx = Math.max(mx, cloudAlpha); 408 409 // Sun glow 410 float centreX = width*sunAzimuth; 411 float centreY = height*sunElevation; 412 float dx = x-centreX; 413 float dy = y-centreY; 414 float distance2 = dx*dx+dy*dy; 415// float sun = 0; 416 //distance2 = (float)Math.sqrt(distance2); 417distance2 = (float)Math.pow(distance2, glowFalloff); 418 float sun = /*amount**/10*(float)Math.exp(-distance2*glow*0.1f); 419// sun = glow*10*(float)Math.exp(-distance2); 420 421 // Sun glow onto sky 422 r += sun * sunR; 423 g += sun * sunG; 424 b += sun * sunB; 425 426 427// float cloudColor = cloudAlpha *sun; 428// Bump map 429/* 430 float nnx = x-cx; 431 float nny = y-1; 432 nnx /= xscale; 433 nny /= xscale * stretch; 434 float gf = evaluate(nnx, nny); 435 float gradient = fg-gf; 436if (y == 100)System.out.println(fg+" "+gf+gradient); 437 cloudColor += amount * gradient; 438*/ 439// ... 440/* 441 r += (cloudColor-r) * cloudAlpha; 442 g += (cloudColor-g) * cloudAlpha; 443 b += (cloudColor-b) * cloudAlpha; 444*/ 445 // Clouds get darker as they get thicker 446 float ca = (1-cloudAlpha*cloudAlpha*cloudAlpha*cloudAlpha) /** (1 + sun)*/ * amount; 447 float cloudR = sunR * ca; 448 float cloudG = sunG * ca; 449 float cloudB = sunB * ca; 450 451 // Apply the haziness as we move further away 452 cloudAlpha *= haze; 453 454 // Composite the clouds on the sky 455 float iCloudAlpha = (1-cloudAlpha); 456 r = iCloudAlpha*r + cloudAlpha*cloudR; 457 g = iCloudAlpha*g + cloudAlpha*cloudG; 458 b = iCloudAlpha*b + cloudAlpha*cloudB; 459 460 // Exposure 461 float exposure = gain; 462 r = 1 - (float)Math.exp(-r * exposure); 463 g = 1 - (float)Math.exp(-g * exposure); 464 b = 1 - (float)Math.exp(-b * exposure); 465 466 int ir = (int)(255*r) << 16; 467 int ig = (int)(255*g) << 8; 468 int ib = (int)(255*b); 469 v = 0xff000000|ir|ig|ib; 470 return v; 471 } 472 473 public String toString() { 474 return "Texture/Sky..."; 475 } 476 477 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 478 Object o; 479 if((o=parameters.removeEL(KeyImpl.init("Amount")))!=null)setAmount(ImageFilterUtil.toFloatValue(o,"Amount")); 480 if((o=parameters.removeEL(KeyImpl.init("Stretch")))!=null)setStretch(ImageFilterUtil.toFloatValue(o,"Stretch")); 481 if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle")); 482 if((o=parameters.removeEL(KeyImpl.init("Operation")))!=null)setOperation(ImageFilterUtil.toIntValue(o,"Operation")); 483 if((o=parameters.removeEL(KeyImpl.init("Octaves")))!=null)setOctaves(ImageFilterUtil.toFloatValue(o,"Octaves")); 484 if((o=parameters.removeEL(KeyImpl.init("H")))!=null)setH(ImageFilterUtil.toFloatValue(o,"H")); 485 if((o=parameters.removeEL(KeyImpl.init("Lacunarity")))!=null)setLacunarity(ImageFilterUtil.toFloatValue(o,"Lacunarity")); 486 if((o=parameters.removeEL(KeyImpl.init("Gain")))!=null)setGain(ImageFilterUtil.toFloatValue(o,"Gain")); 487 if((o=parameters.removeEL(KeyImpl.init("Bias")))!=null)setBias(ImageFilterUtil.toFloatValue(o,"Bias")); 488 if((o=parameters.removeEL(KeyImpl.init("T")))!=null)setT(ImageFilterUtil.toFloatValue(o,"T")); 489 if((o=parameters.removeEL(KeyImpl.init("FOV")))!=null)setFOV(ImageFilterUtil.toFloatValue(o,"FOV")); 490 if((o=parameters.removeEL(KeyImpl.init("CloudCover")))!=null)setCloudCover(ImageFilterUtil.toFloatValue(o,"CloudCover")); 491 if((o=parameters.removeEL(KeyImpl.init("CloudSharpness")))!=null)setCloudSharpness(ImageFilterUtil.toFloatValue(o,"CloudSharpness")); 492 if((o=parameters.removeEL(KeyImpl.init("Glow")))!=null)setGlow(ImageFilterUtil.toFloatValue(o,"Glow")); 493 if((o=parameters.removeEL(KeyImpl.init("GlowFalloff")))!=null)setGlowFalloff(ImageFilterUtil.toFloatValue(o,"GlowFalloff")); 494 if((o=parameters.removeEL(KeyImpl.init("Haziness")))!=null)setHaziness(ImageFilterUtil.toFloatValue(o,"Haziness")); 495 if((o=parameters.removeEL(KeyImpl.init("SunElevation")))!=null)setSunElevation(ImageFilterUtil.toFloatValue(o,"SunElevation")); 496 if((o=parameters.removeEL(KeyImpl.init("SunAzimuth")))!=null)setSunAzimuth(ImageFilterUtil.toFloatValue(o,"SunAzimuth")); 497 if((o=parameters.removeEL(KeyImpl.init("SunColor")))!=null)setSunColor(ImageFilterUtil.toColorRGB(o,"SunColor")); 498 if((o=parameters.removeEL(KeyImpl.init("CameraElevation")))!=null)setCameraElevation(ImageFilterUtil.toFloatValue(o,"CameraElevation")); 499 if((o=parameters.removeEL(KeyImpl.init("CameraAzimuth")))!=null)setCameraAzimuth(ImageFilterUtil.toFloatValue(o,"CameraAzimuth")); 500 if((o=parameters.removeEL(KeyImpl.init("WindSpeed")))!=null)setWindSpeed(ImageFilterUtil.toFloatValue(o,"WindSpeed")); 501 if((o=parameters.removeEL(KeyImpl.init("Time")))!=null)setTime(ImageFilterUtil.toFloatValue(o,"Time")); 502 if((o=parameters.removeEL(KeyImpl.init("Scale")))!=null)setScale(ImageFilterUtil.toFloatValue(o,"Scale")); 503 if((o=parameters.removeEL(KeyImpl.init("Dimensions")))!=null){ 504 int[] dim=ImageFilterUtil.toDimensions(o,"Dimensions"); 505 setDimensions(dim[0],dim[1]); 506 } 507 508 // check for arguments not supported 509 if(parameters.size()>0) { 510 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, Stretch, Angle, Operation, Octaves, H, Lacunarity, Gain, Bias, T, FOV, CloudCover, CloudSharpness, Glow, GlowFalloff, Haziness, SunElevation, SunAzimuth, SunColor, CameraElevation, CameraAzimuth, WindSpeed, Time, Scale, Dimensions]"); 511 } 512 513 return filter(src, dst); 514 } 515}