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.image.BufferedImage;
036
037import lucee.runtime.engine.ThreadLocalPageContext;
038import lucee.runtime.exp.ExpressionException;
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 and image by performing coordinate conversions between rectangular and polar coordinates.
047 */
048public class PolarFilter extends TransformFilter  implements DynFiltering {
049        
050        /**
051     * Convert from rectangular to polar coordinates.
052     */
053    public final static int RECT_TO_POLAR = 0;
054
055        /**
056     * Convert from polar to rectangular coordinates.
057     */
058        public final static int POLAR_TO_RECT = 1;
059
060        /**
061     * Invert the image in a circle.
062     */
063        public final static int INVERT_IN_CIRCLE = 2;
064
065        private int type;
066        private float width, height;
067        private float centreX, centreY;
068        private float radius;
069
070        /**
071     * Construct a PolarFilter.
072     */
073    public PolarFilter() {
074                this(RECT_TO_POLAR);
075        }
076
077        /**
078     * Construct a PolarFilter.
079     * @param type the distortion type
080     */
081        public PolarFilter(int type) {
082                super(ConvolveFilter.CLAMP_EDGES);
083                this.type=type;
084        }
085
086    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
087                this.width = src.getWidth();
088                this.height = src.getHeight();
089                centreX = width/2;
090                centreY = height/2;
091                radius = Math.max(centreY, centreX);
092                return super.filter( src, dst );
093        }
094        
095        /**
096     * Set the distortion type, valid values are
097     * - RECT_TO_POLAR = Convert from rectangular to polar coordinates
098     * - POLAR_TO_RECT = Convert from polar to rectangular coordinates
099     * - INVERT_IN_CIRCLE = Invert the image in a circle
100     */
101        public void setType(String type) throws ExpressionException {
102                type=type.trim().toUpperCase();
103                if("RECT_TO_POLAR".equals(type)) this.type = RECT_TO_POLAR;
104                else if("POLAR_TO_RECT".equals(type)) this.type = POLAR_TO_RECT;
105                else if("INVERT_IN_CIRCLE".equals(type)) this.type = INVERT_IN_CIRCLE;
106                else
107                        throw new ExpressionException("inavlid type defintion ["+type+"], valid types are [RECT_TO_POLAR,POLAR_TO_RECT,INVERT_IN_CIRCLE]");
108        }
109
110        /**
111     * Get the distortion type.
112     * @return the distortion type
113     * @see #setType
114     */
115        public int getType() {
116                return type;
117        }
118
119        private float sqr(float x) {
120                return x*x;
121        }
122
123        protected void transformInverse(int x, int y, float[] out) {
124                float theta, t;
125                float m, xmax, ymax;
126                float r = 0;
127                
128                switch (type) {
129                case RECT_TO_POLAR:
130                        theta = 0;
131                        if (x >= centreX) {
132                                if (y > centreY) {
133                                        theta = ImageMath.PI - (float)Math.atan(((x - centreX))/((y - centreY)));
134                                        r = (float)Math.sqrt(sqr (x - centreX) + sqr (y - centreY));
135                                } else if (y < centreY) {
136                                        theta = (float)Math.atan (((x - centreX))/((centreY - y)));
137                                        r = (float)Math.sqrt (sqr (x - centreX) + sqr (centreY - y));
138                                } else {
139                                        theta = ImageMath.HALF_PI;
140                                        r = x - centreX;
141                                }
142                        } else if (x < centreX) {
143                                if (y < centreY) {
144                                        theta = ImageMath.TWO_PI - (float)Math.atan (((centreX -x))/((centreY - y)));
145                                        r = (float)Math.sqrt (sqr (centreX - x) + sqr (centreY - y));
146                                } else if (y > centreY) {
147                                        theta = ImageMath.PI + (float)Math.atan (((centreX - x))/((y - centreY)));
148                                        r = (float)Math.sqrt (sqr (centreX - x) + sqr (y - centreY));
149                                } else {
150                                        theta = 1.5f * ImageMath.PI;
151                                        r = centreX - x;
152                                }
153                        }
154                        if (x != centreX)
155                                m = Math.abs (((y - centreY)) / ((x - centreX)));
156                        else
157                                m = 0;
158                        
159                        if (m <= (height / width)) {
160                                if (x == centreX) {
161                                        xmax = 0;
162                                        ymax = centreY;
163                                } else {
164                                        xmax = centreX;
165                                        ymax = m * xmax;
166                                }
167                        } else {
168                                ymax = centreY;
169                                xmax = ymax / m;
170                        }
171                        
172                        out[0] = (width-1) - (width - 1)/ImageMath.TWO_PI * theta;
173                        out[1] = height * r / radius;
174                        break;
175                case POLAR_TO_RECT:
176                        theta = x / width * ImageMath.TWO_PI;
177                        float theta2;
178
179                        if (theta >= 1.5f * ImageMath.PI)
180                                theta2 = ImageMath.TWO_PI - theta;
181                        else if (theta >= ImageMath.PI)
182                                theta2 = theta - ImageMath.PI;
183                        else if (theta >= 0.5f * ImageMath.PI)
184                                theta2 = ImageMath.PI - theta;
185                        else
186                                theta2 = theta;
187        
188                        t = (float)Math.tan(theta2);
189                        if (t != 0)
190                                m = 1.0f / t;
191                        else
192                                m = 0;
193        
194                        if (m <= ((height) / (width))) {
195                                if (theta2 == 0) {
196                                        xmax = 0;
197                                        ymax = centreY;
198                                } else {
199                                        xmax = centreX;
200                                        ymax = m * xmax;
201                                }
202                        } else {
203                                ymax = centreY;
204                                xmax = ymax / m;
205                        }
206        
207                        r = radius * (y / (height));
208
209                        float nx = -r * (float)Math.sin(theta2);
210                        float ny = r * (float)Math.cos(theta2);
211                        
212                        if (theta >= 1.5f * ImageMath.PI) {
213                                out[0] = centreX - nx;
214                                out[1] = centreY - ny;
215                        } else if (theta >= Math.PI) {
216                                out[0] = centreX - nx;
217                                out[1] = centreY + ny;
218                        } else if (theta >= 0.5 * Math.PI) {
219                                out[0] = centreX + nx;
220                                out[1] = centreY + ny;
221                        } else {
222                                out[0] = centreX + nx;
223                                out[1] = centreY - ny;
224                        }
225                        break;
226                case INVERT_IN_CIRCLE:
227                        float dx = x-centreX;
228                        float dy = y-centreY;
229                        float distance2 = dx*dx+dy*dy;
230                        out[0] = centreX + centreX*centreX * dx/distance2;
231                        out[1] = centreY + centreY*centreY * dy/distance2;
232                        break;
233                }
234        }
235
236        public String toString() {
237                return "Distort/Polar Coordinates...";
238        }
239
240        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=null;//ImageUtil.createBufferedImage(src);
241                Object o;
242                if((o=parameters.removeEL(KeyImpl.init("Type")))!=null)setType(ImageFilterUtil.toString(o,"Type"));
243                if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction"));
244                if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toString(o,"Interpolation"));
245
246                // check for arguments not supported
247                if(parameters.size()>0) {
248                        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 [Type, EdgeAction, Interpolation]");
249                }
250
251                return filter(src, dst);
252        }
253}