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 } 90 setPathIterator(PathIterator iterator)91 public void setPathIterator(PathIterator iterator) { 92 reset(); 93 mPath.append(iterator, false /*connect*/); 94 } 95 96 // ---- native methods ---- 97 98 @LayoutlibDelegate init1()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 init2(long nPath)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 native_reset(long nPath)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 native_rewind(long nPath)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 native_set(long native_dst, long native_src)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 native_isConvex(long nPath)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 native_getFillType(long nPath)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 native_setFillType(long nPath, int ft)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 native_isEmpty(long nPath)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 native_isRect(long nPath, RectF rect)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 native_computeBounds(long nPath, RectF bounds)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 native_incReserve(long nPath, int extraPtCount)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 native_moveTo(long nPath, float x, float y)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 native_rMoveTo(long nPath, float dx, float dy)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 native_lineTo(long nPath, float x, float y)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 native_rLineTo(long nPath, float dx, float dy)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 native_quadTo(long nPath, float x1, float y1, float x2, float y2)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 native_rQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2)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 native_cubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)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 native_rCubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)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 native_arcTo(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)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 native_close(long nPath)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 native_addRect(long nPath, float left, float top, float right, float bottom, int dir)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 native_addOval(long nPath, float left, float top, float right, float bottom, int dir)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 native_addCircle(long nPath, float x, float y, float radius, int dir)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 native_addArc(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle)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 native_addRoundRect(long nPath, float left, float top, float right, float bottom, float rx, float ry, int dir)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 native_addRoundRect(long nPath, float left, float top, float right, float bottom, float[] radii, int dir)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 native_addPath(long nPath, long src, float dx, float dy)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 native_addPath(long nPath, long src)411 /*package*/ static void native_addPath(long nPath, long src) { 412 addPath(nPath, src, null /*transform*/); 413 } 414 415 @LayoutlibDelegate native_addPath(long nPath, long src, long matrix)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 native_offset(long nPath, float dx, float dy)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 native_setLastPoint(long nPath, float dx, float dy)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 native_transform(long nPath, long matrix, long dst_path)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 native_transform(long nPath, long matrix)466 /*package*/ static void native_transform(long nPath, long matrix) { 467 native_transform(nPath, matrix, 0); 468 } 469 470 @LayoutlibDelegate native_op(long nPath1, long nPath2, int op, long result)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 finalizer(long nPath)477 /*package*/ static void finalizer(long nPath) { 478 sManager.removeJavaReferenceFor(nPath); 479 } 480 481 @LayoutlibDelegate native_approximate(long nPath, float error)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 set(Path_Delegate delegate)525 private void set(Path_Delegate delegate) { 526 mPath.reset(); 527 setFillType(delegate.mFillType); 528 mPath.append(delegate.mPath, false /*connect*/); 529 } 530 setFillType(FillType fillType)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 */ getWindingRule(FillType type)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 getDirection(int direction)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 addPath(long destPath, long srcPath, AffineTransform transform)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 */ hasPoints()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 */ isEmpty()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 */ fillBounds(RectF bounds)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 */ moveTo(float x, float y)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 */ rMoveTo(float dx, float dy)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 */ lineTo(float x, float y)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 */ rLineTo(float dx, float dy)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 */ quadTo(float x1, float y1, float x2, float y2)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 */ rQuadTo(float dx1, float dy1, float dx2, float dy2)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 */ cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)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 */ rCubicTo(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)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 */ arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)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 */ close()807 public void close() { 808 mPath.closePath(); 809 } 810 resetLastPointFromPath()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 */ addRect(float left, float top, float right, float bottom, int dir)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 */ offset(float dx, float dy)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 */ transform(Matrix_Delegate matrix, Path_Delegate dst)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