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}