001/*
002Copyright 2006 Jerry Huxtable
003
004Licensed under the Apache License, Version 2.0 (the "License");
005you may not use this file except in compliance with the License.
006You may obtain a copy of the License at
007
008   http://www.apache.org/licenses/LICENSE-2.0
009
010Unless required by applicable law or agreed to in writing, software
011distributed under the License is distributed on an "AS IS" BASIS,
012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013See the License for the specific language governing permissions and
014limitations under the License.
015*/
016
017package com.jhlabs.image;
018
019import java.awt.*;
020import java.awt.image.*;
021import java.awt.geom.*;
022
023/**
024 * A filter which performs a perspective distortion on an image.
025 * Coordinates are treated as if the image was a unit square, i.e. the bottom-right corner of the image is at (1, 1).
026 * The filter maps the unit square onto an arbitrary convex quadrilateral or vice versa.
027 */
028public class PerspectiveFilter extends TransformFilter {
029
030        private float x0, y0, x1, y1, x2, y2, x3, y3;
031        private float dx1, dy1, dx2, dy2, dx3, dy3;
032        private float A, B, C, D, E, F, G, H, I;
033        private float a11, a12, a13, a21, a22, a23, a31, a32, a33;
034    private boolean scaled;
035    private boolean clip = false;
036        
037        /**
038     * Construct a PerspectiveFilter.
039     */
040    public PerspectiveFilter() {
041                this( 0, 0, 1, 0, 1, 1, 0, 1);
042        }
043        
044        /**
045     * Construct a PerspectiveFilter.
046     * @param x0 the new position of the top left corner
047     * @param y0 the new position of the top left corner
048     * @param x1 the new position of the top right corner
049     * @param y1 the new position of the top right corner
050     * @param x2 the new position of the bottom right corner
051     * @param y2 the new position of the bottom right corner
052     * @param x3 the new position of the bottom left corner
053     * @param y3 the new position of the bottom left corner
054     */
055        public PerspectiveFilter(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) {
056                unitSquareToQuad(x0, y0, x1, y1, x2, y2, x3, y3);
057        }
058        
059    public void setClip( boolean clip ) {
060        this.clip = clip;
061    }
062    
063    public boolean getClip() {
064        return clip;
065    }
066    
067        /**
068     * Set the new positions of the image corners.
069     * This is the same as unitSquareToQuad, but the coordinates are in image pixels, not relative to the unit square.
070     * This method is provided as a convenience.
071     * @param x0 the new position of the top left corner
072     * @param y0 the new position of the top left corner
073     * @param x1 the new position of the top right corner
074     * @param y1 the new position of the top right corner
075     * @param x2 the new position of the bottom right corner
076     * @param y2 the new position of the bottom right corner
077     * @param x3 the new position of the bottom left corner
078     * @param y3 the new position of the bottom left corner
079     */
080        public void setCorners(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) {
081                unitSquareToQuad( x0, y0, x1, y1, x2, y2, x3, y3 );
082        scaled = true;
083        }
084
085    /**
086     * Set the transform to map the unit square onto a quadrilateral. When filtering, all coordinates will be scaled
087     * by the size of the image.
088     * @param x0 the new position of the top left corner
089     * @param y0 the new position of the top left corner
090     * @param x1 the new position of the top right corner
091     * @param y1 the new position of the top right corner
092     * @param x2 the new position of the bottom right corner
093     * @param y2 the new position of the bottom right corner
094     * @param x3 the new position of the bottom left corner
095     * @param y3 the new position of the bottom left corner
096     */
097        public void unitSquareToQuad( float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3 ) {
098                this.x0 = x0;
099                this.y0 = y0;
100                this.x1 = x1;
101                this.y1 = y1;
102                this.x2 = x2;
103                this.y2 = y2;
104                this.x3 = x3;
105                this.y3 = y3;
106                
107                dx1 = x1-x2;
108                dy1 = y1-y2;
109                dx2 = x3-x2;
110                dy2 = y3-y2;
111                dx3 = x0-x1+x2-x3;
112                dy3 = y0-y1+y2-y3;
113                
114                if (dx3 == 0 && dy3 == 0) {
115                        a11 = x1-x0;
116                        a21 = x2-x1;
117                        a31 = x0;
118                        a12 = y1-y0;
119                        a22 = y2-y1;
120                        a32 = y0;
121                        a13 = a23 = 0;
122                } else {
123                        a13 = (dx3*dy2-dx2*dy3)/(dx1*dy2-dy1*dx2);
124                        a23 = (dx1*dy3-dy1*dx3)/(dx1*dy2-dy1*dx2);
125                        a11 = x1-x0+a13*x1;
126                        a21 = x3-x0+a23*x3;
127                        a31 = x0;
128                        a12 = y1-y0+a13*y1;
129                        a22 = y3-y0+a23*y3;
130                        a32 = y0;
131                }
132        a33 = 1;
133        scaled = false;
134        }
135
136    /**
137     * Set the transform to map a quadrilateral onto the unit square. When filtering, all coordinates will be scaled
138     * by the size of the image.
139     * @param x0 the old position of the top left corner
140     * @param y0 the old position of the top left corner
141     * @param x1 the old position of the top right corner
142     * @param y1 the old position of the top right corner
143     * @param x2 the old position of the bottom right corner
144     * @param y2 the old position of the bottom right corner
145     * @param x3 the old position of the bottom left corner
146     * @param y3 the old position of the bottom left corner
147     */
148        public void quadToUnitSquare( float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3 ) {
149                unitSquareToQuad( x0, y0, x1, y1, x2, y2, x3, y3 );
150
151        // Invert the transformation
152        float ta11 = a22*a33 - a32*a23;
153        float ta21 = a32*a13 - a12*a33;
154        float ta31 = a12*a23 - a22*a13;
155        float ta12 = a31*a23 - a21*a33;
156        float ta22 = a11*a33 - a31*a13;
157        float ta32 = a21*a13 - a11*a23;
158        float ta13 = a21*a32 - a31*a22;
159        float ta23 = a31*a12 - a11*a32;
160        float ta33 = a11*a22 - a21*a12;
161        float f = 1.0f/ta33;
162
163        a11 = ta11*f;
164        a21 = ta12*f;
165        a31 = ta13*f;
166        a12 = ta21*f;
167        a22 = ta22*f;
168        a32 = ta23*f;
169        a13 = ta31*f;
170        a23 = ta32*f;
171        a33 = 1.0f;
172        }
173
174    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
175            A = a22*a33 - a32*a23;
176            B = a31*a23 - a21*a33;
177            C = a21*a32 - a31*a22;
178            D = a32*a13 - a12*a33;
179            E = a11*a33 - a31*a13;
180            F = a31*a12 - a11*a32;
181            G = a12*a23 - a22*a13;
182            H = a21*a13 - a11*a23;
183            I = a11*a22 - a21*a12;
184        if ( !scaled ) {
185            int width = src.getWidth();
186            int height = src.getHeight();
187            float invWidth = 1.0f/width;
188            float invHeight = 1.0f/height;
189
190            A *= invWidth;
191            D *= invWidth;
192            G *= invWidth;
193            B *= invHeight;
194            E *= invHeight;
195            H *= invHeight;
196        }
197
198        return super.filter( src, dst );
199    }
200
201        protected void transformSpace( Rectangle rect ) {
202                if ( scaled ) {
203            rect.x = (int)Math.min( Math.min( x0, x1 ), Math.min( x2, x3 ) );
204            rect.y = (int)Math.min( Math.min( y0, y1 ), Math.min( y2, y3 ) );
205            rect.width = (int)Math.max( Math.max( x0, x1 ), Math.max( x2, x3 ) ) - rect.x;
206            rect.height = (int)Math.max( Math.max( y0, y1 ), Math.max( y2, y3 ) ) - rect.y;
207            return;
208        }
209        if ( !clip ) {
210            float w = (float)rect.getWidth(), h = (float)rect.getHeight();
211            Rectangle r = new Rectangle();
212            r.add( getPoint2D( new Point2D.Float(0, 0), null ) );
213            r.add( getPoint2D( new Point2D.Float(w, 0), null ) );
214            r.add( getPoint2D( new Point2D.Float(0, h), null ) );
215            r.add( getPoint2D( new Point2D.Float(w, h), null ) );
216            rect.setRect( r );
217        }
218        }
219
220    /**
221     * Get the origin of the output image. Use this for working out where to draw your new image.
222     * @return the X origin.
223     */
224        public float getOriginX() {
225                return x0 - (int)Math.min( Math.min( x0, x1 ), Math.min( x2, x3 ) );
226        }
227
228    /**
229     * Get the origin of the output image. Use this for working out where to draw your new image.
230     * @return the Y origin.
231     */
232        public float getOriginY() {
233                return y0 - (int)Math.min( Math.min( y0, y1 ), Math.min( y2, y3 ) );
234        }
235
236    public Rectangle2D getBounds2D( BufferedImage src ) {
237        if ( clip ) 
238            return new Rectangle(0, 0, src.getWidth(), src.getHeight());
239        float w = src.getWidth(), h = src.getHeight();
240                Rectangle2D r = new Rectangle2D.Float();
241        r.add( getPoint2D( new Point2D.Float(0, 0), null ) );
242        r.add( getPoint2D( new Point2D.Float(w, 0), null ) );
243        r.add( getPoint2D( new Point2D.Float(0, h), null ) );
244        r.add( getPoint2D( new Point2D.Float(w, h), null ) );
245        return r;
246    }
247    
248    public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
249        if ( dstPt == null )
250            dstPt = new Point2D.Float();
251        float x = (float)srcPt.getX();
252        float y = (float)srcPt.getY();
253        float f = 1.0f/(x*a13 + y*a23 + a33);
254        dstPt.setLocation( (x*a11 + y*a21 + a31)*f, (x*a12 + y*a22 + a32)*f );
255        return dstPt;
256    }
257
258        protected void transformInverse( int x, int y, float[] out ) {
259                out[0] = originalSpace.width * (A*x+B*y+C)/(G*x+H*y+I);
260                out[1] = originalSpace.height * (D*x+E*y+F)/(G*x+H*y+I);
261        }
262
263        public String toString() {
264                return "Distort/Perspective...";
265        }
266}
267