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