1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics; 18 19 import com.android.ide.common.rendering.api.LayoutLog; 20 import com.android.layoutlib.bridge.Bridge; 21 import com.android.layoutlib.bridge.impl.DelegateManager; 22 import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 23 24 import android.annotation.NonNull; 25 import android.graphics.Path.Direction; 26 import android.graphics.Path.FillType; 27 28 import java.awt.Shape; 29 import java.awt.geom.AffineTransform; 30 import java.awt.geom.Arc2D; 31 import java.awt.geom.Area; 32 import java.awt.geom.Ellipse2D; 33 import java.awt.geom.GeneralPath; 34 import java.awt.geom.Path2D; 35 import java.awt.geom.PathIterator; 36 import java.awt.geom.Point2D; 37 import java.awt.geom.Rectangle2D; 38 import java.awt.geom.RoundRectangle2D; 39 import java.util.ArrayList; 40 41 /** 42 * Delegate implementing the native methods of android.graphics.Path 43 * 44 * Through the layoutlib_create tool, the original native methods of Path have been replaced 45 * by calls to methods of the same name in this delegate class. 46 * 47 * This class behaves like the original native implementation, but in Java, keeping previously 48 * native data into its own objects and mapping them to int that are sent back and forth between 49 * it and the original Path class. 50 * 51 * @see DelegateManager 52 * 53 */ 54 public final class Path_Delegate { 55 56 // ---- delegate manager ---- 57 private static final DelegateManager<Path_Delegate> sManager = 58 new DelegateManager<Path_Delegate>(Path_Delegate.class); 59 60 private static final float EPSILON = 1e-4f; 61 62 // ---- delegate data ---- 63 private FillType mFillType = FillType.WINDING; 64 private Path2D mPath = new Path2D.Double(); 65 66 private float mLastX = 0; 67 private float mLastY = 0; 68 69 // true if the path contains does not contain a curve or line. 70 private boolean mCachedIsEmpty = true; 71 72 // ---- Public Helper methods ---- 73 getDelegate(long nPath)74 public static Path_Delegate getDelegate(long nPath) { 75 return sManager.getDelegate(nPath); 76 } 77 getJavaShape()78 public Path2D getJavaShape() { 79 return mPath; 80 } 81 setJavaShape(Shape shape)82 public void setJavaShape(Shape shape) { 83 reset(); 84 mPath.append(shape, false /*connect*/); 85 } 86 reset()87 public void reset() { 88 mPath.reset(); 89 mLastX = 0; 90 mLastY = 0; 91 } 92 setPathIterator(PathIterator iterator)93 public void setPathIterator(PathIterator iterator) { 94 reset(); 95 mPath.append(iterator, false /*connect*/); 96 } 97 98 // ---- native methods ---- 99 100 @LayoutlibDelegate nInit()101 /*package*/ static long nInit() { 102 // create the delegate 103 Path_Delegate newDelegate = new Path_Delegate(); 104 105 return sManager.addNewDelegate(newDelegate); 106 } 107 108 @LayoutlibDelegate nInit(long nPath)109 /*package*/ static long nInit(long nPath) { 110 // create the delegate 111 Path_Delegate newDelegate = new Path_Delegate(); 112 113 // get the delegate to copy, which could be null if nPath is 0 114 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 115 if (pathDelegate != null) { 116 newDelegate.set(pathDelegate); 117 } 118 119 return sManager.addNewDelegate(newDelegate); 120 } 121 122 @LayoutlibDelegate nReset(long nPath)123 /*package*/ static void nReset(long nPath) { 124 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 125 if (pathDelegate == null) { 126 return; 127 } 128 129 pathDelegate.reset(); 130 } 131 132 @LayoutlibDelegate nRewind(long nPath)133 /*package*/ static void nRewind(long nPath) { 134 // call out to reset since there's nothing to optimize in 135 // terms of data structs. 136 nReset(nPath); 137 } 138 139 @LayoutlibDelegate nSet(long native_dst, long nSrc)140 /*package*/ static void nSet(long native_dst, long nSrc) { 141 Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst); 142 if (pathDstDelegate == null) { 143 return; 144 } 145 146 Path_Delegate pathSrcDelegate = sManager.getDelegate(nSrc); 147 if (pathSrcDelegate == null) { 148 return; 149 } 150 151 pathDstDelegate.set(pathSrcDelegate); 152 } 153 154 @LayoutlibDelegate nIsConvex(long nPath)155 /*package*/ static boolean nIsConvex(long nPath) { 156 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 157 "Path.isConvex is not supported.", null, null); 158 return true; 159 } 160 161 @LayoutlibDelegate nGetFillType(long nPath)162 /*package*/ static int nGetFillType(long nPath) { 163 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 164 if (pathDelegate == null) { 165 return 0; 166 } 167 168 return pathDelegate.mFillType.nativeInt; 169 } 170 171 @LayoutlibDelegate nSetFillType(long nPath, int ft)172 public static void nSetFillType(long nPath, int ft) { 173 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 174 if (pathDelegate == null) { 175 return; 176 } 177 178 pathDelegate.setFillType(Path.sFillTypeArray[ft]); 179 } 180 181 @LayoutlibDelegate nIsEmpty(long nPath)182 /*package*/ static boolean nIsEmpty(long nPath) { 183 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 184 return pathDelegate == null || pathDelegate.isEmpty(); 185 186 } 187 188 @LayoutlibDelegate nIsRect(long nPath, RectF rect)189 /*package*/ static boolean nIsRect(long nPath, RectF rect) { 190 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 191 if (pathDelegate == null) { 192 return false; 193 } 194 195 // create an Area that can test if the path is a rect 196 Area area = new Area(pathDelegate.mPath); 197 if (area.isRectangular()) { 198 if (rect != null) { 199 pathDelegate.fillBounds(rect); 200 } 201 202 return true; 203 } 204 205 return false; 206 } 207 208 @LayoutlibDelegate nComputeBounds(long nPath, RectF bounds)209 /*package*/ static void nComputeBounds(long nPath, RectF bounds) { 210 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 211 if (pathDelegate == null) { 212 return; 213 } 214 215 pathDelegate.fillBounds(bounds); 216 } 217 218 @LayoutlibDelegate nIncReserve(long nPath, int extraPtCount)219 /*package*/ static void nIncReserve(long nPath, int extraPtCount) { 220 // since we use a java2D path, there's no way to pre-allocate new points, 221 // so we do nothing. 222 } 223 224 @LayoutlibDelegate nMoveTo(long nPath, float x, float y)225 /*package*/ static void nMoveTo(long nPath, float x, float y) { 226 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 227 if (pathDelegate == null) { 228 return; 229 } 230 231 pathDelegate.moveTo(x, y); 232 } 233 234 @LayoutlibDelegate nRMoveTo(long nPath, float dx, float dy)235 /*package*/ static void nRMoveTo(long nPath, float dx, float dy) { 236 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 237 if (pathDelegate == null) { 238 return; 239 } 240 241 pathDelegate.rMoveTo(dx, dy); 242 } 243 244 @LayoutlibDelegate nLineTo(long nPath, float x, float y)245 /*package*/ static void nLineTo(long nPath, float x, float y) { 246 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 247 if (pathDelegate == null) { 248 return; 249 } 250 251 pathDelegate.lineTo(x, y); 252 } 253 254 @LayoutlibDelegate nRLineTo(long nPath, float dx, float dy)255 /*package*/ static void nRLineTo(long nPath, float dx, float dy) { 256 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 257 if (pathDelegate == null) { 258 return; 259 } 260 261 pathDelegate.rLineTo(dx, dy); 262 } 263 264 @LayoutlibDelegate nQuadTo(long nPath, float x1, float y1, float x2, float y2)265 /*package*/ static void nQuadTo(long nPath, float x1, float y1, float x2, float y2) { 266 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 267 if (pathDelegate == null) { 268 return; 269 } 270 271 pathDelegate.quadTo(x1, y1, x2, y2); 272 } 273 274 @LayoutlibDelegate nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2)275 /*package*/ static void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2) { 276 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 277 if (pathDelegate == null) { 278 return; 279 } 280 281 pathDelegate.rQuadTo(dx1, dy1, dx2, dy2); 282 } 283 284 @LayoutlibDelegate nCubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)285 /*package*/ static void nCubicTo(long nPath, float x1, float y1, 286 float x2, float y2, float x3, float y3) { 287 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 288 if (pathDelegate == null) { 289 return; 290 } 291 292 pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3); 293 } 294 295 @LayoutlibDelegate nRCubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)296 /*package*/ static void nRCubicTo(long nPath, float x1, float y1, 297 float x2, float y2, float x3, float y3) { 298 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 299 if (pathDelegate == null) { 300 return; 301 } 302 303 pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3); 304 } 305 306 @LayoutlibDelegate nArcTo(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)307 /*package*/ static void nArcTo(long nPath, float left, float top, float right, 308 float bottom, 309 float startAngle, float sweepAngle, boolean forceMoveTo) { 310 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 311 if (pathDelegate == null) { 312 return; 313 } 314 315 pathDelegate.arcTo(left, top, right, bottom, startAngle, sweepAngle, forceMoveTo); 316 } 317 318 @LayoutlibDelegate nClose(long nPath)319 /*package*/ static void nClose(long nPath) { 320 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 321 if (pathDelegate == null) { 322 return; 323 } 324 325 pathDelegate.close(); 326 } 327 328 @LayoutlibDelegate nAddRect(long nPath, float left, float top, float right, float bottom, int dir)329 /*package*/ static void nAddRect(long nPath, 330 float left, float top, float right, float bottom, int dir) { 331 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 332 if (pathDelegate == null) { 333 return; 334 } 335 336 pathDelegate.addRect(left, top, right, bottom, dir); 337 } 338 339 @LayoutlibDelegate nAddOval(long nPath, float left, float top, float right, float bottom, int dir)340 /*package*/ static void nAddOval(long nPath, float left, float top, float right, 341 float bottom, int dir) { 342 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 343 if (pathDelegate == null) { 344 return; 345 } 346 347 pathDelegate.mPath.append(new Ellipse2D.Float( 348 left, top, right - left, bottom - top), false); 349 } 350 351 @LayoutlibDelegate nAddCircle(long nPath, float x, float y, float radius, int dir)352 /*package*/ static void nAddCircle(long nPath, float x, float y, float radius, int dir) { 353 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 354 if (pathDelegate == null) { 355 return; 356 } 357 358 // because x/y is the center of the circle, need to offset this by the radius 359 pathDelegate.mPath.append(new Ellipse2D.Float( 360 x - radius, y - radius, radius * 2, radius * 2), false); 361 } 362 363 @LayoutlibDelegate nAddArc(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle)364 /*package*/ static void nAddArc(long nPath, float left, float top, float right, 365 float bottom, float startAngle, float sweepAngle) { 366 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 367 if (pathDelegate == null) { 368 return; 369 } 370 371 // because x/y is the center of the circle, need to offset this by the radius 372 pathDelegate.mPath.append(new Arc2D.Float( 373 left, top, right - left, bottom - top, 374 -startAngle, -sweepAngle, Arc2D.OPEN), false); 375 } 376 377 @LayoutlibDelegate nAddRoundRect(long nPath, float left, float top, float right, float bottom, float rx, float ry, int dir)378 /*package*/ static void nAddRoundRect(long nPath, float left, float top, float right, 379 float bottom, float rx, float ry, int dir) { 380 381 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 382 if (pathDelegate == null) { 383 return; 384 } 385 386 pathDelegate.mPath.append(new RoundRectangle2D.Float( 387 left, top, right - left, bottom - top, rx * 2, ry * 2), false); 388 } 389 390 @LayoutlibDelegate nAddRoundRect(long nPath, float left, float top, float right, float bottom, float[] radii, int dir)391 /*package*/ static void nAddRoundRect(long nPath, float left, float top, float right, 392 float bottom, float[] radii, int dir) { 393 394 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 395 if (pathDelegate == null) { 396 return; 397 } 398 399 float[] cornerDimensions = new float[radii.length]; 400 for (int i = 0; i < radii.length; i++) { 401 cornerDimensions[i] = 2 * radii[i]; 402 } 403 pathDelegate.mPath.append(new RoundRectangle(left, top, right - left, bottom - top, 404 cornerDimensions), false); 405 } 406 407 @LayoutlibDelegate nAddPath(long nPath, long src, float dx, float dy)408 /*package*/ static void nAddPath(long nPath, long src, float dx, float dy) { 409 addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy)); 410 } 411 412 @LayoutlibDelegate nAddPath(long nPath, long src)413 /*package*/ static void nAddPath(long nPath, long src) { 414 addPath(nPath, src, null /*transform*/); 415 } 416 417 @LayoutlibDelegate nAddPath(long nPath, long src, long matrix)418 /*package*/ static void nAddPath(long nPath, long src, long matrix) { 419 Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); 420 if (matrixDelegate == null) { 421 return; 422 } 423 424 addPath(nPath, src, matrixDelegate.getAffineTransform()); 425 } 426 427 @LayoutlibDelegate nOffset(long nPath, float dx, float dy)428 /*package*/ static void nOffset(long nPath, float dx, float dy) { 429 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 430 if (pathDelegate == null) { 431 return; 432 } 433 434 pathDelegate.offset(dx, dy); 435 } 436 437 @LayoutlibDelegate nSetLastPoint(long nPath, float dx, float dy)438 /*package*/ static void nSetLastPoint(long nPath, float dx, float dy) { 439 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 440 if (pathDelegate == null) { 441 return; 442 } 443 444 pathDelegate.mLastX = dx; 445 pathDelegate.mLastY = dy; 446 } 447 448 @LayoutlibDelegate nTransform(long nPath, long matrix, long dst_path)449 /*package*/ static void nTransform(long nPath, long matrix, 450 long dst_path) { 451 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 452 if (pathDelegate == null) { 453 return; 454 } 455 456 Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); 457 if (matrixDelegate == null) { 458 return; 459 } 460 461 // this can be null if dst_path is 0 462 Path_Delegate dstDelegate = sManager.getDelegate(dst_path); 463 464 pathDelegate.transform(matrixDelegate, dstDelegate); 465 } 466 467 @LayoutlibDelegate nTransform(long nPath, long matrix)468 /*package*/ static void nTransform(long nPath, long matrix) { 469 nTransform(nPath, matrix, 0); 470 } 471 472 @LayoutlibDelegate nOp(long nPath1, long nPath2, int op, long result)473 /*package*/ static boolean nOp(long nPath1, long nPath2, int op, long result) { 474 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null); 475 return false; 476 } 477 478 @LayoutlibDelegate nFinalize(long nPath)479 /*package*/ static void nFinalize(long nPath) { 480 sManager.removeJavaReferenceFor(nPath); 481 } 482 483 @LayoutlibDelegate nApproximate(long nPath, float error)484 /*package*/ static float[] nApproximate(long nPath, float error) { 485 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 486 if (pathDelegate == null) { 487 return null; 488 } 489 // Get a FlatteningIterator 490 PathIterator iterator = pathDelegate.getJavaShape().getPathIterator(null, error); 491 492 float segment[] = new float[6]; 493 float totalLength = 0; 494 ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>(); 495 Point2D.Float previousPoint = null; 496 while (!iterator.isDone()) { 497 int type = iterator.currentSegment(segment); 498 Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]); 499 // MoveTo shouldn't affect the length 500 if (previousPoint != null && type != PathIterator.SEG_MOVETO) { 501 totalLength += currentPoint.distance(previousPoint); 502 } 503 previousPoint = currentPoint; 504 points.add(currentPoint); 505 iterator.next(); 506 } 507 508 int nPoints = points.size(); 509 float[] result = new float[nPoints * 3]; 510 previousPoint = null; 511 // Distance that we've covered so far. Used to calculate the fraction of the path that 512 // we've covered up to this point. 513 float walkedDistance = .0f; 514 for (int i = 0; i < nPoints; i++) { 515 Point2D.Float point = points.get(i); 516 float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f; 517 walkedDistance += distance; 518 result[i * 3] = walkedDistance / totalLength; 519 result[i * 3 + 1] = point.x; 520 result[i * 3 + 2] = point.y; 521 522 previousPoint = point; 523 } 524 525 return result; 526 } 527 528 // ---- Private helper methods ---- 529 set(Path_Delegate delegate)530 private void set(Path_Delegate delegate) { 531 mPath.reset(); 532 setFillType(delegate.mFillType); 533 mPath.append(delegate.mPath, false /*connect*/); 534 } 535 setFillType(FillType fillType)536 private void setFillType(FillType fillType) { 537 mFillType = fillType; 538 mPath.setWindingRule(getWindingRule(fillType)); 539 } 540 541 /** 542 * Returns the Java2D winding rules matching a given Android {@link FillType}. 543 * @param type the android fill type 544 * @return the matching java2d winding rule. 545 */ getWindingRule(FillType type)546 private static int getWindingRule(FillType type) { 547 switch (type) { 548 case WINDING: 549 case INVERSE_WINDING: 550 return GeneralPath.WIND_NON_ZERO; 551 case EVEN_ODD: 552 case INVERSE_EVEN_ODD: 553 return GeneralPath.WIND_EVEN_ODD; 554 555 default: 556 assert false; 557 return GeneralPath.WIND_NON_ZERO; 558 } 559 } 560 561 @NonNull getDirection(int direction)562 private static Direction getDirection(int direction) { 563 for (Direction d : Direction.values()) { 564 if (direction == d.nativeInt) { 565 return d; 566 } 567 } 568 569 assert false; 570 return null; 571 } 572 addPath(long destPath, long srcPath, AffineTransform transform)573 public static void addPath(long destPath, long srcPath, AffineTransform transform) { 574 Path_Delegate destPathDelegate = sManager.getDelegate(destPath); 575 if (destPathDelegate == null) { 576 return; 577 } 578 579 Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath); 580 if (srcPathDelegate == null) { 581 return; 582 } 583 584 if (transform != null) { 585 destPathDelegate.mPath.append( 586 srcPathDelegate.mPath.getPathIterator(transform), false); 587 } else { 588 destPathDelegate.mPath.append(srcPathDelegate.mPath, false); 589 } 590 } 591 592 593 /** 594 * Returns whether the path already contains any points. 595 * Note that this is different to 596 * {@link #isEmpty} because if all elements are {@link PathIterator#SEG_MOVETO}, 597 * {@link #isEmpty} will return true while hasPoints will return false. 598 */ hasPoints()599 public boolean hasPoints() { 600 return !mPath.getPathIterator(null).isDone(); 601 } 602 603 /** 604 * Returns whether the path is empty (contains no lines or curves). 605 * @see Path#isEmpty 606 */ isEmpty()607 public boolean isEmpty() { 608 if (!mCachedIsEmpty) { 609 return false; 610 } 611 612 float[] coords = new float[6]; 613 mCachedIsEmpty = Boolean.TRUE; 614 for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) { 615 int type = it.currentSegment(coords); 616 if (type != PathIterator.SEG_MOVETO) { 617 // Once we know that the path is not empty, we do not need to check again unless 618 // Path#reset is called. 619 mCachedIsEmpty = false; 620 return false; 621 } 622 } 623 624 return true; 625 } 626 627 /** 628 * Fills the given {@link RectF} with the path bounds. 629 * @param bounds the RectF to be filled. 630 */ fillBounds(RectF bounds)631 public void fillBounds(RectF bounds) { 632 Rectangle2D rect = mPath.getBounds2D(); 633 bounds.left = (float)rect.getMinX(); 634 bounds.right = (float)rect.getMaxX(); 635 bounds.top = (float)rect.getMinY(); 636 bounds.bottom = (float)rect.getMaxY(); 637 } 638 639 /** 640 * Set the beginning of the next contour to the point (x,y). 641 * 642 * @param x The x-coordinate of the start of a new contour 643 * @param y The y-coordinate of the start of a new contour 644 */ moveTo(float x, float y)645 public void moveTo(float x, float y) { 646 mPath.moveTo(mLastX = x, mLastY = y); 647 } 648 649 /** 650 * Set the beginning of the next contour relative to the last point on the 651 * previous contour. If there is no previous contour, this is treated the 652 * same as moveTo(). 653 * 654 * @param dx The amount to add to the x-coordinate of the end of the 655 * previous contour, to specify the start of a new contour 656 * @param dy The amount to add to the y-coordinate of the end of the 657 * previous contour, to specify the start of a new contour 658 */ rMoveTo(float dx, float dy)659 public void rMoveTo(float dx, float dy) { 660 dx += mLastX; 661 dy += mLastY; 662 mPath.moveTo(mLastX = dx, mLastY = dy); 663 } 664 665 /** 666 * Add a line from the last point to the specified point (x,y). 667 * If no moveTo() call has been made for this contour, the first point is 668 * automatically set to (0,0). 669 * 670 * @param x The x-coordinate of the end of a line 671 * @param y The y-coordinate of the end of a line 672 */ lineTo(float x, float y)673 public void lineTo(float x, float y) { 674 if (!hasPoints()) { 675 mPath.moveTo(mLastX = 0, mLastY = 0); 676 } 677 mPath.lineTo(mLastX = x, mLastY = y); 678 } 679 680 /** 681 * Same as lineTo, but the coordinates are considered relative to the last 682 * point on this contour. If there is no previous point, then a moveTo(0,0) 683 * is inserted automatically. 684 * 685 * @param dx The amount to add to the x-coordinate of the previous point on 686 * this contour, to specify a line 687 * @param dy The amount to add to the y-coordinate of the previous point on 688 * this contour, to specify a line 689 */ rLineTo(float dx, float dy)690 public void rLineTo(float dx, float dy) { 691 if (!hasPoints()) { 692 mPath.moveTo(mLastX = 0, mLastY = 0); 693 } 694 695 if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) { 696 // The delta is so small that this shouldn't generate a line 697 return; 698 } 699 700 dx += mLastX; 701 dy += mLastY; 702 mPath.lineTo(mLastX = dx, mLastY = dy); 703 } 704 705 /** 706 * Add a quadratic bezier from the last point, approaching control point 707 * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for 708 * this contour, the first point is automatically set to (0,0). 709 * 710 * @param x1 The x-coordinate of the control point on a quadratic curve 711 * @param y1 The y-coordinate of the control point on a quadratic curve 712 * @param x2 The x-coordinate of the end point on a quadratic curve 713 * @param y2 The y-coordinate of the end point on a quadratic curve 714 */ quadTo(float x1, float y1, float x2, float y2)715 public void quadTo(float x1, float y1, float x2, float y2) { 716 mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); 717 } 718 719 /** 720 * Same as quadTo, but the coordinates are considered relative to the last 721 * point on this contour. If there is no previous point, then a moveTo(0,0) 722 * is inserted automatically. 723 * 724 * @param dx1 The amount to add to the x-coordinate of the last point on 725 * this contour, for the control point of a quadratic curve 726 * @param dy1 The amount to add to the y-coordinate of the last point on 727 * this contour, for the control point of a quadratic curve 728 * @param dx2 The amount to add to the x-coordinate of the last point on 729 * this contour, for the end point of a quadratic curve 730 * @param dy2 The amount to add to the y-coordinate of the last point on 731 * this contour, for the end point of a quadratic curve 732 */ rQuadTo(float dx1, float dy1, float dx2, float dy2)733 public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { 734 if (!hasPoints()) { 735 mPath.moveTo(mLastX = 0, mLastY = 0); 736 } 737 dx1 += mLastX; 738 dy1 += mLastY; 739 dx2 += mLastX; 740 dy2 += mLastY; 741 mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2); 742 } 743 744 /** 745 * Add a cubic bezier from the last point, approaching control points 746 * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been 747 * made for this contour, the first point is automatically set to (0,0). 748 * 749 * @param x1 The x-coordinate of the 1st control point on a cubic curve 750 * @param y1 The y-coordinate of the 1st control point on a cubic curve 751 * @param x2 The x-coordinate of the 2nd control point on a cubic curve 752 * @param y2 The y-coordinate of the 2nd control point on a cubic curve 753 * @param x3 The x-coordinate of the end point on a cubic curve 754 * @param y3 The y-coordinate of the end point on a cubic curve 755 */ cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)756 public void cubicTo(float x1, float y1, float x2, float y2, 757 float x3, float y3) { 758 if (!hasPoints()) { 759 mPath.moveTo(0, 0); 760 } 761 mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); 762 } 763 764 /** 765 * Same as cubicTo, but the coordinates are considered relative to the 766 * current point on this contour. If there is no previous point, then a 767 * moveTo(0,0) is inserted automatically. 768 */ rCubicTo(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)769 public void rCubicTo(float dx1, float dy1, float dx2, float dy2, 770 float dx3, float dy3) { 771 if (!hasPoints()) { 772 mPath.moveTo(mLastX = 0, mLastY = 0); 773 } 774 dx1 += mLastX; 775 dy1 += mLastY; 776 dx2 += mLastX; 777 dy2 += mLastY; 778 dx3 += mLastX; 779 dy3 += mLastY; 780 mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3); 781 } 782 783 /** 784 * Append the specified arc to the path as a new contour. If the start of 785 * the path is different from the path's current last point, then an 786 * automatic lineTo() is added to connect the current contour to the 787 * start of the arc. However, if the path is empty, then we call moveTo() 788 * with the first point of the arc. The sweep angle is tread mod 360. 789 * 790 * @param left The left of oval defining shape and size of the arc 791 * @param top The top of oval defining shape and size of the arc 792 * @param right The right of oval defining shape and size of the arc 793 * @param bottom The bottom of oval defining shape and size of the arc 794 * @param startAngle Starting angle (in degrees) where the arc begins 795 * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated 796 * mod 360. 797 * @param forceMoveTo If true, always begin a new contour with the arc 798 */ arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)799 public void arcTo(float left, float top, float right, float bottom, float startAngle, 800 float sweepAngle, 801 boolean forceMoveTo) { 802 Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle, 803 -sweepAngle, Arc2D.OPEN); 804 mPath.append(arc, true /*connect*/); 805 806 resetLastPointFromPath(); 807 } 808 809 /** 810 * Close the current contour. If the current point is not equal to the 811 * first point of the contour, a line segment is automatically added. 812 */ close()813 public void close() { 814 mPath.closePath(); 815 } 816 resetLastPointFromPath()817 private void resetLastPointFromPath() { 818 Point2D last = mPath.getCurrentPoint(); 819 mLastX = (float) last.getX(); 820 mLastY = (float) last.getY(); 821 } 822 823 /** 824 * Add a closed rectangle contour to the path 825 * 826 * @param left The left side of a rectangle to add to the path 827 * @param top The top of a rectangle to add to the path 828 * @param right The right side of a rectangle to add to the path 829 * @param bottom The bottom of a rectangle to add to the path 830 * @param dir The direction to wind the rectangle's contour 831 */ addRect(float left, float top, float right, float bottom, int dir)832 public void addRect(float left, float top, float right, float bottom, 833 int dir) { 834 moveTo(left, top); 835 836 Direction direction = getDirection(dir); 837 838 switch (direction) { 839 case CW: 840 lineTo(right, top); 841 lineTo(right, bottom); 842 lineTo(left, bottom); 843 break; 844 case CCW: 845 lineTo(left, bottom); 846 lineTo(right, bottom); 847 lineTo(right, top); 848 break; 849 } 850 851 close(); 852 853 resetLastPointFromPath(); 854 } 855 856 /** 857 * Offset the path by (dx,dy), returning true on success 858 * 859 * @param dx The amount in the X direction to offset the entire path 860 * @param dy The amount in the Y direction to offset the entire path 861 */ offset(float dx, float dy)862 public void offset(float dx, float dy) { 863 GeneralPath newPath = new GeneralPath(); 864 865 PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); 866 867 newPath.append(iterator, false /*connect*/); 868 mPath = newPath; 869 } 870 871 /** 872 * Transform the points in this path by matrix, and write the answer 873 * into dst. If dst is null, then the the original path is modified. 874 * 875 * @param matrix The matrix to apply to the path 876 * @param dst The transformed path is written here. If dst is null, 877 * then the the original path is modified 878 */ transform(Matrix_Delegate matrix, Path_Delegate dst)879 public void transform(Matrix_Delegate matrix, Path_Delegate dst) { 880 if (matrix.hasPerspective()) { 881 assert false; 882 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE, 883 "android.graphics.Path#transform() only " + 884 "supports affine transformations.", null, null /*data*/); 885 } 886 887 GeneralPath newPath = new GeneralPath(); 888 889 PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform()); 890 891 newPath.append(iterator, false /*connect*/); 892 893 if (dst != null) { 894 dst.mPath = newPath; 895 } else { 896 mPath = newPath; 897 } 898 } 899 } 900