Source for java.awt.image.AffineTransformOp

   1: /* AffineTransformOp.java --  This class performs affine
   2:    transformation between two images or rasters in 2 dimensions.
   3:    Copyright (C) 2004, 2006 Free Software Foundation
   4: 
   5: This file is part of GNU Classpath.
   6: 
   7: GNU Classpath is free software; you can redistribute it and/or modify
   8: it under the terms of the GNU General Public License as published by
   9: the Free Software Foundation; either version 2, or (at your option)
  10: any later version.
  11: 
  12: GNU Classpath is distributed in the hope that it will be useful, but
  13: WITHOUT ANY WARRANTY; without even the implied warranty of
  14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15: General Public License for more details.
  16: 
  17: You should have received a copy of the GNU General Public License
  18: along with GNU Classpath; see the file COPYING.  If not, write to the
  19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  20: 02110-1301 USA.
  21: 
  22: Linking this library statically or dynamically with other modules is
  23: making a combined work based on this library.  Thus, the terms and
  24: conditions of the GNU General Public License cover the whole
  25: combination.
  26: 
  27: As a special exception, the copyright holders of this library give you
  28: permission to link this library with independent modules to produce an
  29: executable, regardless of the license terms of these independent
  30: modules, and to copy and distribute the resulting executable under
  31: terms of your choice, provided that you also meet, for each linked
  32: independent module, the terms and conditions of the license of that
  33: module.  An independent module is a module which is not derived from
  34: or based on this library.  If you modify this library, you may extend
  35: this exception to your version of the library, but you are not
  36: obligated to do so.  If you do not wish to do so, delete this
  37: exception statement from your version. */
  38: 
  39: package java.awt.image;
  40: 
  41: import java.awt.Graphics2D;
  42: import java.awt.Point;
  43: import java.awt.Rectangle;
  44: import java.awt.RenderingHints;
  45: import java.awt.geom.AffineTransform;
  46: import java.awt.geom.NoninvertibleTransformException;
  47: import java.awt.geom.Point2D;
  48: import java.awt.geom.Rectangle2D;
  49: import java.util.Arrays;
  50: 
  51: /**
  52:  * AffineTransformOp performs matrix-based transformations (translations,
  53:  * scales, flips, rotations, and shears).
  54:  *
  55:  * If interpolation is required, nearest neighbour, bilinear, and bicubic
  56:  * methods are available.
  57:  *
  58:  * @author Olga Rodimina (rodimina@redhat.com)
  59:  * @author Francis Kung (fkung@redhat.com)
  60:  */
  61: public class AffineTransformOp implements BufferedImageOp, RasterOp
  62: {
  63:     public static final int TYPE_NEAREST_NEIGHBOR = 1;
  64: 
  65:     public static final int TYPE_BILINEAR = 2;
  66: 
  67:     /**
  68:      * @since 1.5.0
  69:      */
  70:     public static final int TYPE_BICUBIC = 3;
  71: 
  72:     private AffineTransform transform;
  73:     private RenderingHints hints;
  74: 
  75:     /**
  76:      * Construct AffineTransformOp with the given xform and interpolationType.
  77:      * Interpolation type can be TYPE_BILINEAR, TYPE_BICUBIC or
  78:      * TYPE_NEAREST_NEIGHBOR.
  79:      *
  80:      * @param xform AffineTransform that will applied to the source image
  81:      * @param interpolationType type of interpolation used
  82:      * @throws ImagingOpException if the transform matrix is noninvertible
  83:      */
  84:     public AffineTransformOp (AffineTransform xform, int interpolationType)
  85:     {
  86:       this.transform = xform;
  87:       if (xform.getDeterminant() == 0)
  88:         throw new ImagingOpException(null);
  89: 
  90:       switch (interpolationType)
  91:       {
  92:       case TYPE_BILINEAR:
  93:         hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION,
  94:                                     RenderingHints.VALUE_INTERPOLATION_BILINEAR);
  95:         break;
  96:       case TYPE_BICUBIC:
  97:         hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION,
  98:                                     RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  99:         break;
 100:       default:
 101:         hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION,
 102:                                     RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
 103:       }
 104:     }
 105: 
 106:     /**
 107:      * Construct AffineTransformOp with the given xform and rendering hints.
 108:      *
 109:      * @param xform AffineTransform that will applied to the source image
 110:      * @param hints rendering hints that will be used during transformation
 111:      * @throws ImagingOpException if the transform matrix is noninvertible
 112:      */
 113:     public AffineTransformOp (AffineTransform xform, RenderingHints hints)
 114:     {
 115:       this.transform = xform;
 116:       this.hints = hints;
 117:       if (xform.getDeterminant() == 0)
 118:         throw new ImagingOpException(null);
 119:     }
 120: 
 121:     /**
 122:      * Creates a new BufferedImage with the size equal to that of the
 123:      * transformed image and the correct number of bands. The newly created
 124:      * image is created with the specified ColorModel.
 125:      * If a ColorModel is not specified, an appropriate ColorModel is used.
 126:      *
 127:      * @param src the source image.
 128:      * @param destCM color model for the destination image (can be null).
 129:      * @return a new compatible destination image.
 130:      */
 131:     public BufferedImage createCompatibleDestImage (BufferedImage src,
 132:                                                     ColorModel destCM)
 133:     {
 134:       if (destCM != null)
 135:         return new BufferedImage(destCM,
 136:                                  createCompatibleDestRaster(src.getRaster()),
 137:                                  src.isAlphaPremultiplied(), null);
 138: 
 139:       // This behaviour was determined by Mauve testcases, and is compatible
 140:       // with the reference implementation
 141:       if (src.getType() == BufferedImage.TYPE_INT_ARGB_PRE
 142:           || src.getType() == BufferedImage.TYPE_4BYTE_ABGR
 143:           || src.getType() == BufferedImage.TYPE_4BYTE_ABGR_PRE)
 144:         return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
 145: 
 146:       else
 147:         return new BufferedImage(src.getWidth(), src.getHeight(),
 148:                                  BufferedImage.TYPE_INT_ARGB);
 149:     }
 150: 
 151:     /**
 152:      * Creates a new WritableRaster with the size equal to the transformed
 153:      * source raster and correct number of bands .
 154:      *
 155:      * @param src the source raster.
 156:      * @throws RasterFormatException if resulting width or height of raster is 0.
 157:      * @return a new compatible raster.
 158:      */
 159:     public WritableRaster createCompatibleDestRaster (Raster src)
 160:     {
 161:       Rectangle2D rect = getBounds2D(src);
 162: 
 163:       if (rect.getWidth() == 0 || rect.getHeight() == 0)
 164:         throw new RasterFormatException("width or height is 0");
 165: 
 166:       return src.createCompatibleWritableRaster((int) rect.getWidth(),
 167:                                                 (int) rect.getHeight());
 168:     }
 169: 
 170:     /**
 171:      * Transforms source image using transform specified at the constructor.
 172:      * The resulting transformed image is stored in the destination image if one
 173:      * is provided; otherwise a new BufferedImage is created and returned.
 174:      *
 175:      * @param src source image
 176:      * @param dst destination image
 177:      * @throws IllegalArgumentException if the source and destination image are
 178:      *          the same
 179:      * @return transformed source image.
 180:      */
 181:     public final BufferedImage filter (BufferedImage src, BufferedImage dst)
 182:     {
 183:       if (dst == src)
 184:         throw new IllegalArgumentException("src image cannot be the same as "
 185:                                          + "the dst image");
 186: 
 187:       // If the destination image is null, then use a compatible BufferedImage
 188:       if (dst == null)
 189:         dst = createCompatibleDestImage(src, null);
 190: 
 191:       Graphics2D gr = dst.createGraphics();
 192:       gr.setRenderingHints(hints);
 193:       gr.drawImage(src, transform, null);
 194:       return dst;
 195:     }
 196: 
 197:     /**
 198:      * Transforms source raster using transform specified at the constructor.
 199:      * The resulting raster is stored in the destination raster if it is not
 200:      * null, otherwise a new raster is created and returned.
 201:      *
 202:      * @param src source raster
 203:      * @param dst destination raster
 204:      * @throws IllegalArgumentException if the source and destination are not
 205:      *          compatible
 206:      * @return transformed raster.
 207:      */
 208:     public final WritableRaster filter(Raster src, WritableRaster dst)
 209:     {
 210:       // Initial checks
 211:       if (dst == src)
 212:         throw new IllegalArgumentException("src image cannot be the same as"
 213:                                            + " the dst image");
 214: 
 215:       if (dst == null)
 216:         dst = createCompatibleDestRaster(src);
 217: 
 218:       if (src.getNumBands() != dst.getNumBands())
 219:         throw new IllegalArgumentException("src and dst must have same number"
 220:                                            + " of bands");
 221: 
 222:       // Optimization for rasters that can be represented in the RGB colormodel:
 223:       // wrap the rasters in images, and let Cairo do the transformation
 224:       if (ColorModel.getRGBdefault().isCompatibleSampleModel(src.getSampleModel())
 225:           && ColorModel.getRGBdefault().isCompatibleSampleModel(dst.getSampleModel()))
 226:         {
 227:           WritableRaster src2 = Raster.createWritableRaster(src.getSampleModel(),
 228:                                                             src.getDataBuffer(),
 229:                                                             new Point(src.getMinX(),
 230:                                                                       src.getMinY()));
 231:           BufferedImage iSrc = new BufferedImage(ColorModel.getRGBdefault(),
 232:                                                  src2, false, null);
 233:           BufferedImage iDst = new BufferedImage(ColorModel.getRGBdefault(), dst,
 234:                                                  false, null);
 235: 
 236:           return filter(iSrc, iDst).getRaster();
 237:         }
 238: 
 239:       // Otherwise, we need to do the transformation in java code...
 240:       // Create arrays to hold all the points
 241:       double[] dstPts = new double[dst.getHeight() * dst.getWidth() * 2];
 242:       double[] srcPts = new double[dst.getHeight() * dst.getWidth() * 2];
 243: 
 244:       // Populate array with all points in the *destination* raster
 245:       int i = 0;
 246:       for (int x = 0; x < dst.getWidth(); x++)
 247:         {
 248:           for (int y = 0; y < dst.getHeight(); y++)
 249:             {
 250:               dstPts[i++] = x;
 251:               dstPts[i++] = y;
 252:             }
 253:         }
 254:       Rectangle srcbounds = src.getBounds();
 255: 
 256:       // Use an inverse transform to map each point in the destination to
 257:       // a point in the source.  Note that, while all points in the destination
 258:       // matrix are integers, this is not necessarily true for points in the
 259:       // source (hence why interpolation is required)
 260:       try
 261:         {
 262:           AffineTransform inverseTx = transform.createInverse();
 263:           inverseTx.transform(dstPts, 0, srcPts, 0, dstPts.length / 2);
 264:         }
 265:       catch (NoninvertibleTransformException e)
 266:         {
 267:           // Shouldn't happen since the constructor traps this
 268:           throw new ImagingOpException(e.getMessage());
 269:         }
 270: 
 271:       // Different interpolation methods...
 272:       if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
 273:         filterNearest(src, dst, dstPts, srcPts);
 274: 
 275:       else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
 276:         filterBilinear(src, dst, dstPts, srcPts);
 277: 
 278:       else          // bicubic
 279:         filterBicubic(src, dst, dstPts, srcPts);
 280: 
 281:       return dst;
 282:     }
 283: 
 284:     /**
 285:      * Transforms source image using transform specified at the constructor and
 286:      * returns bounds of the transformed image.
 287:      *
 288:      * @param src image to be transformed
 289:      * @return bounds of the transformed image.
 290:      */
 291:     public final Rectangle2D getBounds2D (BufferedImage src)
 292:     {
 293:       return getBounds2D (src.getRaster());
 294:     }
 295: 
 296:     /**
 297:      * Returns bounds of the transformed raster.
 298:      *
 299:      * @param src raster to be transformed
 300:      * @return bounds of the transformed raster.
 301:      */
 302:     public final Rectangle2D getBounds2D (Raster src)
 303:     {
 304:       return transform.createTransformedShape(src.getBounds()).getBounds2D();
 305:     }
 306: 
 307:     /**
 308:      * Returns interpolation type used during transformations.
 309:      *
 310:      * @return interpolation type
 311:      */
 312:     public final int getInterpolationType ()
 313:     {
 314:       if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
 315:         return TYPE_BILINEAR;
 316: 
 317:       else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC))
 318:         return TYPE_BICUBIC;
 319: 
 320:       else
 321:         return TYPE_NEAREST_NEIGHBOR;
 322:     }
 323: 
 324:     /**
 325:      * Returns location of the transformed source point. The resulting point
 326:      * is stored in the dstPt if one is specified.
 327:      *
 328:      * @param srcPt point to be transformed
 329:      * @param dstPt destination point
 330:      * @return the location of the transformed source point.
 331:      */
 332:     public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt)
 333:     {
 334:       return transform.transform (srcPt, dstPt);
 335:     }
 336: 
 337:     /**
 338:      * Returns rendering hints that are used during transformation.
 339:      *
 340:      * @return the rendering hints used in this Op.
 341:      */
 342:     public final RenderingHints getRenderingHints ()
 343:     {
 344:       return hints;
 345:     }
 346: 
 347:     /**
 348:      * Returns transform used in transformation between source and destination
 349:      * image.
 350:      *
 351:      * @return the transform used in this Op.
 352:      */
 353:     public final AffineTransform getTransform ()
 354:     {
 355:       return transform;
 356:     }
 357: 
 358:     /**
 359:      * Perform nearest-neighbour filtering
 360:      *
 361:      * @param src the source raster
 362:      * @param dst the destination raster
 363:      * @param dpts array of points on the destination raster
 364:      * @param pts array of corresponding points on the source raster
 365:      */
 366:     private void filterNearest(Raster src, WritableRaster dst, double[] dpts,
 367:                                double[] pts)
 368:     {
 369:       Rectangle srcbounds = src.getBounds();
 370: 
 371:       // For all points on the destination raster, copy the value from the
 372:       // corrosponding (rounded) source point
 373:       for (int i = 0; i < dpts.length; i += 2)
 374:         {
 375:           int srcX = (int) Math.round(pts[i]) + src.getMinX();
 376:           int srcY = (int) Math.round(pts[i + 1]) + src.getMinY();
 377: 
 378:           if (srcbounds.contains(srcX, srcY))
 379:             dst.setDataElements((int) dpts[i] + dst.getMinX(),
 380:                                 (int) dpts[i + 1] + dst.getMinY(),
 381:                                 src.getDataElements(srcX, srcY, null));
 382:         }
 383:     }
 384: 
 385:     /**
 386:      * Perform bilinear filtering
 387:      *
 388:      * @param src the source raster
 389:      * @param dst the destination raster
 390:      * @param dpts array of points on the destination raster
 391:      * @param pts array of corresponding points on the source raster
 392:      */
 393:     private void filterBilinear(Raster src, WritableRaster dst, double[] dpts,
 394:                               double[] pts)
 395:     {
 396:       Rectangle srcbounds = src.getBounds();
 397: 
 398:       Object xyarr = null;
 399:       Object xp1arr = null;
 400:       Object yp1arr = null;
 401:       Object xyp1arr = null;
 402: 
 403:       double xy;
 404:       double xp1;
 405:       double yp1;
 406:       double xyp1;
 407: 
 408:       double[] result = new double[src.getNumBands()];
 409: 
 410:       // For all points in the destination raster, use bilinear interpolation
 411:       // to find the value from the corrosponding source points
 412:       for (int i = 0; i < dpts.length; i += 2)
 413:         {
 414:           int srcX = (int) Math.round(pts[i]) + src.getMinX();
 415:           int srcY = (int) Math.round(pts[i + 1]) + src.getMinY();
 416: 
 417:           if (srcbounds.contains(srcX, srcY))
 418:             {
 419:               // Corner case at the bottom or right edge; use nearest neighbour
 420:               if (pts[i] >= src.getWidth() - 1
 421:                   || pts[i + 1] >= src.getHeight() - 1)
 422:                 dst.setDataElements((int) dpts[i] + dst.getMinX(),
 423:                                     (int) dpts[i + 1] + dst.getMinY(),
 424:                                     src.getDataElements(srcX, srcY, null));
 425: 
 426:               // Standard case, apply the bilinear formula
 427:               else
 428:                 {
 429:                   int x = (int) Math.floor(pts[i] + src.getMinX());
 430:                   int y = (int) Math.floor(pts[i + 1] + src.getMinY());
 431:                   double xdiff = pts[i] + src.getMinX() - x;
 432:                   double ydiff = pts[i + 1] + src.getMinY() - y;
 433: 
 434:                   // Get surrounding pixels used in interpolation... optimized
 435:                   // to use the smallest datatype possible.
 436:                   if (src.getTransferType() == DataBuffer.TYPE_DOUBLE
 437:                       || src.getTransferType() == DataBuffer.TYPE_FLOAT)
 438:                     {
 439:                       xyarr = src.getPixel(x, y, (double[])xyarr);
 440:                       xp1arr  = src.getPixel(x+1, y, (double[])xp1arr);
 441:                       yp1arr = src.getPixel(x, y+1, (double[])yp1arr);
 442:                       xyp1arr = src.getPixel(x+1, y+1, (double[])xyp1arr);
 443:                     }
 444:                   else
 445:                     {
 446:                       xyarr = src.getPixel(x, y, (int[])xyarr);
 447:                       xp1arr  = src.getPixel(x+1, y, (int[])xp1arr);
 448:                       yp1arr = src.getPixel(x, y+1, (int[])yp1arr);
 449:                       xyp1arr = src.getPixel(x+1, y+1, (int[])xyp1arr);
 450:                     }
 451:                   // using
 452:                   // array[] pixels = src.getPixels(x, y, 2, 2, pixels);
 453:                   // instead of doing four individual src.getPixel() calls
 454:                   // should be faster, but benchmarking shows that it's not...
 455: 
 456:                   // Run interpolation for each band
 457:                   for (int j = 0; j < src.getNumBands(); j++)
 458:                     {
 459:                       // Pull individual sample values out of array
 460:                       if (src.getTransferType() == DataBuffer.TYPE_DOUBLE
 461:                           || src.getTransferType() == DataBuffer.TYPE_FLOAT)
 462:                         {
 463:                           xy = ((double[])xyarr)[j];
 464:                           xp1  = ((double[])xp1arr)[j];
 465:                           yp1 = ((double[])yp1arr)[j];
 466:                           xyp1 = ((double[])xyp1arr)[j];
 467:                         }
 468:                       else
 469:                         {
 470:                           xy = ((int[])xyarr)[j];
 471:                           xp1  = ((int[])xp1arr)[j];
 472:                           yp1 = ((int[])yp1arr)[j];
 473:                           xyp1 = ((int[])xyp1arr)[j];
 474:                         }
 475: 
 476:                       // If all four samples are identical, there's no need to
 477:                       // calculate anything
 478:                       if (xy == xp1 && xy == yp1 && xy == xyp1)
 479:                         result[j] = xy;
 480: 
 481:                       // Run bilinear interpolation formula
 482:                       else
 483:                         result[j] = (xy * (1-xdiff) + xp1 * xdiff)
 484:                                       * (1-ydiff)
 485:                                     + (yp1 * (1-xdiff) + xyp1 * xdiff)
 486:                                       * ydiff;
 487:                     }
 488: 
 489:                   dst.setPixel((int)dpts[i] + dst.getMinX(),
 490:                                (int)dpts[i+1] + dst.getMinY(),
 491:                                result);
 492:                 }
 493:             }
 494:         }
 495:     }
 496: 
 497:     /**
 498:      * Perform bicubic filtering
 499:      * based on http://local.wasp.uwa.edu.au/~pbourke/colour/bicubic/
 500:      *
 501:      * @param src the source raster
 502:      * @param dst the destination raster
 503:      * @param dpts array of points on the destination raster
 504:      * @param pts array of corresponding points on the source raster
 505:      */
 506:     private void filterBicubic(Raster src, WritableRaster dst, double[] dpts,
 507:                                double[] pts)
 508:     {
 509:       Rectangle srcbounds = src.getBounds();
 510:       double[] result = new double[src.getNumBands()];
 511:       Object pixels = null;
 512: 
 513:       // For all points on the destination raster, perform bicubic interpolation
 514:       // from corrosponding source points
 515:       for (int i = 0; i < dpts.length; i += 2)
 516:         {
 517:           if (srcbounds.contains((int) Math.round(pts[i]) + src.getMinX(),
 518:                                  (int) Math.round(pts[i + 1]) + src.getMinY()))
 519:             {
 520:               int x = (int) Math.floor(pts[i] + src.getMinX());
 521:               int y = (int) Math.floor(pts[i + 1] + src.getMinY());
 522:               double dx = pts[i] + src.getMinX() - x;
 523:               double dy = pts[i + 1] + src.getMinY() - y;
 524:               Arrays.fill(result, 0);
 525: 
 526:               for (int m = - 1; m < 3; m++)
 527:                 for (int n = - 1; n < 3; n++)
 528:                   {
 529:                     // R(x) = ( P(x+2)^3 - 4 P(x+1)^3 + 6 P(x)^3 - 4 P(x-1)^3 ) / 6
 530:                     double r1 = 0;
 531:                     double r2 = 0;
 532: 
 533:                     // Calculate R(m - dx)
 534:                     double rx = m - dx + 2;
 535:                     r1 += rx * rx * rx;
 536: 
 537:                     rx = m - dx + 1;
 538:                     if (rx > 0)
 539:                       r1 -= 4 * rx * rx * rx;
 540: 
 541:                     rx = m - dx;
 542:                     if (rx > 0)
 543:                       r1 += 6 * rx * rx * rx;
 544: 
 545:                     rx = m - dx - 1;
 546:                     if (rx > 0)
 547:                       r1 -= 4 * rx * rx * rx;
 548: 
 549:                     r1 /= 6;
 550: 
 551:                     // Calculate R(dy - n);
 552:                     rx = dy - n + 2;
 553:                     if (rx > 0)
 554:                       r2 += rx * rx * rx;
 555: 
 556:                     rx = dy - n + 1;
 557:                     if (rx > 0)
 558:                       r2 -= 4 * rx * rx * rx;
 559: 
 560:                     rx = dy - n;
 561:                     if (rx > 0)
 562:                       r2 += 6 * rx * rx * rx;
 563: 
 564:                     rx = dy - n - 1;
 565:                     if (rx > 0)
 566:                       r2 -= 4 * rx * rx * rx;
 567: 
 568:                     r2 /= 6;
 569: 
 570:                     // Calculate F(i+m, j+n) R(m - dx) R(dy - n)
 571:                     // Check corner cases
 572:                     int srcX = x + m;
 573:                     if (srcX >= src.getMinX() + src.getWidth())
 574:                       srcX = src.getMinX() + src.getWidth() - 1;
 575:                     else if (srcX < src.getMinX())
 576:                       srcX = src.getMinX();
 577: 
 578:                     int srcY = y + n;
 579:                     if (srcY >= src.getMinY() + src.getHeight())
 580:                       srcY = src.getMinY() + src.getHeight() - 1;
 581:                     else if (srcY < src.getMinY())
 582:                       srcY = src.getMinY();
 583: 
 584:                     // Calculate once for each band, using the smallest
 585:                     // datatype possible
 586:                     if (src.getTransferType() == DataBuffer.TYPE_DOUBLE
 587:                         || src.getTransferType() == DataBuffer.TYPE_FLOAT)
 588:                       {
 589:                         pixels = src.getPixel(srcX, srcY, (double[])pixels);
 590:                         for (int j = 0; j < result.length; j++)
 591:                           result[j] += ((double[])pixels)[j] * r1 * r2;
 592:                       }
 593:                     else
 594:                       {
 595:                         pixels = src.getPixel(srcX, srcY, (int[])pixels);
 596:                         for (int j = 0; j < result.length; j++)
 597:                           result[j] += ((int[])pixels)[j] * r1 * r2;
 598:                       }
 599:                   }
 600: 
 601:               // Put it all together
 602:               dst.setPixel((int)dpts[i] + dst.getMinX(),
 603:                            (int)dpts[i+1] + dst.getMinY(),
 604:                            result);
 605:             }
 606:         }
 607:     }
 608: }