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.FunctionException;
040import lucee.runtime.exp.PageException;
041import lucee.runtime.img.ImageUtil;
042import lucee.runtime.type.KeyImpl;
043import lucee.runtime.type.Struct;
044import lucee.runtime.type.util.CollectionUtil;
045
046/**
047 * A filter which performs a perspective distortion on an image.
048 */
049public class PerspectiveFilter extends TransformFilter  implements DynFiltering {
050
051        private float xlt, ylt, xrt, yrt, xrb, yrb, xlb, ylb;
052        private float dx1, dy1, dx2, dy2, dx3, dy3;
053        private float A, B, C, D, E, F, G, H, I;
054        
055        /**
056     * Construct a PerspectiveFilter.
057     */
058    public PerspectiveFilter() {
059                this(0, 0, 0, 0, 0, 0, 0, 0);
060                //this(0, 0, 100, 0, 100, 100, 0, 100);
061        }
062        
063        /**
064     * Construct a PerspectiveFilter.
065     * @param x0 the new position of the top left corner
066     * @param y0 the new position of the top left corner
067     * @param x1 the new position of the top right corner
068     * @param y1 the new position of the top right corner
069     * @param x2 the new position of the bottom right corner
070     * @param y2 the new position of the bottom right corner
071     * @param x3 the new position of the bottom left corner
072     * @param y3 the new position of the bottom left corner
073     */
074        public PerspectiveFilter(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) {
075                setCorners(x0, y0, x1, y1, x2, y2, x3, y3);
076        }
077        
078        /**
079     * Set the new positions of the image corners.
080     * @param x0 the new position of the top left corner
081     * @param y0 the new position of the top left corner
082     * @param x1 the new position of the top right corner
083     * @param y1 the new position of the top right corner
084     * @param x2 the new position of the bottom right corner
085     * @param y2 the new position of the bottom right corner
086     * @param x3 the new position of the bottom left corner
087     * @param y3 the new position of the bottom left corner
088     */
089        public void setCorners(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) {
090                this.xlt = x0;
091                this.ylt = y0;
092                this.xrt = x1;
093                this.yrt = y1;
094                this.xrb = x2;
095                this.yrb = y2;
096                this.xlb = x3;
097                this.ylb = y3;
098                
099                dx1 = x1-x2;
100                dy1 = y1-y2;
101                dx2 = x3-x2;
102                dy2 = y3-y2;
103                dx3 = x0-x1+x2-x3;
104                dy3 = y0-y1+y2-y3;
105                
106                float a11, a12, a13, a21, a22, a23, a31, a32;
107
108                if (dx3 == 0 && dy3 == 0) {
109                        a11 = x1-x0;
110                        a21 = x2-x1;
111                        a31 = x0;
112                        a12 = y1-y0;
113                        a22 = y2-y1;
114                        a32 = y0;
115                        a13 = a23 = 0;
116                } else {
117                        a13 = (dx3*dy2-dx2*dy3)/(dx1*dy2-dy1*dx2);
118                        a23 = (dx1*dy3-dy1*dx3)/(dx1*dy2-dy1*dx2);
119                        a11 = x1-x0+a13*x1;
120                        a21 = x3-x0+a23*x3;
121                        a31 = x0;
122                        a12 = y1-y0+a13*y1;
123                        a22 = y3-y0+a23*y3;
124                        a32 = y0;
125                }
126
127            A = a22 - a32*a23;
128            B = a31*a23 - a21;
129            C = a21*a32 - a31*a22;
130            D = a32*a13 - a12;
131            E = a11 - a31*a13;
132            F = a31*a12 - a11*a32;
133            G = a12*a23 - a22*a13;
134            H = a21*a13 - a11*a23;
135            I = a11*a22 - a21*a12;
136        }
137        
138        /**
139         * the new horizontal position of the top left corner, negative values are translated to image-width - x.
140         * @param x0 the x0 to set
141         */
142        public void setXLT(float xlt) {
143                this.xlt = xlt;
144        }
145
146        /**
147         * the new vertical position of the top left corner, negative values are translated to image-height - y.
148         * @param y0 the y0 to set
149         */
150        public void setYLT(float ylt) {
151                this.ylt = ylt;
152        }
153
154        /**
155         * the new horizontal position of the top right corner, negative values are translated to image-width - x.
156         * @param x1 the x1 to set
157         */
158        public void setXRT(float xrt) {
159                this.xrt = xrt;
160        }
161
162        /**
163         * the new vertical position of the top right corner, negative values are translated to image-height - y.
164         * @param y1 the y1 to set
165         */
166        public void setYRT(float yrt) {
167                this.yrt = yrt;
168        }
169
170        /**
171         * the new horizontal position of the bottom right corner, negative values are translated to image-width - x.
172         * @param x2 the x2 to set
173         */
174        public void setXRB(float xrb) {
175                this.xrb = xrb;
176        }
177        
178        /**
179         * the new vertical position of the bottom right corner, negative values are translated to image-height - y.
180         * @param y2 the y2 to set
181         */
182        public void setYRB(float yrb) {
183                this.yrb = yrb;
184        }
185
186        /**
187         * the new horizontal position of the bottom left corner, negative values are translated to image-width - x.
188         * @param xlb the x3 to set
189         */
190        public void setXLB(float xlb) {
191                this.xlb = xlb;
192        }
193
194        /**
195         * the new vertical position of the bottom left corner, negative values are translated to image-height - y.
196         * @param y3 the y3 to set
197         */
198        public void setYLB(float ylb) {
199                this.ylb = ylb;
200        }
201
202
203        protected void transformSpace(Rectangle rect) {
204                rect.x = (int)Math.min( Math.min( xlt, xrt ), Math.min( xrb, xlb ) );
205                rect.y = (int)Math.min( Math.min( ylt, yrt ), Math.min( yrb, ylb ) );
206                rect.width = (int)Math.max( Math.max( xlt, xrt ), Math.max( xrb, xlb ) ) - rect.x;
207                rect.height = (int)Math.max( Math.max( ylt, yrt ), Math.max( yrb, ylb ) ) - rect.y;
208        }
209
210    /**
211     * Get the origin of the output image. Use this for working out where to draw your new image.
212     * @return the X origin.
213     */
214        public float getOriginX() {
215                return xlt - (int)Math.min( Math.min( xlt, xrt ), Math.min( xrb, xlb ) );
216        }
217
218    /**
219     * Get the origin of the output image. Use this for working out where to draw your new image.
220     * @return the Y origin.
221     */
222        public float getOriginY() {
223                return ylt - (int)Math.min( Math.min( ylt, yrt ), Math.min( yrb, ylb ) );
224        }
225
226/*
227    public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
228        if ( dstPt == null )
229            dstPt = new Point2D.Double();
230
231                dx1 = x1-x2;
232                dy1 = y1-y2;
233                dx2 = x3-x2;
234                dy2 = y3-y2;
235                dx3 = x0-x1+x2-x3;
236                dy3 = y0-y1+y2-y3;
237                
238                float a11, a12, a13, a21, a22, a23, a31, a32;
239
240                if (dx3 == 0 && dy3 == 0) {
241                        a11 = x1-x0;
242                        a21 = x2-x1;
243                        a31 = x0;
244                        a12 = y1-y0;
245                        a22 = y2-y1;
246                        a32 = y0;
247                        a13 = a23 = 0;
248                } else {
249                        a13 = (dx3*dy2-dx2*dy3)/(dx1*dy2-dy1*dx2);
250                        a23 = (dx1*dy3-dy1*dx3)/(dx1*dy2-dy1*dx2);
251                        a11 = x1-x0+a13*x1;
252                        a21 = x3-x0+a23*x3;
253                        a31 = x0;
254                        a12 = y1-y0+a13*y1;
255                        a22 = y3-y0+a23*y3;
256                        a32 = y0;
257                }
258
259                float x = (float)srcPt.getX();
260                float y = (float)srcPt.getY();
261                float D = 1.0f/(a13*x + a23*y + 1);
262
263        dstPt.setLocation( (a11*x + a21*y + a31)*D, (a12*x + a22*y + a32)*D );
264        return dstPt;
265    }
266*/
267
268        protected void transformInverse(int x, int y, float[] out) {
269                out[0] = originalSpace.width * (A*x+B*y+C)/(G*x+H*y+I);
270                out[1] = originalSpace.height * (D*x+E*y+F)/(G*x+H*y+I);
271        }
272
273        public String toString() {
274                return "Distort/Perspective...";
275        }
276        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {
277                Object o;
278                if((o=parameters.removeEL(KeyImpl.init("xlt")))!=null)setXLT(ImageFilterUtil.toFloatValue(o,"xlt"));
279                if((o=parameters.removeEL(KeyImpl.init("ylt")))!=null)setYLT(ImageFilterUtil.toFloatValue(o,"ylt"));
280                
281                if((o=parameters.removeEL(KeyImpl.init("xrt")))!=null)setXRT(ImageFilterUtil.toFloatValue(o,"xrt"));
282                if((o=parameters.removeEL(KeyImpl.init("yrt")))!=null)setYRT(ImageFilterUtil.toFloatValue(o,"yrt"));
283                
284                if((o=parameters.removeEL(KeyImpl.init("xrb")))!=null)setXRB(ImageFilterUtil.toFloatValue(o,"xrb"));
285                if((o=parameters.removeEL(KeyImpl.init("yrb")))!=null)setYRB(ImageFilterUtil.toFloatValue(o,"yrb"));
286                
287                if((o=parameters.removeEL(KeyImpl.init("xlb")))!=null)setXLB(ImageFilterUtil.toFloatValue(o,"xlb"));
288                if((o=parameters.removeEL(KeyImpl.init("ylb")))!=null)setYLB(ImageFilterUtil.toFloatValue(o,"ylb"));
289                
290                if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction"));
291                if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toString(o,"Interpolation"));
292
293                // check for arguments not supported
294                if(parameters.size()>0) {
295                        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 [Corners, EdgeAction, Interpolation]");
296                }
297
298                return filter(src, (BufferedImage)null);
299        }
300        
301        public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
302        
303                int width = src.getWidth();
304            int height = src.getHeight();
305
306            if(xrt==0)xrt=width;
307            if(xrb==0)xrb=width;
308            if(yrb==0)yrb=height;
309            if(ylb==0)ylb=height;
310            
311            if(xlt<0) xlt=width+xlt;
312            if(xrt<0) xrt=width+xrt;
313            if(xrb<0) xrb=width+xrb;
314            if(xlb<0) xlb=width+xlb;
315            
316            if(ylt<0) ylt=width+ylt;
317            if(yrt<0) yrt=width+yrt;
318            if(yrb<0) yrb=width+yrb;
319            if(ylb<0) ylb=width+ylb;
320            
321            
322            
323            setCorners(xlt, ylt, xrt, yrt, xrb, yrb, xlb, ylb);
324            
325
326            float t=ylt<yrt?ylt:yrt;
327            float l=xlt<xlb?xlt:xlb;
328            
329            float b=ylb>yrb?ylb:yrb;
330            float r=xrt>xrb?xrt:xrb;
331            
332            float w=r-l;
333            float h=b-t;
334            
335            dst=ImageUtil.createBufferedImage(src,Math.round(w),Math.round(h));
336            
337            
338            
339            return super.filter(src, dst);
340                        
341        }
342}
343