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