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