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