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.geom.Point2D;
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.img.math.Noise;
043import lucee.runtime.type.KeyImpl;
044import lucee.runtime.type.Struct;
045import lucee.runtime.type.util.CollectionUtil;
046
047/**
048 * An experimental filter for rendering lens flares.
049 */
050public class FlareFilter extends PointFilter  implements DynFiltering {
051
052        private int rays = 50;
053        private float radius;
054        private float baseAmount = 1.0f;
055        private float ringAmount = 0.2f;
056        private float rayAmount = 0.1f;
057        private int color = 0xffffffff;
058        private int width, height;
059        private float centreX = 0.5f, centreY = 0.5f;
060        private float ringWidth = 1.6f;
061        
062        private float linear = 0.03f;
063        private float gauss = 0.006f;
064        private float mix = 0.50f;
065        private float falloff = 6.0f;
066        private float sigma;
067
068        private float icentreX, icentreY;
069
070        public FlareFilter() {
071                setRadius(50.0f);
072        }
073
074        public void setColor(int color) {
075                this.color = color;
076        }
077
078        public int getColor() {
079                return color;
080        }
081
082        public void setRingWidth(float ringWidth) {
083                this.ringWidth = ringWidth;
084        }
085
086        public float getRingWidth() {
087                return ringWidth;
088        }
089        
090        public void setBaseAmount(float baseAmount) {
091                this.baseAmount = baseAmount;
092        }
093
094        public float getBaseAmount() {
095                return baseAmount;
096        }
097
098        public void setRingAmount(float ringAmount) {
099                this.ringAmount = ringAmount;
100        }
101
102        public float getRingAmount() {
103                return ringAmount;
104        }
105
106        public void setRayAmount(float rayAmount) {
107                this.rayAmount = rayAmount;
108        }
109
110        public float getRayAmount() {
111                return rayAmount;
112        }
113
114        public void setCentreY( float centreY ) {
115                this.centreY = centreY;
116        }
117
118        public void setCentreX( float centreX ) {
119                this.centreX = centreX;
120        }
121
122        /*public void setCentre( Point2D centre ) {
123                this.centreX = (float)centre.getX();
124                this.centreY = (float)centre.getY();
125        }*/
126
127        public Point2D getCentre() {
128                return new Point2D.Float( centreX, centreY );
129        }
130        
131        /**
132         * Set the radius of the effect.
133         * @param radius the radius
134     * @min-value 0
135     * @see #getRadius
136         */
137        public void setRadius(float radius) {
138                this.radius = radius;
139                sigma = radius/3;
140        }
141
142        /**
143         * Get the radius of the effect.
144         * @return the radius
145     * @see #setRadius
146         */
147        public float getRadius() {
148                return radius;
149        }
150
151        public void setDimensions(int width, int height) {
152                this.width = width;
153                this.height = height;
154                icentreX = centreX*width;
155                icentreY = centreY*height;
156                super.setDimensions(width, height);
157        }
158        
159        public int filterRGB(int x, int y, int rgb) {
160                float dx = x-icentreX;
161                float dy = y-icentreY;
162                float distance = (float)Math.sqrt(dx*dx+dy*dy);
163                float a = (float)Math.exp(-distance*distance*gauss)*mix + (float)Math.exp(-distance*linear)*(1-mix);
164                float ring;
165
166                a *= baseAmount;
167
168                if (distance > radius + ringWidth)
169                        a = ImageMath.lerp((distance - (radius + ringWidth))/falloff, a, 0);
170
171                if (distance < radius - ringWidth || distance > radius + ringWidth)
172                        ring = 0;
173                else {
174                ring = Math.abs(distance-radius)/ringWidth;
175                ring = 1 - ring*ring*(3 - 2*ring);
176                ring *= ringAmount;
177                }
178
179                a += ring;
180
181                float angle = (float)Math.atan2(dx, dy)+ImageMath.PI;
182                angle = (ImageMath.mod(angle/ImageMath.PI*17 + 1.0f + Noise.noise1(angle*10), 1.0f) - 0.5f)*2;
183                angle = Math.abs(angle);
184                angle = (float)Math.pow(angle, 5.0);
185
186                float b = rayAmount * angle / (1 + distance*0.1f);
187                a += b;
188
189                a = ImageMath.clamp(a, 0, 1);
190                return ImageMath.mixColors(a, rgb, color);
191        }
192
193        public String toString() {
194                return "Stylize/Flare...";
195        }
196        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
197                Object o;
198                if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toFloatValue(o,"Radius"));
199                //if((o=parameters.removeEL(KeyImpl.init("Centre")))!=null)setCentre(ImageFilterUtil.toPoint2D(o,"Centre"));
200                if((o=parameters.removeEL(KeyImpl.init("RingWidth")))!=null)setRingWidth(ImageFilterUtil.toFloatValue(o,"RingWidth"));
201                if((o=parameters.removeEL(KeyImpl.init("BaseAmount")))!=null)setBaseAmount(ImageFilterUtil.toFloatValue(o,"BaseAmount"));
202                if((o=parameters.removeEL(KeyImpl.init("RingAmount")))!=null)setRingAmount(ImageFilterUtil.toFloatValue(o,"RingAmount"));
203                if((o=parameters.removeEL(KeyImpl.init("RayAmount")))!=null)setRayAmount(ImageFilterUtil.toFloatValue(o,"RayAmount"));
204                if((o=parameters.removeEL(KeyImpl.init("Color")))!=null)setColor(ImageFilterUtil.toColorRGB(o,"Color"));
205                if((o=parameters.removeEL(KeyImpl.init("Dimensions")))!=null){
206                        int[] dim=ImageFilterUtil.toDimensions(o,"Dimensions");
207                        setDimensions(dim[0],dim[1]);
208                }
209
210                // check for arguments not supported
211                if(parameters.size()>0) {
212                        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 [Radius, Centre, RingWidth, BaseAmount, RingAmount, RayAmount, Color, Dimensions]");
213                }
214
215                return filter(src, dst);
216        }
217}