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