VectorDrawable.java revision ddbbb8ae8561c9e61becd03faa40997d76ab5a51
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15package android.graphics.drawable; 16 17import android.content.res.Resources; 18import android.content.res.Resources.Theme; 19import android.content.res.TypedArray; 20import android.graphics.Canvas; 21import android.graphics.ColorFilter; 22import android.graphics.Matrix; 23import android.graphics.Paint; 24import android.graphics.Path; 25import android.graphics.PathMeasure; 26import android.graphics.PixelFormat; 27import android.graphics.Rect; 28import android.graphics.Region; 29import android.util.AttributeSet; 30import android.util.Log; 31import android.util.Xml; 32 33import com.android.internal.R; 34 35import org.xmlpull.v1.XmlPullParser; 36import org.xmlpull.v1.XmlPullParserException; 37import org.xmlpull.v1.XmlPullParserFactory; 38 39import java.io.IOException; 40import java.util.ArrayList; 41import java.util.Arrays; 42import java.util.Collection; 43import java.util.HashMap; 44 45/** 46 * This lets you create a drawable based on an XML vector graphic It can be 47 * defined in an XML file with the <code><vector></code> element. 48 * <p/> 49 * The vector drawable has 6 elements: 50 * <p/> 51 * <dl> 52 * <dt><code><vector></code></dt> 53 * <dd>The attribute <code>android:versionCode</code> defines the version of 54 * VectorDrawable</dd> 55 * <dt><code><size></code></dt> 56 * <dd>Used to defined the intrinsic Width Height size of the drawable using 57 * <code>android:width</code> and <code>android:height</code></dd> 58 * <dt><code><viewport></code></dt> 59 * <dd>Used to defined the size of the virtual canvas the paths are drawn on. 60 * The size is defined using the attributes <code>android:viewportHeight</code> 61 * <code>android:viewportWidth</code></dd> 62 * <dt><code><group></code></dt> 63 * <dd>Defines the static 2D image.</dd> 64 * <dt><code><path></code></dt> 65 * <dd>Defines paths to be drawn. The path elements must be within a group 66 * <dl> 67 * <dt><code>android:name</code> 68 * <dd>Defines the name of the path.</dd></dt> 69 * <dt><code>android:pathData</code> 70 * <dd>Defines path string.</dd></dt> 71 * <dt><code>android:fill</code> 72 * <dd>Defines the color to fill the path (none if not present).</dd></dt> 73 * <dt><code>android:stroke</code> 74 * <dd>Defines the color to draw the path outline (none if not present).</dd> 75 * </dt> 76 * <dt><code>android:strokeWidth</code> 77 * <dd>The width a path stroke</dd></dt> 78 * <dt><code>android:strokeOpacity</code> 79 * <dd>The opacity of a path stroke</dd></dt> 80 * <dt><code>android:rotation</code> 81 * <dd>The amount to rotation the path stroke.</dd></dt> 82 * <dt><code>android:pivotX</code> 83 * <dd>The X coordinate of the center of rotation of a path</dd></dt> 84 * <dt><code>android:pivotY</code> 85 * <dd>The Y coordinate of the center of rotation of a path</dd></dt> 86 * <dt><code>android:fillOpacity</code> 87 * <dd>The opacity to fill the path with</dd></dt> 88 * <dt><code>android:trimPathStart</code> 89 * <dd>The fraction of the path to trim from the start from 0 to 1</dd></dt> 90 * <dt><code>android:trimPathEnd</code> 91 * <dd>The fraction of the path to trim from the end from 0 to 1</dd></dt> 92 * <dt><code>android:trimPathOffset</code> 93 * <dd>Shift trim region (allows showed region to include the start and end) 94 * from 0 to 1</dd></dt> 95 * <dt><code>android:clipToPath</code> 96 * <dd>Path will set the clip path</dd></dt> 97 * <dt><code>android:strokeLineCap</code> 98 * <dd>Sets the linecap for a stroked path: butt, round, square</dd></dt> 99 * <dt><code>android:strokeLineJoin</code> 100 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel</dd></dt> 101 * <dt><code>android:strokeMiterLimit</code> 102 * <dd>Sets the Miter limit for a stroked path</dd></dt> 103 * </dl> 104 * </dd> 105 */ 106public class VectorDrawable extends Drawable { 107 private static final String LOGTAG = VectorDrawable.class.getSimpleName(); 108 109 private static final String SHAPE_SIZE = "size"; 110 private static final String SHAPE_VIEWPORT = "viewport"; 111 private static final String SHAPE_GROUP = "group"; 112 private static final String SHAPE_PATH = "path"; 113 private static final String SHAPE_VECTOR = "vector"; 114 115 private static final int LINECAP_BUTT = 0; 116 private static final int LINECAP_ROUND = 1; 117 private static final int LINECAP_SQUARE = 2; 118 119 private static final int LINEJOIN_MITER = 0; 120 private static final int LINEJOIN_ROUND = 1; 121 private static final int LINEJOIN_BEVEL = 2; 122 123 private final VectorDrawableState mVectorState; 124 125 private int mAlpha = 0xFF; 126 127 public VectorDrawable() { 128 mVectorState = new VectorDrawableState(null); 129 } 130 131 private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) { 132 mVectorState = new VectorDrawableState(state); 133 134 if (theme != null && canApplyTheme()) { 135 applyTheme(theme); 136 } 137 } 138 139 @Override 140 public ConstantState getConstantState() { 141 return mVectorState; 142 } 143 144 @Override 145 public void draw(Canvas canvas) { 146 final int saveCount = canvas.save(); 147 final Rect bounds = getBounds(); 148 canvas.translate(bounds.left, bounds.top); 149 mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height()); 150 canvas.restoreToCount(saveCount); 151 } 152 153 @Override 154 public void setAlpha(int alpha) { 155 // TODO correct handling of transparent 156 if (mAlpha != alpha) { 157 mAlpha = alpha; 158 invalidateSelf(); 159 } 160 } 161 162 @Override 163 public void setColorFilter(ColorFilter colorFilter) { 164 // TODO: support color filter 165 } 166 167 @Override 168 public int getOpacity() { 169 return PixelFormat.TRANSLUCENT; 170 } 171 172 /** 173 * Sets padding for this shape, defined by a Rect object. Define the padding 174 * in the Rect object as: left, top, right, bottom. 175 */ 176 public void setPadding(Rect padding) { 177 setPadding(padding.left, padding.top, padding.right, padding.bottom); 178 } 179 180 /** 181 * Sets padding for the shape. 182 * 183 * @param left padding for the left side (in pixels) 184 * @param top padding for the top (in pixels) 185 * @param right padding for the right side (in pixels) 186 * @param bottom padding for the bottom (in pixels) 187 */ 188 public void setPadding(int left, int top, int right, int bottom) { 189 if ((left | top | right | bottom) == 0) { 190 mVectorState.mPadding = null; 191 } else { 192 if (mVectorState.mPadding == null) { 193 mVectorState.mPadding = new Rect(); 194 } 195 mVectorState.mPadding.set(left, top, right, bottom); 196 } 197 invalidateSelf(); 198 } 199 200 @Override 201 public int getIntrinsicWidth() { 202 return (int) mVectorState.mVPathRenderer.mBaseWidth; 203 } 204 205 @Override 206 public int getIntrinsicHeight() { 207 return (int) mVectorState.mVPathRenderer.mBaseHeight; 208 } 209 210 @Override 211 public boolean getPadding(Rect padding) { 212 if (mVectorState.mPadding != null) { 213 padding.set(mVectorState.mPadding); 214 return true; 215 } else { 216 return super.getPadding(padding); 217 } 218 } 219 220 @Override 221 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 222 throws XmlPullParserException, IOException { 223 final VPathRenderer p = inflateInternal(res, parser, attrs, theme); 224 setPathRenderer(p); 225 } 226 227 @Override 228 public boolean canApplyTheme() { 229 return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme(); 230 } 231 232 @Override 233 public void applyTheme(Theme t) { 234 super.applyTheme(t); 235 236 final VectorDrawableState state = mVectorState; 237 final VPathRenderer path = state.mVPathRenderer; 238 if (path != null && path.canApplyTheme()) { 239 path.applyTheme(t); 240 } 241 } 242 243 /** @hide */ 244 public static VectorDrawable create(Resources resources, int rid) { 245 try { 246 final XmlPullParser xpp = resources.getXml(rid); 247 final AttributeSet attrs = Xml.asAttributeSet(xpp); 248 final XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 249 factory.setNamespaceAware(true); 250 251 final VectorDrawable drawable = new VectorDrawable(); 252 drawable.inflate(resources, xpp, attrs); 253 254 return drawable; 255 } catch (XmlPullParserException e) { 256 Log.e(LOGTAG, "parser error", e); 257 } catch (IOException e) { 258 Log.e(LOGTAG, "parser error", e); 259 } 260 return null; 261 } 262 263 private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, 264 Theme theme) throws XmlPullParserException, IOException { 265 final VPathRenderer pathRenderer = new VPathRenderer(); 266 267 boolean noSizeTag = true; 268 boolean noViewportTag = true; 269 boolean noGroupTag = true; 270 boolean noPathTag = true; 271 272 VGroup currentGroup = null; 273 274 int eventType = parser.getEventType(); 275 while (eventType != XmlPullParser.END_DOCUMENT) { 276 if (eventType == XmlPullParser.START_TAG) { 277 final String tagName = parser.getName(); 278 if (SHAPE_PATH.equals(tagName)) { 279 final VPath path = new VPath(); 280 path.inflate(res, attrs, theme); 281 currentGroup.add(path); 282 noPathTag = false; 283 } else if (SHAPE_SIZE.equals(tagName)) { 284 pathRenderer.parseSize(res, attrs); 285 noSizeTag = false; 286 } else if (SHAPE_VIEWPORT.equals(tagName)) { 287 pathRenderer.parseViewport(res, attrs); 288 noViewportTag = false; 289 } else if (SHAPE_GROUP.equals(tagName)) { 290 currentGroup = new VGroup(); 291 pathRenderer.mGroupList.add(currentGroup); 292 noGroupTag = false; 293 } else if (SHAPE_VECTOR.equals(tagName)) { 294 final TypedArray a = res.obtainAttributes(attrs, R.styleable.VectorDrawable); 295 296 // Parsing the version information. 297 // Right now, we only support version "1". 298 // If the xml didn't specify the version number, the default 299 // version is "1". 300 final int versionCode = a.getInt(R.styleable.VectorDrawable_versionCode, 1); 301 if (versionCode != 1) { 302 throw new IllegalArgumentException( 303 "So far, VectorDrawable only support version 1"); 304 } 305 306 a.recycle(); 307 } 308 } 309 310 eventType = parser.next(); 311 } 312 313 if (noSizeTag || noViewportTag || noGroupTag || noPathTag) { 314 final StringBuffer tag = new StringBuffer(); 315 316 if (noSizeTag) { 317 tag.append(SHAPE_SIZE); 318 } 319 320 if (noViewportTag) { 321 if (tag.length() > 0) { 322 tag.append(" & "); 323 } 324 tag.append(SHAPE_SIZE); 325 } 326 327 if (noGroupTag) { 328 if (tag.length() > 0) { 329 tag.append(" & "); 330 } 331 tag.append(SHAPE_GROUP); 332 } 333 334 if (noPathTag) { 335 if (tag.length() > 0) { 336 tag.append(" or "); 337 } 338 tag.append(SHAPE_PATH); 339 } 340 341 throw new XmlPullParserException("no " + tag + " defined"); 342 } 343 344 // post parse cleanup 345 pathRenderer.parseFinish(); 346 return pathRenderer; 347 } 348 349 private void setPathRenderer(VPathRenderer pathRenderer) { 350 mVectorState.mVPathRenderer = pathRenderer; 351 } 352 353 private static class VectorDrawableState extends ConstantState { 354 int mChangingConfigurations; 355 VPathRenderer mVPathRenderer; 356 Rect mPadding; 357 358 public VectorDrawableState(VectorDrawableState copy) { 359 if (copy != null) { 360 mChangingConfigurations = copy.mChangingConfigurations; 361 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); 362 mPadding = new Rect(copy.mPadding); 363 } 364 } 365 366 @Override 367 public Drawable newDrawable() { 368 return new VectorDrawable(this, null, null); 369 } 370 371 @Override 372 public Drawable newDrawable(Resources res) { 373 return new VectorDrawable(this, res, null); 374 } 375 376 @Override 377 public Drawable newDrawable(Resources res, Theme theme) { 378 return new VectorDrawable(this, res, theme); 379 } 380 381 @Override 382 public int getChangingConfigurations() { 383 return mChangingConfigurations; 384 } 385 } 386 387 private static class VPathRenderer { 388 private final Path mPath = new Path(); 389 private final Path mRenderPath = new Path(); 390 private final Matrix mMatrix = new Matrix(); 391 392 private VPath[] mCurrentPaths; 393 private Paint mStrokePaint; 394 private Paint mFillPaint; 395 private PathMeasure mPathMeasure; 396 397 final ArrayList<VGroup> mGroupList = new ArrayList<VGroup>(); 398 399 float mBaseWidth = 1; 400 float mBaseHeight = 1; 401 float mViewportWidth; 402 float mViewportHeight; 403 404 public VPathRenderer() { 405 } 406 407 public VPathRenderer(VPathRenderer copy) { 408 mGroupList.addAll(copy.mGroupList); 409 if (copy.mCurrentPaths != null) { 410 mCurrentPaths = new VPath[copy.mCurrentPaths.length]; 411 for (int i = 0; i < mCurrentPaths.length; i++) { 412 mCurrentPaths[i] = new VPath(copy.mCurrentPaths[i]); 413 } 414 } 415 416 mBaseWidth = copy.mBaseWidth; 417 mBaseHeight = copy.mBaseHeight; 418 mViewportWidth = copy.mViewportHeight; 419 mViewportHeight = copy.mViewportHeight; 420 } 421 422 public boolean canApplyTheme() { 423 final ArrayList<VGroup> groups = mGroupList; 424 for (int i = groups.size() - 1; i >= 0; i--) { 425 final ArrayList<VPath> paths = groups.get(i).mVGList; 426 for (int j = paths.size() - 1; j >= 0; j--) { 427 final VPath path = paths.get(j); 428 if (path.canApplyTheme()) { 429 return true; 430 } 431 } 432 } 433 434 return false; 435 } 436 437 public void applyTheme(Theme t) { 438 final ArrayList<VGroup> groups = mGroupList; 439 for (int i = groups.size() - 1; i >= 0; i--) { 440 final ArrayList<VPath> paths = groups.get(i).mVGList; 441 for (int j = paths.size() - 1; j >= 0; j--) { 442 final VPath path = paths.get(j); 443 if (path.canApplyTheme()) { 444 path.applyTheme(t); 445 } 446 } 447 } 448 449 } 450 451 public void draw(Canvas canvas, int w, int h) { 452 if (mCurrentPaths == null) { 453 Log.e(LOGTAG,"mCurrentPaths == null"); 454 return; 455 } 456 457 for (int i = 0; i < mCurrentPaths.length; i++) { 458 if (mCurrentPaths[i] != null) { 459 drawPath(mCurrentPaths[i], canvas, w, h); 460 } 461 } 462 } 463 464 private void drawPath(VPath vPath, Canvas canvas, int w, int h) { 465 final float scale = Math.min(h / mViewportHeight, w / mViewportWidth); 466 467 vPath.toPath(mPath); 468 final Path path = mPath; 469 470 if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) { 471 float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f; 472 float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f; 473 474 if (mPathMeasure == null) { 475 mPathMeasure = new PathMeasure(); 476 } 477 mPathMeasure.setPath(mPath, false); 478 479 float len = mPathMeasure.getLength(); 480 start = start * len; 481 end = end * len; 482 path.reset(); 483 if (start > end) { 484 mPathMeasure.getSegment(start, len, path, true); 485 mPathMeasure.getSegment(0f, end, path, true); 486 } else { 487 mPathMeasure.getSegment(start, end, path, true); 488 } 489 path.rLineTo(0, 0); // fix bug in measure 490 } 491 492 mRenderPath.reset(); 493 mMatrix.reset(); 494 495 mMatrix.postRotate(vPath.mRotate, vPath.mPivotX, vPath.mPivotY); 496 mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f); 497 mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); 498 499 mRenderPath.addPath(path, mMatrix); 500 501 if (vPath.mClip) { 502 canvas.clipPath(mRenderPath, Region.Op.REPLACE); 503 } 504 505 if (vPath.mFillColor != 0) { 506 if (mFillPaint == null) { 507 mFillPaint = new Paint(); 508 mFillPaint.setStyle(Paint.Style.FILL); 509 mFillPaint.setAntiAlias(true); 510 } 511 512 mFillPaint.setColor(vPath.mFillColor); 513 canvas.drawPath(mRenderPath, mFillPaint); 514 } 515 516 if (vPath.mStrokeColor != 0) { 517 if (mStrokePaint == null) { 518 mStrokePaint = new Paint(); 519 mStrokePaint.setStyle(Paint.Style.STROKE); 520 mStrokePaint.setAntiAlias(true); 521 } 522 523 final Paint strokePaint = mStrokePaint; 524 if (vPath.mStrokeLineJoin != null) { 525 strokePaint.setStrokeJoin(vPath.mStrokeLineJoin); 526 } 527 528 if (vPath.mStrokeLineCap != null) { 529 strokePaint.setStrokeCap(vPath.mStrokeLineCap); 530 } 531 532 strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale); 533 strokePaint.setColor(vPath.mStrokeColor); 534 strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale); 535 canvas.drawPath(mRenderPath, strokePaint); 536 } 537 } 538 539 /** 540 * Build the "current" path based on the first group 541 * TODO: improve memory use & performance or move to C++ 542 */ 543 public void parseFinish() { 544 final Collection<VPath> paths = mGroupList.get(0).getPaths(); 545 mCurrentPaths = paths.toArray(new VPath[paths.size()]); 546 for (int i = 0; i < mCurrentPaths.length; i++) { 547 mCurrentPaths[i] = new VPath(mCurrentPaths[i]); 548 } 549 } 550 551 private void parseViewport(Resources r, AttributeSet attrs) 552 throws XmlPullParserException { 553 final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport); 554 mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth, 0); 555 mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight, 0); 556 if (mViewportWidth == 0 || mViewportHeight == 0) { 557 throw new XmlPullParserException(a.getPositionDescription()+ 558 "<viewport> tag requires viewportWidth & viewportHeight to be set"); 559 } 560 a.recycle(); 561 } 562 563 private void parseSize(Resources r, AttributeSet attrs) 564 throws XmlPullParserException { 565 final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize); 566 mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, 0); 567 mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, 0); 568 if (mBaseWidth == 0 || mBaseHeight == 0) { 569 throw new XmlPullParserException(a.getPositionDescription()+ 570 "<size> tag requires width & height to be set"); 571 } 572 a.recycle(); 573 } 574 575 } 576 577 private static class VGroup { 578 private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>(); 579 private final ArrayList<VPath> mVGList = new ArrayList<VPath>(); 580 581 public void add(VPath path) { 582 String id = path.getID(); 583 mVGPathMap.put(id, path); 584 mVGList.add(path); 585 } 586 587 /** 588 * Must return in order of adding 589 * @return ordered list of paths 590 */ 591 public Collection<VPath> getPaths() { 592 return mVGList; 593 } 594 595 } 596 597 private static class VPath { 598 private static final int MAX_STATES = 10; 599 600 private int[] mThemeAttrs; 601 602 int mStrokeColor = 0; 603 float mStrokeWidth = 0; 604 float mStrokeOpacity = Float.NaN; 605 606 int mFillColor = 0; 607 int mFillRule; 608 float mFillOpacity = Float.NaN; 609 610 float mRotate = 0; 611 float mPivotX = 0; 612 float mPivotY = 0; 613 614 float mTrimPathStart = 0; 615 float mTrimPathEnd = 1; 616 float mTrimPathOffset = 0; 617 618 boolean mClip = false; 619 Paint.Cap mStrokeLineCap = Paint.Cap.BUTT; 620 Paint.Join mStrokeLineJoin = Paint.Join.MITER; 621 float mStrokeMiterlimit = 4; 622 623 private VNode[] mNode = null; 624 private String mId; 625 private int[] mCheckState = new int[MAX_STATES]; 626 private boolean[] mCheckValue = new boolean[MAX_STATES]; 627 private int mNumberOfStates = 0; 628 629 public VPath() { 630 // Empty constructor. 631 } 632 633 public VPath(VPath p) { 634 copyFrom(p); 635 } 636 637 public void toPath(Path path) { 638 path.reset(); 639 if (mNode != null) { 640 VNode.createPath(mNode, path); 641 } 642 } 643 644 public String getID() { 645 return mId; 646 } 647 648 private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) { 649 switch (id) { 650 case LINECAP_BUTT: 651 return Paint.Cap.BUTT; 652 case LINECAP_ROUND: 653 return Paint.Cap.ROUND; 654 case LINECAP_SQUARE: 655 return Paint.Cap.SQUARE; 656 default: 657 return defValue; 658 } 659 } 660 661 private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) { 662 switch (id) { 663 case LINEJOIN_MITER: 664 return Paint.Join.MITER; 665 case LINEJOIN_ROUND: 666 return Paint.Join.ROUND; 667 case LINEJOIN_BEVEL: 668 return Paint.Join.BEVEL; 669 default: 670 return defValue; 671 } 672 } 673 674 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 675 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath); 676 final int[] themeAttrs = a.extractThemeAttrs(); 677 mThemeAttrs = themeAttrs; 678 679 // NOTE: The set of attributes loaded here MUST match the 680 // set of attributes loaded in applyTheme. 681 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_clipToPath] == 0) { 682 mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip); 683 } 684 685 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0) { 686 mId = a.getString(R.styleable.VectorDrawablePath_name); 687 } 688 689 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) { 690 mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData)); 691 } 692 693 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) { 694 mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor); 695 } 696 697 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fillOpacity] == 0) { 698 mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity); 699 } 700 701 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_rotation] == 0) { 702 mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate); 703 } 704 705 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotX] == 0) { 706 mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX); 707 } 708 709 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotY] == 0) { 710 mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY); 711 } 712 713 if (themeAttrs == null 714 || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) { 715 mStrokeLineCap = getStrokeLineCap( 716 a.getInt(R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap); 717 } 718 719 if (themeAttrs == null 720 || themeAttrs[R.styleable.VectorDrawablePath_strokeLineJoin] == 0) { 721 mStrokeLineJoin = getStrokeLineJoin( 722 a.getInt(R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin); 723 } 724 725 if (themeAttrs == null 726 || themeAttrs[R.styleable.VectorDrawablePath_strokeMiterLimit] == 0) { 727 mStrokeMiterlimit = a.getFloat( 728 R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit); 729 } 730 731 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_stroke] == 0) { 732 mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor); 733 } 734 735 if (themeAttrs == null 736 || themeAttrs[R.styleable.VectorDrawablePath_strokeOpacity] == 0) { 737 mStrokeOpacity = a.getFloat( 738 R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity); 739 } 740 741 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_strokeWidth] == 0) { 742 mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth); 743 } 744 745 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_trimPathEnd] == 0) { 746 mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd); 747 } 748 749 if (themeAttrs == null 750 || themeAttrs[R.styleable.VectorDrawablePath_trimPathOffset] == 0) { 751 mTrimPathOffset = a.getFloat( 752 R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset); 753 } 754 755 if (themeAttrs == null 756 || themeAttrs[R.styleable.VectorDrawablePath_trimPathStart] == 0) { 757 mTrimPathStart = a.getFloat( 758 R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart); 759 } 760 761 updateColorAlphas(); 762 763 a.recycle(); 764 } 765 766 public boolean canApplyTheme() { 767 return mThemeAttrs != null; 768 } 769 770 public void applyTheme(Theme t) { 771 if (mThemeAttrs == null) { 772 return; 773 } 774 775 final TypedArray a = t.resolveAttributes( 776 mThemeAttrs, R.styleable.VectorDrawablePath, 0, 0); 777 778 mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip); 779 780 if (a.hasValue(R.styleable.VectorDrawablePath_name)) { 781 mId = a.getString(R.styleable.VectorDrawablePath_name); 782 } 783 784 if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) { 785 mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData)); 786 } 787 788 mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor); 789 mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity); 790 791 mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate); 792 mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX); 793 mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY); 794 795 mStrokeLineCap = getStrokeLineCap(a.getInt( 796 R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap); 797 mStrokeLineJoin = getStrokeLineJoin(a.getInt( 798 R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin); 799 mStrokeMiterlimit = a.getFloat( 800 R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit); 801 mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor); 802 mStrokeOpacity = a.getFloat( 803 R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity); 804 mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth); 805 806 mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd); 807 mTrimPathOffset = a.getFloat( 808 R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset); 809 mTrimPathStart = a.getFloat( 810 R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart); 811 812 updateColorAlphas(); 813 } 814 815 private void updateColorAlphas() { 816 if (!Float.isNaN(mFillOpacity)) { 817 mFillColor &= 0x00FFFFFF; 818 mFillColor |= ((int) (0xFF * mFillOpacity)) << 24; 819 } 820 821 if (!Float.isNaN(mStrokeOpacity)) { 822 mStrokeColor &= 0x00FFFFFF; 823 mStrokeColor |= ((int) (0xFF * mStrokeOpacity)) << 24; 824 } 825 } 826 827 private static int nextStart(String s, int end) { 828 char c; 829 830 while (end < s.length()) { 831 c = s.charAt(end); 832 if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) { 833 return end; 834 } 835 end++; 836 } 837 return end; 838 } 839 840 private void addNode(ArrayList<VectorDrawable.VNode> list, char cmd, float[] val) { 841 list.add(new VectorDrawable.VNode(cmd, val)); 842 } 843 844 /** 845 * parse the floats in the string 846 * this is an optimized version of 847 * parseFloat(s.split(",|\\s")); 848 * 849 * @param s the string containing a command and list of floats 850 * @return array of floats 851 */ 852 private static float[] getFloats(String s) { 853 if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') { 854 return new float[0]; 855 } 856 try { 857 float[] tmp = new float[s.length()]; 858 int count = 0; 859 int pos = 1, end; 860 while ((end = extract(s, pos)) >= 0) { 861 if (pos < end) { 862 tmp[count++] = Float.parseFloat(s.substring(pos, end)); 863 } 864 pos = end + 1; 865 } 866 // handle the final float if there is one 867 if (pos < s.length()) { 868 tmp[count++] = Float.parseFloat(s.substring(pos, s.length())); 869 } 870 return Arrays.copyOf(tmp, count); 871 } catch (NumberFormatException e){ 872 Log.e(LOGTAG,"error in parsing \""+s+"\""); 873 throw e; 874 } 875 } 876 877 /** 878 * calculate the position of the next comma or space 879 * @param s the string to search 880 * @param start the position to start searching 881 * @return the position of the next comma or space or -1 if none found 882 */ 883 private static int extract(String s, int start) { 884 int space = s.indexOf(' ', start); 885 int comma = s.indexOf(',', start); 886 if (space == -1) { 887 return comma; 888 } 889 if (comma == -1) { 890 return space; 891 } 892 return (comma > space) ? space : comma; 893 } 894 895 private VectorDrawable.VNode[] parsePath(String value) { 896 int start = 0; 897 int end = 1; 898 899 ArrayList<VectorDrawable.VNode> list = new ArrayList<VectorDrawable.VNode>(); 900 while (end < value.length()) { 901 end = nextStart(value, end); 902 String s = value.substring(start, end); 903 float[] val = getFloats(s); 904 addNode(list, s.charAt(0), val); 905 906 start = end; 907 end++; 908 } 909 if ((end - start) == 1 && start < value.length()) { 910 911 addNode(list, value.charAt(start), new float[0]); 912 } 913 return list.toArray(new VectorDrawable.VNode[list.size()]); 914 } 915 916 public void copyFrom(VPath p1) { 917 mNode = new VNode[p1.mNode.length]; 918 for (int i = 0; i < mNode.length; i++) { 919 mNode[i] = new VNode(p1.mNode[i]); 920 } 921 mId = p1.mId; 922 mStrokeColor = p1.mStrokeColor; 923 mFillColor = p1.mFillColor; 924 mStrokeWidth = p1.mStrokeWidth; 925 mRotate = p1.mRotate; 926 mPivotX = p1.mPivotX; 927 mPivotY = p1.mPivotY; 928 mTrimPathStart = p1.mTrimPathStart; 929 mTrimPathEnd = p1.mTrimPathEnd; 930 mTrimPathOffset = p1.mTrimPathOffset; 931 mStrokeLineCap = p1.mStrokeLineCap; 932 mStrokeLineJoin = p1.mStrokeLineJoin; 933 mStrokeMiterlimit = p1.mStrokeMiterlimit; 934 mNumberOfStates = p1.mNumberOfStates; 935 for (int i = 0; i < mNumberOfStates; i++) { 936 mCheckState[i] = p1.mCheckState[i]; 937 mCheckValue[i] = p1.mCheckValue[i]; 938 } 939 940 mFillRule = p1.mFillRule; 941 } 942 } 943 944 private static class VNode { 945 private char mType; 946 private float[] mParams; 947 948 public VNode(char type, float[] params) { 949 mType = type; 950 mParams = params; 951 } 952 953 public VNode(VNode n) { 954 mType = n.mType; 955 mParams = Arrays.copyOf(n.mParams, n.mParams.length); 956 } 957 958 public static void createPath(VNode[] node, Path path) { 959 float[] current = new float[4]; 960 char previousCommand = 'm'; 961 for (int i = 0; i < node.length; i++) { 962 addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); 963 previousCommand = node[i].mType; 964 } 965 } 966 967 private static void addCommand(Path path, float[] current, 968 char previousCmd, char cmd, float[] val) { 969 970 int incr = 2; 971 float currentX = current[0]; 972 float currentY = current[1]; 973 float ctrlPointX = current[2]; 974 float ctrlPointY = current[3]; 975 float reflectiveCtrlPointX; 976 float reflectiveCtrlPointY; 977 978 switch (cmd) { 979 case 'z': 980 case 'Z': 981 path.close(); 982 return; 983 case 'm': 984 case 'M': 985 case 'l': 986 case 'L': 987 case 't': 988 case 'T': 989 incr = 2; 990 break; 991 case 'h': 992 case 'H': 993 case 'v': 994 case 'V': 995 incr = 1; 996 break; 997 case 'c': 998 case 'C': 999 incr = 6; 1000 break; 1001 case 's': 1002 case 'S': 1003 case 'q': 1004 case 'Q': 1005 incr = 4; 1006 break; 1007 case 'a': 1008 case 'A': 1009 incr = 7; 1010 break; 1011 } 1012 for (int k = 0; k < val.length; k += incr) { 1013 switch (cmd) { 1014 case 'm': // moveto - Start a new sub-path (relative) 1015 path.rMoveTo(val[k + 0], val[k + 1]); 1016 currentX += val[k + 0]; 1017 currentY += val[k + 1]; 1018 break; 1019 case 'M': // moveto - Start a new sub-path 1020 path.moveTo(val[k + 0], val[k + 1]); 1021 currentX = val[k + 0]; 1022 currentY = val[k + 1]; 1023 break; 1024 case 'l': // lineto - Draw a line from the current point (relative) 1025 path.rLineTo(val[k + 0], val[k + 1]); 1026 currentX += val[k + 0]; 1027 currentY += val[k + 1]; 1028 break; 1029 case 'L': // lineto - Draw a line from the current point 1030 path.lineTo(val[k + 0], val[k + 1]); 1031 currentX = val[k + 0]; 1032 currentY = val[k + 1]; 1033 break; 1034 case 'z': // closepath - Close the current subpath 1035 case 'Z': // closepath - Close the current subpath 1036 path.close(); 1037 break; 1038 case 'h': // horizontal lineto - Draws a horizontal line (relative) 1039 path.rLineTo(val[k + 0], 0); 1040 currentX += val[k + 0]; 1041 break; 1042 case 'H': // horizontal lineto - Draws a horizontal line 1043 path.lineTo(val[k + 0], currentY); 1044 currentX = val[k + 0]; 1045 break; 1046 case 'v': // vertical lineto - Draws a vertical line from the current point (r) 1047 path.rLineTo(0, val[k + 0]); 1048 currentY += val[k + 0]; 1049 break; 1050 case 'V': // vertical lineto - Draws a vertical line from the current point 1051 path.lineTo(currentX, val[k + 0]); 1052 currentY = val[k + 0]; 1053 break; 1054 case 'c': // curveto - Draws a cubic Bézier curve (relative) 1055 path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], 1056 val[k + 4], val[k + 5]); 1057 1058 ctrlPointX = currentX + val[k + 2]; 1059 ctrlPointY = currentY + val[k + 3]; 1060 currentX += val[k + 4]; 1061 currentY += val[k + 5]; 1062 1063 break; 1064 case 'C': // curveto - Draws a cubic Bézier curve 1065 path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], 1066 val[k + 4], val[k + 5]); 1067 currentX = val[k + 4]; 1068 currentY = val[k + 5]; 1069 ctrlPointX = val[k + 2]; 1070 ctrlPointY = val[k + 3]; 1071 break; 1072 case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) 1073 reflectiveCtrlPointX = 0; 1074 reflectiveCtrlPointY = 0; 1075 if (previousCmd == 'c' || previousCmd == 's' 1076 || previousCmd == 'C' || previousCmd == 'S') { 1077 reflectiveCtrlPointX = currentX - ctrlPointX; 1078 reflectiveCtrlPointY = currentY - ctrlPointY; 1079 } 1080 path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, 1081 val[k + 0], val[k + 1], 1082 val[k + 2], val[k + 3]); 1083 1084 ctrlPointX = currentX + val[k + 0]; 1085 ctrlPointY = currentY + val[k + 1]; 1086 currentX += val[k + 2]; 1087 currentY += val[k + 3]; 1088 break; 1089 case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) 1090 reflectiveCtrlPointX = currentX; 1091 reflectiveCtrlPointY = currentY; 1092 if (previousCmd == 'c' || previousCmd == 's' 1093 || previousCmd == 'C' || previousCmd == 'S') { 1094 reflectiveCtrlPointX = 2 * currentX - ctrlPointX; 1095 reflectiveCtrlPointY = 2 * currentY - ctrlPointY; 1096 } 1097 path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, 1098 val[k + 0], val[k + 1], val[k + 2], val[k + 3]); 1099 ctrlPointX = val[k + 0]; 1100 ctrlPointY = val[k + 1]; 1101 currentX = val[k + 2]; 1102 currentY = val[k + 3]; 1103 break; 1104 case 'q': // Draws a quadratic Bézier (relative) 1105 path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); 1106 ctrlPointX = currentX + val[k + 0]; 1107 ctrlPointY = currentY + val[k + 1]; 1108 currentX += val[k + 2]; 1109 currentY += val[k + 3]; 1110 break; 1111 case 'Q': // Draws a quadratic Bézier 1112 path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); 1113 ctrlPointX = val[k + 0]; 1114 ctrlPointY = val[k + 1]; 1115 currentX = val[k + 2]; 1116 currentY = val[k + 3]; 1117 break; 1118 case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) 1119 reflectiveCtrlPointX = 0; 1120 reflectiveCtrlPointY = 0; 1121 if (previousCmd == 'q' || previousCmd == 't' 1122 || previousCmd == 'Q' || previousCmd == 'T') { 1123 reflectiveCtrlPointX = currentX - ctrlPointX; 1124 reflectiveCtrlPointY = currentY - ctrlPointY; 1125 } 1126 path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, 1127 val[k + 0], val[k + 1]); 1128 ctrlPointX = currentX + reflectiveCtrlPointX; 1129 ctrlPointY = currentY + reflectiveCtrlPointY; 1130 currentX += val[k + 0]; 1131 currentY += val[k + 1]; 1132 break; 1133 case 'T': // Draws a quadratic Bézier curve (reflective control point) 1134 reflectiveCtrlPointX = currentX; 1135 reflectiveCtrlPointY = currentY; 1136 if (previousCmd == 'q' || previousCmd == 't' 1137 || previousCmd == 'Q' || previousCmd == 'T') { 1138 reflectiveCtrlPointX = 2 * currentX - ctrlPointX; 1139 reflectiveCtrlPointY = 2 * currentY - ctrlPointY; 1140 } 1141 path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, 1142 val[k + 0], val[k + 1]); 1143 ctrlPointX = reflectiveCtrlPointX; 1144 ctrlPointY = reflectiveCtrlPointY; 1145 currentX = val[k + 0]; 1146 currentY = val[k + 1]; 1147 break; 1148 case 'a': // Draws an elliptical arc 1149 // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) 1150 drawArc(path, 1151 currentX, 1152 currentY, 1153 val[k + 5] + currentX, 1154 val[k + 6] + currentY, 1155 val[k + 0], 1156 val[k + 1], 1157 val[k + 2], 1158 val[k + 3] != 0, 1159 val[k + 4] != 0); 1160 currentX += val[k + 5]; 1161 currentY += val[k + 6]; 1162 ctrlPointX = currentX; 1163 ctrlPointY = currentY; 1164 break; 1165 case 'A': // Draws an elliptical arc 1166 drawArc(path, 1167 currentX, 1168 currentY, 1169 val[k + 5], 1170 val[k + 6], 1171 val[k + 0], 1172 val[k + 1], 1173 val[k + 2], 1174 val[k + 3] != 0, 1175 val[k + 4] != 0); 1176 currentX = val[k + 5]; 1177 currentY = val[k + 6]; 1178 ctrlPointX = currentX; 1179 ctrlPointY = currentY; 1180 break; 1181 } 1182 previousCmd = cmd; 1183 } 1184 current[0] = currentX; 1185 current[1] = currentY; 1186 current[2] = ctrlPointX; 1187 current[3] = ctrlPointY; 1188 } 1189 1190 private static void drawArc(Path p, 1191 float x0, 1192 float y0, 1193 float x1, 1194 float y1, 1195 float a, 1196 float b, 1197 float theta, 1198 boolean isMoreThanHalf, 1199 boolean isPositiveArc) { 1200 1201 /* Convert rotation angle from degrees to radians */ 1202 double thetaD = Math.toRadians(theta); 1203 /* Pre-compute rotation matrix entries */ 1204 double cosTheta = Math.cos(thetaD); 1205 double sinTheta = Math.sin(thetaD); 1206 /* Transform (x0, y0) and (x1, y1) into unit space */ 1207 /* using (inverse) rotation, followed by (inverse) scale */ 1208 double x0p = (x0 * cosTheta + y0 * sinTheta) / a; 1209 double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; 1210 double x1p = (x1 * cosTheta + y1 * sinTheta) / a; 1211 double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; 1212 1213 /* Compute differences and averages */ 1214 double dx = x0p - x1p; 1215 double dy = y0p - y1p; 1216 double xm = (x0p + x1p) / 2; 1217 double ym = (y0p + y1p) / 2; 1218 /* Solve for intersecting unit circles */ 1219 double dsq = dx * dx + dy * dy; 1220 if (dsq == 0.0) { 1221 Log.w(LOGTAG, " Points are coincident"); 1222 return; /* Points are coincident */ 1223 } 1224 double disc = 1.0 / dsq - 1.0 / 4.0; 1225 if (disc < 0.0) { 1226 Log.w(LOGTAG, "Points are too far apart " + dsq); 1227 float adjust = (float) (Math.sqrt(dsq) / 1.99999); 1228 drawArc(p, x0, y0, x1, y1, a * adjust, 1229 b * adjust, theta, isMoreThanHalf, isPositiveArc); 1230 return; /* Points are too far apart */ 1231 } 1232 double s = Math.sqrt(disc); 1233 double sdx = s * dx; 1234 double sdy = s * dy; 1235 double cx; 1236 double cy; 1237 if (isMoreThanHalf == isPositiveArc) { 1238 cx = xm - sdy; 1239 cy = ym + sdx; 1240 } else { 1241 cx = xm + sdy; 1242 cy = ym - sdx; 1243 } 1244 1245 double eta0 = Math.atan2((y0p - cy), (x0p - cx)); 1246 1247 double eta1 = Math.atan2((y1p - cy), (x1p - cx)); 1248 1249 double sweep = (eta1 - eta0); 1250 if (isPositiveArc != (sweep >= 0)) { 1251 if (sweep > 0) { 1252 sweep -= 2 * Math.PI; 1253 } else { 1254 sweep += 2 * Math.PI; 1255 } 1256 } 1257 1258 cx *= a; 1259 cy *= b; 1260 double tcx = cx; 1261 cx = cx * cosTheta - cy * sinTheta; 1262 cy = tcx * sinTheta + cy * cosTheta; 1263 1264 arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); 1265 } 1266 1267 /** 1268 * Converts an arc to cubic Bezier segments and records them in p. 1269 * 1270 * @param p The target for the cubic Bezier segments 1271 * @param cx The x coordinate center of the ellipse 1272 * @param cy The y coordinate center of the ellipse 1273 * @param a The radius of the ellipse in the horizontal direction 1274 * @param b The radius of the ellipse in the vertical direction 1275 * @param e1x E(eta1) x coordinate of the starting point of the arc 1276 * @param e1y E(eta2) y coordinate of the starting point of the arc 1277 * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane 1278 * @param start The start angle of the arc on the ellipse 1279 * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse 1280 */ 1281 private static void arcToBezier(Path p, 1282 double cx, 1283 double cy, 1284 double a, 1285 double b, 1286 double e1x, 1287 double e1y, 1288 double theta, 1289 double start, 1290 double sweep) { 1291 // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html 1292 // and http://www.spaceroots.org/documents/ellipse/node22.html 1293 1294 // Maximum of 45 degrees per cubic Bezier segment 1295 int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI)); 1296 1297 double eta1 = start; 1298 double cosTheta = Math.cos(theta); 1299 double sinTheta = Math.sin(theta); 1300 double cosEta1 = Math.cos(eta1); 1301 double sinEta1 = Math.sin(eta1); 1302 double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); 1303 double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); 1304 1305 double anglePerSegment = sweep / numSegments; 1306 for (int i = 0; i < numSegments; i++) { 1307 double eta2 = eta1 + anglePerSegment; 1308 double sinEta2 = Math.sin(eta2); 1309 double cosEta2 = Math.cos(eta2); 1310 double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); 1311 double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); 1312 double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; 1313 double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; 1314 double tanDiff2 = Math.tan((eta2 - eta1) / 2); 1315 double alpha = 1316 Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; 1317 double q1x = e1x + alpha * ep1x; 1318 double q1y = e1y + alpha * ep1y; 1319 double q2x = e2x - alpha * ep2x; 1320 double q2y = e2y - alpha * ep2y; 1321 1322 p.cubicTo((float) q1x, 1323 (float) q1y, 1324 (float) q2x, 1325 (float) q2y, 1326 (float) e2x, 1327 (float) e2y); 1328 eta1 = eta2; 1329 e1x = e2x; 1330 e1y = e2y; 1331 ep1x = ep2x; 1332 ep1y = ep2y; 1333 } 1334 } 1335 1336 } 1337} 1338