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.Date;
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.type.KeyImpl;
045import lucee.runtime.type.Struct;
046import lucee.runtime.type.util.CollectionUtil;
047
048public class PlasmaFilter extends WholeImageFilter  implements DynFiltering {
049        
050        public float turbulence = 1.0f;
051        private float scaling = 0.0f;
052        private Colormap colormap = new LinearColormap();
053        private Random randomGenerator;
054        private long seed = 567;
055        private boolean useColormap = false;
056        private boolean useImageColors = false;
057
058        public PlasmaFilter() {
059                randomGenerator = new Random();
060        }
061
062        /**
063     * Specifies the turbulence of the texture.
064     * @param turbulence the turbulence of the texture.
065     * @min-value 0
066     * @max-value 10
067     * @see #getTurbulence
068     */
069        public void setTurbulence(float turbulence) {
070                this.turbulence = turbulence;
071        }
072
073        /**
074     * Returns the turbulence of the effect.
075     * @return the turbulence of the effect.
076     * @see #setTurbulence
077     */
078        public float getTurbulence() {
079                return turbulence;
080        }
081
082        public void setScaling(float scaling) {
083                this.scaling = scaling;
084        }
085
086        public float getScaling() {
087                return scaling;
088        }
089
090    /**
091     * Set the colormap to be used for the filter.
092     * @param colormap the colormap
093     * @see #getColormap
094     */
095        public void setColormap(Colormap colormap) {
096                this.colormap = colormap;
097        }
098        
099    /**
100     * Get the colormap to be used for the filter.
101     * @return the colormap
102     * @see #setColormap
103     */
104        public Colormap getColormap() {
105                return colormap;
106        }
107        
108        public void setUseColormap(boolean useColormap) {
109                this.useColormap = useColormap;
110        }
111
112        public boolean getUseColormap() {
113                return useColormap;
114        }
115
116        public void setUseImageColors(boolean useImageColors) {
117                this.useImageColors = useImageColors;
118        }
119
120        public boolean getUseImageColors() {
121                return useImageColors;
122        }
123
124        public void setSeed(int seed) {
125                this.seed = seed;
126        }
127
128        public int getSeed() {
129                return (int)seed;
130        }
131
132        public void randomize() {
133                seed = new Date().getTime();
134        }
135        
136        private int randomRGB(int[] inPixels, int x, int y) {
137                if (useImageColors) {
138                        return inPixels[y*originalSpace.width+x];
139                } 
140                int r = (int)(255 * randomGenerator.nextFloat());
141                int g = (int)(255 * randomGenerator.nextFloat());
142                int b = (int)(255 * randomGenerator.nextFloat());
143                return 0xff000000 | (r << 16) | (g << 8) | b;
144        }
145
146        private int displace(int rgb, float amount) {
147                int r = (rgb >> 16) & 0xff;
148                int g = (rgb >> 8) & 0xff;
149                int b = rgb & 0xff;
150                r = PixelUtils.clamp(r + (int)(amount * (randomGenerator.nextFloat()-0.5)));
151                g = PixelUtils.clamp(g + (int)(amount * (randomGenerator.nextFloat()-0.5)));
152                b = PixelUtils.clamp(b + (int)(amount * (randomGenerator.nextFloat()-0.5)));
153                return 0xff000000 | (r << 16) | (g << 8) | b;
154        }
155
156        private int average(int rgb1, int rgb2) {
157                return PixelUtils.combinePixels(rgb1, rgb2, PixelUtils.AVERAGE);
158        }
159
160        private int getPixel(int x, int y, int[] pixels, int stride) {
161                return pixels[y*stride+x];
162        }
163        
164        private void putPixel(int x, int y, int rgb, int[] pixels, int stride) {
165                pixels[y*stride+x] = rgb;
166        }
167        
168        private boolean doPixel(int x1, int y1, int x2, int y2, int[] pixels, int stride, int depth, int scale) {
169                int mx, my;
170
171                if (depth == 0) {
172                        int ml, mr, mt, mb, mm, t;
173
174                        int tl = getPixel(x1, y1, pixels, stride);
175                        int bl = getPixel(x1, y2, pixels, stride);
176                        int tr = getPixel(x2, y1, pixels, stride);
177                        int br = getPixel(x2, y2, pixels, stride);
178
179                        float amount = (256.0f / (2.0f * scale)) * turbulence;
180
181                        mx = (x1 + x2) / 2;
182                        my = (y1 + y2) / 2;
183
184                        if (mx == x1 && mx == x2 && my == y1 && my == y2)
185                                return true;
186
187                        if (mx != x1 || mx != x2) {
188                                ml = average(tl, bl);
189                                ml = displace(ml, amount);
190                                putPixel(x1, my, ml, pixels, stride);
191
192                                if (x1 != x2){
193                                        mr = average(tr, br);
194                                        mr = displace(mr, amount);
195                                        putPixel(x2, my, mr, pixels, stride);
196                                }
197                        }
198
199                        if (my != y1 || my != y2){
200                                if (x1 != mx || my != y2){
201                                        mb = average(bl, br);
202                                        mb = displace(mb, amount);
203                                        putPixel(mx, y2, mb, pixels, stride);
204                                }
205
206                                if (y1 != y2){
207                                        mt = average(tl, tr);
208                                        mt = displace(mt, amount);
209                                        putPixel(mx, y1, mt, pixels, stride);
210                                }
211                        }
212
213                        if (y1 != y2 || x1 != x2) {
214                                mm = average(tl, br);
215                                t = average(bl, tr);
216                                mm = average(mm, t);
217                                mm = displace(mm, amount);
218                                putPixel(mx, my, mm, pixels, stride);
219                        }
220
221                        if (x2-x1 < 3 && y2-y1 < 3)
222                                return false;
223                        return true;
224                }
225
226                mx = (x1 + x2) / 2;
227                my = (y1 + y2) / 2;
228
229                doPixel(x1, y1, mx, my, pixels, stride, depth-1, scale+1);
230                doPixel(x1, my, mx ,y2, pixels, stride, depth-1, scale+1);
231                doPixel(mx, y1, x2 , my, pixels, stride, depth-1, scale+1);
232                return doPixel(mx, my, x2, y2, pixels, stride, depth-1, scale+1);
233        }
234        
235        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
236                int[] outPixels = new int[width * height];
237
238                randomGenerator.setSeed(seed);
239
240                int w1 = width-1;
241                int h1 = height-1;
242                putPixel(0, 0, randomRGB(inPixels, 0, 0), outPixels, width);
243                putPixel(w1, 0, randomRGB(inPixels, w1, 0), outPixels, width);
244                putPixel(0, h1, randomRGB(inPixels, 0, h1), outPixels, width);
245                putPixel(w1, h1, randomRGB(inPixels, w1, h1), outPixels, width);
246                putPixel(w1/2, h1/2, randomRGB(inPixels, w1/2, h1/2), outPixels, width);
247                putPixel(0, h1/2, randomRGB(inPixels, 0, h1/2), outPixels, width);
248                putPixel(w1, h1/2, randomRGB(inPixels, w1, h1/2), outPixels, width);
249                putPixel(w1/2, 0, randomRGB(inPixels, w1/2, 0), outPixels, width);
250                putPixel(w1/2, h1, randomRGB(inPixels, w1/2, h1), outPixels, width);
251
252                int depth = 1;
253                while (doPixel(0, 0, width-1, height-1, outPixels, width, depth, 0))
254                        depth++;
255
256                if (useColormap && colormap != null) {
257                        int index = 0;
258                        for (int y = 0; y < height; y++) {
259                                for (int x = 0; x < width; x++) {
260                                        outPixels[index] = colormap.getColor((outPixels[index] & 0xff)/255.0f);
261                                        index++;
262                                }
263                        }
264                }
265                return outPixels;
266        }
267
268        public String toString() {
269                return "Texture/Plasma...";
270        }
271        
272        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
273                Object o;
274                if((o=parameters.removeEL(KeyImpl.init("Colormap")))!=null)setColormap(ImageFilterUtil.toColormap(o,"Colormap"));
275                if((o=parameters.removeEL(KeyImpl.init("Turbulence")))!=null)setTurbulence(ImageFilterUtil.toFloatValue(o,"Turbulence"));
276                if((o=parameters.removeEL(KeyImpl.init("Scaling")))!=null)setScaling(ImageFilterUtil.toFloatValue(o,"Scaling"));
277                if((o=parameters.removeEL(KeyImpl.init("UseColormap")))!=null)setUseColormap(ImageFilterUtil.toBooleanValue(o,"UseColormap"));
278                if((o=parameters.removeEL(KeyImpl.init("UseImageColors")))!=null)setUseImageColors(ImageFilterUtil.toBooleanValue(o,"UseImageColors"));
279                if((o=parameters.removeEL(KeyImpl.init("Seed")))!=null)setSeed(ImageFilterUtil.toIntValue(o,"Seed"));
280
281                // check for arguments not supported
282                if(parameters.size()>0) {
283                        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 [Colormap, Turbulence, Scaling, UseColormap, UseImageColors, Seed]");
284                }
285
286                return filter(src, dst);
287        }
288}