001/*
002Copyright 2006 Jerry Huxtable
003
004Licensed under the Apache License, Version 2.0 (the "License");
005you may not use this file except in compliance with the License.
006You may obtain a copy of the License at
007
008   http://www.apache.org/licenses/LICENSE-2.0
009
010Unless required by applicable law or agreed to in writing, software
011distributed under the License is distributed on an "AS IS" BASIS,
012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013See the License for the specific language governing permissions and
014limitations under the License.
015*/
016
017package com.jhlabs.image;
018
019import java.awt.*;
020import java.awt.image.*;
021import java.util.*;
022import com.jhlabs.math.*;
023
024/**
025 * A filter which simulates underwater caustics. This can be animated to get a bottom-of-the-swimming-pool effect.
026 */
027public class CausticsFilter extends WholeImageFilter {
028
029        private float scale = 32;
030        private float angle = 0.0f;
031        private int brightness = 10;
032        private float amount = 1.0f;
033        private float turbulence = 1.0f;
034        private float dispersion = 0.0f;
035        private float time = 0.0f;
036        private int samples = 2;
037        private int bgColor = 0xff799fff;
038
039        private float s, c;
040
041        public CausticsFilter() {
042        }
043
044        /**
045     * Specifies the scale of the texture.
046     * @param scale the scale of the texture.
047     * @min-value 1
048     * @max-value 300+
049     * @see #getScale
050     */
051        public void setScale(float scale) {
052                this.scale = scale;
053        }
054
055        /**
056     * Returns the scale of the texture.
057     * @return the scale of the texture.
058     * @see #setScale
059     */
060        public float getScale() {
061                return scale;
062        }
063
064        /**
065     * Set the brightness.
066     * @param brightness the brightness.
067     * @min-value 0
068     * @max-value 1
069     * @see #getBrightness
070     */
071        public void setBrightness(int brightness) {
072                this.brightness = brightness;
073        }
074
075        /**
076     * Get the brightness.
077     * @return the brightness.
078     * @see #setBrightness
079     */
080        public int getBrightness() {
081                return brightness;
082        }
083
084        /**
085     * Specifies the turbulence of the texture.
086     * @param turbulence the turbulence of the texture.
087     * @min-value 0
088     * @max-value 1
089     * @see #getTurbulence
090     */
091        public void setTurbulence(float turbulence) {
092                this.turbulence = turbulence;
093        }
094
095        /**
096     * Returns the turbulence of the effect.
097     * @return the turbulence of the effect.
098     * @see #setTurbulence
099     */
100        public float getTurbulence() {
101                return turbulence;
102        }
103
104        /**
105         * Set the amount of effect.
106         * @param amount the amount
107     * @min-value 0
108     * @max-value 1
109     * @see #getAmount
110         */
111        public void setAmount(float amount) {
112                this.amount = amount;
113        }
114        
115        /**
116         * Get the amount of effect.
117         * @return the amount
118     * @see #setAmount
119         */
120        public float getAmount() {
121                return amount;
122        }
123        
124        /**
125         * Set the dispersion.
126         * @param dispersion the dispersion
127     * @min-value 0
128     * @max-value 1
129     * @see #getDispersion
130         */
131        public void setDispersion(float dispersion) {
132                this.dispersion = dispersion;
133        }
134        
135        /**
136         * Get the dispersion.
137         * @return the dispersion
138     * @see #setDispersion
139         */
140        public float getDispersion() {
141                return dispersion;
142        }
143        
144        /**
145         * Set the time. Use this to animate the effect.
146         * @param time the time
147     * @see #getTime
148         */
149        public void setTime(float time) {
150                this.time = time;
151        }
152        
153        /**
154         * Set the time.
155         * @return the time
156     * @see #setTime
157         */
158        public float getTime() {
159                return time;
160        }
161        
162        /**
163         * Set the number of samples per pixel. More samples means better quality, but slower rendering.
164         * @param samples the number of samples
165     * @see #getSamples
166         */
167        public void setSamples(int samples) {
168                this.samples = samples;
169        }
170        
171        /**
172         * Get the number of samples per pixel.
173         * @return the number of samples
174     * @see #setSamples
175         */
176        public int getSamples() {
177                return samples;
178        }
179        
180        /**
181         * Set the background color.
182         * @param c the color
183     * @see #getBgColor
184         */
185        public void setBgColor(int c) {
186                bgColor = c;
187        }
188
189        /**
190         * Get the background color.
191         * @return the color
192     * @see #setBgColor
193         */
194        public int getBgColor() {
195                return bgColor;
196        }
197
198        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
199                Random random = new Random(0);
200
201                s = (float)Math.sin(0.1);
202                c = (float)Math.cos(0.1);
203
204                int srcWidth = originalSpace.width;
205                int srcHeight = originalSpace.height;
206                int outWidth = transformedSpace.width;
207                int outHeight = transformedSpace.height;
208                int index = 0;
209                int[] pixels = new int[outWidth * outHeight];
210
211                for (int y = 0; y < outHeight; y++) {
212                        for (int x = 0; x < outWidth; x++) {
213                                pixels[index++] = bgColor;
214                        }
215                }
216                
217                int v = brightness/samples;
218                if (v == 0)
219                        v = 1;
220
221                float rs = 1.0f/scale;
222                float d = 0.95f;
223                index = 0;
224                for (int y = 0; y < outHeight; y++) {
225                        for (int x = 0; x < outWidth; x++) {
226                                for (int s = 0; s < samples; s++) {
227                                        float sx = x+random.nextFloat();
228                                        float sy = y+random.nextFloat();
229                                        float nx = sx*rs;
230                                        float ny = sy*rs;
231                                        float xDisplacement, yDisplacement;
232                                        float focus = 0.1f+amount;
233                                        xDisplacement = evaluate(nx-d, ny) - evaluate(nx+d, ny);
234                                        yDisplacement = evaluate(nx, ny+d) - evaluate(nx, ny-d);
235
236                                        if (dispersion > 0) {
237                                                for (int c = 0; c < 3; c++) {
238                                                        float ca = (1+c*dispersion);
239                                                        float srcX = sx + scale*focus * xDisplacement*ca;
240                                                        float srcY = sy + scale*focus * yDisplacement*ca;
241
242                                                        if (srcX < 0 || srcX >= outWidth-1 || srcY < 0 || srcY >= outHeight-1) {
243                                                        } else {
244                                                                int i = ((int)srcY)*outWidth+(int)srcX;
245                                                                int rgb = pixels[i];
246                                                                int r = (rgb >> 16) & 0xff;
247                                                                int g = (rgb >> 8) & 0xff;
248                                                                int b = rgb & 0xff;
249                                                                if (c == 2)
250                                                                        r += v;
251                                                                else if (c == 1)
252                                                                        g += v;
253                                                                else
254                                                                        b += v;
255                                                                if (r > 255)
256                                                                        r = 255;
257                                                                if (g > 255)
258                                                                        g = 255;
259                                                                if (b > 255)
260                                                                        b = 255;
261                                                                pixels[i] = 0xff000000 | (r << 16) | (g << 8) | b;
262                                                        }
263                                                }
264                                        } else {
265                                                float srcX = sx + scale*focus * xDisplacement;
266                                                float srcY = sy + scale*focus * yDisplacement;
267
268                                                if (srcX < 0 || srcX >= outWidth-1 || srcY < 0 || srcY >= outHeight-1) {
269                                                } else {
270                                                        int i = ((int)srcY)*outWidth+(int)srcX;
271                                                        int rgb = pixels[i];
272                                                        int r = (rgb >> 16) & 0xff;
273                                                        int g = (rgb >> 8) & 0xff;
274                                                        int b = rgb & 0xff;
275                                                        r += v;
276                                                        g += v;
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                                }
288                        }
289                }
290                return pixels;
291        }
292
293        private static int add(int rgb, float brightness) {
294                int r = (rgb >> 16) & 0xff;
295                int g = (rgb >> 8) & 0xff;
296                int b = rgb & 0xff;
297                r += brightness;
298                g += brightness;
299                b += brightness;
300                if (r > 255)
301                        r = 255;
302                if (g > 255)
303                        g = 255;
304                if (b > 255)
305                        b = 255;
306                return 0xff000000 | (r << 16) | (g << 8) | b;
307        }
308        
309        private static int add(int rgb, float brightness, int c) {
310                int r = (rgb >> 16) & 0xff;
311                int g = (rgb >> 8) & 0xff;
312                int b = rgb & 0xff;
313                if (c == 2)
314                        r += brightness;
315                else if (c == 1)
316                        g += brightness;
317                else
318                        b += brightness;
319                if (r > 255)
320                        r = 255;
321                if (g > 255)
322                        g = 255;
323                if (b > 255)
324                        b = 255;
325                return 0xff000000 | (r << 16) | (g << 8) | b;
326        }
327        
328        private static float turbulence2(float x, float y, float time, float octaves) {
329                float value = 0.0f;
330                float remainder;
331                float lacunarity = 2.0f;
332                float f = 1.0f;
333                int i;
334                
335                // to prevent "cascading" effects
336                x += 371;
337                y += 529;
338                
339                for (i = 0; i < (int)octaves; i++) {
340                        value += Noise.noise3(x, y, time) / f;
341                        x *= lacunarity;
342                        y *= lacunarity;
343                        f *= 2;
344                }
345
346                remainder = octaves - (int)octaves;
347                if (remainder != 0)
348                        value += remainder * Noise.noise3(x, y, time) / f;
349
350                return value;
351        }
352
353        private float evaluate(float x, float y) {
354                float xt = s*x + c*time;
355                float tt = c*x - c*time;
356                float f = turbulence == 0.0 ? Noise.noise3(xt, y, tt) : turbulence2(xt, y, tt, turbulence);
357                return f;
358        }
359        
360        public String toString() {
361                return "Texture/Caustics...";
362        }
363        
364}