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 distorts an image by twisting it from the centre out.
047 * The twisting is centred at the centre of the image and extends out to the smallest of
048 * the width and height. Pixels outside this radius are unaffected.
049 */
050public class TwirlFilter extends TransformFilter  implements DynFiltering {
051        
052        private float angle = 0;
053        private float centreX = 0.5f;
054        private float centreY = 0.5f;
055        private float radius = 100;
056
057        private float radius2 = 0;
058        private float icentreX;
059        private float icentreY;
060
061        /**
062         * Construct a TwirlFilter with no distortion.
063         */
064        public TwirlFilter() {
065                super(ConvolveFilter.CLAMP_EDGES );
066        }
067
068        /**
069         * Set the angle of twirl in radians. 0 means no distortion.
070         * @param angle the angle of twirl. This is the angle by which pixels at the nearest edge of the image will move.
071     * @see #getAngle
072         */
073        public void setAngle(float angle) {
074                this.angle = angle;
075        }
076        
077        /**
078         * Get the angle of twist.
079         * @return the angle in radians.
080     * @see #setAngle
081         */
082        public float getAngle() {
083                return angle;
084        }
085        
086        /**
087         * Set the centre of the effect in the X direction as a proportion of the image size.
088         * @param centreX the center
089     * @see #getCentreX
090         */
091        public void setCentreX( float centreX ) {
092                this.centreX = centreX;
093        }
094
095        /**
096         * Get the centre of the effect in the X direction as a proportion of the image size.
097         * @return the center
098     * @see #setCentreX
099         */
100        public float getCentreX() {
101                return centreX;
102        }
103        
104        /**
105         * Set the centre of the effect in the Y direction as a proportion of the image size.
106         * @param centreY the center
107     * @see #getCentreY
108         */
109        public void setCentreY( float centreY ) {
110                this.centreY = centreY;
111        }
112
113        /**
114         * Get the centre of the effect in the Y direction as a proportion of the image size.
115         * @return the center
116     * @see #setCentreY
117         */
118        public float getCentreY() {
119                return centreY;
120        }
121        
122        /**
123         * Set the centre of the effect as a proportion of the image size.
124         * @param centre the center
125     * @see #getCentre
126         */
127        public void setCentre( Point2D centre ) {
128                this.centreX = (float)centre.getX();
129                this.centreY = (float)centre.getY();
130        }
131
132        /**
133         * Get the centre of the effect as a proportion of the image size.
134         * @return the center
135     * @see #setCentre
136         */
137        public Point2D getCentre() {
138                return new Point2D.Float( centreX, centreY );
139        }
140        
141        /**
142         * Set the radius of the effect.
143         * @param radius the radius
144     * @min-value 0
145     * @see #getRadius
146         */
147        public void setRadius(float radius) {
148                this.radius = radius;
149        }
150
151        /**
152         * Get the radius of the effect.
153         * @return the radius
154     * @see #setRadius
155         */
156        public float getRadius() {
157                return radius;
158        }
159
160    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
161                icentreX = src.getWidth() * centreX;
162                icentreY = src.getHeight() * centreY;
163                if ( radius == 0 )
164                        radius = Math.min(icentreX, icentreY);
165                radius2 = radius*radius;
166                return super.filter( src, dst );
167        }
168        
169        protected void transformInverse(int x, int y, float[] out) {
170                float dx = x-icentreX;
171                float dy = y-icentreY;
172                float distance = dx*dx + dy*dy;
173                if (distance > radius2) {
174                        out[0] = x;
175                        out[1] = y;
176                } else {
177                        distance = (float)Math.sqrt(distance);
178                        float a = (float)Math.atan2(dy, dx) + angle * (radius-distance) / radius;
179                        out[0] = icentreX + distance*(float)Math.cos(a);
180                        out[1] = icentreY + distance*(float)Math.sin(a);
181                }
182        }
183
184        public String toString() {
185                return "Distort/Twirl...";
186        }
187
188        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=null;//ImageUtil.createBufferedImage(src);
189                Object o;
190                if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toFloatValue(o,"Radius"));
191                if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle"));
192                if((o=parameters.removeEL(KeyImpl.init("CentreX")))!=null)setCentreX(ImageFilterUtil.toFloatValue(o,"CentreX"));
193                if((o=parameters.removeEL(KeyImpl.init("CentreY")))!=null)setCentreY(ImageFilterUtil.toFloatValue(o,"CentreY"));
194                //if((o=parameters.removeEL(KeyImpl.init("Centre")))!=null)setCentre(ImageFilterUtil.toPoint2D(o,"Centre"));
195                if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction"));
196                if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toString(o,"Interpolation"));
197
198                // check for arguments not supported
199                if(parameters.size()>0) {
200                        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, Angle, CentreX, CentreY, Centre, EdgeAction, Interpolation]");
201                }
202
203                return filter(src, dst);
204        }
205}