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 for (int i = 0; i < nPoints; i++) { 512 Point2D.Float point = points.get(i); 513 float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f; 514 result[i * 3] = distance / totalLength; 515 result[i * 3 + 1] = point.x; 516 result[i * 3 + 2] = point.y; 517 518 totalLength += distance; 519 previousPoint = point; 520 } 521 522 return result; 523 } 524 525 // ---- Private helper methods ---- 526 set(Path_Delegate delegate)527 private void set(Path_Delegate delegate) { 528 mPath.reset(); 529 setFillType(delegate.mFillType); 530 mPath.append(delegate.mPath, false /*connect*/); 531 } 532 setFillType(FillType fillType)533 private void setFillType(FillType fillType) { 534 mFillType = fillType; 535 mPath.setWindingRule(getWindingRule(fillType)); 536 } 537 538 /** 539 * Returns the Java2D winding rules matching a given Android {@link FillType}. 540 * @param type the android fill type 541 * @return the matching java2d winding rule. 542 */ getWindingRule(FillType type)543 private static int getWindingRule(FillType type) { 544 switch (type) { 545 case WINDING: 546 case INVERSE_WINDING: 547 return GeneralPath.WIND_NON_ZERO; 548 case EVEN_ODD: 549 case INVERSE_EVEN_ODD: 550 return GeneralPath.WIND_EVEN_ODD; 551 552 default: 553 assert false; 554 return GeneralPath.WIND_NON_ZERO; 555 } 556 } 557 558 @NonNull getDirection(int direction)559 private static Direction getDirection(int direction) { 560 for (Direction d : Direction.values()) { 561 if (direction == d.nativeInt) { 562 return d; 563 } 564 } 565 566 assert false; 567 return null; 568 } 569 addPath(long destPath, long srcPath, AffineTransform transform)570 public static void addPath(long destPath, long srcPath, AffineTransform transform) { 571 Path_Delegate destPathDelegate = sManager.getDelegate(destPath); 572 if (destPathDelegate == null) { 573 return; 574 } 575 576 Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath); 577 if (srcPathDelegate == null) { 578 return; 579 } 580 581 if (transform != null) { 582 destPathDelegate.mPath.append( 583 srcPathDelegate.mPath.getPathIterator(transform), false); 584 } else { 585 destPathDelegate.mPath.append(srcPathDelegate.mPath, false); 586 } 587 } 588 589 590 /** 591 * Returns whether the path already contains any points. 592 * Note that this is different to 593 * {@link #isEmpty} because if all elements are {@link PathIterator#SEG_MOVETO}, 594 * {@link #isEmpty} will return true while hasPoints will return false. 595 */ hasPoints()596 public boolean hasPoints() { 597 return !mPath.getPathIterator(null).isDone(); 598 } 599 600 /** 601 * Returns whether the path is empty (contains no lines or curves). 602 * @see Path#isEmpty 603 */ isEmpty()604 public boolean isEmpty() { 605 if (!mCachedIsEmpty) { 606 return false; 607 } 608 609 float[] coords = new float[6]; 610 mCachedIsEmpty = Boolean.TRUE; 611 for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) { 612 int type = it.currentSegment(coords); 613 if (type != PathIterator.SEG_MOVETO) { 614 // Once we know that the path is not empty, we do not need to check again unless 615 // Path#reset is called. 616 mCachedIsEmpty = false; 617 return false; 618 } 619 } 620 621 return true; 622 } 623 624 /** 625 * Fills the given {@link RectF} with the path bounds. 626 * @param bounds the RectF to be filled. 627 */ fillBounds(RectF bounds)628 public void fillBounds(RectF bounds) { 629 Rectangle2D rect = mPath.getBounds2D(); 630 bounds.left = (float)rect.getMinX(); 631 bounds.right = (float)rect.getMaxX(); 632 bounds.top = (float)rect.getMinY(); 633 bounds.bottom = (float)rect.getMaxY(); 634 } 635 636 /** 637 * Set the beginning of the next contour to the point (x,y). 638 * 639 * @param x The x-coordinate of the start of a new contour 640 * @param y The y-coordinate of the start of a new contour 641 */ moveTo(float x, float y)642 public void moveTo(float x, float y) { 643 mPath.moveTo(mLastX = x, mLastY = y); 644 } 645 646 /** 647 * Set the beginning of the next contour relative to the last point on the 648 * previous contour. If there is no previous contour, this is treated the 649 * same as moveTo(). 650 * 651 * @param dx The amount to add to the x-coordinate of the end of the 652 * previous contour, to specify the start of a new contour 653 * @param dy The amount to add to the y-coordinate of the end of the 654 * previous contour, to specify the start of a new contour 655 */ rMoveTo(float dx, float dy)656 public void rMoveTo(float dx, float dy) { 657 dx += mLastX; 658 dy += mLastY; 659 mPath.moveTo(mLastX = dx, mLastY = dy); 660 } 661 662 /** 663 * Add a line from the last point to the specified point (x,y). 664 * If no moveTo() call has been made for this contour, the first point is 665 * automatically set to (0,0). 666 * 667 * @param x The x-coordinate of the end of a line 668 * @param y The y-coordinate of the end of a line 669 */ lineTo(float x, float y)670 public void lineTo(float x, float y) { 671 if (!hasPoints()) { 672 mPath.moveTo(mLastX = 0, mLastY = 0); 673 } 674 mPath.lineTo(mLastX = x, mLastY = y); 675 } 676 677 /** 678 * Same as lineTo, but the coordinates are considered relative to the last 679 * point on this contour. If there is no previous point, then a moveTo(0,0) 680 * is inserted automatically. 681 * 682 * @param dx The amount to add to the x-coordinate of the previous point on 683 * this contour, to specify a line 684 * @param dy The amount to add to the y-coordinate of the previous point on 685 * this contour, to specify a line 686 */ rLineTo(float dx, float dy)687 public void rLineTo(float dx, float dy) { 688 if (!hasPoints()) { 689 mPath.moveTo(mLastX = 0, mLastY = 0); 690 } 691 692 if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) { 693 // The delta is so small that this shouldn't generate a line 694 return; 695 } 696 697 dx += mLastX; 698 dy += mLastY; 699 mPath.lineTo(mLastX = dx, mLastY = dy); 700 } 701 702 /** 703 * Add a quadratic bezier from the last point, approaching control point 704 * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for 705 * this contour, the first point is automatically set to (0,0). 706 * 707 * @param x1 The x-coordinate of the control point on a quadratic curve 708 * @param y1 The y-coordinate of the control point on a quadratic curve 709 * @param x2 The x-coordinate of the end point on a quadratic curve 710 * @param y2 The y-coordinate of the end point on a quadratic curve 711 */ quadTo(float x1, float y1, float x2, float y2)712 public void quadTo(float x1, float y1, float x2, float y2) { 713 mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); 714 } 715 716 /** 717 * Same as quadTo, but the coordinates are considered relative to the last 718 * point on this contour. If there is no previous point, then a moveTo(0,0) 719 * is inserted automatically. 720 * 721 * @param dx1 The amount to add to the x-coordinate of the last point on 722 * this contour, for the control point of a quadratic curve 723 * @param dy1 The amount to add to the y-coordinate of the last point on 724 * this contour, for the control point of a quadratic curve 725 * @param dx2 The amount to add to the x-coordinate of the last point on 726 * this contour, for the end point of a quadratic curve 727 * @param dy2 The amount to add to the y-coordinate of the last point on 728 * this contour, for the end point of a quadratic curve 729 */ rQuadTo(float dx1, float dy1, float dx2, float dy2)730 public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { 731 if (!hasPoints()) { 732 mPath.moveTo(mLastX = 0, mLastY = 0); 733 } 734 dx1 += mLastX; 735 dy1 += mLastY; 736 dx2 += mLastX; 737 dy2 += mLastY; 738 mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2); 739 } 740 741 /** 742 * Add a cubic bezier from the last point, approaching control points 743 * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been 744 * made for this contour, the first point is automatically set to (0,0). 745 * 746 * @param x1 The x-coordinate of the 1st control point on a cubic curve 747 * @param y1 The y-coordinate of the 1st control point on a cubic curve 748 * @param x2 The x-coordinate of the 2nd control point on a cubic curve 749 * @param y2 The y-coordinate of the 2nd control point on a cubic curve 750 * @param x3 The x-coordinate of the end point on a cubic curve 751 * @param y3 The y-coordinate of the end point on a cubic curve 752 */ cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)753 public void cubicTo(float x1, float y1, float x2, float y2, 754 float x3, float y3) { 755 if (!hasPoints()) { 756 mPath.moveTo(0, 0); 757 } 758 mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); 759 } 760 761 /** 762 * Same as cubicTo, but the coordinates are considered relative to the 763 * current point on this contour. If there is no previous point, then a 764 * moveTo(0,0) is inserted automatically. 765 */ rCubicTo(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)766 public void rCubicTo(float dx1, float dy1, float dx2, float dy2, 767 float dx3, float dy3) { 768 if (!hasPoints()) { 769 mPath.moveTo(mLastX = 0, mLastY = 0); 770 } 771 dx1 += mLastX; 772 dy1 += mLastY; 773 dx2 += mLastX; 774 dy2 += mLastY; 775 dx3 += mLastX; 776 dy3 += mLastY; 777 mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3); 778 } 779 780 /** 781 * Append the specified arc to the path as a new contour. If the start of 782 * the path is different from the path's current last point, then an 783 * automatic lineTo() is added to connect the current contour to the 784 * start of the arc. However, if the path is empty, then we call moveTo() 785 * with the first point of the arc. The sweep angle is tread mod 360. 786 * 787 * @param left The left of oval defining shape and size of the arc 788 * @param top The top of oval defining shape and size of the arc 789 * @param right The right of oval defining shape and size of the arc 790 * @param bottom The bottom of oval defining shape and size of the arc 791 * @param startAngle Starting angle (in degrees) where the arc begins 792 * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated 793 * mod 360. 794 * @param forceMoveTo If true, always begin a new contour with the arc 795 */ arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)796 public void arcTo(float left, float top, float right, float bottom, float startAngle, 797 float sweepAngle, 798 boolean forceMoveTo) { 799 Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle, 800 -sweepAngle, Arc2D.OPEN); 801 mPath.append(arc, true /*connect*/); 802 803 resetLastPointFromPath(); 804 } 805 806 /** 807 * Close the current contour. If the current point is not equal to the 808 * first point of the contour, a line segment is automatically added. 809 */ close()810 public void close() { 811 mPath.closePath(); 812 } 813 resetLastPointFromPath()814 private void resetLastPointFromPath() { 815 Point2D last = mPath.getCurrentPoint(); 816 mLastX = (float) last.getX(); 817 mLastY = (float) last.getY(); 818 } 819 820 /** 821 * Add a closed rectangle contour to the path 822 * 823 * @param left The left side of a rectangle to add to the path 824 * @param top The top of a rectangle to add to the path 825 * @param right The right side of a rectangle to add to the path 826 * @param bottom The bottom of a rectangle to add to the path 827 * @param dir The direction to wind the rectangle's contour 828 */ addRect(float left, float top, float right, float bottom, int dir)829 public void addRect(float left, float top, float right, float bottom, 830 int dir) { 831 moveTo(left, top); 832 833 Direction direction = getDirection(dir); 834 835 switch (direction) { 836 case CW: 837 lineTo(right, top); 838 lineTo(right, bottom); 839 lineTo(left, bottom); 840 break; 841 case CCW: 842 lineTo(left, bottom); 843 lineTo(right, bottom); 844 lineTo(right, top); 845 break; 846 } 847 848 close(); 849 850 resetLastPointFromPath(); 851 } 852 853 /** 854 * Offset the path by (dx,dy), returning true on success 855 * 856 * @param dx The amount in the X direction to offset the entire path 857 * @param dy The amount in the Y direction to offset the entire path 858 */ offset(float dx, float dy)859 public void offset(float dx, float dy) { 860 GeneralPath newPath = new GeneralPath(); 861 862 PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); 863 864 newPath.append(iterator, false /*connect*/); 865 mPath = newPath; 866 } 867 868 /** 869 * Transform the points in this path by matrix, and write the answer 870 * into dst. If dst is null, then the the original path is modified. 871 * 872 * @param matrix The matrix to apply to the path 873 * @param dst The transformed path is written here. If dst is null, 874 * then the the original path is modified 875 */ transform(Matrix_Delegate matrix, Path_Delegate dst)876 public void transform(Matrix_Delegate matrix, Path_Delegate dst) { 877 if (matrix.hasPerspective()) { 878 assert false; 879 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE, 880 "android.graphics.Path#transform() only " + 881 "supports affine transformations.", null, null /*data*/); 882 } 883 884 GeneralPath newPath = new GeneralPath(); 885 886 PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform()); 887 888 newPath.append(iterator, false /*connect*/); 889 890 if (dst != null) { 891 dst.mPath = newPath; 892 } else { 893 mPath = newPath; 894 } 895 } 896 } 897