001: /*
002: * $RCSfile: WarpAffine.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:23 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.awt.Rectangle;
015: import java.awt.geom.AffineTransform;
016: import java.awt.geom.Point2D;
017:
018: /**
019: * A description of an Affine warp.
020: *
021: * <p> The transform is specified as a mapping from destination
022: * space to source space, a backward mapping, as opposed to the
023: * forward mapping used in AffineOpImage.
024: *
025: * <p> The source position (x', y') of a point (x, y) is given by the
026: * first order (affine) bivariate polynomials:
027: *
028: * <pre>
029: * x' = p(x, y) = c1 + c2*x + c3*y
030: * y' = q(x, y) = c4 + c5*x + c6*y
031: * </pre>
032: *
033: * <p> <code>WarpAffine</code> is marked final so that it may be more
034: * easily inlined.
035: *
036: */
037: public final class WarpAffine extends WarpPolynomial {
038:
039: private float c1, c2, c3; // coefficients for X
040: private float c4, c5, c6; // coefficients for Y
041:
042: private float invc1, invc2, invc3; // inverse xform coefficients for X
043: private float invc4, invc5, invc6; // inverse xform coefficients for Y
044:
045: private AffineTransform transform;
046: private AffineTransform invTransform;
047:
048: /**
049: * @param transform
050: * @return An array of <code>float</code>s.
051: */
052: private static final float[] xCoeffsHelper(AffineTransform transform) {
053: float[] coeffs = new float[3];
054: coeffs[0] = (float) transform.getTranslateX();
055: coeffs[1] = (float) transform.getScaleX();
056: coeffs[2] = (float) transform.getShearX();
057: return coeffs;
058: }
059:
060: private static final float[] yCoeffsHelper(AffineTransform transform) {
061: float[] coeffs = new float[3];
062: coeffs[0] = (float) transform.getTranslateY();
063: coeffs[1] = (float) transform.getShearY();
064: coeffs[2] = (float) transform.getScaleY();
065: return coeffs;
066: }
067:
068: /**
069: * Constructs a <code>WarpAffine</code> with a given transform mapping
070: * destination pixels into source space. The transform is
071: * given by:
072: *
073: * <pre>
074: * x' = xCoeffs[0] + xCoeffs[1]*x + xCoeffs[2]*y;
075: * y' = yCoeffs[0] + yCoeffs[1]*x + yCoeffs[2]*y;
076: * </pre>
077: *
078: * where <code>x', y'</code> are the source image coordinates
079: * and <code>x, y</code> are the destination image coordinates.
080: *
081: * @param xCoeffs The 3 destination to source transform coefficients for
082: * the X coordinate.
083: * @param yCoeffs The 3 destination to source transform coefficients for
084: * the Y coordinate.
085: * @param preScaleX The scale factor to apply to input (dest) X positions.
086: * @param preScaleY The scale factor to apply to input (dest) Y positions.
087: * @param postScaleX The scale factor to apply to the evaluated x transform
088: * @param postScaleY The scale factor to apply to the evaluated y transform
089: *
090: * @throws IllegalArgumentException if array <code>xCoeffs</code> or
091: * <code>yCoeffs</code> does not have length of 3.
092: */
093: public WarpAffine(float[] xCoeffs, float[] yCoeffs,
094: float preScaleX, float preScaleY, float postScaleX,
095: float postScaleY) {
096: super (xCoeffs, yCoeffs, preScaleX, preScaleY, postScaleX,
097: postScaleY);
098:
099: if (xCoeffs.length != 3 || yCoeffs.length != 3) {
100: throw new IllegalArgumentException(JaiI18N
101: .getString("WarpAffine0"));
102: }
103:
104: c1 = xCoeffs[0];
105: c2 = xCoeffs[1];
106: c3 = xCoeffs[2];
107:
108: c4 = yCoeffs[0];
109: c5 = yCoeffs[1];
110: c6 = yCoeffs[2];
111:
112: transform = getTransform();
113:
114: // Transform inversion may throw NoninvertibleTransformException
115: try {
116: invTransform = transform.createInverse();
117:
118: invc1 = (float) invTransform.getTranslateX();
119: invc2 = (float) invTransform.getScaleX();
120: invc3 = (float) invTransform.getShearX();
121:
122: invc4 = (float) invTransform.getTranslateY();
123: invc5 = (float) invTransform.getShearY();
124: invc6 = (float) invTransform.getScaleY();
125: } catch (java.awt.geom.NoninvertibleTransformException e) {
126: // Transform can't be inverted, so set inverse to null
127: invTransform = null;
128: }
129: }
130:
131: /**
132: * Constructs a <code>WarpAffine</code> with pre- and post-scale
133: * factors of 1.
134: *
135: * @param xCoeffs The 3 destination to source transform coefficients for
136: * the X coordinate.
137: * @param yCoeffs The 3 destination to source transform coefficients for
138: * the Y coordinate.
139: */
140: public WarpAffine(float[] xCoeffs, float[] yCoeffs) {
141: this (xCoeffs, yCoeffs, 1.0F, 1.0F, 1.0F, 1.0F);
142: }
143:
144: /**
145: * Constructs a <code>WarpAffine</code> with a given transform mapping
146: * destination pixels into source space. Note that this is
147: * a backward mapping as opposed to the forward mapping used in
148: * AffineOpImage.
149: *
150: * @param transform The destination to source transform.
151: * @param preScaleX The scale factor to apply to source X positions.
152: * @param preScaleY The scale factor to apply to source Y positions.
153: * @param postScaleX The scale factor to apply to destination X positions.
154: * @param postScaleY The scale factor to apply to destination Y positions.
155: */
156: public WarpAffine(AffineTransform transform, float preScaleX,
157: float preScaleY, float postScaleX, float postScaleY) {
158: this (xCoeffsHelper(transform), yCoeffsHelper(transform),
159: preScaleX, preScaleY, postScaleX, postScaleY);
160: }
161:
162: /**
163: * Constructs a <code>WarpAffine</code> with pre- and post-scale
164: * factors of 1.
165: *
166: * @param transform An <code>AffineTransform</code> mapping dest to source
167: * coordinates.
168: */
169: public WarpAffine(AffineTransform transform) {
170: this (transform, 1.0F, 1.0F, 1.0F, 1.0F);
171: }
172:
173: /**
174: * Returns a clone of the <code>AffineTransform</code> associated
175: * with this <code>WarpAffine</code> object.
176: *
177: * @return An <code>AffineTransform</code>.
178: */
179: public AffineTransform getTransform() {
180: return new AffineTransform(c2, c5, c3, c6, c1, c4);
181: }
182:
183: /**
184: * Computes the source subpixel positions for a given rectangular
185: * destination region, subsampled with an integral period. The
186: * destination region is specified using normal integral (full
187: * pixel) coordinates. The source positions returned by the
188: * method are specified in floating point.
189: *
190: * @param x The minimum X coordinate of the destination region.
191: * @param y The minimum Y coordinate of the destination region.
192: * @param width The width of the destination region.
193: * @param height The height of the destination region.
194: * @param periodX The horizontal sampling period.
195: * @param periodY The vertical sampling period.
196: *
197: * @param destRect A <code>float</code> array containing at least
198: * <code>2*((width+periodX-1)/periodX)*
199: * ((height+periodY-1)/periodY)</code>
200: * elements, or <code>null</code>. If <code>null</code>, a
201: * new array will be constructed.
202: *
203: * @return A reference to the <code>destRect</code> parameter if
204: * it is non-<code>null</code>, or a new
205: * <code>float</code> array otherwise.
206: */
207: public float[] warpSparseRect(int x, int y, int width, int height,
208: int periodX, int periodY, float[] destRect) {
209:
210: //XXX: This method should do its calculations in doubles
211: if (destRect == null) {
212: destRect = new float[((width + periodX - 1) / periodX)
213: * ((height + periodY - 1) / periodY) * 2];
214: }
215:
216: //
217: // Original formula
218: // x' = c1 + c2*x + c3*y
219: // y' = c4 + c5*x + c6*y
220: //
221: // Take in preScale, postScale, and 0.5 shift
222: // x' = (c1 + c2*(x+0.5)*preScaleX +
223: // c3*(y+0.5)*preScaleY) * postScaleX - 0.5
224: // y' = (c4 + c5*(x+0.5)*preScaleX +
225: // c6*(y+0.5)*preScaleY) * postScaleY - 0.5
226: //
227: // The next point, increment by periodX
228: // x' = (c1 + c2*(x+periodX+0.5)*preScaleX +
229: // c3*(y+0.5)*preScaleY) * postScaleX - 0.5
230: // y' = (c4 + c5*(x+periodX+0.5)*preScaleX +
231: // c6*(y+0.5)*preScaleY) * postScaleY - 0.5
232: //
233: // The difference between the 2 points
234: // dx = c2 * periodX * preScaleX * postScaleX
235: // dy = c5 * periodX * preScaleX * postScaleY
236: //
237:
238: float px1 = periodX * preScaleX; // power for period X
239:
240: float dx = c2 * px1 * postScaleX; // delta x for x poly
241: float dy = c5 * px1 * postScaleY; // delta x for y poly
242:
243: float x1 = (x + 0.5F) * preScaleX; // power for x
244:
245: width += x;
246: height += y;
247: int index = 0; // destRect index
248:
249: for (int j = y; j < height; j += periodY) {
250: float y1 = (j + 0.5F) * preScaleY; // power for current y
251:
252: // The warped position for the first point of the current line.
253: float wx = (c1 + c2 * x1 + c3 * y1) * postScaleX - 0.5F;
254: float wy = (c4 + c5 * x1 + c6 * y1) * postScaleY - 0.5F;
255:
256: for (int i = x; i < width; i += periodX) {
257: destRect[index++] = wx;
258: destRect[index++] = wy;
259:
260: wx += dx;
261: wy += dy;
262: }
263: }
264:
265: return destRect;
266: }
267:
268: /**
269: * Computes a Rectangle that is guaranteed to enclose the region
270: * of the source that is required in order to produce a given
271: * rectangular output region.
272: *
273: * @param destRect The Rectangle in destination coordinates.
274: *
275: * @return A <code>Rectangle</code> in the source coordinate
276: * system that is guaranteed to contain all pixels
277: * referenced by the output of <code>warpRect()</code> on
278: * the destination region, or <code>null</code>.
279: *
280: * @throws IllegalArgumentException if <code>destRect</code> is
281: * <code>null</code>.
282: */
283: public Rectangle mapDestRect(Rectangle destRect) {
284: if (destRect == null) {
285: throw new IllegalArgumentException(JaiI18N
286: .getString("Generic0"));
287: }
288:
289: int dx0 = destRect.x;
290: int dx1 = destRect.x + destRect.width;
291: int dy0 = destRect.y;
292: int dy1 = destRect.y + destRect.height;
293:
294: float[] pt;
295: float sx0, sx1, sy0, sy1;
296:
297: pt = mapDestPoint(dx0, dy0);
298: sx0 = pt[0];
299: sx1 = pt[0];
300: sy0 = pt[1];
301: sy1 = pt[1];
302:
303: pt = mapDestPoint(dx1, dy0);
304: sx0 = Math.min(sx0, pt[0]);
305: sx1 = Math.max(sx1, pt[0]);
306: sy0 = Math.min(sy0, pt[1]);
307: sy1 = Math.max(sy1, pt[1]);
308:
309: pt = mapDestPoint(dx0, dy1);
310: sx0 = Math.min(sx0, pt[0]);
311: sx1 = Math.max(sx1, pt[0]);
312: sy0 = Math.min(sy0, pt[1]);
313: sy1 = Math.max(sy1, pt[1]);
314:
315: pt = mapDestPoint(dx1, dy1);
316: sx0 = Math.min(sx0, pt[0]);
317: sx1 = Math.max(sx1, pt[0]);
318: sy0 = Math.min(sy0, pt[1]);
319: sy1 = Math.max(sy1, pt[1]);
320:
321: int x = (int) Math.floor(sx0);
322: int y = (int) Math.floor(sy0);
323: int w = (int) Math.ceil(sx1 - x);
324: int h = (int) Math.ceil(sy1 - y);
325:
326: return new Rectangle(x, y, w, h);
327: }
328:
329: /**
330: * Computes a Rectangle that is guaranteed to enclose the region
331: * of the destination to which the source rectangle maps.
332: *
333: * @param srcRect The Rectangle in source coordinates.
334: *
335: * @return A <code>Rectangle</code> in the destination coordinate
336: * system that is guaranteed to contain all pixels
337: * within the forward mapping of the source rectangle.
338: *
339: * @throws IllegalArgumentException if <code>srctRect</code> is
340: * <code>null</code>.
341: *
342: * @since JAI 1.1
343: */
344: public Rectangle mapSourceRect(Rectangle srcRect) {
345: if (srcRect == null) {
346: throw new IllegalArgumentException(JaiI18N
347: .getString("Generic0"));
348: }
349:
350: //
351: // According to spec, we return null if no forward
352: // mapping can be derived.
353: //
354: if (invTransform == null) {
355: return null;
356: }
357:
358: int sx0 = srcRect.x;
359: int sx1 = srcRect.x + srcRect.width;
360: int sy0 = srcRect.y;
361: int sy1 = srcRect.y + srcRect.height;
362:
363: float[] pt;
364: float dx0, dx1, dy0, dy1;
365:
366: pt = mapSrcPoint(sx0, sy0);
367: dx0 = pt[0];
368: dx1 = pt[0];
369: dy0 = pt[1];
370: dy1 = pt[1];
371:
372: pt = mapSrcPoint(sx1, sy0);
373: dx0 = Math.min(dx0, pt[0]);
374: dx1 = Math.max(dx1, pt[0]);
375: dy0 = Math.min(dy0, pt[1]);
376: dy1 = Math.max(dy1, pt[1]);
377:
378: pt = mapSrcPoint(sx0, sy1);
379: dx0 = Math.min(dx0, pt[0]);
380: dx1 = Math.max(dx1, pt[0]);
381: dy0 = Math.min(dy0, pt[1]);
382: dy1 = Math.max(dy1, pt[1]);
383:
384: pt = mapSrcPoint(sx1, sy1);
385: dx0 = Math.min(dx0, pt[0]);
386: dx1 = Math.max(dx1, pt[0]);
387: dy0 = Math.min(dy0, pt[1]);
388: dy1 = Math.max(dy1, pt[1]);
389:
390: int x = (int) Math.floor(dx0);
391: int y = (int) Math.floor(dy0);
392: int w = (int) Math.ceil(dx1 - x);
393: int h = (int) Math.ceil(dy1 - y);
394:
395: return new Rectangle(x, y, w, h);
396: }
397:
398: // Maps a dest point to the source.
399: private float[] mapDestPoint(int x, int y) {
400: //XXX: This method should do its calculations in doubles
401:
402: //
403: // x' = (c1 + c2*(x+0.5)*preScaleX +
404: // c3*(y+0.5)*preScaleY) * postScaleX - 0.5
405: // y' = (c4 + c5*(x+0.5)*preScaleX +
406: // c6*(y+0.5)*preScaleY) * postScaleY - 0.5
407: //
408: float fx = (x + 0.5F) * preScaleX; // pixel energy is at center
409: float fy = (y + 0.5F) * preScaleY;
410:
411: float[] p = new float[2];
412: p[0] = (c1 + c2 * fx + c3 * fy) * postScaleX - 0.5F;
413: p[1] = (c4 + c5 * fx + c6 * fy) * postScaleY - 0.5F;
414:
415: return p;
416: }
417:
418: // Maps a source point to the dest.
419: private float[] mapSrcPoint(int x, int y) {
420: //XXX: This method should do its calculations in doubles
421:
422: //
423: // x' = (invc1 + invc2*(x+0.5)*preScaleX +
424: // invc3*(y+0.5)*preScaleY) * postScaleX - 0.5
425: // y' = (invc4 + invc5*(x+0.5)*preScaleX +
426: // invc6*(y+0.5)*preScaleY) * postScaleY - 0.5
427: //
428: float fx = (x + 0.5F) * preScaleX; // pixel energy is at center
429: float fy = (y + 0.5F) * preScaleY;
430:
431: float[] p = new float[2];
432: p[0] = (invc1 + invc2 * fx + invc3 * fy) * postScaleX - 0.5F;
433: p[1] = (invc4 + invc5 * fx + invc6 * fy) * postScaleY - 0.5F;
434:
435: return p;
436: }
437:
438: /**
439: * Computes the source point corresponding to the supplied point.
440: *
441: * <p>This method returns the value of <code>pt</code> in the following
442: * code snippet:
443: *
444: * <pre>
445: * double dx = (destPt.getX() + 0.5)*preScaleX;
446: * double dy = (destPt.getY() + 0.5)*preScaleY;
447: * Point2D pt = (Point2D)destPt.clone();
448: * pt.setLocation((c1 + c2*dx + c3*dy)*postScaleX - 0.5F,
449: * (c4 + c5*dx + c6*dy)*postScaleY - 0.5F);
450: * </pre>
451: * </p>
452: *
453: * @param destPt the position in destination image coordinates
454: * to map to source image coordinates.
455: *
456: * @return a <code>Point2D</code> of the same class as
457: * <code>destPt</code>.
458: *
459: * @throws IllegalArgumentException if <code>destPt</code> is
460: * <code>null</code>.
461: *
462: * @since JAI 1.1.2
463: */
464: public Point2D mapDestPoint(Point2D destPt) {
465: if (destPt == null) {
466: throw new IllegalArgumentException(JaiI18N
467: .getString("Generic0"));
468: }
469:
470: double dx = (destPt.getX() + 0.5) * preScaleX;
471: double dy = (destPt.getY() + 0.5) * preScaleY;
472:
473: Point2D pt = (Point2D) destPt.clone();
474:
475: pt.setLocation((c1 + c2 * dx + c3 * dy) * postScaleX - 0.5F,
476: (c4 + c5 * dx + c6 * dy) * postScaleY - 0.5F);
477:
478: return pt;
479: }
480:
481: /**
482: * Computes the destination point corresponding to the supplied point.
483: *
484: * <p>If the transform is invertible, this method returns the value of
485: * <code>pt</code> in the following code snippet:
486: *
487: * <pre>
488: * double sx = (sourcePt.getX() + 0.5F)/postScaleX;
489: * double sy = (sourcePt.getY() + 0.5F)/postScaleY;
490: * Point2D pt = (Point2D)sourcePt.clone();
491: * pt.setLocation((invc1 + invc2*sx + invc3*sy)/preScaleX - 0.5F,
492: * (invc4 + invc5*sx + invc6*sy)/preScaleY - 0.5F);
493: * </pre>
494: *
495: * where <code>invc*</code> are the inverse transform coefficients. If
496: * the transform is not invertible, <code>null</code> is returned.</p>
497: *
498: * @param sourcePt the position in source image coordinates
499: * to map to destination image coordinates.
500: *
501: * @return a <code>Point2D</code> of the same class as
502: * <code>sourcePt</code> or <code>null> if the transform is
503: * not invertible.
504: *
505: * @throws IllegalArgumentException if <code>destPt</code> is
506: * <code>null</code>.
507: *
508: * @since JAI 1.1.2
509: */
510: public Point2D mapSourcePoint(Point2D sourcePt) {
511: if (sourcePt == null) {
512: throw new IllegalArgumentException(JaiI18N
513: .getString("Generic0"));
514: }
515:
516: if (invTransform == null) {
517: return null;
518: }
519:
520: double sx = (sourcePt.getX() + 0.5F) / postScaleX;
521: double sy = (sourcePt.getY() + 0.5F) / postScaleY;
522:
523: Point2D pt = (Point2D) sourcePt.clone();
524:
525: pt.setLocation((invc1 + invc2 * sx + invc3 * sy) / preScaleX
526: - 0.5F, (invc4 + invc5 * sx + invc6 * sy) / preScaleY
527: - 0.5F);
528:
529: return pt;
530: }
531: }
|