Path_Delegate.java revision 56222cfbe9973c518f7e8c9113c614de80b5a4b2
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.layoutlib.bridge.Bridge; 20import com.android.layoutlib.bridge.BridgeConstants; 21import com.android.layoutlib.bridge.impl.DelegateManager; 22 23import android.graphics.Path.Direction; 24import android.graphics.Path.FillType; 25 26import java.awt.Shape; 27import java.awt.geom.AffineTransform; 28import java.awt.geom.Arc2D; 29import java.awt.geom.Area; 30import java.awt.geom.GeneralPath; 31import java.awt.geom.PathIterator; 32import java.awt.geom.Point2D; 33import java.awt.geom.Rectangle2D; 34 35/** 36 * Delegate implementing the native methods of android.graphics.Path 37 * 38 * Through the layoutlib_create tool, the original native methods of Path have been replaced 39 * by calls to methods of the same name in this delegate class. 40 * 41 * This class behaves like the original native implementation, but in Java, keeping previously 42 * native data into its own objects and mapping them to int that are sent back and forth between 43 * it and the original Path class. 44 * 45 * @see DelegateManager 46 * 47 */ 48public final class Path_Delegate { 49 50 // ---- delegate manager ---- 51 private static final DelegateManager<Path_Delegate> sManager = 52 new DelegateManager<Path_Delegate>(); 53 54 // ---- delegate data ---- 55 private FillType mFillType = FillType.WINDING; 56 private GeneralPath mPath = new GeneralPath(); 57 58 private float mLastX = 0; 59 private float mLastY = 0; 60 61 // ---- Public Helper methods ---- 62 63 public static Path_Delegate getDelegate(int nPath) { 64 return sManager.getDelegate(nPath); 65 } 66 67 public Shape getJavaShape() { 68 return mPath; 69 } 70 71 public void setJavaShape(Shape shape) { 72 mPath.reset(); 73 mPath.append(shape, false /*connect*/); 74 } 75 76 public void reset() { 77 mPath.reset(); 78 } 79 80 public void setPathIterator(PathIterator iterator) { 81 mPath.reset(); 82 mPath.append(iterator, false /*connect*/); 83 } 84 85 // ---- native methods ---- 86 87 /*package*/ static int init1() { 88 // create the delegate 89 Path_Delegate newDelegate = new Path_Delegate(); 90 91 return sManager.addDelegate(newDelegate); 92 } 93 94 /*package*/ static int init2(int nPath) { 95 // create the delegate 96 Path_Delegate newDelegate = new Path_Delegate(); 97 98 // get the delegate to copy, which could be null if nPath is 0 99 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 100 if (pathDelegate != null) { 101 newDelegate.set(pathDelegate); 102 } 103 104 return sManager.addDelegate(newDelegate); 105 } 106 107 /*package*/ static void native_reset(int nPath) { 108 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 109 if (pathDelegate == null) { 110 return; 111 } 112 113 pathDelegate.mPath.reset(); 114 } 115 116 /*package*/ static void native_rewind(int nPath) { 117 // call out to reset since there's nothing to optimize in 118 // terms of data structs. 119 native_reset(nPath); 120 } 121 122 /*package*/ static void native_set(int native_dst, int native_src) { 123 Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst); 124 if (pathDstDelegate == null) { 125 return; 126 } 127 128 Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src); 129 if (pathSrcDelegate == null) { 130 return; 131 } 132 133 pathDstDelegate.set(pathSrcDelegate); 134 } 135 136 /*package*/ static int native_getFillType(int nPath) { 137 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 138 if (pathDelegate == null) { 139 return 0; 140 } 141 142 return pathDelegate.mFillType.nativeInt; 143 } 144 145 /*package*/ static void native_setFillType(int nPath, int ft) { 146 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 147 if (pathDelegate == null) { 148 return; 149 } 150 151 pathDelegate.mFillType = Path.sFillTypeArray[ft]; 152 } 153 154 /*package*/ static boolean native_isEmpty(int nPath) { 155 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 156 if (pathDelegate == null) { 157 return true; 158 } 159 160 return pathDelegate.isEmpty(); 161 } 162 163 /*package*/ static boolean native_isRect(int nPath, RectF rect) { 164 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 165 if (pathDelegate == null) { 166 return false; 167 } 168 169 // create an Area that can test if the path is a rect 170 Area area = new Area(pathDelegate.mPath); 171 if (area.isRectangular()) { 172 if (rect != null) { 173 pathDelegate.fillBounds(rect); 174 } 175 176 return true; 177 } 178 179 return false; 180 } 181 182 /*package*/ static void native_computeBounds(int nPath, RectF bounds) { 183 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 184 if (pathDelegate == null) { 185 return; 186 } 187 188 pathDelegate.fillBounds(bounds); 189 } 190 191 /*package*/ static void native_incReserve(int nPath, int extraPtCount) { 192 // since we use a java2D path, there's no way to pre-allocate new points, 193 // so we do nothing. 194 } 195 196 /*package*/ static void native_moveTo(int nPath, float x, float y) { 197 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 198 if (pathDelegate == null) { 199 return; 200 } 201 202 pathDelegate.moveTo(x, y); 203 } 204 205 /*package*/ static void native_rMoveTo(int nPath, float dx, float dy) { 206 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 207 if (pathDelegate == null) { 208 return; 209 } 210 211 pathDelegate.rMoveTo(dx, dy); 212 } 213 214 /*package*/ static void native_lineTo(int nPath, float x, float y) { 215 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 216 if (pathDelegate == null) { 217 return; 218 } 219 220 pathDelegate.lineTo(x, y); 221 } 222 223 /*package*/ static void native_rLineTo(int nPath, float dx, float dy) { 224 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 225 if (pathDelegate == null) { 226 return; 227 } 228 229 pathDelegate.rLineTo(dx, dy); 230 } 231 232 /*package*/ static void native_quadTo(int nPath, float x1, float y1, float x2, float y2) { 233 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 234 if (pathDelegate == null) { 235 return; 236 } 237 238 pathDelegate.quadTo(x1, y1, x2, y2); 239 } 240 241 /*package*/ static void native_rQuadTo(int nPath, float dx1, float dy1, float dx2, float dy2) { 242 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 243 if (pathDelegate == null) { 244 return; 245 } 246 247 pathDelegate.rQuadTo(dx1, dy1, dx2, dy2); 248 } 249 250 /*package*/ static void native_cubicTo(int nPath, float x1, float y1, 251 float x2, float y2, float x3, float y3) { 252 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 253 if (pathDelegate == null) { 254 return; 255 } 256 257 pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3); 258 } 259 260 /*package*/ static void native_rCubicTo(int nPath, float x1, float y1, 261 float x2, float y2, float x3, float y3) { 262 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 263 if (pathDelegate == null) { 264 return; 265 } 266 267 pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3); 268 } 269 270 /*package*/ static void native_arcTo(int nPath, RectF oval, 271 float startAngle, float sweepAngle, boolean forceMoveTo) { 272 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 273 if (pathDelegate == null) { 274 return; 275 } 276 277 pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo); 278 } 279 280 /*package*/ static void native_close(int nPath) { 281 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 282 if (pathDelegate == null) { 283 return; 284 } 285 286 pathDelegate.close(); 287 } 288 289 /*package*/ static void native_addRect(int nPath, RectF rect, int dir) { 290 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 291 if (pathDelegate == null) { 292 return; 293 } 294 295 pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir); 296 } 297 298 /*package*/ static void native_addRect(int nPath, 299 float left, float top, float right, float bottom, int dir) { 300 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 301 if (pathDelegate == null) { 302 return; 303 } 304 305 pathDelegate.addRect(left, top, right, bottom, dir); 306 } 307 308 /*package*/ static void native_addOval(int nPath, RectF oval, int dir) { 309 // FIXME 310 throw new UnsupportedOperationException(); 311 } 312 313 /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) { 314 // FIXME 315 throw new UnsupportedOperationException(); 316 } 317 318 /*package*/ static void native_addArc(int nPath, RectF oval, 319 float startAngle, float sweepAngle) { 320 // FIXME 321 throw new UnsupportedOperationException(); 322 } 323 324 /*package*/ static void native_addRoundRect(int nPath, RectF rect, 325 float rx, float ry, int dir) { 326 // FIXME 327 throw new UnsupportedOperationException(); 328 } 329 330 /*package*/ static void native_addRoundRect(int nPath, RectF r, float[] radii, int dir) { 331 // FIXME 332 throw new UnsupportedOperationException(); 333 } 334 335 /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) { 336 // FIXME 337 throw new UnsupportedOperationException(); 338 } 339 340 /*package*/ static void native_addPath(int nPath, int src) { 341 native_addPath(nPath, src, 0, 0); 342 } 343 344 /*package*/ static void native_addPath(int nPath, int src, int matrix) { 345 // FIXME 346 throw new UnsupportedOperationException(); 347 } 348 349 /*package*/ static void native_offset(int nPath, float dx, float dy, int dst_path) { 350 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 351 if (pathDelegate == null) { 352 return; 353 } 354 355 // could be null if the int is 0; 356 Path_Delegate dstDelegate = sManager.getDelegate(dst_path); 357 358 pathDelegate.offset(dx, dy, dstDelegate); 359 } 360 361 /*package*/ static void native_offset(int nPath, float dx, float dy) { 362 native_offset(nPath, dx, dy, 0); 363 } 364 365 /*package*/ static void native_setLastPoint(int nPath, float dx, float dy) { 366 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 367 if (pathDelegate == null) { 368 return; 369 } 370 371 pathDelegate.mLastX = dx; 372 pathDelegate.mLastY = dy; 373 } 374 375 /*package*/ static void native_transform(int nPath, int matrix, 376 int dst_path) { 377 Path_Delegate pathDelegate = sManager.getDelegate(nPath); 378 if (pathDelegate == null) { 379 return; 380 } 381 382 Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); 383 if (matrixDelegate == null) { 384 return; 385 } 386 387 // this can be null if dst_path is 0 388 Path_Delegate dstDelegate = sManager.getDelegate(dst_path); 389 390 pathDelegate.transform(matrixDelegate, dstDelegate); 391 } 392 393 /*package*/ static void native_transform(int nPath, int matrix) { 394 native_transform(nPath, matrix, 0); 395 } 396 397 /*package*/ static void finalizer(int nPath) { 398 sManager.removeDelegate(nPath); 399 } 400 401 402 // ---- Private helper methods ---- 403 404 private void set(Path_Delegate delegate) { 405 mPath.reset(); 406 setFillType(delegate.mFillType); 407 mPath.append(delegate.mPath, false /*connect*/); 408 } 409 410 private void setFillType(FillType fillType) { 411 mFillType = fillType; 412 mPath.setWindingRule(getWindingRule(fillType)); 413 } 414 415 /** 416 * Returns the Java2D winding rules matching a given Android {@link FillType}. 417 * @param type the android fill type 418 * @return the matching java2d winding rule. 419 */ 420 private static int getWindingRule(FillType type) { 421 switch (type) { 422 case WINDING: 423 case INVERSE_WINDING: 424 return GeneralPath.WIND_NON_ZERO; 425 case EVEN_ODD: 426 case INVERSE_EVEN_ODD: 427 return GeneralPath.WIND_EVEN_ODD; 428 } 429 430 assert false; 431 throw new IllegalArgumentException(); 432 } 433 434 private static Direction getDirection(int direction) { 435 for (Direction d : Direction.values()) { 436 if (direction == d.nativeInt) { 437 return d; 438 } 439 } 440 441 assert false; 442 return null; 443 } 444 445 /** 446 * Returns whether the path is empty. 447 * @return true if the path is empty. 448 */ 449 private boolean isEmpty() { 450 return mPath.getCurrentPoint() == null; 451 } 452 453 /** 454 * Fills the given {@link RectF} with the path bounds. 455 * @param bounds the RectF to be filled. 456 */ 457 private void fillBounds(RectF bounds) { 458 Rectangle2D rect = mPath.getBounds2D(); 459 bounds.left = (float)rect.getMinX(); 460 bounds.right = (float)rect.getMaxX(); 461 bounds.top = (float)rect.getMinY(); 462 bounds.bottom = (float)rect.getMaxY(); 463 } 464 465 /** 466 * Set the beginning of the next contour to the point (x,y). 467 * 468 * @param x The x-coordinate of the start of a new contour 469 * @param y The y-coordinate of the start of a new contour 470 */ 471 private void moveTo(float x, float y) { 472 mPath.moveTo(mLastX = x, mLastY = y); 473 } 474 475 /** 476 * Set the beginning of the next contour relative to the last point on the 477 * previous contour. If there is no previous contour, this is treated the 478 * same as moveTo(). 479 * 480 * @param dx The amount to add to the x-coordinate of the end of the 481 * previous contour, to specify the start of a new contour 482 * @param dy The amount to add to the y-coordinate of the end of the 483 * previous contour, to specify the start of a new contour 484 */ 485 private void rMoveTo(float dx, float dy) { 486 dx += mLastX; 487 dy += mLastY; 488 mPath.moveTo(mLastX = dx, mLastY = dy); 489 } 490 491 /** 492 * Add a line from the last point to the specified point (x,y). 493 * If no moveTo() call has been made for this contour, the first point is 494 * automatically set to (0,0). 495 * 496 * @param x The x-coordinate of the end of a line 497 * @param y The y-coordinate of the end of a line 498 */ 499 private void lineTo(float x, float y) { 500 mPath.lineTo(mLastX = x, mLastY = y); 501 } 502 503 /** 504 * Same as lineTo, but the coordinates are considered relative to the last 505 * point on this contour. If there is no previous point, then a moveTo(0,0) 506 * is inserted automatically. 507 * 508 * @param dx The amount to add to the x-coordinate of the previous point on 509 * this contour, to specify a line 510 * @param dy The amount to add to the y-coordinate of the previous point on 511 * this contour, to specify a line 512 */ 513 private void rLineTo(float dx, float dy) { 514 if (isEmpty()) { 515 mPath.moveTo(mLastX = 0, mLastY = 0); 516 } 517 dx += mLastX; 518 dy += mLastY; 519 mPath.lineTo(mLastX = dx, mLastY = dy); 520 } 521 522 /** 523 * Add a quadratic bezier from the last point, approaching control point 524 * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for 525 * this contour, the first point is automatically set to (0,0). 526 * 527 * @param x1 The x-coordinate of the control point on a quadratic curve 528 * @param y1 The y-coordinate of the control point on a quadratic curve 529 * @param x2 The x-coordinate of the end point on a quadratic curve 530 * @param y2 The y-coordinate of the end point on a quadratic curve 531 */ 532 private void quadTo(float x1, float y1, float x2, float y2) { 533 mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); 534 } 535 536 /** 537 * Same as quadTo, but the coordinates are considered relative to the last 538 * point on this contour. If there is no previous point, then a moveTo(0,0) 539 * is inserted automatically. 540 * 541 * @param dx1 The amount to add to the x-coordinate of the last point on 542 * this contour, for the control point of a quadratic curve 543 * @param dy1 The amount to add to the y-coordinate of the last point on 544 * this contour, for the control point of a quadratic curve 545 * @param dx2 The amount to add to the x-coordinate of the last point on 546 * this contour, for the end point of a quadratic curve 547 * @param dy2 The amount to add to the y-coordinate of the last point on 548 * this contour, for the end point of a quadratic curve 549 */ 550 private void rQuadTo(float dx1, float dy1, float dx2, float dy2) { 551 if (isEmpty()) { 552 mPath.moveTo(mLastX = 0, mLastY = 0); 553 } 554 dx1 += mLastX; 555 dy1 += mLastY; 556 dx2 += mLastX; 557 dy2 += mLastY; 558 mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2); 559 } 560 561 /** 562 * Add a cubic bezier from the last point, approaching control points 563 * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been 564 * made for this contour, the first point is automatically set to (0,0). 565 * 566 * @param x1 The x-coordinate of the 1st control point on a cubic curve 567 * @param y1 The y-coordinate of the 1st control point on a cubic curve 568 * @param x2 The x-coordinate of the 2nd control point on a cubic curve 569 * @param y2 The y-coordinate of the 2nd control point on a cubic curve 570 * @param x3 The x-coordinate of the end point on a cubic curve 571 * @param y3 The y-coordinate of the end point on a cubic curve 572 */ 573 private void cubicTo(float x1, float y1, float x2, float y2, 574 float x3, float y3) { 575 mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); 576 } 577 578 /** 579 * Same as cubicTo, but the coordinates are considered relative to the 580 * current point on this contour. If there is no previous point, then a 581 * moveTo(0,0) is inserted automatically. 582 */ 583 private void rCubicTo(float dx1, float dy1, float dx2, float dy2, 584 float dx3, float dy3) { 585 if (isEmpty()) { 586 mPath.moveTo(mLastX = 0, mLastY = 0); 587 } 588 dx1 += mLastX; 589 dy1 += mLastY; 590 dx2 += mLastX; 591 dy2 += mLastY; 592 dx3 += mLastX; 593 dy3 += mLastY; 594 mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3); 595 } 596 597 /** 598 * Append the specified arc to the path as a new contour. If the start of 599 * the path is different from the path's current last point, then an 600 * automatic lineTo() is added to connect the current contour to the 601 * start of the arc. However, if the path is empty, then we call moveTo() 602 * with the first point of the arc. The sweep angle is tread mod 360. 603 * 604 * @param oval The bounds of oval defining shape and size of the arc 605 * @param startAngle Starting angle (in degrees) where the arc begins 606 * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated 607 * mod 360. 608 * @param forceMoveTo If true, always begin a new contour with the arc 609 */ 610 private void arcTo(RectF oval, float startAngle, float sweepAngle, 611 boolean forceMoveTo) { 612 Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), startAngle, 613 sweepAngle, Arc2D.OPEN); 614 mPath.append(arc, true /*connect*/); 615 616 resetLastPointFromPath(); 617 } 618 619 /** 620 * Close the current contour. If the current point is not equal to the 621 * first point of the contour, a line segment is automatically added. 622 */ 623 private void close() { 624 mPath.closePath(); 625 } 626 627 private void resetLastPointFromPath() { 628 Point2D last = mPath.getCurrentPoint(); 629 mLastX = (float) last.getX(); 630 mLastY = (float) last.getY(); 631 } 632 633 /** 634 * Add a closed rectangle contour to the path 635 * 636 * @param left The left side of a rectangle to add to the path 637 * @param top The top of a rectangle to add to the path 638 * @param right The right side of a rectangle to add to the path 639 * @param bottom The bottom of a rectangle to add to the path 640 * @param dir The direction to wind the rectangle's contour 641 */ 642 private void addRect(float left, float top, float right, float bottom, 643 int dir) { 644 moveTo(left, top); 645 646 Direction direction = getDirection(dir); 647 648 switch (direction) { 649 case CW: 650 lineTo(right, top); 651 lineTo(right, bottom); 652 lineTo(left, bottom); 653 break; 654 case CCW: 655 lineTo(left, bottom); 656 lineTo(right, bottom); 657 lineTo(right, top); 658 break; 659 } 660 661 close(); 662 663 resetLastPointFromPath(); 664 } 665 666 /** 667 * Offset the path by (dx,dy), returning true on success 668 * 669 * @param dx The amount in the X direction to offset the entire path 670 * @param dy The amount in the Y direction to offset the entire path 671 * @param dst The translated path is written here. If this is null, then 672 * the original path is modified. 673 */ 674 public void offset(float dx, float dy, Path_Delegate dst) { 675 GeneralPath newPath = new GeneralPath(); 676 677 PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); 678 679 newPath.append(iterator, false /*connect*/); 680 681 if (dst != null) { 682 dst.mPath = newPath; 683 } else { 684 mPath = newPath; 685 } 686 } 687 688 /** 689 * Transform the points in this path by matrix, and write the answer 690 * into dst. If dst is null, then the the original path is modified. 691 * 692 * @param matrix The matrix to apply to the path 693 * @param dst The transformed path is written here. If dst is null, 694 * then the the original path is modified 695 */ 696 public void transform(Matrix_Delegate matrix, Path_Delegate dst) { 697 if (matrix.hasPerspective()) { 698 assert false; 699 Bridge.getLog().fidelityWarning(BridgeConstants.TAG_MATRIX, 700 "android.graphics.Path#transform() only " + 701 "supports affine transformations.", null); 702 } 703 704 GeneralPath newPath = new GeneralPath(); 705 706 PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform()); 707 708 newPath.append(iterator, false /*connect*/); 709 710 if (dst != null) { 711 dst.mPath = newPath; 712 } else { 713 mPath = newPath; 714 } 715 } 716} 717