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