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