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.type.KeyImpl;
042import lucee.runtime.type.Struct;
043import lucee.runtime.type.util.CollectionUtil;
044
045/**
046 * A filter which produces a water ripple distortion.
047 */
048public class WaterFilter extends TransformFilter  implements DynFiltering {
049        
050        private float wavelength = 16;
051        private float amplitude = 10;
052        private float phase = 0;
053        private float centreX = 0.5f;
054        private float centreY = 0.5f;
055        private float radius = 50;
056
057        private float radius2 = 0;
058        private float icentreX;
059        private float icentreY;
060
061        public WaterFilter() {
062                super(ConvolveFilter.CLAMP_EDGES );
063        }
064
065        /**
066         * Set the wavelength of the ripples.
067         * @param wavelength the wavelength
068     * @see #getWavelength
069         */
070        public void setWavelength(float wavelength) {
071                this.wavelength = wavelength;
072        }
073
074        /**
075         * Get the wavelength of the ripples.
076         * @return the wavelength
077     * @see #setWavelength
078         */
079        public float getWavelength() {
080                return wavelength;
081        }
082
083        /**
084         * Set the amplitude of the ripples.
085         * @param amplitude the amplitude
086     * @see #getAmplitude
087         */
088        public void setAmplitude(float amplitude) {
089                this.amplitude = amplitude;
090        }
091
092        /**
093         * Get the amplitude of the ripples.
094         * @return the amplitude
095     * @see #setAmplitude
096         */
097        public float getAmplitude() {
098                return amplitude;
099        }
100
101        /**
102         * Set the phase of the ripples.
103         * @param phase the phase
104     * @see #getPhase
105         */
106        public void setPhase(float phase) {
107                this.phase = phase;
108        }
109
110        /**
111         * Get the phase of the ripples.
112         * @return the phase
113     * @see #setPhase
114         */
115        public float getPhase() {
116                return phase;
117        }
118
119        /**
120         * Set the centre of the effect in the X direction as a proportion of the image size.
121         * @param centreX the center
122     * @see #getCentreX
123         */
124        public void setCentreX( float centreX ) {
125                this.centreX = centreX;
126        }
127
128        /**
129         * Get the centre of the effect in the X direction as a proportion of the image size.
130         * @return the center
131     * @see #setCentreX
132         */
133        public float getCentreX() {
134                return centreX;
135        }
136        
137        /**
138         * Set the centre of the effect in the Y direction as a proportion of the image size.
139         * @param centreY the center
140     * @see #getCentreY
141         */
142        public void setCentreY( float centreY ) {
143                this.centreY = centreY;
144        }
145
146        /**
147         * Get the centre of the effect in the Y direction as a proportion of the image size.
148         * @return the center
149     * @see #setCentreY
150         */
151        public float getCentreY() {
152                return centreY;
153        }
154        
155        /**
156         * Set the centre of the effect as a proportion of the image size.
157         * @param centre the center
158     * @see #getCentre
159         */
160        public void setCentre( Point2D centre ) {
161                this.centreX = (float)centre.getX();
162                this.centreY = (float)centre.getY();
163        }
164
165        /**
166         * Get the centre of the effect as a proportion of the image size.
167         * @return the center
168     * @see #setCentre
169         */
170        public Point2D getCentre() {
171                return new Point2D.Float( centreX, centreY );
172        }
173        
174        /**
175         * Set the radius of the effect.
176         * @param radius the radius
177     * @min-value 0
178     * @see #getRadius
179         */
180        public void setRadius(float radius) {
181                this.radius = radius;
182        }
183
184        /**
185         * Get the radius of the effect.
186         * @return the radius
187     * @see #setRadius
188         */
189        public float getRadius() {
190                return radius;
191        }
192
193        private boolean inside(int v, int a, int b) {
194                return a <= v && v <= b;
195        }
196        
197    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
198                icentreX = src.getWidth() * centreX;
199                icentreY = src.getHeight() * centreY;
200                if ( radius == 0 )
201                        radius = Math.min(icentreX, icentreY);
202                radius2 = radius*radius;
203                return super.filter( src, dst );
204        }
205        
206        protected void transformInverse(int x, int y, float[] out) {
207                float dx = x-icentreX;
208                float dy = y-icentreY;
209                float distance2 = dx*dx + dy*dy;
210                if (distance2 > radius2) {
211                        out[0] = x;
212                        out[1] = y;
213                } else {
214                        float distance = (float)Math.sqrt(distance2);
215                        float amount = amplitude * (float)Math.sin(distance / wavelength * ImageMath.TWO_PI - phase);
216                        amount *= (radius-distance)/radius;
217                        if ( distance != 0 )
218                                amount *= wavelength/distance;
219                        out[0] = x + dx*amount;
220                        out[1] = y + dy*amount;
221                }
222        }
223
224        public String toString() {
225                return "Distort/Water Ripples...";
226        }
227        
228        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=null;//ImageUtil.createBufferedImage(src);
229                Object o;
230                if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toFloatValue(o,"Radius"));
231                if((o=parameters.removeEL(KeyImpl.init("CentreX")))!=null)setCentreX(ImageFilterUtil.toFloatValue(o,"CentreX"));
232                if((o=parameters.removeEL(KeyImpl.init("CentreY")))!=null)setCentreY(ImageFilterUtil.toFloatValue(o,"CentreY"));
233                //if((o=parameters.removeEL(KeyImpl.init("Centre")))!=null)setCentre(ImageFilterUtil.toPoint2D(o,"Centre"));
234                if((o=parameters.removeEL(KeyImpl.init("Wavelength")))!=null)setWavelength(ImageFilterUtil.toFloatValue(o,"Wavelength"));
235                if((o=parameters.removeEL(KeyImpl.init("Amplitude")))!=null)setAmplitude(ImageFilterUtil.toFloatValue(o,"Amplitude"));
236                if((o=parameters.removeEL(KeyImpl.init("Phase")))!=null)setPhase(ImageFilterUtil.toFloatValue(o,"Phase"));
237                if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction"));
238                if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toString(o,"Interpolation"));
239
240                // check for arguments not supported
241                if(parameters.size()>0) {
242                        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, CentreX, CentreY, Centre, Wavelength, Amplitude, Phase, EdgeAction, Interpolation]");
243                }
244
245                return filter(src, dst);
246        }
247}