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 74 public static Path_Delegate getDelegate(long nPath) { 75 return sManager.getDelegate(nPath); 76 } 77 78 public Path2D getJavaShape() { 79 return mPath; 80 } 81 82 public void setJavaShape(Shape shape) { 83 reset(); 84 mPath.append(shape, false /*connect*/); 85 } 86 87 public void reset() { 88 mPath.reset(); 89 mLastX = 0; 90 mLastY = 0; 91 } 92 93 public void setPathIterator(PathIterator iterator) { 94 reset(); 95 mPath.append(iterator, false /*connect*/); 96 } 97 98 // ---- native methods ---- 99 100 @LayoutlibDelegate 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 413 /*package*/ static void nAddPath(long nPath, long src) { 414 addPath(nPath, src, null /*transform*/); 415 } 416 417 @LayoutlibDelegate 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 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 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 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 468 /*package*/ static void nTransform(long nPath, long matrix) { 469 nTransform(nPath, matrix, 0); 470 } 471 472 @LayoutlibDelegate 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 479 /*package*/ static void nFinalize(long nPath) { 480 sManager.removeJavaReferenceFor(nPath); 481 } 482 483 @LayoutlibDelegate 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 530 private void set(Path_Delegate delegate) { 531 mPath.reset(); 532 setFillType(delegate.mFillType); 533 mPath.append(delegate.mPath, false /*connect*/); 534 } 535 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 813 public void close() { 814 mPath.closePath(); 815 } 816 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 */ 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 */ 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 */ 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