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