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