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;
037
038import lucee.runtime.engine.ThreadLocalPageContext;
039import lucee.runtime.exp.ExpressionException;
040import lucee.runtime.exp.FunctionException;
041import lucee.runtime.exp.PageException;
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 * A filter which distorts an image by rippling it in the X or Y directions.
049 * The amplitude and wavelength of rippling can be specified as well as whether 
050 * pixels going off the edges are wrapped or not.
051 */
052public class RippleFilter extends TransformFilter  implements DynFiltering {
053        
054    /**
055     * Sine wave ripples.
056     */
057        public final static int SINE = 0;
058
059    /**
060     * Sawtooth wave ripples.
061     */
062        public final static int SAWTOOTH = 1;
063
064    /**
065     * Triangle wave ripples.
066     */
067        public final static int TRIANGLE = 2;
068
069    /**
070     * Noise ripples.
071     */
072        public final static int NOISE = 3;
073
074        private float xAmplitude, yAmplitude;
075        private float xWavelength, yWavelength;
076        private int waveType;
077
078        /**
079         * Construct a RippleFilter.
080         */
081        public RippleFilter() {
082                xAmplitude = 5.0f;
083                yAmplitude = 0.0f;
084                xWavelength = yWavelength = 16.0f;
085        }
086
087        /**
088         * Set the amplitude of ripple in the X direction.
089         * @param xAmplitude the amplitude (in pixels).
090     * @see #getXAmplitude
091         */
092        public void setXAmplitude(float xAmplitude) {
093                this.xAmplitude = xAmplitude;
094        }
095
096        /**
097         * Get the amplitude of ripple in the X direction.
098         * @return the amplitude (in pixels).
099     * @see #setXAmplitude
100         */
101        public float getXAmplitude() {
102                return xAmplitude;
103        }
104
105        /**
106         * Set the wavelength of ripple in the X direction.
107         * @param xWavelength the wavelength (in pixels).
108     * @see #getXWavelength
109         */
110        public void setXWavelength(float xWavelength) {
111                this.xWavelength = xWavelength;
112        }
113
114        /**
115         * Get the wavelength of ripple in the X direction.
116         * @return the wavelength (in pixels).
117     * @see #setXWavelength
118         */
119        public float getXWavelength() {
120                return xWavelength;
121        }
122
123        /**
124         * Set the amplitude of ripple in the Y direction.
125         * @param yAmplitude the amplitude (in pixels).
126     * @see #getYAmplitude
127         */
128        public void setYAmplitude(float yAmplitude) {
129                this.yAmplitude = yAmplitude;
130        }
131
132        /**
133         * Get the amplitude of ripple in the Y direction.
134         * @return the amplitude (in pixels).
135     * @see #setYAmplitude
136         */
137        public float getYAmplitude() {
138                return yAmplitude;
139        }
140
141        /**
142         * Set the wavelength of ripple in the Y direction.
143         * @param yWavelength the wavelength (in pixels).
144     * @see #getYWavelength
145         */
146        public void setYWavelength(float yWavelength) {
147                this.yWavelength = yWavelength;
148        }
149
150        /**
151         * Get the wavelength of ripple in the Y direction.
152         * @return the wavelength (in pixels).
153     * @see #setYWavelength
154         */
155        public float getYWavelength() {
156                return yWavelength;
157        }
158
159
160        /**
161         * Set the wave type.
162         * valid values are:
163         * - sine (default):  Sine wave ripples.
164         * - sawtooth: Sawtooth wave ripples.
165         * - triangle: Triangle wave ripples.
166         * - noise: Noise ripples.
167     * @param waveType the type.
168         * @throws ExpressionException 
169     * @see #getWaveType
170         */
171        public void setWaveType(String waveType) throws ExpressionException {
172
173                String str=waveType.trim().toUpperCase();
174                if("SINE".equals(str)) this.waveType = SINE;
175                else if("SAWTOOTH".equals(str)) this.waveType = SAWTOOTH;
176                else if("TRIANGLE".equals(str)) this.waveType = TRIANGLE;
177                else if("NOISE".equals(str)) this.waveType = NOISE;
178                else 
179                        throw new ExpressionException("invalid value ["+waveType+"] for waveType, valid values are [sine,sawtooth,triangle,noise]");
180        
181        }
182
183        /**
184         * Get the wave type.
185         * @return the type.
186     * @see #setWaveType
187         */
188        public int getWaveType() {
189                return waveType;
190        }
191
192        protected void transformSpace(Rectangle r) {
193                if (edgeAction == ConvolveFilter.ZERO_EDGES) {
194                        r.x -= (int)xAmplitude;
195                        r.width += (int)(2*xAmplitude);
196                        r.y -= (int)yAmplitude;
197                        r.height += (int)(2*yAmplitude);
198                }
199        }
200
201        protected void transformInverse(int x, int y, float[] out) {
202                float nx = y / xWavelength;
203                float ny = x / yWavelength;
204                float fx, fy;
205                switch (waveType) {
206                case SINE:
207                default:
208                        fx = (float)Math.sin(nx);
209                        fy = (float)Math.sin(ny);
210                        break;
211                case SAWTOOTH:
212                        fx = ImageMath.mod(nx, 1);
213                        fy = ImageMath.mod(ny, 1);
214                        break;
215                case TRIANGLE:
216                        fx = ImageMath.triangle(nx);
217                        fy = ImageMath.triangle(ny);
218                        break;
219                case NOISE:
220                        fx = Noise.noise1(nx);
221                        fy = Noise.noise1(ny);
222                        break;
223                }
224                out[0] = x + xAmplitude * fx;
225                out[1] = y + yAmplitude * fy;
226        }
227
228        public String toString() {
229                return "Distort/Ripple...";
230        }
231
232        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {
233                //BufferedImage dst=ImageUtil.createBufferedImage(src,src.getWidth()+400,src.getHeight()+400);
234                Object o;
235                if((o=parameters.removeEL(KeyImpl.init("XAmplitude")))!=null)setXAmplitude(ImageFilterUtil.toFloatValue(o,"XAmplitude"));
236                if((o=parameters.removeEL(KeyImpl.init("XWavelength")))!=null)setXWavelength(ImageFilterUtil.toFloatValue(o,"XWavelength"));
237                if((o=parameters.removeEL(KeyImpl.init("YAmplitude")))!=null)setYAmplitude(ImageFilterUtil.toFloatValue(o,"YAmplitude"));
238                if((o=parameters.removeEL(KeyImpl.init("YWavelength")))!=null)setYWavelength(ImageFilterUtil.toFloatValue(o,"YWavelength"));
239                if((o=parameters.removeEL(KeyImpl.init("WaveType")))!=null)setWaveType(ImageFilterUtil.toString(o,"WaveType"));
240                if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction"));
241                if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toString(o,"Interpolation"));
242
243                // check for arguments not supported
244                if(parameters.size()>0) {
245                        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 [XAmplitude, XWavelength, YAmplitude, YWavelength, WaveType, EdgeAction, Interpolation]");
246                }
247
248                return filter(src, (BufferedImage)null);
249        }
250}