001: /*
002: * $RCSfile: WarpPerspective.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.1 $
009: * $Date: 2005/02/11 04:57:24 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.awt.Rectangle;
015: import java.awt.geom.NoninvertibleTransformException;
016: import java.awt.geom.Point2D;
017:
018: /**
019: * A description of a perspective (projective) warp.
020: *
021: * <p> The transform is specified as a mapping from destination
022: * space to source space. This is a backward mapping, as opposed
023: * to the forward mapping used in the "Affine" operation.
024: *
025: */
026: public final class WarpPerspective extends Warp {
027:
028: private PerspectiveTransform transform;
029: private PerspectiveTransform invTransform;
030:
031: /**
032: * Constructs a <code>WarpPerspective</code> with a given
033: * transform mapping destination pixels into source space. Note
034: * that this is a backward mapping as opposed to the forward
035: * mapping used in AffineOpImage.
036: *
037: * @param transform The destination to source transform.
038: * @throws IllegalArgumentException if transform is null
039: */
040: public WarpPerspective(PerspectiveTransform transform) {
041: if (transform == null) {
042: throw new IllegalArgumentException(JaiI18N
043: .getString("WarpPerspective0"));
044: }
045:
046: this .transform = transform;
047:
048: // Transform could be non-invertible.
049: // If so the transform is set to null.
050: try {
051: invTransform = transform.createInverse();
052: } catch (NoninvertibleTransformException e) {
053: invTransform = null;
054: } catch (CloneNotSupportedException e) {
055: invTransform = null;
056: }
057:
058: }
059:
060: /**
061: * Returns a clone of the <code>PerspectiveTransform</code>
062: * associated with this <code>WarpPerspective</code> object.
063: *
064: * @return An instance of <code>PerspectiveTransform</code>.
065: */
066: public PerspectiveTransform getTransform() {
067: return (PerspectiveTransform) transform.clone();
068: }
069:
070: /**
071: * Computes the source subpixel positions for a given rectangular
072: * destination region, subsampled with an integral period. The
073: * destination region is specified using normal integral (full
074: * pixel) coordinates. The source positions returned by the
075: * method are specified in floating point.
076: *
077: * @param x The minimum X coordinate of the destination region.
078: * @param y The minimum Y coordinate of the destination region.
079: * @param width The width of the destination region.
080: * @param height The height of the destination region.
081: * @param periodX The horizontal sampling period.
082: * @param periodY The horizontal sampling period.
083: *
084: * @param destRect A <code>float</code> array containing at least
085: * <code>2*((width+periodX-1)/periodX)*
086: * ((height+periodY-1)/periodY)</code>
087: * elements, or <code>null</code>. If <code>null</code>, a
088: * new array will be constructed.
089: *
090: * @return A reference to the <code>destRect</code> parameter if
091: * it is non-<code>null</code>, or a new
092: * <code>float</code> array otherwise.
093: * @throw ArrayBoundsException if destRect is too small.
094: */
095: public float[] warpSparseRect(int x, int y, int width, int height,
096: int periodX, int periodY, float[] destRect) {
097: if (destRect == null) {
098: destRect = new float[2 * ((width + periodX - 1) / periodX)
099: * ((height + periodY - 1) / periodY)];
100: }
101:
102: double[][] matrix = new double[3][3];
103: matrix = transform.getMatrix(matrix);
104: float m00 = (float) matrix[0][0];
105: float m01 = (float) matrix[0][1];
106: float m02 = (float) matrix[0][2];
107: float m10 = (float) matrix[1][0];
108: float m11 = (float) matrix[1][1];
109: float m12 = (float) matrix[1][2];
110: float m20 = (float) matrix[2][0];
111: float m21 = (float) matrix[2][1];
112: float m22 = (float) matrix[2][2];
113:
114: //
115: // x' = (m00x + m01y + m02) / (m20x + m21y + m22)
116: // y' = (m10x + m11y + m12) / (m20x + m21y + m22)
117: //
118:
119: float dx = m00 * periodX;
120: float dy = m10 * periodX;
121: float dw = m20 * periodX;
122:
123: float sx = x + 0.5F; // shift coordinate by 0.5
124:
125: width += x;
126: height += y;
127: int index = 0; // destRect index
128:
129: for (int j = y; j < height; j += periodY) {
130: float sy = j + 0.5F;
131:
132: float wx = m00 * sx + m01 * sy + m02;
133: float wy = m10 * sx + m11 * sy + m12;
134: float w = m20 * sx + m21 * sy + m22;
135:
136: for (int i = x; i < width; i += periodX) {
137: float tx, ty;
138: try {
139: tx = wx / w;
140: ty = wy / w;
141: } catch (java.lang.ArithmeticException e) {
142: // w is 0, do not warp
143: tx = i + 0.5F; // to be subtracted below
144: ty = j + 0.5F;
145: }
146:
147: destRect[index++] = tx - 0.5F;
148: destRect[index++] = ty - 0.5F;
149:
150: wx += dx;
151: wy += dy;
152: w += dw;
153: }
154: }
155:
156: return destRect;
157: }
158:
159: /**
160: * Computes a Rectangle that is guaranteed to enclose the region
161: * of the source that is required in order to produce a given
162: * rectangular output region.
163: *
164: * @param destRect The <code>Rectangle</code> in destination coordinates.
165: * @throws IllegalArgumentException if destRect is null.
166: * @return A <code>Rectangle</code> in the source coordinate
167: * system that is guaranteed to contain all pixels
168: * referenced by the output of <code>warpRect()</code> on
169: * the destination region.
170: */
171: public Rectangle mapDestRect(Rectangle destRect) {
172: if (destRect == null) {
173: throw new IllegalArgumentException(JaiI18N
174: .getString("Generic0"));
175: }
176:
177: int x0 = destRect.x;
178: int x1 = destRect.x + destRect.width;
179: int y0 = destRect.y;
180: int y1 = destRect.y + destRect.height;
181:
182: Point2D[] pts = new Point2D[4];
183: pts[0] = new Point2D.Float(x0, y0);
184: pts[1] = new Point2D.Float(x1, y0);
185: pts[2] = new Point2D.Float(x0, y1);
186: pts[3] = new Point2D.Float(x1, y1);
187:
188: transform.transform(pts, 0, pts, 0, 4);
189:
190: int minX = Integer.MAX_VALUE;
191: int maxX = Integer.MIN_VALUE;
192: int minY = Integer.MAX_VALUE;
193: int maxY = Integer.MIN_VALUE;
194:
195: for (int i = 0; i < 4; i++) {
196: int px = (int) pts[i].getX();
197: int py = (int) pts[i].getY();
198:
199: minX = Math.min(minX, px);
200: maxX = Math.max(maxX, px);
201: minY = Math.min(minY, py);
202: maxY = Math.max(maxY, py);
203: }
204:
205: return new Rectangle(minX, minY, maxX - minX, maxY - minY);
206: }
207:
208: /**
209: * Computes a Rectangle that is guaranteed to enclose the region
210: * of the source that is required in order to produce a given
211: * rectangular output region.
212: *
213: * @param srcRect The <code>Rectangle</code> in source coordinates.
214: * @throws IllegalArgumentException is srcRect is null.
215: * @return A <code>Rectangle</code> in the destination coordinate
216: * system that is guaranteed to contain all pixels
217: * within the forward mapping of the source rectangle.
218: *
219: * @since JAI 1.1
220: */
221: public Rectangle mapSourceRect(Rectangle srcRect) {
222: if (srcRect == null) {
223: throw new IllegalArgumentException(JaiI18N
224: .getString("Generic0"));
225: }
226:
227: // Return null if no forward mapping could be derived
228: if (invTransform == null) {
229: return null;
230: }
231:
232: int x0 = srcRect.x;
233: int x1 = srcRect.x + srcRect.width;
234: int y0 = srcRect.y;
235: int y1 = srcRect.y + srcRect.height;
236:
237: Point2D[] pts = new Point2D[4];
238: pts[0] = new Point2D.Float(x0, y0);
239: pts[1] = new Point2D.Float(x1, y0);
240: pts[2] = new Point2D.Float(x0, y1);
241: pts[3] = new Point2D.Float(x1, y1);
242:
243: invTransform.transform(pts, 0, pts, 0, 4);
244:
245: int minX = Integer.MAX_VALUE;
246: int maxX = Integer.MIN_VALUE;
247: int minY = Integer.MAX_VALUE;
248: int maxY = Integer.MIN_VALUE;
249:
250: for (int i = 0; i < 4; i++) {
251: int px = (int) pts[i].getX();
252: int py = (int) pts[i].getY();
253:
254: minX = Math.min(minX, px);
255: maxX = Math.max(maxX, px);
256: minY = Math.min(minY, py);
257: maxY = Math.max(maxY, py);
258: }
259:
260: return new Rectangle(minX, minY, maxX - minX, maxY - minY);
261: }
262:
263: /**
264: * Computes the source point corresponding to the supplied point.
265: *
266: * <p>This method returns the return value of
267: * <code>transform.transform(destPt, null)</code>.</p>
268: *
269: * @param destPt the position in destination image coordinates
270: * to map to source image coordinates.
271: *
272: * @return a <code>Point2D</code> of the same class as
273: * <code>destPt</code>.
274: *
275: * @throws IllegalArgumentException if <code>destPt</code> is
276: * <code>null</code>.
277: *
278: * @since JAI 1.1.2
279: */
280: public Point2D mapDestPoint(Point2D destPt) {
281: if (destPt == null) {
282: throw new IllegalArgumentException(JaiI18N
283: .getString("Generic0"));
284: }
285:
286: return transform.transform(destPt, null);
287: }
288:
289: /**
290: * Computes the destination point corresponding to the supplied point.
291: *
292: * <p>If the transform is invertible, this method returns the return
293: * value of <code>transform.inverseTransform(destPt, null)</code>. If
294: * the transform is not invertible, <code>null</code> is returned.</p>
295: *
296: * @param sourcePt the position in source image coordinates
297: * to map to destination image coordinates.
298: *
299: * @return a <code>Point2D</code> of the same class as
300: * <code>sourcePt</code> or <code>null> if the transform is
301: * not invertible.
302: *
303: * @throws IllegalArgumentException if <code>sourcePt</code> is
304: * <code>null</code>.
305: *
306: * @since JAI 1.1.2
307: */
308: public Point2D mapSourcePoint(Point2D sourcePt) {
309: if (sourcePt == null) {
310: throw new IllegalArgumentException(JaiI18N
311: .getString("Generic0"));
312: }
313:
314: return invTransform != null ? invTransform.transform(sourcePt,
315: null) : null;
316: }
317: }
|