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.Rectangle;
036import java.awt.image.BufferedImage;
037import java.util.Random;
038
039import lucee.runtime.engine.ThreadLocalPageContext;
040import lucee.runtime.exp.FunctionException;
041import lucee.runtime.exp.PageException;
042import lucee.runtime.img.ImageUtil;
043import lucee.runtime.img.math.Noise;
044import lucee.runtime.type.KeyImpl;
045import lucee.runtime.type.Struct;
046import lucee.runtime.type.util.CollectionUtil;
047/**
048 * A filter which simulates underwater caustics. This can be animated to get a bottom-of-the-swimming-pool effect.
049 */
050public class CausticsFilter extends WholeImageFilter  implements DynFiltering {
051
052        private float scale = 32;
053        //private float angle = 0.0f;
054        private int brightness = 10;
055        private float amount = 1.0f;
056        private float turbulence = 1.0f;
057        private float dispersion = 0.0f;
058        private float time = 0.0f;
059        private int samples = 2;
060        private int bgColor = 0xff799fff;
061
062        private float s, c;
063
064        public CausticsFilter() {
065        }
066
067        /**
068     * Specifies the scale of the texture.
069     * @param scale the scale of the texture.
070     * @min-value 1
071     * @max-value 300+
072     * @see #getScale
073     */
074        public void setScale(float scale) {
075                this.scale = scale;
076        }
077
078        /**
079     * Returns the scale of the texture.
080     * @return the scale of the texture.
081     * @see #setScale
082     */
083        public float getScale() {
084                return scale;
085        }
086
087        /**
088     * Set the brightness.
089     * @param brightness the brightness.
090     * @min-value 0
091     * @max-value 1
092     * @see #getBrightness
093     */
094        public void setBrightness(int brightness) {
095                this.brightness = brightness;
096        }
097
098        /**
099     * Get the brightness.
100     * @return the brightness.
101     * @see #setBrightness
102     */
103        public int getBrightness() {
104                return brightness;
105        }
106
107        /**
108     * Specifies the turbulence of the texture.
109     * @param turbulence the turbulence of the texture.
110     * @min-value 0
111     * @max-value 1
112     * @see #getTurbulence
113     */
114        public void setTurbulence(float turbulence) {
115                this.turbulence = turbulence;
116        }
117
118        /**
119     * Returns the turbulence of the effect.
120     * @return the turbulence of the effect.
121     * @see #setTurbulence
122     */
123        public float getTurbulence() {
124                return turbulence;
125        }
126
127        /**
128         * Set the amount of effect.
129         * @param amount the amount
130     * @min-value 0
131     * @max-value 1
132     * @see #getAmount
133         */
134        public void setAmount(float amount) {
135                this.amount = amount;
136        }
137        
138        /**
139         * Get the amount of effect.
140         * @return the amount
141     * @see #setAmount
142         */
143        public float getAmount() {
144                return amount;
145        }
146        
147        /**
148         * Set the dispersion.
149         * @param dispersion the dispersion
150     * @min-value 0
151     * @max-value 1
152     * @see #getDispersion
153         */
154        public void setDispersion(float dispersion) {
155                this.dispersion = dispersion;
156        }
157        
158        /**
159         * Get the dispersion.
160         * @return the dispersion
161     * @see #setDispersion
162         */
163        public float getDispersion() {
164                return dispersion;
165        }
166        
167        /**
168         * Set the time. Use this to animate the effect.
169         * @param time the time
170     * @see #getTime
171         */
172        public void setTime(float time) {
173                this.time = time;
174        }
175        
176        /**
177         * Set the time.
178         * @return the time
179     * @see #setTime
180         */
181        public float getTime() {
182                return time;
183        }
184        
185        /**
186         * Set the number of samples per pixel. More samples means better quality, but slower rendering.
187         * @param samples the number of samples
188     * @see #getSamples
189         */
190        public void setSamples(int samples) {
191                this.samples = samples;
192        }
193        
194        /**
195         * Get the number of samples per pixel.
196         * @return the number of samples
197     * @see #setSamples
198         */
199        public int getSamples() {
200                return samples;
201        }
202        
203        /**
204         * Set the background color.
205         * @param c the color
206     * @see #getBgColor
207         */
208        public void setBgColor(int c) {
209                bgColor = c;
210        }
211
212        /**
213         * Get the background color.
214         * @return the color
215     * @see #setBgColor
216         */
217        public int getBgColor() {
218                return bgColor;
219        }
220
221        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
222                Random random = new Random(0);
223
224                s = (float)Math.sin(0.1);
225                c = (float)Math.cos(0.1);
226
227                //int srcWidth = originalSpace.width;
228                //int srcHeight = originalSpace.height;
229                int outWidth = transformedSpace.width;
230                int outHeight = transformedSpace.height;
231                int index = 0;
232                int[] pixels = new int[outWidth * outHeight];
233
234                for (int y = 0; y < outHeight; y++) {
235                        for (int x = 0; x < outWidth; x++) {
236                                pixels[index++] = bgColor;
237                        }
238                }
239                
240                int v = brightness/samples;
241                if (v == 0)
242                        v = 1;
243
244                float rs = 1.0f/scale;
245                float d = 0.95f;
246                index = 0;
247                for (int y = 0; y < outHeight; y++) {
248                        for (int x = 0; x < outWidth; x++) {
249                                for (int s = 0; s < samples; s++) {
250                                        float sx = x+random.nextFloat();
251                                        float sy = y+random.nextFloat();
252                                        float nx = sx*rs;
253                                        float ny = sy*rs;
254                                        float xDisplacement, yDisplacement;
255                                        float focus = 0.1f+amount;
256                                        xDisplacement = evaluate(nx-d, ny) - evaluate(nx+d, ny);
257                                        yDisplacement = evaluate(nx, ny+d) - evaluate(nx, ny-d);
258
259                                        if (dispersion > 0) {
260                                                for (int c = 0; c < 3; c++) {
261                                                        float ca = (1+c*dispersion);
262                                                        float srcX = sx + scale*focus * xDisplacement*ca;
263                                                        float srcY = sy + scale*focus * yDisplacement*ca;
264
265                                                        if (srcX < 0 || srcX >= outWidth-1 || srcY < 0 || srcY >= outHeight-1) {
266                                                        } else {
267                                                                int i = ((int)srcY)*outWidth+(int)srcX;
268                                                                int rgb = pixels[i];
269                                                                int r = (rgb >> 16) & 0xff;
270                                                                int g = (rgb >> 8) & 0xff;
271                                                                int b = rgb & 0xff;
272                                                                if (c == 2)
273                                                                        r += v;
274                                                                else if (c == 1)
275                                                                        g += v;
276                                                                else
277                                                                        b += v;
278                                                                if (r > 255)
279                                                                        r = 255;
280                                                                if (g > 255)
281                                                                        g = 255;
282                                                                if (b > 255)
283                                                                        b = 255;
284                                                                pixels[i] = 0xff000000 | (r << 16) | (g << 8) | b;
285                                                        }
286                                                }
287                                        } else {
288                                                float srcX = sx + scale*focus * xDisplacement;
289                                                float srcY = sy + scale*focus * yDisplacement;
290
291                                                if (srcX < 0 || srcX >= outWidth-1 || srcY < 0 || srcY >= outHeight-1) {
292                                                } else {
293                                                        int i = ((int)srcY)*outWidth+(int)srcX;
294                                                        int rgb = pixels[i];
295                                                        int r = (rgb >> 16) & 0xff;
296                                                        int g = (rgb >> 8) & 0xff;
297                                                        int b = rgb & 0xff;
298                                                        r += v;
299                                                        g += v;
300                                                        b += v;
301                                                        if (r > 255)
302                                                                r = 255;
303                                                        if (g > 255)
304                                                                g = 255;
305                                                        if (b > 255)
306                                                                b = 255;
307                                                        pixels[i] = 0xff000000 | (r << 16) | (g << 8) | b;
308                                                }
309                                        }
310                                }
311                        }
312                }
313                return pixels;
314        }
315
316        /*private static int add(int rgb, float brightness) {
317                int r = (rgb >> 16) & 0xff;
318                int g = (rgb >> 8) & 0xff;
319                int b = rgb & 0xff;
320                r += brightness;
321                g += brightness;
322                b += brightness;
323                if (r > 255)
324                        r = 255;
325                if (g > 255)
326                        g = 255;
327                if (b > 255)
328                        b = 255;
329                return 0xff000000 | (r << 16) | (g << 8) | b;
330        }*/
331        
332        /*private static int add(int rgb, float brightness, int c) {
333                int r = (rgb >> 16) & 0xff;
334                int g = (rgb >> 8) & 0xff;
335                int b = rgb & 0xff;
336                if (c == 2)
337                        r += brightness;
338                else if (c == 1)
339                        g += brightness;
340                else
341                        b += brightness;
342                if (r > 255)
343                        r = 255;
344                if (g > 255)
345                        g = 255;
346                if (b > 255)
347                        b = 255;
348                return 0xff000000 | (r << 16) | (g << 8) | b;
349        }*/
350        
351        private static float turbulence2(float x, float y, float time, float octaves) {
352                float value = 0.0f;
353                float remainder;
354                float lacunarity = 2.0f;
355                float f = 1.0f;
356                int i;
357                
358                // to prevent "cascading" effects
359                x += 371;
360                y += 529;
361                
362                for (i = 0; i < (int)octaves; i++) {
363                        value += Noise.noise3(x, y, time) / f;
364                        x *= lacunarity;
365                        y *= lacunarity;
366                        f *= 2;
367                }
368
369                remainder = octaves - (int)octaves;
370                if (remainder != 0)
371                        value += remainder * Noise.noise3(x, y, time) / f;
372
373                return value;
374        }
375
376        private float evaluate(float x, float y) {
377                float xt = s*x + c*time;
378                float tt = c*x - c*time;
379                float f = turbulence == 0.0 ? Noise.noise3(xt, y, tt) : turbulence2(xt, y, tt, turbulence);
380                return f;
381        }
382        
383        public String toString() {
384                return "Texture/Caustics...";
385        }
386        
387        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
388                Object o;
389                if((o=parameters.removeEL(KeyImpl.init("Amount")))!=null)setAmount(ImageFilterUtil.toFloatValue(o,"Amount"));
390                if((o=parameters.removeEL(KeyImpl.init("Brightness")))!=null)setBrightness(ImageFilterUtil.toIntValue(o,"Brightness"));
391                if((o=parameters.removeEL(KeyImpl.init("Turbulence")))!=null)setTurbulence(ImageFilterUtil.toFloatValue(o,"Turbulence"));
392                if((o=parameters.removeEL(KeyImpl.init("Dispersion")))!=null)setDispersion(ImageFilterUtil.toFloatValue(o,"Dispersion"));
393                if((o=parameters.removeEL(KeyImpl.init("BgColor")))!=null)setBgColor(ImageFilterUtil.toColorRGB(o,"BgColor"));
394                if((o=parameters.removeEL(KeyImpl.init("Time")))!=null)setTime(ImageFilterUtil.toFloatValue(o,"Time"));
395                if((o=parameters.removeEL(KeyImpl.init("Scale")))!=null)setScale(ImageFilterUtil.toFloatValue(o,"Scale"));
396                if((o=parameters.removeEL(KeyImpl.init("Samples")))!=null)setSamples(ImageFilterUtil.toIntValue(o,"Samples"));
397
398                // check for arguments not supported
399                if(parameters.size()>0) {
400                        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, Brightness, Turbulence, Dispersion, BgColor, Time, Scale, Samples]");
401                }
402
403                return filter(src, dst);
404        }
405}