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 SmearFilter extends WholeImageFilter  implements DynFiltering {
049        
050        public final static int CROSSES = 0;
051        public final static int LINES = 1;
052        public final static int CIRCLES = 2;
053        public final static int SQUARES = 3;
054
055        private Colormap colormap = new LinearColormap();
056        private float angle = 0;
057        private float density = 0.5f;
058        private float scatter = 0.0f;
059        private int distance = 8;
060        private Random randomGenerator;
061        private long seed = 567;
062        private int shape = LINES;
063        private float mix = 0.5f;
064        private int fadeout = 0;
065        private boolean background = false;
066
067        public SmearFilter() {
068                randomGenerator = new Random();
069        }
070
071        public void setShape(int shape) {
072                this.shape = shape;
073        }
074
075        public int getShape() {
076                return shape;
077        }
078
079        public void setDistance(int distance) {
080                this.distance = distance;
081        }
082
083        public int getDistance() {
084                return distance;
085        }
086
087        public void setDensity(float density) {
088                this.density = density;
089        }
090
091        public float getDensity() {
092                return density;
093        }
094
095        public void setScatter(float scatter) {
096                this.scatter = scatter;
097        }
098
099        public float getScatter() {
100                return scatter;
101        }
102
103        /**
104     * Specifies the angle of the texture.
105     * @param angle the angle of the texture.
106     * @angle
107     * @see #getAngle
108     */
109        public void setAngle(float angle) {
110                this.angle = angle;
111        }
112
113        /**
114     * Returns the angle of the texture.
115     * @return the angle of the texture.
116     * @see #setAngle
117     */
118        public float getAngle() {
119                return angle;
120        }
121
122        public void setMix(float mix) {
123                this.mix = mix;
124        }
125
126        public float getMix() {
127                return mix;
128        }
129
130        public void setFadeout(int fadeout) {
131                this.fadeout = fadeout;
132        }
133
134        public int getFadeout() {
135                return fadeout;
136        }
137
138        public void setBackground(boolean background) {
139                this.background = background;
140        }
141
142        public boolean getBackground() {
143                return background;
144        }
145
146        public void randomize() {
147                seed = new Date().getTime();
148        }
149        
150        private float random(float low, float high) {
151                return low+(high-low) * randomGenerator.nextFloat();
152        }
153        
154        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
155                int[] outPixels = new int[width * height];
156
157                randomGenerator.setSeed(seed);
158                float sinAngle = (float)Math.sin(angle);
159                float cosAngle = (float)Math.cos(angle);
160
161                int i = 0;
162                int numShapes;
163
164                for (int y = 0; y < height; y++)
165                        for (int x = 0; x < width; x++) {
166                                outPixels[i] = background ? 0xffffffff : inPixels[i];
167                                i++;
168                        }
169
170                switch (shape) {
171                case CROSSES:
172                        //Crosses
173                        numShapes = (int)(2*density*width * height / (distance + 1));
174                        for (i = 0; i < numShapes; i++) {
175                                int x = (randomGenerator.nextInt() & 0x7fffffff) % width;
176                                int y = (randomGenerator.nextInt() & 0x7fffffff) % height;
177                                int length = randomGenerator.nextInt() % distance + 1;
178                                int rgb = inPixels[y*width+x];
179                                for (int x1 = x - length; x1 < x + length + 1; x1++) {
180                                        if (x1 >= 0 && x1 < width) {
181                                                int rgb2 = background ? 0xffffffff : outPixels[y*width+x1];
182                                                outPixels[y*width+x1] = ImageMath.mixColors(mix, rgb2, rgb);
183                                        }
184                                }
185                                for (int y1 = y - length; y1 < y + length + 1; y1++) {
186                                        if (y1 >= 0 && y1 < height) {
187                                                int rgb2 = background ? 0xffffffff : outPixels[y1*width+x];
188                                                outPixels[y1*width+x] = ImageMath.mixColors(mix, rgb2, rgb);
189                                        }
190                                }
191                        }
192                        break;
193                case LINES:
194                        numShapes = (int)(2*density*width * height / 2);
195
196                        for (i = 0; i < numShapes; i++) {
197                                int sx = (randomGenerator.nextInt() & 0x7fffffff) % width;
198                                int sy = (randomGenerator.nextInt() & 0x7fffffff) % height;
199                                int rgb = inPixels[sy*width+sx];
200                                int length = (randomGenerator.nextInt() & 0x7fffffff) % distance;
201                                int dx = (int)(length*cosAngle);
202                                int dy = (int)(length*sinAngle);
203
204                                int x0 = sx-dx;
205                                int y0 = sy-dy;
206                                int x1 = sx+dx;
207                                int y1 = sy+dy;
208                                int x, y, d, incrE, incrNE, ddx, ddy;
209                                
210                                if (x1 < x0)
211                                        ddx = -1;
212                                else
213                                        ddx = 1;
214                                if (y1 < y0)
215                                        ddy = -1;
216                                else
217                                        ddy = 1;
218                                dx = x1-x0;
219                                dy = y1-y0;
220                                dx = Math.abs(dx);
221                                dy = Math.abs(dy);
222                                x = x0;
223                                y = y0;
224
225                                if (x < width && x >= 0 && y < height && y >= 0) {
226                                        int rgb2 = background ? 0xffffffff : outPixels[y*width+x];
227                                        outPixels[y*width+x] = ImageMath.mixColors(mix, rgb2, rgb);
228                                }
229                                if (Math.abs(dx) > Math.abs(dy)) {
230                                        d = 2*dy-dx;
231                                        incrE = 2*dy;
232                                        incrNE = 2*(dy-dx);
233
234                                        while (x != x1) {
235                                                if (d <= 0)
236                                                        d += incrE;
237                                                else {
238                                                        d += incrNE;
239                                                        y += ddy;
240                                                }
241                                                x += ddx;
242                                                if (x < width && x >= 0 && y < height && y >= 0) {
243                                                        int rgb2 = background ? 0xffffffff : outPixels[y*width+x];
244                                                        outPixels[y*width+x] = ImageMath.mixColors(mix, rgb2, rgb);
245                                                }
246                                        }
247                                } else {
248                                        d = 2*dx-dy;
249                                        incrE = 2*dx;
250                                        incrNE = 2*(dx-dy);
251
252                                        while (y != y1) {
253                                                if (d <= 0)
254                                                        d += incrE;
255                                                else {
256                                                        d += incrNE;
257                                                        x += ddx;
258                                                }
259                                                y += ddy;
260                                                if (x < width && x >= 0 && y < height && y >= 0) {
261                                                        int rgb2 = background ? 0xffffffff : outPixels[y*width+x];
262                                                        outPixels[y*width+x] = ImageMath.mixColors(mix, rgb2, rgb);
263                                                }
264                                        }
265                                }
266                        }
267                        break;
268                case SQUARES:
269                case CIRCLES:
270                        int radius = distance+1;
271                        int radius2 = radius * radius;
272                        numShapes = (int)(2*density*width * height / radius);
273                        for (i = 0; i < numShapes; i++) {
274                                int sx = (randomGenerator.nextInt() & 0x7fffffff) % width;
275                                int sy = (randomGenerator.nextInt() & 0x7fffffff) % height;
276                                int rgb = inPixels[sy*width+sx];
277                                for (int x = sx - radius; x < sx + radius + 1; x++) {
278                                        for (int y = sy - radius; y < sy + radius + 1; y++) {
279                                                int f;
280                                                if (shape == CIRCLES)
281                                                        f = (x - sx) * (x - sx) + (y - sy) * (y - sy);
282                                                else
283                                                        f = 0;
284                                                if (x >= 0 && x < width && y >= 0 && y < height && f <= radius2) {
285                                                        int rgb2 = background ? 0xffffffff : outPixels[y*width+x];
286                                                        outPixels[y*width+x] = ImageMath.mixColors(mix, rgb2, rgb);
287                                                }
288                                        }
289                                }
290                        }
291                }
292
293                return outPixels;
294        }
295
296        public String toString() {
297                return "Effects/Smear...";
298        }
299        
300        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
301                Object o;
302                if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle"));
303                if((o=parameters.removeEL(KeyImpl.init("Density")))!=null)setDensity(ImageFilterUtil.toFloatValue(o,"Density"));
304                if((o=parameters.removeEL(KeyImpl.init("Distance")))!=null)setDistance(ImageFilterUtil.toIntValue(o,"Distance"));
305                if((o=parameters.removeEL(KeyImpl.init("Shape")))!=null)setShape(ImageFilterUtil.toIntValue(o,"Shape"));
306                if((o=parameters.removeEL(KeyImpl.init("Scatter")))!=null)setScatter(ImageFilterUtil.toFloatValue(o,"Scatter"));
307                if((o=parameters.removeEL(KeyImpl.init("Mix")))!=null)setMix(ImageFilterUtil.toFloatValue(o,"Mix"));
308                if((o=parameters.removeEL(KeyImpl.init("Fadeout")))!=null)setFadeout(ImageFilterUtil.toIntValue(o,"Fadeout"));
309                if((o=parameters.removeEL(KeyImpl.init("Background")))!=null)setBackground(ImageFilterUtil.toBooleanValue(o,"Background"));
310
311                // check for arguments not supported
312                if(parameters.size()>0) {
313                        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 [Angle, Density, Distance, Shape, Scatter, Mix, Fadeout, Background]");
314                }
315
316                return filter(src, dst);
317        }
318}