1 /*
2  * $RCSfile: WarpGrid.java,v $
3  *
4  * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
5  *
6  * Use is subject to license terms.
7  *
8  * $Revision: 1.1 $
9  * $Date: 2005/02/11 04:57:24 $
10  * $State: Exp $
11  */
12 package com.lightcrafts.mediax.jai;
13 import java.awt.geom.Point2D;
14 
15 
16 /**
17  * A regular grid-based description of an image warp.
18  *
19  * <p> The mapping from destination pixels to source positions is
20  * described by bilinear interpolation within a rectilinear grid of
21  * points with known mappings.
22  *
23  * <p> Given a destination pixel coordinate (x, y) that lies within
24  * a cell having corners at (x0, y0), (x1, y0), (x0, y1) and (x1, y1),
25  * with source coordinates defined at each respective corner equal
26  * to (sx0, sy0), (sx1, sy1), (sx2, sy2) and (sx3, sy3), the
27  * source position (sx, sy) that maps onto (x, y) is given by the formulas:
28  *
29  * <pre>
30  * xfrac = (x - x0)/(x1 - x0)
31  * yfrac = (y - y0)/(y1 - y0)
32  *
33  * s = sx0 + (sx1 - sx0)*xfrac
34  * t = sy0 + (sy1 - sy0)*xfrac
35  *
36  * u = sx2 + (sx3 - sx2)*xfrac
37  * v = sy2 + (sy3 - sy2)*xfrac
38  *
39  * sx = s + (u - s)*yfrac
40  * sy = t + (v - t)*yfrac
41  * </pre>
42  *
43  * <p> In other words, the source x and y values are interpolated
44  * horizontally along the top and bottom edges of the grid cell,
45  * and the results are interpolated vertically:
46  *
47  * <pre>
48  * (x0, y0) ->            (x1, y0) ->
49  *   (sx0, sy0)             (sx1, sy1)
50  *    +------------+---------+
51  *    |            |\        |
52  *    |            | (s, t)  |
53  *    |            |         |
54  *    |            |         |
55  *    |            |         |
56  *    |            |         |
57  *    | (x, y) ->  |         |
58  *    |  (sx, sy)--+         |
59  *    |            |         |
60  *    |            |         |
61  *    |            | (u, v)  |
62  *    |            |/        |
63  *    +------------+---------+
64  * (x0, y1) ->          (x1, y1) ->
65  *   (sx2, sy2)           (sx3, sy3)
66  * </pre>
67  *
68  * <p> Points outside the bounds of the cells defining the grid warp will
69  * be mapped to the source image using the identity transformation.
70  *
71  * <p> WarpGrid is marked final so that it may be more easily inlined.
72  *
73  */
74 public final class WarpGrid extends Warp {
75 
76     private int xStart;
77     private int yStart;
78 
79     private int xEnd;
80     private int yEnd;
81 
82     private int xStep;
83     private int yStep;
84 
85     private int xNumCells;
86     private int yNumCells;
87 
88     private float[] xWarpPos;
89     private float[] yWarpPos;
90 
91     /**
92      * @param xStart
93      * @param xStep
94      * @param xNumCells
95      * @param yStart
96      * @param yStep
97      * @param yNumCells
98      * @param warpPositions
99      */
initialize(int xStart, int xStep, int xNumCells, int yStart, int yStep, int yNumCells, float[] warpPositions)100     private void initialize(int xStart, int xStep, int xNumCells,
101                             int yStart, int yStep, int yNumCells,
102                             float[] warpPositions) {
103         this.xStart = xStart;
104         this.yStart = yStart;
105 
106         this.xEnd = xStart + xStep * xNumCells;
107         this.yEnd = yStart + yStep * yNumCells;
108 
109         this.xStep = xStep;
110         this.yStep = yStep;
111 
112         this.xNumCells = xNumCells;
113         this.yNumCells = yNumCells;
114 
115         int xNumGrids = xNumCells + 1;
116         int yNumGrids = yNumCells + 1;
117 
118         int numNodes = yNumGrids*xNumGrids;
119 
120         xWarpPos = new float[numNodes];
121         yWarpPos = new float[numNodes];
122 
123         int index = 0;
124         for (int idx = 0; idx < numNodes; idx++) {
125             xWarpPos[idx] = warpPositions[index++];
126             yWarpPos[idx] = warpPositions[index++];
127         }
128     }
129 
130     /**
131      * Constructs a WarpGrid with a given grid-based transform mapping
132      * destination pixels into source space.  Note that this is
133      * a backward mapping as opposed to the forward mapping used in
134      * AffineOpImage.
135      *
136      * <p> The grid is defined by a set of equal-sized cells.
137      * The grid starts at (xStart, yStart).  Each cell has width
138      * equal to xStep and height equal to yStep, and there are
139      * xNumCells cells horizontally and yNumCells cells vertically.
140      *
141      * <p> The local mapping within each cell is defined by
142      * the values in the table parameter.  This parameter must
143      * contain 2*(xNumCells + 1)*(yNumCells + 1) values, which
144      * alternately contain the source X and Y coordinates to which
145      * each destination grid intersection point maps.
146      * The cells are enumerated in row-major order, that is,
147      * all the grid points along a row are enumerated first, then
148      * the grid points for the next row are enumerated, and so on.
149      *
150      * <p> As an example, suppose xNumCells is equal to 2 and
151      * yNumCells is equal 1.  Then the order of the data in table
152      * would be:
153      *
154      * <pre>
155      * x00, y00, x10, y10, x20, y20, x01, y01, x11, y11, x21, y21
156      * </pre>
157      *
158      * for a total of 2*(2 + 1)*(1 + 1) = 12 elements.
159      *
160      * @param xStart the minimum X coordinate of the grid.
161      * @param xStep the horizontal spacing between grid cells.
162      * @param xNumCells the number of grid cell columns.
163      * @param yStart the minimum Y coordinate of the grid.
164      * @param yStep the vertical spacing between grid cells.
165      * @param yNumCells the number of grid cell rows.
166      * @param warpPositions a float array of length 2*(xNumCells + 1)*
167      *        (yNumCells + 1) containing the warp positions at the
168      *        grid points, in row-major order.
169      * @throws IllegalArgumentException if the length of warpPositions is incorrect
170      */
WarpGrid(int xStart, int xStep, int xNumCells, int yStart, int yStep, int yNumCells, float[] warpPositions)171     public WarpGrid(int xStart, int xStep, int xNumCells,
172                     int yStart, int yStep, int yNumCells,
173                     float[] warpPositions) {
174         if (warpPositions.length != 2 * (xNumCells + 1) * (yNumCells + 1)) {
175             throw new IllegalArgumentException(JaiI18N.getString("WarpGrid0"));
176         }
177 
178         initialize(xStart, xStep, xNumCells,
179                    yStart, yStep, yNumCells,
180                    warpPositions);
181     }
182 
183     /**
184      * Constructs a WarpGrid object by sampling the displacements
185      * given by another Warp object of any kind.
186      *
187      * <p> The grid is defined by a set of equal-sized cells.
188      * The grid starts at (xStart, yStart).  Each cell has width
189      * equal to xStep and height equal to yStep, and there are
190      * xNumCells cells horizontally and yNumCells cells vertically.
191      *
192      * @param master the Warp object used to initialize the grid
193      *        displacements.
194      * @param xStart the minimum X coordinate of the grid.
195      * @param xStep the horizontal spacing between grid cells.
196      * @param xNumCells the number of grid cell columns.
197      * @param yStart the minimum Y coordinate of the grid.
198      * @param yStep the vertical spacing between grid cells.
199      * @param yNumCells the number of grid cell rows.
200      */
WarpGrid(Warp master, int xStart, int xStep, int xNumCells, int yStart, int yStep, int yNumCells)201     public WarpGrid(Warp master,
202                     int xStart, int xStep, int xNumCells,
203                     int yStart, int yStep, int yNumCells) {
204         int size = 2 * (xNumCells + 1) * (yNumCells + 1);
205 
206         float[] warpPositions = new float[size];
207         warpPositions = master.warpSparseRect(xStart, yStart,
208                                               xNumCells * xStep + 1, // width
209                                               yNumCells * yStep + 1, // height
210                                               xStep, yStep,
211                                               warpPositions);
212 
213         initialize(xStart, xStep, xNumCells,
214                    yStart, yStep, yNumCells,
215                    warpPositions);
216     }
217 
218     /** Returns the minimum X coordinate of the grid. */
getXStart()219     public int getXStart() {
220         return xStart;
221     }
222 
223     /** Returns the minimum Y coordinate of the grid. */
getYStart()224     public int getYStart() {
225         return yStart;
226     }
227 
228     /** Returns the horizontal spacing between grid cells. */
getXStep()229     public int getXStep() {
230         return xStep;
231     }
232 
233     /** Returns the vertical spacing between grid cells. */
getYStep()234     public int getYStep() {
235         return yStep;
236     }
237 
238     /** Returns the number of grid cell columns. */
getXNumCells()239     public int getXNumCells() {
240         return xNumCells;
241     }
242 
243     /** Returns the number of grid cell rows. */
getYNumCells()244     public int getYNumCells() {
245         return yNumCells;
246     }
247 
248     /** Returns the horizontal warp positions at the grid points. */
getXWarpPos()249     public float[] getXWarpPos() {
250         return xWarpPos;
251     }
252 
253     /** Returns the vertical warp positions at the grid points. */
getYWarpPos()254     public float[] getYWarpPos() {
255         return yWarpPos;
256     }
257 
258     /**
259      * Copies source to destination, no warpping.
260      *
261      * @param x1
262      * @param x2
263      * @param y1
264      * @param y2
265      * @param periodX
266      * @param periodY
267      * @param offset
268      * @param stride
269      * @param destRect
270      * @return An array of <code>float</code>s.
271      * @throws IllegalArgumentException if destRect is null
272      * @throws ArrayBoundsException if destRect is too small
273      */
noWarpSparseRect(int x1, int x2, int y1, int y2, int periodX, int periodY, int offset, int stride, float[] destRect)274     private float[] noWarpSparseRect(int x1, int x2,
275                                      int y1, int y2,
276                                      int periodX, int periodY,
277                                      int offset, int stride,
278                                      float[] destRect) {
279 
280         if ( destRect == null ) {
281             throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
282         }
283 
284         for (int j = y1; j <= y2; j += periodY) {
285             int index = offset;
286             offset += stride;
287 
288             for (int i = x1; i <= x2; i += periodX) {
289                 destRect[index++] = i;
290                 destRect[index++] = j;
291             }
292         }
293 
294         return destRect;
295     }
296 
297     /**
298      * Computes the source subpixel positions for a given rectangular
299      * destination region, subsampled with an integral period.
300      *
301      * <p> Points outside the bounds of the cells defining the grid warp will
302      * be mapped to the source image using the identity transformation.
303 
304      * @param x  The minimum X coordinate of the destination region.
305      * @param y  The minimum Y coordinate of the destination region.
306      * @param width  The width of the destination region.
307      * @param height  The height of the destination region.
308      * @param periodX  The horizontal sampling period.
309      * @param periodY  The vertical sampling period.
310      * @param destRect  An int array containing at least
311      *        2*((width+periodX-1)/periodX)*((height+periodY-1)/periodY)
312      *        elements, or <code>null</code>.  If <code>null</code>, a
313      *        new array will be constructed.
314      *
315      * @return a reference to the destRect parameter if it is
316      *         non-<code>null</code>, or a new int array of length
317      *         2*width*height otherwise.
318      * @throws ArrayBoundsException if destRect is too small
319      */
warpSparseRect(int x, int y, int width, int height, int periodX, int periodY, float[] destRect)320     public float[] warpSparseRect(int x, int y,
321                                   int width, int height,
322                                   int periodX, int periodY,
323                                   float[] destRect) {
324         // Number of points (x, y) per scanline
325         int stride = 2 * ((width + periodX - 1) / periodX);
326 
327         if (destRect == null) {
328             destRect = new float[stride * ((height + periodY - 1) / periodY)];
329         }
330 
331         int x1 = x;			// first x point
332         int x2 = x + width - 1;		// last x point
333         int y1 = y;			// first y point
334         int y2 = y + height - 1;	// last y point
335 
336         if (y1 >= yEnd || y2 < yStart || x1 >= xEnd || x2 < xStart) {
337             // destRect is completely outside of warp grid
338             return noWarpSparseRect(x1, x2, y1, y2, periodX, periodY,
339                                     0, stride, destRect);
340         }
341 
342         if (y1 < yStart) {	// the rectangle above the warp grid area
343             int periods = (yStart - y1 + periodY - 1) / periodY;
344             noWarpSparseRect(x1, x2, y1, yStart - 1, periodX, periodY,
345                              0, stride, destRect);
346             y1 += periods * periodY;
347         }
348 
349         if (y2 >= yEnd) {	// the rectangle below the warp grid area
350             int periods = (yEnd - y + periodY - 1) / periodY;
351             noWarpSparseRect(x1, x2, y + periods * periodY, y2,
352                              periodX, periodY,
353                              periods * stride, stride, destRect);
354             // One period up should be inside warp grid
355             y2 = y + (periods - 1) * periodY;
356         }
357 
358         if (x1 < xStart) {	// the rectangle left of the warp grid area
359             int periods = (xStart - x1 + periodX - 1) / periodX;
360             noWarpSparseRect(x1, xStart - 1, y1, y2, periodX, periodY,
361                              (y1 - y) / periodY * stride, stride, destRect);
362             x1 += periods * periodX;
363         }
364 
365         if (x2 >= xEnd) {	// the rectangle right of the warp grid area
366             int periods = (xEnd - x + periodX - 1) / periodX;
367             noWarpSparseRect(x + periods * periodX, x2, y1, y2,
368                              periodX, periodY,
369                              (y1 - y) / periodY * stride + periods * 2,
370                              stride, destRect);
371             // One period left should be inside warp grid
372             x2 = x + (periods - 1) * periodX;
373         }
374 
375         //
376         // Now the rectangle is within warp grid, that is
377         // xStart <= x1 <= x2 < xEnd and yStart <= y1 <= y2 < yEnd.
378         //
379         // address = s0(1-x)(1-y) + s1x(1-y) + s2(1-x)y + s3xy
380         //
381 
382         // A table stores the number of points inside each cell
383         int[] cellPoints = new int[xNumCells];
384         for (int i = x1; i <= x2; i += periodX) {
385             cellPoints[(i - xStart) / xStep]++;
386         }
387 
388         int offset = (y1 - y) / periodY * stride + (x1 - x) / periodX * 2;
389 
390         // Store the number of horizontal grid nodes.
391         int xNumGrids = xNumCells + 1;
392 
393         // Fractional step in X.
394         float deltaX = (float)periodX/(float)xStep;
395 
396         // The rectangle within the warp grid
397         for (int j = y1; j <= y2; j += periodY) {
398             int index = offset;
399             offset += stride;
400 
401             int yCell = (j - yStart) / yStep;
402             int yGrid = yStart + yCell * yStep;
403             float yFrac = (float)(j + 0.5F - yGrid) / (float)yStep;
404 
405             // Cache some values to avoid two multiplications per x loop.
406             float deltaTop = (1.0F - yFrac)*deltaX;
407             float deltaBottom = yFrac*deltaX;
408 
409             int i = x1;
410             while (i <= x2) {
411                 // Entering a new cell, set up
412                 int xCell = (i - xStart) / xStep;
413                 int xGrid = xStart + xCell * xStep;
414                 float xFrac = (float)(i + 0.5F - xGrid) / (float)xStep;
415 
416                 int nodeOffset = yCell*xNumGrids + xCell;
417                 float wx0 = xWarpPos[nodeOffset];
418                 float wy0 = yWarpPos[nodeOffset];
419                 float wx1 = xWarpPos[++nodeOffset];
420                 float wy1 = yWarpPos[nodeOffset];
421                 nodeOffset += xNumCells; // NB: xNumCells == xNumGrids - 1
422                 float wx2 = xWarpPos[nodeOffset];
423                 float wy2 = yWarpPos[nodeOffset];
424                 float wx3 = xWarpPos[++nodeOffset];
425                 float wy3 = yWarpPos[nodeOffset];
426 
427                 float s = wx0 + (wx1 - wx0) * xFrac;
428                 float t = wy0 + (wy1 - wy0) * xFrac;
429                 float u = wx2 + (wx3 - wx2) * xFrac;
430                 float v = wy2 + (wy3 - wy2) * xFrac;
431 
432                 float wx = s + (u - s) * yFrac;
433                 float wy = t + (v - t) * yFrac;
434 
435                 // Delta in x and y.
436                 float dx = (wx1 - wx0)*deltaTop + (wx3 - wx2)*deltaBottom;
437                 float dy = (wy1 - wy0)*deltaTop + (wy3 - wy2)*deltaBottom;
438 
439                 // The points inside the current cell
440                 int nPoints = cellPoints[xCell];
441                 for (int k = 0; k < nPoints; k++) {
442                     destRect[index++] = wx - 0.5F;
443                     destRect[index++] = wy - 0.5F;
444 
445                     wx += dx;
446                     wy += dy;
447                     i += periodX;
448                 }
449             }
450         }
451 
452         return destRect;
453     }
454 
455     /**
456      * Computes the source point corresponding to the supplied point.
457      *
458      * <p>This method returns the value of <code>pt</code> in the following
459      * code snippet:
460      *
461      * <pre>
462      * float[] sxy = warpSparseRect((int)destPt.getX(), (int)destPt.getY(),
463      *                              2, 2, 1, 1, null);
464      *
465      * double wtRight  = destPt.getX() - (int)destPt.getX();
466      * double wtLeft   = 1.0 - wtRight;
467      * double wtBottom = destPt.getY() - (int)destPt.getY();
468      * double wtTop    = 1.0 - wtBottom;
469      *
470      * Point2D pt = (Point2D)destPt.clone();
471      * pt.setLocation((sxy[0]*wtLeft + sxy[2]*wtRight)*wtTop +
472      *                (sxy[4]*wtLeft + sxy[6]*wtRight)*wtBottom,
473      *                (sxy[1]*wtLeft + sxy[3]*wtRight)*wtTop +
474      *                (sxy[5]*wtLeft + sxy[7]*wtRight)*wtBottom);
475      * </pre>
476      * </p>
477      *
478      * @param destPt the position in destination image coordinates
479      * to map to source image coordinates.
480      *
481      * @return a <code>Point2D</code> of the same class as
482      * <code>destPt</code>.
483      *
484      * @throws IllegalArgumentException if <code>destPt</code> is
485      * <code>null</code>.
486      *
487      * @since JAI 1.1.2
488      */
mapDestPoint(Point2D destPt)489     public Point2D mapDestPoint(Point2D destPt) {
490         if (destPt == null) {
491             throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
492         }
493 
494         float[] sxy = warpSparseRect((int)destPt.getX(), (int)destPt.getY(),
495                                      2, 2, 1, 1, null);
496 
497         double wtRight  = destPt.getX() - (int)destPt.getX();
498         double wtLeft   = 1.0 - wtRight;
499         double wtBottom = destPt.getY() - (int)destPt.getY();
500         double wtTop    = 1.0 - wtBottom;
501 
502         Point2D pt = (Point2D)destPt.clone();
503         pt.setLocation((sxy[0]*wtLeft + sxy[2]*wtRight)*wtTop +
504                        (sxy[4]*wtLeft + sxy[6]*wtRight)*wtBottom,
505                        (sxy[1]*wtLeft + sxy[3]*wtRight)*wtTop +
506                        (sxy[5]*wtLeft + sxy[7]*wtRight)*wtBottom);
507 
508         return pt;
509     }
510 }
511