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    }