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