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.Point;
036import java.awt.image.BufferedImage;
037
038import lucee.runtime.engine.ThreadLocalPageContext;
039import lucee.runtime.exp.FunctionException;
040import lucee.runtime.exp.PageException;
041import lucee.runtime.img.ImageUtil;
042import lucee.runtime.type.KeyImpl;
043import lucee.runtime.type.Struct;
044import lucee.runtime.type.util.CollectionUtil;
045
046/**
047 * A filter which draws a coloured gradient. This is largely superceded by GradientPaint in Java1.2, but does provide a few
048 * more gradient options.
049 */
050public class GradientFilter extends AbstractBufferedImageOp  implements DynFiltering {
051
052        public final static int LINEAR = 0;
053        public final static int BILINEAR = 1;
054        public final static int RADIAL = 2;
055        public final static int CONICAL = 3;
056        public final static int BICONICAL = 4;
057        public final static int SQUARE = 5;
058
059        public final static int INT_LINEAR = 0;
060        public final static int INT_CIRCLE_UP = 1;
061        public final static int INT_CIRCLE_DOWN = 2;
062        public final static int INT_SMOOTH = 3;
063
064        private float angle = 0;
065        private int color1 = 0xff000000;
066        private int color2 = 0xffffffff;
067        private Point p1 = new Point(0, 0), p2 = new Point(64, 64);
068        private boolean repeat = false;
069        private float x1;
070        private float y1;
071        private float dx;
072        private float dy;
073        private Colormap colormap = null;
074        private int type;
075        private int interpolation = INT_LINEAR;
076        private int paintMode = PixelUtils.NORMAL;
077
078        public GradientFilter() {
079                colormap = new LinearColormap(color1, color2);
080        }
081
082        public GradientFilter(Point p1, Point p2, int color1, int color2, boolean repeat, int type, int interpolation) {
083                this.p1 = p1;
084                this.p2 = p2;
085                this.color1 = color1;
086                this.color2 = color2;
087                this.repeat = repeat;
088                this.type = type;
089                this.interpolation = interpolation;
090                colormap = new LinearColormap(color1, color2);
091        }
092
093        public void setPoint1(Point point1) {
094                this.p1 = point1;
095        }
096
097        public Point getPoint1() {
098                return p1;
099        }
100
101        public void setPoint2(Point point2) {
102                this.p2 = point2;
103        }
104
105        public Point getPoint2() {
106                return p2;
107        }
108
109        public void setType(int type) {
110                this.type = type;
111        }
112
113        public int getType() {
114                return type;
115        }
116
117        public void setInterpolation(int interpolation) {
118                this.interpolation = interpolation;
119        }
120
121        public int getInterpolation() {
122                return interpolation;
123        }
124
125        /**
126     * Specifies the angle of the texture.
127     * @param angle the angle of the texture.
128     * @angle
129     * @see #getAngle
130     */
131        public void setAngle(float angle) {
132                this.angle = angle;
133                p2 = new Point((int)(64*Math.cos(angle)), (int)(64*Math.sin(angle)));
134        }
135        
136        /**
137     * Returns the angle of the texture.
138     * @return the angle of the texture.
139     * @see #setAngle
140     */
141        public float getAngle() {
142                return angle;
143        }
144        
145    /**
146     * Set the colormap to be used for the filter.
147     * @param colormap the colormap
148     * @see #getColormap
149     */
150        public void setColormap(Colormap colormap) {
151                this.colormap = colormap;
152        }
153        
154    /**
155     * Get the colormap to be used for the filter.
156     * @return the colormap
157     * @see #setColormap
158     */
159        public Colormap getColormap() {
160                return colormap;
161        }
162        
163        public void setPaintMode(int paintMode) {
164                this.paintMode = paintMode;
165        }
166
167        public int getPaintMode() {
168                return paintMode;
169        }
170
171    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
172        int width = src.getWidth();
173        int height = src.getHeight();
174
175        if ( dst == null )
176            dst = createCompatibleDestImage( src, null );
177
178                //int rgb1, rgb2;
179                float x1, y1, x2, y2;
180                x1 = p1.x;
181                x2 = p2.x;
182
183                if (x1 > x2 && type != RADIAL) {
184                        y1 = x1;
185                        x1 = x2;
186                        x2 = y1;
187                        y1 = p2.y;
188                        y2 = p1.y;
189                        //rgb1 = color2;
190                        //rgb2 = color1;
191                } else {
192                        y1 = p1.y;
193                        y2 = p2.y;
194                        //rgb1 = color1;
195                        //rgb2 = color2;
196                }
197                float dx = x2 - x1;
198                float dy = y2 - y1;
199                float lenSq = dx * dx + dy * dy;
200                this.x1 = x1;
201                this.y1 = y1;
202                if (lenSq >= Float.MIN_VALUE) {
203                        dx = dx / lenSq;
204                        dy = dy / lenSq;
205                        if (repeat) {
206                                dx = dx % 1.0f;
207                                dy = dy % 1.0f;
208                        }
209                }
210                this.dx = dx;
211                this.dy = dy;
212
213        int[] pixels = new int[width];
214        for (int y = 0; y < height; y++ ) {
215                        getRGB( src, 0, y, width, 1, pixels );
216                        switch (type) {
217                        case LINEAR:
218                        case BILINEAR:
219                                linearGradient(pixels, y, width, 1);
220                                break;
221                        case RADIAL:
222                                radialGradient(pixels, y, width, 1);
223                                break;
224                        case CONICAL:
225                        case BICONICAL:
226                                conicalGradient(pixels, y, width, 1);
227                                break;
228                        case SQUARE:
229                                squareGradient(pixels, y, width, 1);
230                                break;
231                        }
232                        setRGB( dst, 0, y, width, 1, pixels );
233        }
234                return dst;
235    }
236
237        private void repeatGradient(int[] pixels, int w, int h, float rowrel, float dx, float dy) {
238                int off = 0;
239                for (int y = 0; y < h; y++) {
240                        float colrel = rowrel;
241                        int j = w;
242                        int rgb;
243                        while (--j >= 0) {
244                                if (type == BILINEAR)
245                                        rgb = colormap.getColor(map(ImageMath.triangle(colrel)));
246                                else
247                                        rgb = colormap.getColor(map(ImageMath.mod(colrel, 1.0f)));
248                                pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode);
249                                off++;
250                                colrel += dx;
251                        }
252                        rowrel += dy;
253                }
254        }
255
256        private void singleGradient(int[] pixels, int w, int h, float rowrel, float dx, float dy) {
257                int off = 0;
258                for (int y = 0; y < h; y++) {
259                        float colrel = rowrel;
260                        int j = w;
261                        int rgb;
262                        if (colrel <= 0.0) {
263                                rgb = colormap.getColor(0);
264                                do {
265                                        pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode);
266                                        off++;
267                                        colrel += dx;
268                                } while (--j > 0 && colrel <= 0.0);
269                        }
270                        while (colrel < 1.0 && --j >= 0) {
271                                if (type == BILINEAR)
272                                        rgb = colormap.getColor(map(ImageMath.triangle(colrel)));
273                                else
274                                        rgb = colormap.getColor(map(colrel));
275                                pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode);
276                                off++;
277                                colrel += dx;
278                        }
279                        if (j > 0) {
280                                if (type == BILINEAR)
281                                        rgb = colormap.getColor(0.0f);
282                                else
283                                        rgb = colormap.getColor(1.0f);
284                                do {
285                                        pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode);
286                                        off++;
287                                } while (--j > 0);
288                        }
289                        rowrel += dy;
290                }
291        }
292
293        private void linearGradient(int[] pixels, int y, int w, int h) {
294                int x = 0;
295                float rowrel = (x - x1) * dx + (y - y1) * dy;
296                if (repeat)
297                        repeatGradient(pixels, w, h, rowrel, dx, dy);
298                else
299                        singleGradient(pixels, w, h, rowrel, dx, dy);
300        }
301        
302        private void radialGradient(int[] pixels, int y, int w, int h) {
303                int off = 0;
304                float radius = distance(p2.x-p1.x, p2.y-p1.y);
305                for (int x = 0; x < w; x++) {
306                        float distance = distance(x-p1.x, y-p1.y);
307                        float ratio = distance / radius;
308                        if (repeat)
309                                ratio = ratio % 2;
310                        else if (ratio > 1.0)
311                                ratio = 1.0f;
312                        int rgb = colormap.getColor(map(ratio));
313                        pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode);
314                        off++;
315                }
316        }
317        
318        private void squareGradient(int[] pixels, int y, int w, int h) {
319                int off = 0;
320                float radius = Math.max(Math.abs(p2.x-p1.x), Math.abs(p2.y-p1.y));
321                for (int x = 0; x < w; x++) {
322                        float distance = Math.max(Math.abs(x-p1.x), Math.abs(y-p1.y));
323                        float ratio = distance / radius;
324                        if (repeat)
325                                ratio = ratio % 2;
326                        else if (ratio > 1.0)
327                                ratio = 1.0f;
328                        int rgb = colormap.getColor(map(ratio));
329                        pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode);
330                        off++;
331                }
332        }
333        
334        private void conicalGradient(int[] pixels, int y, int w, int h) {
335                int off = 0;
336                float angle0 = (float)Math.atan2(p2.x-p1.x, p2.y-p1.y);
337                for (int x = 0; x < w; x++) {
338                        float angle = (float)(Math.atan2(x-p1.x, y-p1.y) - angle0) / (ImageMath.TWO_PI);
339                        angle += 1.0f;
340                        angle %= 1.0f;
341                        if (type == BICONICAL)
342                                angle = ImageMath.triangle(angle);
343                        int rgb = colormap.getColor(map(angle));
344                        pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode);
345                        off++;
346                }
347        }
348        
349        private float map(float v) {
350                if (repeat)
351                        v = v > 1.0 ? 2.0f-v : v;
352                switch (interpolation) {
353                case INT_CIRCLE_UP:
354                        v = ImageMath.circleUp(ImageMath.clamp(v, 0.0f, 1.0f));
355                        break;
356                case INT_CIRCLE_DOWN:
357                        v = ImageMath.circleDown(ImageMath.clamp(v, 0.0f, 1.0f));
358                        break;
359                case INT_SMOOTH:
360                        v = ImageMath.smoothStep(0, 1, v);
361                        break;
362                }
363                return v;
364        }
365        
366        private float distance(float a, float b) {
367                return (float)Math.sqrt(a*a+b*b);
368        }
369        
370        public String toString() {
371                return "Other/Gradient Fill...";
372        }
373        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
374                Object o;
375                if((o=parameters.removeEL(KeyImpl.init("Colormap")))!=null)setColormap(ImageFilterUtil.toColormap(o,"Colormap"));
376                if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toIntValue(o,"Interpolation"));
377                if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle"));
378                if((o=parameters.removeEL(KeyImpl.init("Point1")))!=null)setPoint1(ImageFilterUtil.toPoint(o,"Point1"));
379                if((o=parameters.removeEL(KeyImpl.init("Point2")))!=null)setPoint2(ImageFilterUtil.toPoint(o,"Point2"));
380                if((o=parameters.removeEL(KeyImpl.init("PaintMode")))!=null)setPaintMode(ImageFilterUtil.toIntValue(o,"PaintMode"));
381                if((o=parameters.removeEL(KeyImpl.init("Type")))!=null)setType(ImageFilterUtil.toIntValue(o,"Type"));
382
383                // check for arguments not supported
384                if(parameters.size()>0) {
385                        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, Interpolation, Angle, Point1, Point2, PaintMode, Type]");
386                }
387
388                return filter(src, dst);
389        }
390}