VectorDrawable.java revision b3c56086d802ae28888dd97ba1f49bd6cee0b673
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.ColorStateList; 18import android.content.res.Resources; 19import android.content.res.Resources.Theme; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.Color; 23import android.graphics.ColorFilter; 24import android.graphics.Matrix; 25import android.graphics.Paint; 26import android.graphics.Path; 27import android.graphics.PathMeasure; 28import android.graphics.PixelFormat; 29import android.graphics.PorterDuffColorFilter; 30import android.graphics.Rect; 31import android.graphics.Region; 32import android.graphics.PorterDuff.Mode; 33import android.util.ArrayMap; 34import android.util.AttributeSet; 35import android.util.Log; 36import android.util.Xml; 37 38import com.android.internal.R; 39 40import org.xmlpull.v1.XmlPullParser; 41import org.xmlpull.v1.XmlPullParserException; 42import org.xmlpull.v1.XmlPullParserFactory; 43 44import java.io.IOException; 45import java.util.ArrayList; 46import java.util.Arrays; 47import java.util.Stack; 48 49/** 50 * This lets you create a drawable based on an XML vector graphic It can be 51 * defined in an XML file with the <code><vector></code> element. 52 * <p/> 53 * The vector drawable has the following elements: 54 * <p/> 55 * <dl> 56 * <dt><code><vector></code></dt> 57 * <dd>Used to defined a vector drawable</dd> 58 * <dt><code><size></code></dt> 59 * <dd>Used to defined the intrinsic Width Height size of the drawable using 60 * <code>android:width</code> and <code>android:height</code></dd> 61 * <dt><code><viewport></code></dt> 62 * <dd>Used to defined the size of the virtual canvas the paths are drawn on. 63 * The size is defined using the attributes <code>android:viewportHeight</code> 64 * <code>android:viewportWidth</code></dd> 65 * <dt><code><group></code></dt> 66 * <dd>Defines a group of paths or subgroups, plus transformation information. 67 * The transformations are defined in the same coordinates as the viewport. 68 * And the transformations are applied in the order of scale, rotate then translate. </dd> 69 * <dt><code>android:rotation</code> 70 * <dd>The degrees of rotation of the group.</dd></dt> 71 * <dt><code>android:pivotX</code> 72 * <dd>The X coordinate of the pivot for the scale and rotation of the group</dd></dt> 73 * <dt><code>android:pivotY</code> 74 * <dd>The Y coordinate of the pivot for the scale and rotation of the group</dd></dt> 75 * <dt><code>android:scaleX</code> 76 * <dd>The amount of scale on the X Coordinate</dd></dt> 77 * <dt><code>android:scaleY</code> 78 * <dd>The amount of scale on the Y coordinate</dd></dt> 79 * <dt><code>android:translateX</code> 80 * <dd>The amount of translation on the X coordinate</dd></dt> 81 * <dt><code>android:translateY</code> 82 * <dd>The amount of translation on the Y coordinate</dd></dt> 83 * <dt><code><path></code></dt> 84 * <dd>Defines paths to be drawn. 85 * <dl> 86 * <dt><code>android:name</code> 87 * <dd>Defines the name of the path.</dd></dt> 88 * <dt><code>android:pathData</code> 89 * <dd>Defines path string. This is using exactly same format as "d" attribute 90 * in the SVG's path data</dd></dt> 91 * <dt><code>android:fill</code> 92 * <dd>Defines the color to fill the path (none if not present).</dd></dt> 93 * <dt><code>android:stroke</code> 94 * <dd>Defines the color to draw the path outline (none if not present).</dd> 95 * </dt> 96 * <dt><code>android:strokeWidth</code> 97 * <dd>The width a path stroke</dd></dt> 98 * <dt><code>android:strokeOpacity</code> 99 * <dd>The opacity of a path stroke</dd></dt> 100 * <dt><code>android:fillOpacity</code> 101 * <dd>The opacity to fill the path with</dd></dt> 102 * <dt><code>android:trimPathStart</code> 103 * <dd>The fraction of the path to trim from the start from 0 to 1</dd></dt> 104 * <dt><code>android:trimPathEnd</code> 105 * <dd>The fraction of the path to trim from the end from 0 to 1</dd></dt> 106 * <dt><code>android:trimPathOffset</code> 107 * <dd>Shift trim region (allows showed region to include the start and end) 108 * from 0 to 1</dd></dt> 109 * <dt><code>android:clipToPath</code> 110 * <dd>Path will set the clip path</dd></dt> 111 * <dt><code>android:strokeLineCap</code> 112 * <dd>Sets the linecap for a stroked path: butt, round, square</dd></dt> 113 * <dt><code>android:strokeLineJoin</code> 114 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel</dd></dt> 115 * <dt><code>android:strokeMiterLimit</code> 116 * <dd>Sets the Miter limit for a stroked path</dd></dt> 117 * </dl> 118 * </dd> 119 */ 120public class VectorDrawable extends Drawable { 121 private static final String LOGTAG = VectorDrawable.class.getSimpleName(); 122 123 private static final String SHAPE_SIZE = "size"; 124 private static final String SHAPE_VIEWPORT = "viewport"; 125 private static final String SHAPE_GROUP = "group"; 126 private static final String SHAPE_PATH = "path"; 127 private static final String SHAPE_VECTOR = "vector"; 128 129 private static final int LINECAP_BUTT = 0; 130 private static final int LINECAP_ROUND = 1; 131 private static final int LINECAP_SQUARE = 2; 132 133 private static final int LINEJOIN_MITER = 0; 134 private static final int LINEJOIN_ROUND = 1; 135 private static final int LINEJOIN_BEVEL = 2; 136 137 private static final boolean DBG_VECTOR_DRAWABLE = false; 138 139 private final VectorDrawableState mVectorState; 140 141 private final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>(); 142 143 private PorterDuffColorFilter mTintFilter; 144 145 public VectorDrawable() { 146 mVectorState = new VectorDrawableState(null); 147 } 148 149 private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) { 150 mVectorState = new VectorDrawableState(state); 151 152 if (theme != null && canApplyTheme()) { 153 applyTheme(theme); 154 } 155 156 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 157 mVectorState.mVPathRenderer.setColorFilter(mTintFilter); 158 } 159 160 Object getTargetByName(String name) { 161 return mVGTargetsMap.get(name); 162 } 163 164 @Override 165 public ConstantState getConstantState() { 166 return mVectorState; 167 } 168 169 @Override 170 public void draw(Canvas canvas) { 171 final int saveCount = canvas.save(); 172 final Rect bounds = getBounds(); 173 canvas.translate(bounds.left, bounds.top); 174 mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height()); 175 canvas.restoreToCount(saveCount); 176 } 177 178 @Override 179 public int getAlpha() { 180 return mVectorState.mVPathRenderer.getRootAlpha(); 181 } 182 183 @Override 184 public void setAlpha(int alpha) { 185 if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) { 186 mVectorState.mVPathRenderer.setRootAlpha(alpha); 187 invalidateSelf(); 188 } 189 } 190 191 @Override 192 public void setColorFilter(ColorFilter colorFilter) { 193 final VectorDrawableState state = mVectorState; 194 if (colorFilter != null) { 195 // Color filter overrides tint. 196 mTintFilter = null; 197 } else if (state.mTint != null && state.mTintMode != null) { 198 // Restore the tint filter, if we need one. 199 final int color = state.mTint.getColorForState(getState(), Color.TRANSPARENT); 200 mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); 201 colorFilter = mTintFilter; 202 } 203 204 state.mVPathRenderer.setColorFilter(colorFilter); 205 invalidateSelf(); 206 } 207 208 @Override 209 public void setTint(ColorStateList tint, Mode tintMode) { 210 final VectorDrawableState state = mVectorState; 211 if (state.mTint != tint || state.mTintMode != tintMode) { 212 state.mTint = tint; 213 state.mTintMode = tintMode; 214 215 mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); 216 mVectorState.mVPathRenderer.setColorFilter(mTintFilter); 217 invalidateSelf(); 218 } 219 } 220 221 @Override 222 protected boolean onStateChange(int[] stateSet) { 223 final VectorDrawableState state = mVectorState; 224 if (state.mTint != null && state.mTintMode != null) { 225 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 226 mVectorState.mVPathRenderer.setColorFilter(mTintFilter); 227 return true; 228 } 229 return false; 230 } 231 232 @Override 233 public int getOpacity() { 234 return PixelFormat.TRANSLUCENT; 235 } 236 237 /** 238 * Sets padding for this shape, defined by a Rect object. Define the padding 239 * in the Rect object as: left, top, right, bottom. 240 */ 241 public void setPadding(Rect padding) { 242 setPadding(padding.left, padding.top, padding.right, padding.bottom); 243 } 244 245 /** 246 * Sets padding for the shape. 247 * 248 * @param left padding for the left side (in pixels) 249 * @param top padding for the top (in pixels) 250 * @param right padding for the right side (in pixels) 251 * @param bottom padding for the bottom (in pixels) 252 */ 253 public void setPadding(int left, int top, int right, int bottom) { 254 if ((left | top | right | bottom) == 0) { 255 mVectorState.mPadding = null; 256 } else { 257 if (mVectorState.mPadding == null) { 258 mVectorState.mPadding = new Rect(); 259 } 260 mVectorState.mPadding.set(left, top, right, bottom); 261 } 262 invalidateSelf(); 263 } 264 265 @Override 266 public int getIntrinsicWidth() { 267 return (int) mVectorState.mVPathRenderer.mBaseWidth; 268 } 269 270 @Override 271 public int getIntrinsicHeight() { 272 return (int) mVectorState.mVPathRenderer.mBaseHeight; 273 } 274 275 @Override 276 public boolean getPadding(Rect padding) { 277 if (mVectorState.mPadding != null) { 278 padding.set(mVectorState.mPadding); 279 return true; 280 } else { 281 return super.getPadding(padding); 282 } 283 } 284 285 @Override 286 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 287 throws XmlPullParserException, IOException { 288 final VPathRenderer p = inflateInternal(res, parser, attrs, theme); 289 setPathRenderer(p); 290 } 291 292 @Override 293 public boolean canApplyTheme() { 294 return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme(); 295 } 296 297 @Override 298 public void applyTheme(Theme t) { 299 super.applyTheme(t); 300 301 final VectorDrawableState state = mVectorState; 302 final VPathRenderer path = state.mVPathRenderer; 303 if (path != null && path.canApplyTheme()) { 304 path.applyTheme(t); 305 } 306 } 307 308 /** @hide */ 309 public static VectorDrawable create(Resources resources, int rid) { 310 try { 311 final XmlPullParser xpp = resources.getXml(rid); 312 final AttributeSet attrs = Xml.asAttributeSet(xpp); 313 final XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 314 factory.setNamespaceAware(true); 315 316 final VectorDrawable drawable = new VectorDrawable(); 317 drawable.inflate(resources, xpp, attrs); 318 319 return drawable; 320 } catch (XmlPullParserException e) { 321 Log.e(LOGTAG, "parser error", e); 322 } catch (IOException e) { 323 Log.e(LOGTAG, "parser error", e); 324 } 325 return null; 326 } 327 328 private static int applyAlpha(int color, float alpha) { 329 int alphaBytes = Color.alpha(color); 330 color &= 0x00FFFFFF; 331 color |= ((int) (alphaBytes * alpha)) << 24; 332 return color; 333 } 334 335 private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, 336 Theme theme) throws XmlPullParserException, IOException { 337 final VPathRenderer pathRenderer = new VPathRenderer(); 338 339 boolean noSizeTag = true; 340 boolean noViewportTag = true; 341 boolean noGroupTag = true; 342 boolean noPathTag = true; 343 344 // Use a stack to help to build the group tree. 345 // The top of the stack is always the current group. 346 final Stack<VGroup> groupStack = new Stack<VGroup>(); 347 groupStack.push(pathRenderer.mRootGroup); 348 349 int eventType = parser.getEventType(); 350 while (eventType != XmlPullParser.END_DOCUMENT) { 351 if (eventType == XmlPullParser.START_TAG) { 352 final String tagName = parser.getName(); 353 final VGroup currentGroup = groupStack.peek(); 354 355 if (SHAPE_PATH.equals(tagName)) { 356 final VPath path = new VPath(); 357 path.inflate(res, attrs, theme); 358 currentGroup.add(path); 359 if (path.getPathName() != null) { 360 mVGTargetsMap.put(path.getPathName(), path); 361 } 362 noPathTag = false; 363 } else if (SHAPE_SIZE.equals(tagName)) { 364 pathRenderer.parseSize(res, attrs); 365 noSizeTag = false; 366 } else if (SHAPE_VIEWPORT.equals(tagName)) { 367 pathRenderer.parseViewport(res, attrs); 368 noViewportTag = false; 369 } else if (SHAPE_GROUP.equals(tagName)) { 370 VGroup newChildGroup = new VGroup(); 371 newChildGroup.inflate(res, attrs, theme); 372 currentGroup.mChildGroupList.add(newChildGroup); 373 groupStack.push(newChildGroup); 374 if (newChildGroup.getGroupName() != null) { 375 mVGTargetsMap.put(newChildGroup.getGroupName(), newChildGroup); 376 } 377 noGroupTag = false; 378 } 379 } else if (eventType == XmlPullParser.END_TAG) { 380 final String tagName = parser.getName(); 381 if (SHAPE_GROUP.equals(tagName)) { 382 groupStack.pop(); 383 } 384 } 385 eventType = parser.next(); 386 } 387 388 // Print the tree out for debug. 389 if (DBG_VECTOR_DRAWABLE) { 390 printGroupTree(pathRenderer.mRootGroup, 0); 391 } 392 393 if (noSizeTag || noViewportTag || noPathTag) { 394 final StringBuffer tag = new StringBuffer(); 395 396 if (noSizeTag) { 397 tag.append(SHAPE_SIZE); 398 } 399 400 if (noViewportTag) { 401 if (tag.length() > 0) { 402 tag.append(" & "); 403 } 404 tag.append(SHAPE_SIZE); 405 } 406 407 if (noPathTag) { 408 if (tag.length() > 0) { 409 tag.append(" or "); 410 } 411 tag.append(SHAPE_PATH); 412 } 413 414 throw new XmlPullParserException("no " + tag + " defined"); 415 } 416 417 return pathRenderer; 418 } 419 420 private void printGroupTree(VGroup currentGroup, int level) { 421 String indent = ""; 422 for (int i = 0 ; i < level ; i++) { 423 indent += " "; 424 } 425 // Print the current node 426 Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName() 427 + " rotation is " + currentGroup.mRotate); 428 Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); 429 // Then print all the children 430 for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { 431 printGroupTree(currentGroup.mChildGroupList.get(i), level + 1); 432 } 433 } 434 435 private void setPathRenderer(VPathRenderer pathRenderer) { 436 mVectorState.mVPathRenderer = pathRenderer; 437 } 438 439 private static class VectorDrawableState extends ConstantState { 440 int mChangingConfigurations; 441 VPathRenderer mVPathRenderer; 442 Rect mPadding; 443 ColorStateList mTint; 444 Mode mTintMode; 445 446 public VectorDrawableState(VectorDrawableState copy) { 447 if (copy != null) { 448 mChangingConfigurations = copy.mChangingConfigurations; 449 // TODO: Make sure the constant state are handled correctly. 450 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); 451 mPadding = new Rect(copy.mPadding); 452 mTint = copy.mTint; 453 mTintMode = copy.mTintMode; 454 } 455 } 456 457 @Override 458 public Drawable newDrawable() { 459 return new VectorDrawable(this, null, null); 460 } 461 462 @Override 463 public Drawable newDrawable(Resources res) { 464 return new VectorDrawable(this, res, null); 465 } 466 467 @Override 468 public Drawable newDrawable(Resources res, Theme theme) { 469 return new VectorDrawable(this, res, theme); 470 } 471 472 @Override 473 public int getChangingConfigurations() { 474 return mChangingConfigurations; 475 } 476 } 477 478 private static class VPathRenderer { 479 /* Right now the internal data structure is organized as a tree. 480 * Each node can be a group node, or a path. 481 * A group node can have groups or paths as children, but a path node has 482 * no children. 483 * One example can be: 484 * Root Group 485 * / | \ 486 * Group Path Group 487 * / \ | 488 * Path Path Path 489 * 490 */ 491 private final VGroup mRootGroup; 492 493 private final Path mPath = new Path(); 494 private final Path mRenderPath = new Path(); 495 private static final Matrix IDENTITY_MATRIX = new Matrix(); 496 497 private Paint mStrokePaint; 498 private Paint mFillPaint; 499 private ColorFilter mColorFilter; 500 private PathMeasure mPathMeasure; 501 502 private float mBaseWidth = 0; 503 private float mBaseHeight = 0; 504 private float mViewportWidth = 0; 505 private float mViewportHeight = 0; 506 private int mRootAlpha = 0xFF; 507 508 private final Matrix mFinalPathMatrix = new Matrix(); 509 510 public VPathRenderer() { 511 mRootGroup = new VGroup(); 512 } 513 514 public void setRootAlpha(int alpha) { 515 mRootAlpha = alpha; 516 } 517 518 public int getRootAlpha() { 519 return mRootAlpha; 520 } 521 522 public VPathRenderer(VPathRenderer copy) { 523 mRootGroup = copy.mRootGroup; 524 mBaseWidth = copy.mBaseWidth; 525 mBaseHeight = copy.mBaseHeight; 526 mViewportWidth = copy.mViewportHeight; 527 mViewportHeight = copy.mViewportHeight; 528 } 529 530 public boolean canApplyTheme() { 531 // If one of the paths can apply theme, then return true; 532 return recursiveCanApplyTheme(mRootGroup); 533 } 534 535 private boolean recursiveCanApplyTheme(VGroup currentGroup) { 536 // We can do a tree traverse here, if there is one path return true, 537 // then we return true for the whole tree. 538 final ArrayList<VPath> paths = currentGroup.mPathList; 539 for (int j = paths.size() - 1; j >= 0; j--) { 540 final VPath path = paths.get(j); 541 if (path.canApplyTheme()) { 542 return true; 543 } 544 } 545 546 final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; 547 548 for (int i = 0; i < childGroups.size(); i++) { 549 VGroup childGroup = childGroups.get(i); 550 if (childGroup.canApplyTheme() 551 || recursiveCanApplyTheme(childGroup)) { 552 return true; 553 } 554 } 555 return false; 556 } 557 558 public void applyTheme(Theme t) { 559 // Apply theme to every path of the tree. 560 recursiveApplyTheme(mRootGroup, t); 561 } 562 563 private void recursiveApplyTheme(VGroup currentGroup, Theme t) { 564 // We can do a tree traverse here, apply theme to all paths which 565 // can apply theme. 566 final ArrayList<VPath> paths = currentGroup.mPathList; 567 for (int j = paths.size() - 1; j >= 0; j--) { 568 final VPath path = paths.get(j); 569 if (path.canApplyTheme()) { 570 path.applyTheme(t); 571 } 572 } 573 574 final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; 575 576 for (int i = 0; i < childGroups.size(); i++) { 577 VGroup childGroup = childGroups.get(i); 578 if (childGroup.canApplyTheme()) { 579 childGroup.applyTheme(t); 580 } 581 recursiveApplyTheme(childGroup, t); 582 } 583 584 } 585 586 public void setColorFilter(ColorFilter colorFilter) { 587 mColorFilter = colorFilter; 588 589 if (mFillPaint != null) { 590 mFillPaint.setColorFilter(colorFilter); 591 } 592 593 if (mStrokePaint != null) { 594 mStrokePaint.setColorFilter(colorFilter); 595 } 596 597 } 598 599 private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix, 600 float currentAlpha, Canvas canvas, int w, int h) { 601 // Calculate current group's matrix by preConcat the parent's and 602 // and the current one on the top of the stack. 603 // Basically the Mfinal = Mviewport * M0 * M1 * M2; 604 // Mi the local matrix at level i of the group tree. 605 currentGroup.mStackedMatrix.set(currentMatrix); 606 607 currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); 608 609 float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha; 610 drawPath(currentGroup, stackedAlpha, canvas, w, h); 611 // Draw the group tree in post order. 612 for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { 613 drawGroupTree(currentGroup.mChildGroupList.get(i), 614 currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h); 615 } 616 } 617 618 public void draw(Canvas canvas, int w, int h) { 619 // Travese the tree in pre-order to draw. 620 drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h); 621 } 622 623 private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) { 624 final float scale = Math.min(h / mViewportHeight, w / mViewportWidth); 625 626 mFinalPathMatrix.set(vGroup.mStackedMatrix); 627 mFinalPathMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f); 628 mFinalPathMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); 629 630 ArrayList<VPath> paths = vGroup.getPaths(); 631 for (int i = 0; i < paths.size(); i++) { 632 VPath vPath = paths.get(i); 633 vPath.toPath(mPath); 634 final Path path = mPath; 635 636 if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) { 637 float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f; 638 float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f; 639 640 if (mPathMeasure == null) { 641 mPathMeasure = new PathMeasure(); 642 } 643 mPathMeasure.setPath(mPath, false); 644 645 float len = mPathMeasure.getLength(); 646 start = start * len; 647 end = end * len; 648 path.reset(); 649 if (start > end) { 650 mPathMeasure.getSegment(start, len, path, true); 651 mPathMeasure.getSegment(0f, end, path, true); 652 } else { 653 mPathMeasure.getSegment(start, end, path, true); 654 } 655 path.rLineTo(0, 0); // fix bug in measure 656 } 657 658 mRenderPath.reset(); 659 660 mRenderPath.addPath(path, mFinalPathMatrix); 661 662 if (vPath.mClip) { 663 canvas.clipPath(mRenderPath, Region.Op.REPLACE); 664 } else { 665 if (vPath.mFillColor != 0) { 666 if (mFillPaint == null) { 667 mFillPaint = new Paint(); 668 mFillPaint.setColorFilter(mColorFilter); 669 mFillPaint.setStyle(Paint.Style.FILL); 670 mFillPaint.setAntiAlias(true); 671 } 672 mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha)); 673 canvas.drawPath(mRenderPath, mFillPaint); 674 } 675 676 if (vPath.mStrokeColor != 0) { 677 if (mStrokePaint == null) { 678 mStrokePaint = new Paint(); 679 mStrokePaint.setColorFilter(mColorFilter); 680 mStrokePaint.setStyle(Paint.Style.STROKE); 681 mStrokePaint.setAntiAlias(true); 682 } 683 684 final Paint strokePaint = mStrokePaint; 685 if (vPath.mStrokeLineJoin != null) { 686 strokePaint.setStrokeJoin(vPath.mStrokeLineJoin); 687 } 688 689 if (vPath.mStrokeLineCap != null) { 690 strokePaint.setStrokeCap(vPath.mStrokeLineCap); 691 } 692 693 strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale); 694 695 strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha)); 696 strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale); 697 canvas.drawPath(mRenderPath, strokePaint); 698 } 699 } 700 } 701 } 702 703 private void parseViewport(Resources r, AttributeSet attrs) 704 throws XmlPullParserException { 705 final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport); 706 mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth, mViewportWidth); 707 mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight, mViewportHeight); 708 709 if (mViewportWidth <= 0) { 710 throw new XmlPullParserException(a.getPositionDescription() + 711 "<viewport> tag requires viewportWidth > 0"); 712 } else if (mViewportHeight <= 0) { 713 throw new XmlPullParserException(a.getPositionDescription() + 714 "<viewport> tag requires viewportHeight > 0"); 715 } 716 717 a.recycle(); 718 } 719 720 private void parseSize(Resources r, AttributeSet attrs) 721 throws XmlPullParserException { 722 final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize); 723 mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, mBaseWidth); 724 mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, mBaseHeight); 725 726 if (mBaseWidth <= 0) { 727 throw new XmlPullParserException(a.getPositionDescription() + 728 "<size> tag requires width > 0"); 729 } else if (mBaseHeight <= 0) { 730 throw new XmlPullParserException(a.getPositionDescription() + 731 "<size> tag requires height > 0"); 732 } 733 734 a.recycle(); 735 } 736 737 } 738 739 static class VGroup { 740 private final ArrayList<VPath> mPathList = new ArrayList<VPath>(); 741 private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>(); 742 743 private float mRotate = 0; 744 private float mPivotX = 0; 745 private float mPivotY = 0; 746 private float mScaleX = 1; 747 private float mScaleY = 1; 748 private float mTranslateX = 0; 749 private float mTranslateY = 0; 750 private float mGroupAlpha = 1; 751 752 // mLocalMatrix is parsed from the XML. 753 private final Matrix mLocalMatrix = new Matrix(); 754 // mStackedMatrix is only used when drawing, it combines all the 755 // parents' local matrices with the current one. 756 private final Matrix mStackedMatrix = new Matrix(); 757 758 private int[] mThemeAttrs; 759 760 private String mGroupName = null; 761 762 /* Getter and Setter */ 763 public float getRotation() { 764 return mRotate; 765 } 766 767 public void setRotation(float rotation) { 768 if (rotation != mRotate) { 769 mRotate = rotation; 770 updateLocalMatrix(); 771 } 772 } 773 774 public float getPivotX() { 775 return mPivotX; 776 } 777 778 public void setPivotX(float pivotX) { 779 if (pivotX != mPivotX) { 780 mPivotX = pivotX; 781 updateLocalMatrix(); 782 } 783 } 784 785 public float getPivotY() { 786 return mPivotY; 787 } 788 789 public void setPivotY(float pivotY) { 790 if (pivotY != mPivotY) { 791 mPivotY = pivotY; 792 updateLocalMatrix(); 793 } 794 } 795 796 public float getScaleX() { 797 return mScaleX; 798 } 799 800 public void setScaleX(float scaleX) { 801 if (scaleX != mScaleX) { 802 mScaleX = scaleX; 803 updateLocalMatrix(); 804 } 805 } 806 807 public float getScaleY() { 808 return mScaleY; 809 } 810 811 public void setScaleY(float scaleY) { 812 if (scaleY != mScaleY) { 813 mScaleY = scaleY; 814 updateLocalMatrix(); 815 } 816 } 817 818 public float getTranslateX() { 819 return mTranslateX; 820 } 821 822 public void setTranslateX(float translateX) { 823 if (translateX != mTranslateX) { 824 mTranslateX = translateX; 825 updateLocalMatrix(); 826 } 827 } 828 829 public float getTranslateY() { 830 return mTranslateY; 831 } 832 833 public void setTranslateY(float translateY) { 834 if (translateY != mTranslateY) { 835 mTranslateY = translateY; 836 updateLocalMatrix(); 837 } 838 } 839 840 public float getAlpha() { 841 return mGroupAlpha; 842 } 843 844 public void setAlpha(float groupAlpha) { 845 if (groupAlpha != mGroupAlpha) { 846 mGroupAlpha = groupAlpha; 847 } 848 } 849 850 public String getGroupName() { 851 return mGroupName; 852 } 853 854 public Matrix getLocalMatrix() { 855 return mLocalMatrix; 856 } 857 858 public void add(VPath path) { 859 mPathList.add(path); 860 } 861 862 public boolean canApplyTheme() { 863 return mThemeAttrs != null; 864 } 865 866 public void applyTheme(Theme t) { 867 if (mThemeAttrs == null) { 868 return; 869 } 870 871 final TypedArray a = t.resolveAttributes( 872 mThemeAttrs, R.styleable.VectorDrawablePath); 873 874 mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate); 875 mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX); 876 mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY); 877 mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX); 878 mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); 879 mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); 880 mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); 881 mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha); 882 updateLocalMatrix(); 883 if (a.hasValue(R.styleable.VectorDrawableGroup_name)) { 884 mGroupName = a.getString(R.styleable.VectorDrawableGroup_name); 885 } 886 a.recycle(); 887 } 888 889 public void inflate(Resources res, AttributeSet attrs, Theme theme) { 890 final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawableGroup); 891 final int[] themeAttrs = a.extractThemeAttrs(); 892 893 mThemeAttrs = themeAttrs; 894 // NOTE: The set of attributes loaded here MUST match the 895 // set of attributes loaded in applyTheme. 896 897 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_rotation] == 0) { 898 mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate); 899 } 900 901 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotX] == 0) { 902 mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX); 903 } 904 905 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotY] == 0) { 906 mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY); 907 } 908 909 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleX] == 0) { 910 mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX); 911 } 912 913 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleY] == 0) { 914 mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); 915 } 916 917 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateX] == 0) { 918 mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); 919 } 920 921 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateY] == 0) { 922 mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); 923 } 924 925 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_name] == 0) { 926 mGroupName = a.getString(R.styleable.VectorDrawableGroup_name); 927 } 928 929 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_alpha] == 0) { 930 mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha); 931 } 932 933 updateLocalMatrix(); 934 a.recycle(); 935 } 936 937 private void updateLocalMatrix() { 938 // The order we apply is the same as the 939 // RenderNode.cpp::applyViewPropertyTransforms(). 940 mLocalMatrix.reset(); 941 mLocalMatrix.postTranslate(-mPivotX, -mPivotY); 942 mLocalMatrix.postScale(mScaleX, mScaleY); 943 mLocalMatrix.postRotate(mRotate, 0, 0); 944 mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); 945 } 946 947 /** 948 * Must return in order of adding 949 * @return ordered list of paths 950 */ 951 public ArrayList<VPath> getPaths() { 952 return mPathList; 953 } 954 955 } 956 957 static class VPath { 958 private int[] mThemeAttrs; 959 960 int mStrokeColor = 0; 961 float mStrokeWidth = 0; 962 float mStrokeOpacity = Float.NaN; 963 int mFillColor = Color.BLACK; 964 int mFillRule; 965 float mFillOpacity = Float.NaN; 966 float mTrimPathStart = 0; 967 float mTrimPathEnd = 1; 968 float mTrimPathOffset = 0; 969 970 boolean mClip = false; 971 Paint.Cap mStrokeLineCap = Paint.Cap.BUTT; 972 Paint.Join mStrokeLineJoin = Paint.Join.MITER; 973 float mStrokeMiterlimit = 4; 974 975 private VNode[] mNode = null; 976 private String mPathName; 977 978 public VPath() { 979 // Empty constructor. 980 } 981 982 public void toPath(Path path) { 983 path.reset(); 984 if (mNode != null) { 985 VNode.createPath(mNode, path); 986 } 987 } 988 989 public String getPathName() { 990 return mPathName; 991 } 992 993 private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) { 994 switch (id) { 995 case LINECAP_BUTT: 996 return Paint.Cap.BUTT; 997 case LINECAP_ROUND: 998 return Paint.Cap.ROUND; 999 case LINECAP_SQUARE: 1000 return Paint.Cap.SQUARE; 1001 default: 1002 return defValue; 1003 } 1004 } 1005 1006 private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) { 1007 switch (id) { 1008 case LINEJOIN_MITER: 1009 return Paint.Join.MITER; 1010 case LINEJOIN_ROUND: 1011 return Paint.Join.ROUND; 1012 case LINEJOIN_BEVEL: 1013 return Paint.Join.BEVEL; 1014 default: 1015 return defValue; 1016 } 1017 } 1018 1019 /* Setters and Getters */ 1020 int getStroke() { 1021 return mStrokeColor; 1022 } 1023 1024 void setStroke(int strokeColor) { 1025 mStrokeColor = strokeColor; 1026 } 1027 1028 float getStrokeWidth() { 1029 return mStrokeWidth; 1030 } 1031 1032 void setStrokeWidth(float strokeWidth) { 1033 mStrokeWidth = strokeWidth; 1034 } 1035 1036 float getStrokeOpacity() { 1037 return mStrokeOpacity; 1038 } 1039 1040 void setStrokeOpacity(float strokeOpacity) { 1041 mStrokeOpacity = strokeOpacity; 1042 } 1043 1044 int getFill() { 1045 return mFillColor; 1046 } 1047 1048 void setFill(int fillColor) { 1049 mFillColor = fillColor; 1050 } 1051 1052 float getFillOpacity() { 1053 return mFillOpacity; 1054 } 1055 1056 void setFillOpacity(float fillOpacity) { 1057 mFillOpacity = fillOpacity; 1058 } 1059 1060 float getTrimPathStart() { 1061 return mTrimPathStart; 1062 } 1063 1064 void setTrimPathStart(float trimPathStart) { 1065 mTrimPathStart = trimPathStart; 1066 } 1067 1068 float getTrimPathEnd() { 1069 return mTrimPathEnd; 1070 } 1071 1072 void setTrimPathEnd(float trimPathEnd) { 1073 mTrimPathEnd = trimPathEnd; 1074 } 1075 1076 float getTrimPathOffset() { 1077 return mTrimPathOffset; 1078 } 1079 1080 void setTrimPathOffset(float trimPathOffset) { 1081 mTrimPathOffset = trimPathOffset; 1082 } 1083 1084 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 1085 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath); 1086 final int[] themeAttrs = a.extractThemeAttrs(); 1087 mThemeAttrs = themeAttrs; 1088 1089 // NOTE: The set of attributes loaded here MUST match the 1090 // set of attributes loaded in applyTheme. 1091 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_clipToPath] == 0) { 1092 mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip); 1093 } 1094 1095 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0) { 1096 mPathName = a.getString(R.styleable.VectorDrawablePath_name); 1097 } 1098 1099 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) { 1100 mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData)); 1101 } 1102 1103 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) { 1104 mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor); 1105 } 1106 1107 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fillOpacity] == 0) { 1108 mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity); 1109 } 1110 1111 if (themeAttrs == null 1112 || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) { 1113 mStrokeLineCap = getStrokeLineCap( 1114 a.getInt(R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap); 1115 } 1116 1117 if (themeAttrs == null 1118 || themeAttrs[R.styleable.VectorDrawablePath_strokeLineJoin] == 0) { 1119 mStrokeLineJoin = getStrokeLineJoin( 1120 a.getInt(R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin); 1121 } 1122 1123 if (themeAttrs == null 1124 || themeAttrs[R.styleable.VectorDrawablePath_strokeMiterLimit] == 0) { 1125 mStrokeMiterlimit = a.getFloat( 1126 R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit); 1127 } 1128 1129 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_stroke] == 0) { 1130 mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor); 1131 } 1132 1133 if (themeAttrs == null 1134 || themeAttrs[R.styleable.VectorDrawablePath_strokeOpacity] == 0) { 1135 mStrokeOpacity = a.getFloat( 1136 R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity); 1137 } 1138 1139 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_strokeWidth] == 0) { 1140 mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth); 1141 } 1142 1143 if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_trimPathEnd] == 0) { 1144 mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd); 1145 } 1146 1147 if (themeAttrs == null 1148 || themeAttrs[R.styleable.VectorDrawablePath_trimPathOffset] == 0) { 1149 mTrimPathOffset = a.getFloat( 1150 R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset); 1151 } 1152 1153 if (themeAttrs == null 1154 || themeAttrs[R.styleable.VectorDrawablePath_trimPathStart] == 0) { 1155 mTrimPathStart = a.getFloat( 1156 R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart); 1157 } 1158 1159 updateColorAlphas(); 1160 1161 a.recycle(); 1162 } 1163 1164 public boolean canApplyTheme() { 1165 return mThemeAttrs != null; 1166 } 1167 1168 public void applyTheme(Theme t) { 1169 if (mThemeAttrs == null) { 1170 return; 1171 } 1172 1173 final TypedArray a = t.resolveAttributes( 1174 mThemeAttrs, R.styleable.VectorDrawablePath); 1175 1176 mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip); 1177 1178 if (a.hasValue(R.styleable.VectorDrawablePath_name)) { 1179 mPathName = a.getString(R.styleable.VectorDrawablePath_name); 1180 } 1181 1182 if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) { 1183 mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData)); 1184 } 1185 1186 mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor); 1187 mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity); 1188 1189 mStrokeLineCap = getStrokeLineCap(a.getInt( 1190 R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap); 1191 mStrokeLineJoin = getStrokeLineJoin(a.getInt( 1192 R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin); 1193 mStrokeMiterlimit = a.getFloat( 1194 R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit); 1195 mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor); 1196 mStrokeOpacity = a.getFloat( 1197 R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity); 1198 mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth); 1199 1200 mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd); 1201 mTrimPathOffset = a.getFloat( 1202 R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset); 1203 mTrimPathStart = a.getFloat( 1204 R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart); 1205 1206 updateColorAlphas(); 1207 a.recycle(); 1208 } 1209 1210 private void updateColorAlphas() { 1211 if (!Float.isNaN(mFillOpacity)) { 1212 mFillColor = applyAlpha(mFillColor, mFillOpacity); 1213 } 1214 1215 if (!Float.isNaN(mStrokeOpacity)) { 1216 mStrokeColor = applyAlpha(mStrokeColor, mStrokeOpacity); 1217 } 1218 } 1219 1220 private static int nextStart(String s, int end) { 1221 char c; 1222 1223 while (end < s.length()) { 1224 c = s.charAt(end); 1225 if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) { 1226 return end; 1227 } 1228 end++; 1229 } 1230 return end; 1231 } 1232 1233 private void addNode(ArrayList<VectorDrawable.VNode> list, char cmd, float[] val) { 1234 list.add(new VectorDrawable.VNode(cmd, val)); 1235 } 1236 1237 /** 1238 * parse the floats in the string 1239 * this is an optimized version of 1240 * parseFloat(s.split(",|\\s")); 1241 * 1242 * @param s the string containing a command and list of floats 1243 * @return array of floats 1244 */ 1245 private static float[] getFloats(String s) { 1246 if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') { 1247 return new float[0]; 1248 } 1249 try { 1250 float[] tmp = new float[s.length()]; 1251 int count = 0; 1252 int pos = 1, end; 1253 while ((end = extract(s, pos)) >= 0) { 1254 if (pos < end) { 1255 tmp[count++] = Float.parseFloat(s.substring(pos, end)); 1256 } 1257 pos = end + 1; 1258 } 1259 // handle the final float if there is one 1260 if (pos < s.length()) { 1261 tmp[count++] = Float.parseFloat(s.substring(pos, s.length())); 1262 } 1263 return Arrays.copyOf(tmp, count); 1264 } catch (NumberFormatException e){ 1265 Log.e(LOGTAG,"error in parsing \""+s+"\""); 1266 throw e; 1267 } 1268 } 1269 1270 /** 1271 * calculate the position of the next comma or space 1272 * @param s the string to search 1273 * @param start the position to start searching 1274 * @return the position of the next comma or space or -1 if none found 1275 */ 1276 private static int extract(String s, int start) { 1277 int space = s.indexOf(' ', start); 1278 int comma = s.indexOf(',', start); 1279 if (space == -1) { 1280 return comma; 1281 } 1282 if (comma == -1) { 1283 return space; 1284 } 1285 return (comma > space) ? space : comma; 1286 } 1287 1288 private VectorDrawable.VNode[] parsePath(String value) { 1289 int start = 0; 1290 int end = 1; 1291 1292 ArrayList<VectorDrawable.VNode> list = new ArrayList<VectorDrawable.VNode>(); 1293 while (end < value.length()) { 1294 end = nextStart(value, end); 1295 String s = value.substring(start, end); 1296 float[] val = getFloats(s); 1297 addNode(list, s.charAt(0), val); 1298 1299 start = end; 1300 end++; 1301 } 1302 if ((end - start) == 1 && start < value.length()) { 1303 1304 addNode(list, value.charAt(start), new float[0]); 1305 } 1306 return list.toArray(new VectorDrawable.VNode[list.size()]); 1307 } 1308 } 1309 1310 private static class VNode { 1311 private char mType; 1312 private float[] mParams; 1313 1314 public VNode(char type, float[] params) { 1315 mType = type; 1316 mParams = params; 1317 } 1318 1319 public VNode(VNode n) { 1320 mType = n.mType; 1321 mParams = Arrays.copyOf(n.mParams, n.mParams.length); 1322 } 1323 1324 public static void createPath(VNode[] node, Path path) { 1325 float[] current = new float[4]; 1326 char previousCommand = 'm'; 1327 for (int i = 0; i < node.length; i++) { 1328 addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); 1329 previousCommand = node[i].mType; 1330 } 1331 } 1332 1333 private static void addCommand(Path path, float[] current, 1334 char previousCmd, char cmd, float[] val) { 1335 1336 int incr = 2; 1337 float currentX = current[0]; 1338 float currentY = current[1]; 1339 float ctrlPointX = current[2]; 1340 float ctrlPointY = current[3]; 1341 float reflectiveCtrlPointX; 1342 float reflectiveCtrlPointY; 1343 1344 switch (cmd) { 1345 case 'z': 1346 case 'Z': 1347 path.close(); 1348 return; 1349 case 'm': 1350 case 'M': 1351 case 'l': 1352 case 'L': 1353 case 't': 1354 case 'T': 1355 incr = 2; 1356 break; 1357 case 'h': 1358 case 'H': 1359 case 'v': 1360 case 'V': 1361 incr = 1; 1362 break; 1363 case 'c': 1364 case 'C': 1365 incr = 6; 1366 break; 1367 case 's': 1368 case 'S': 1369 case 'q': 1370 case 'Q': 1371 incr = 4; 1372 break; 1373 case 'a': 1374 case 'A': 1375 incr = 7; 1376 break; 1377 } 1378 for (int k = 0; k < val.length; k += incr) { 1379 switch (cmd) { 1380 case 'm': // moveto - Start a new sub-path (relative) 1381 path.rMoveTo(val[k + 0], val[k + 1]); 1382 currentX += val[k + 0]; 1383 currentY += val[k + 1]; 1384 break; 1385 case 'M': // moveto - Start a new sub-path 1386 path.moveTo(val[k + 0], val[k + 1]); 1387 currentX = val[k + 0]; 1388 currentY = val[k + 1]; 1389 break; 1390 case 'l': // lineto - Draw a line from the current point (relative) 1391 path.rLineTo(val[k + 0], val[k + 1]); 1392 currentX += val[k + 0]; 1393 currentY += val[k + 1]; 1394 break; 1395 case 'L': // lineto - Draw a line from the current point 1396 path.lineTo(val[k + 0], val[k + 1]); 1397 currentX = val[k + 0]; 1398 currentY = val[k + 1]; 1399 break; 1400 case 'z': // closepath - Close the current subpath 1401 case 'Z': // closepath - Close the current subpath 1402 path.close(); 1403 break; 1404 case 'h': // horizontal lineto - Draws a horizontal line (relative) 1405 path.rLineTo(val[k + 0], 0); 1406 currentX += val[k + 0]; 1407 break; 1408 case 'H': // horizontal lineto - Draws a horizontal line 1409 path.lineTo(val[k + 0], currentY); 1410 currentX = val[k + 0]; 1411 break; 1412 case 'v': // vertical lineto - Draws a vertical line from the current point (r) 1413 path.rLineTo(0, val[k + 0]); 1414 currentY += val[k + 0]; 1415 break; 1416 case 'V': // vertical lineto - Draws a vertical line from the current point 1417 path.lineTo(currentX, val[k + 0]); 1418 currentY = val[k + 0]; 1419 break; 1420 case 'c': // curveto - Draws a cubic Bézier curve (relative) 1421 path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], 1422 val[k + 4], val[k + 5]); 1423 1424 ctrlPointX = currentX + val[k + 2]; 1425 ctrlPointY = currentY + val[k + 3]; 1426 currentX += val[k + 4]; 1427 currentY += val[k + 5]; 1428 1429 break; 1430 case 'C': // curveto - Draws a cubic Bézier curve 1431 path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], 1432 val[k + 4], val[k + 5]); 1433 currentX = val[k + 4]; 1434 currentY = val[k + 5]; 1435 ctrlPointX = val[k + 2]; 1436 ctrlPointY = val[k + 3]; 1437 break; 1438 case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) 1439 reflectiveCtrlPointX = 0; 1440 reflectiveCtrlPointY = 0; 1441 if (previousCmd == 'c' || previousCmd == 's' 1442 || previousCmd == 'C' || previousCmd == 'S') { 1443 reflectiveCtrlPointX = currentX - ctrlPointX; 1444 reflectiveCtrlPointY = currentY - ctrlPointY; 1445 } 1446 path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, 1447 val[k + 0], val[k + 1], 1448 val[k + 2], val[k + 3]); 1449 1450 ctrlPointX = currentX + val[k + 0]; 1451 ctrlPointY = currentY + val[k + 1]; 1452 currentX += val[k + 2]; 1453 currentY += val[k + 3]; 1454 break; 1455 case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) 1456 reflectiveCtrlPointX = currentX; 1457 reflectiveCtrlPointY = currentY; 1458 if (previousCmd == 'c' || previousCmd == 's' 1459 || previousCmd == 'C' || previousCmd == 'S') { 1460 reflectiveCtrlPointX = 2 * currentX - ctrlPointX; 1461 reflectiveCtrlPointY = 2 * currentY - ctrlPointY; 1462 } 1463 path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, 1464 val[k + 0], val[k + 1], val[k + 2], val[k + 3]); 1465 ctrlPointX = val[k + 0]; 1466 ctrlPointY = val[k + 1]; 1467 currentX = val[k + 2]; 1468 currentY = val[k + 3]; 1469 break; 1470 case 'q': // Draws a quadratic Bézier (relative) 1471 path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); 1472 ctrlPointX = currentX + val[k + 0]; 1473 ctrlPointY = currentY + val[k + 1]; 1474 currentX += val[k + 2]; 1475 currentY += val[k + 3]; 1476 break; 1477 case 'Q': // Draws a quadratic Bézier 1478 path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); 1479 ctrlPointX = val[k + 0]; 1480 ctrlPointY = val[k + 1]; 1481 currentX = val[k + 2]; 1482 currentY = val[k + 3]; 1483 break; 1484 case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) 1485 reflectiveCtrlPointX = 0; 1486 reflectiveCtrlPointY = 0; 1487 if (previousCmd == 'q' || previousCmd == 't' 1488 || previousCmd == 'Q' || previousCmd == 'T') { 1489 reflectiveCtrlPointX = currentX - ctrlPointX; 1490 reflectiveCtrlPointY = currentY - ctrlPointY; 1491 } 1492 path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, 1493 val[k + 0], val[k + 1]); 1494 ctrlPointX = currentX + reflectiveCtrlPointX; 1495 ctrlPointY = currentY + reflectiveCtrlPointY; 1496 currentX += val[k + 0]; 1497 currentY += val[k + 1]; 1498 break; 1499 case 'T': // Draws a quadratic Bézier curve (reflective control point) 1500 reflectiveCtrlPointX = currentX; 1501 reflectiveCtrlPointY = currentY; 1502 if (previousCmd == 'q' || previousCmd == 't' 1503 || previousCmd == 'Q' || previousCmd == 'T') { 1504 reflectiveCtrlPointX = 2 * currentX - ctrlPointX; 1505 reflectiveCtrlPointY = 2 * currentY - ctrlPointY; 1506 } 1507 path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, 1508 val[k + 0], val[k + 1]); 1509 ctrlPointX = reflectiveCtrlPointX; 1510 ctrlPointY = reflectiveCtrlPointY; 1511 currentX = val[k + 0]; 1512 currentY = val[k + 1]; 1513 break; 1514 case 'a': // Draws an elliptical arc 1515 // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) 1516 drawArc(path, 1517 currentX, 1518 currentY, 1519 val[k + 5] + currentX, 1520 val[k + 6] + currentY, 1521 val[k + 0], 1522 val[k + 1], 1523 val[k + 2], 1524 val[k + 3] != 0, 1525 val[k + 4] != 0); 1526 currentX += val[k + 5]; 1527 currentY += val[k + 6]; 1528 ctrlPointX = currentX; 1529 ctrlPointY = currentY; 1530 break; 1531 case 'A': // Draws an elliptical arc 1532 drawArc(path, 1533 currentX, 1534 currentY, 1535 val[k + 5], 1536 val[k + 6], 1537 val[k + 0], 1538 val[k + 1], 1539 val[k + 2], 1540 val[k + 3] != 0, 1541 val[k + 4] != 0); 1542 currentX = val[k + 5]; 1543 currentY = val[k + 6]; 1544 ctrlPointX = currentX; 1545 ctrlPointY = currentY; 1546 break; 1547 } 1548 previousCmd = cmd; 1549 } 1550 current[0] = currentX; 1551 current[1] = currentY; 1552 current[2] = ctrlPointX; 1553 current[3] = ctrlPointY; 1554 } 1555 1556 private static void drawArc(Path p, 1557 float x0, 1558 float y0, 1559 float x1, 1560 float y1, 1561 float a, 1562 float b, 1563 float theta, 1564 boolean isMoreThanHalf, 1565 boolean isPositiveArc) { 1566 1567 /* Convert rotation angle from degrees to radians */ 1568 double thetaD = Math.toRadians(theta); 1569 /* Pre-compute rotation matrix entries */ 1570 double cosTheta = Math.cos(thetaD); 1571 double sinTheta = Math.sin(thetaD); 1572 /* Transform (x0, y0) and (x1, y1) into unit space */ 1573 /* using (inverse) rotation, followed by (inverse) scale */ 1574 double x0p = (x0 * cosTheta + y0 * sinTheta) / a; 1575 double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; 1576 double x1p = (x1 * cosTheta + y1 * sinTheta) / a; 1577 double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; 1578 1579 /* Compute differences and averages */ 1580 double dx = x0p - x1p; 1581 double dy = y0p - y1p; 1582 double xm = (x0p + x1p) / 2; 1583 double ym = (y0p + y1p) / 2; 1584 /* Solve for intersecting unit circles */ 1585 double dsq = dx * dx + dy * dy; 1586 if (dsq == 0.0) { 1587 Log.w(LOGTAG, " Points are coincident"); 1588 return; /* Points are coincident */ 1589 } 1590 double disc = 1.0 / dsq - 1.0 / 4.0; 1591 if (disc < 0.0) { 1592 Log.w(LOGTAG, "Points are too far apart " + dsq); 1593 float adjust = (float) (Math.sqrt(dsq) / 1.99999); 1594 drawArc(p, x0, y0, x1, y1, a * adjust, 1595 b * adjust, theta, isMoreThanHalf, isPositiveArc); 1596 return; /* Points are too far apart */ 1597 } 1598 double s = Math.sqrt(disc); 1599 double sdx = s * dx; 1600 double sdy = s * dy; 1601 double cx; 1602 double cy; 1603 if (isMoreThanHalf == isPositiveArc) { 1604 cx = xm - sdy; 1605 cy = ym + sdx; 1606 } else { 1607 cx = xm + sdy; 1608 cy = ym - sdx; 1609 } 1610 1611 double eta0 = Math.atan2((y0p - cy), (x0p - cx)); 1612 1613 double eta1 = Math.atan2((y1p - cy), (x1p - cx)); 1614 1615 double sweep = (eta1 - eta0); 1616 if (isPositiveArc != (sweep >= 0)) { 1617 if (sweep > 0) { 1618 sweep -= 2 * Math.PI; 1619 } else { 1620 sweep += 2 * Math.PI; 1621 } 1622 } 1623 1624 cx *= a; 1625 cy *= b; 1626 double tcx = cx; 1627 cx = cx * cosTheta - cy * sinTheta; 1628 cy = tcx * sinTheta + cy * cosTheta; 1629 1630 arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); 1631 } 1632 1633 /** 1634 * Converts an arc to cubic Bezier segments and records them in p. 1635 * 1636 * @param p The target for the cubic Bezier segments 1637 * @param cx The x coordinate center of the ellipse 1638 * @param cy The y coordinate center of the ellipse 1639 * @param a The radius of the ellipse in the horizontal direction 1640 * @param b The radius of the ellipse in the vertical direction 1641 * @param e1x E(eta1) x coordinate of the starting point of the arc 1642 * @param e1y E(eta2) y coordinate of the starting point of the arc 1643 * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane 1644 * @param start The start angle of the arc on the ellipse 1645 * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse 1646 */ 1647 private static void arcToBezier(Path p, 1648 double cx, 1649 double cy, 1650 double a, 1651 double b, 1652 double e1x, 1653 double e1y, 1654 double theta, 1655 double start, 1656 double sweep) { 1657 // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html 1658 // and http://www.spaceroots.org/documents/ellipse/node22.html 1659 1660 // Maximum of 45 degrees per cubic Bezier segment 1661 int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI)); 1662 1663 double eta1 = start; 1664 double cosTheta = Math.cos(theta); 1665 double sinTheta = Math.sin(theta); 1666 double cosEta1 = Math.cos(eta1); 1667 double sinEta1 = Math.sin(eta1); 1668 double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); 1669 double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); 1670 1671 double anglePerSegment = sweep / numSegments; 1672 for (int i = 0; i < numSegments; i++) { 1673 double eta2 = eta1 + anglePerSegment; 1674 double sinEta2 = Math.sin(eta2); 1675 double cosEta2 = Math.cos(eta2); 1676 double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); 1677 double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); 1678 double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; 1679 double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; 1680 double tanDiff2 = Math.tan((eta2 - eta1) / 2); 1681 double alpha = 1682 Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; 1683 double q1x = e1x + alpha * ep1x; 1684 double q1y = e1y + alpha * ep1y; 1685 double q2x = e2x - alpha * ep2x; 1686 double q2y = e2y - alpha * ep2y; 1687 1688 p.cubicTo((float) q1x, 1689 (float) q1y, 1690 (float) q2x, 1691 (float) q2y, 1692 (float) e2x, 1693 (float) e2y); 1694 eta1 = eta2; 1695 e1x = e2x; 1696 e1y = e2y; 1697 ep1x = ep2x; 1698 ep1y = ep2y; 1699 } 1700 } 1701 1702 } 1703} 1704