blob: 8102894c87f10fc13c673c9bb5a4bf8d0d911e76 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.graphics.drawable;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.NonNull;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.BaseCanvas_Delegate;
import android.graphics.Canvas_Delegate;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Join;
import android.graphics.Paint_Delegate;
import android.graphics.Path;
import android.graphics.Path.FillType;
import android.graphics.PathMeasure;
import android.graphics.Path_Delegate;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.Shader_Delegate;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.util.PathParser_Delegate;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.function.Consumer;
import static android.graphics.Canvas.CLIP_SAVE_FLAG;
import static android.graphics.Canvas.MATRIX_SAVE_FLAG;
import static android.graphics.Paint.Cap.BUTT;
import static android.graphics.Paint.Cap.ROUND;
import static android.graphics.Paint.Cap.SQUARE;
import static android.graphics.Paint.Join.BEVEL;
import static android.graphics.Paint.Join.MITER;
import static android.graphics.Paint.Style;
/**
* Delegate used to provide new implementation of a select few methods of {@link VectorDrawable}
* <p>
* Through the layoutlib_create tool, the original methods of VectorDrawable have been replaced by
* calls to methods of the same name in this delegate class.
*/
@SuppressWarnings("unused")
public class VectorDrawable_Delegate {
private static final String LOGTAG = VectorDrawable_Delegate.class.getSimpleName();
private static final boolean DBG_VECTOR_DRAWABLE = false;
private static final DelegateManager<VNativeObject> sPathManager =
new DelegateManager<>(VNativeObject.class);
private static long addNativeObject(VNativeObject object) {
long ptr = sPathManager.addNewDelegate(object);
object.setNativePtr(ptr);
return ptr;
}
/**
* Obtains styled attributes from the theme, if available, or unstyled resources if the theme is
* null.
*/
private static TypedArray obtainAttributes(
Resources res, Theme theme, AttributeSet set, int[] attrs) {
if (theme == null) {
return res.obtainAttributes(set, attrs);
}
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
private static int applyAlpha(int color, float alpha) {
int alphaBytes = Color.alpha(color);
color &= 0x00FFFFFF;
color |= ((int) (alphaBytes * alpha)) << 24;
return color;
}
@LayoutlibDelegate
static long nCreateTree(long rootGroupPtr) {
return addNativeObject(new VPathRenderer_Delegate(rootGroupPtr));
}
@LayoutlibDelegate
static long nCreateTreeFromCopy(long rendererToCopyPtr, long rootGroupPtr) {
VPathRenderer_Delegate rendererToCopy = VNativeObject.getDelegate(rendererToCopyPtr);
return addNativeObject(new VPathRenderer_Delegate(rendererToCopy,
rootGroupPtr));
}
@LayoutlibDelegate
static void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
float viewportHeight) {
VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
nativePathRenderer.mViewportWidth = viewportWidth;
nativePathRenderer.mViewportHeight = viewportHeight;
}
@LayoutlibDelegate
static boolean nSetRootAlpha(long rendererPtr, float alpha) {
VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
nativePathRenderer.setRootAlpha(alpha);
return true;
}
@LayoutlibDelegate
static float nGetRootAlpha(long rendererPtr) {
VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
return nativePathRenderer.getRootAlpha();
}
@LayoutlibDelegate
static void nSetAntiAlias(long rendererPtr, boolean aa) {
VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
nativePathRenderer.setAntiAlias(aa);
}
@LayoutlibDelegate
static void nSetAllowCaching(long rendererPtr, boolean allowCaching) {
// ignored
}
@LayoutlibDelegate
static int nDraw(long rendererPtr, long canvasWrapperPtr,
long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache) {
VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
Canvas_Delegate.nSave(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
Canvas_Delegate.nClipRect(canvasWrapperPtr,
bounds.left, bounds.top, bounds.right, bounds.bottom,
Region.Op.INTERSECT.nativeInt);
Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.left, bounds.top);
if (needsMirroring) {
Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.width(), 0);
Canvas_Delegate.nScale(canvasWrapperPtr, -1.0f, 1.0f);
}
// At this point, canvas has been translated to the right position.
// And we use this bound for the destination rect for the drawBitmap, so
// we offset to (0, 0);
bounds.offsetTo(0, 0);
nativePathRenderer.draw(canvasWrapperPtr, colorFilterPtr, bounds.width(), bounds.height());
Canvas_Delegate.nRestore(canvasWrapperPtr);
return bounds.width() * bounds.height();
}
@LayoutlibDelegate
static long nCreateFullPath() {
return addNativeObject(new VFullPath_Delegate());
}
@LayoutlibDelegate
static long nCreateFullPath(long nativeFullPathPtr) {
VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr);
return addNativeObject(new VFullPath_Delegate(original));
}
@LayoutlibDelegate
static boolean nGetFullPathProperties(long pathPtr, byte[] propertiesData,
int length) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
ByteBuffer properties = ByteBuffer.wrap(propertiesData);
properties.order(ByteOrder.nativeOrder());
properties.putFloat(VFullPath_Delegate.STROKE_WIDTH_INDEX * 4, path.getStrokeWidth());
properties.putInt(VFullPath_Delegate.STROKE_COLOR_INDEX * 4, path.getStrokeColor());
properties.putFloat(VFullPath_Delegate.STROKE_ALPHA_INDEX * 4, path.getStrokeAlpha());
properties.putInt(VFullPath_Delegate.FILL_COLOR_INDEX * 4, path.getFillColor());
properties.putFloat(VFullPath_Delegate.FILL_ALPHA_INDEX * 4, path.getStrokeAlpha());
properties.putFloat(VFullPath_Delegate.TRIM_PATH_START_INDEX * 4, path.getTrimPathStart());
properties.putFloat(VFullPath_Delegate.TRIM_PATH_END_INDEX * 4, path.getTrimPathEnd());
properties.putFloat(VFullPath_Delegate.TRIM_PATH_OFFSET_INDEX * 4,
path.getTrimPathOffset());
properties.putInt(VFullPath_Delegate.STROKE_LINE_CAP_INDEX * 4, path.getStrokeLineCap());
properties.putInt(VFullPath_Delegate.STROKE_LINE_JOIN_INDEX * 4, path.getStrokeLineJoin());
properties.putFloat(VFullPath_Delegate.STROKE_MITER_LIMIT_INDEX * 4,
path.getStrokeMiterlimit());
properties.putInt(VFullPath_Delegate.FILL_TYPE_INDEX * 4, path.getFillType());
return true;
}
@LayoutlibDelegate
static void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
int strokeLineJoin, int fillType) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeWidth(strokeWidth);
path.setStrokeColor(strokeColor);
path.setStrokeAlpha(strokeAlpha);
path.setFillColor(fillColor);
path.setFillAlpha(fillAlpha);
path.setTrimPathStart(trimPathStart);
path.setTrimPathEnd(trimPathEnd);
path.setTrimPathOffset(trimPathOffset);
path.setStrokeMiterlimit(strokeMiterLimit);
path.setStrokeLineCap(strokeLineCap);
path.setStrokeLineJoin(strokeLineJoin);
path.setFillType(fillType);
}
@LayoutlibDelegate
static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setFillGradient(fillGradientPtr);
}
@LayoutlibDelegate
static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeGradient(strokeGradientPtr);
}
@LayoutlibDelegate
static long nCreateClipPath() {
return addNativeObject(new VClipPath_Delegate());
}
@LayoutlibDelegate
static long nCreateClipPath(long clipPathPtr) {
VClipPath_Delegate original = VNativeObject.getDelegate(clipPathPtr);
return addNativeObject(new VClipPath_Delegate(original));
}
@LayoutlibDelegate
static long nCreateGroup() {
return addNativeObject(new VGroup_Delegate());
}
@LayoutlibDelegate
static long nCreateGroup(long groupPtr) {
VGroup_Delegate original = VNativeObject.getDelegate(groupPtr);
return addNativeObject(new VGroup_Delegate(original, new ArrayMap<>()));
}
@LayoutlibDelegate
static void nSetName(long nodePtr, String name) {
VNativeObject group = VNativeObject.getDelegate(nodePtr);
group.setName(name);
}
@LayoutlibDelegate
static boolean nGetGroupProperties(long groupPtr, float[] propertiesData,
int length) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
FloatBuffer properties = FloatBuffer.wrap(propertiesData);
properties.put(VGroup_Delegate.ROTATE_INDEX, group.getRotation());
properties.put(VGroup_Delegate.PIVOT_X_INDEX, group.getPivotX());
properties.put(VGroup_Delegate.PIVOT_Y_INDEX, group.getPivotY());
properties.put(VGroup_Delegate.SCALE_X_INDEX, group.getScaleX());
properties.put(VGroup_Delegate.SCALE_Y_INDEX, group.getScaleY());
properties.put(VGroup_Delegate.TRANSLATE_X_INDEX, group.getTranslateX());
properties.put(VGroup_Delegate.TRANSLATE_Y_INDEX, group.getTranslateY());
return true;
}
@LayoutlibDelegate
static void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
float pivotY, float scaleX, float scaleY, float translateX, float translateY) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setRotation(rotate);
group.setPivotX(pivotX);
group.setPivotY(pivotY);
group.setScaleX(scaleX);
group.setScaleY(scaleY);
group.setTranslateX(translateX);
group.setTranslateY(translateY);
}
@LayoutlibDelegate
static void nAddChild(long groupPtr, long nodePtr) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.mChildren.add(VNativeObject.getDelegate(nodePtr));
}
@LayoutlibDelegate
static void nSetPathString(long pathPtr, String pathString, int length) {
VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setPathData(PathParser_Delegate.createNodesFromPathData(pathString));
}
/**
* The setters and getters below for paths and groups are here temporarily, and will be removed
* once the animation in AVD is replaced with RenderNodeAnimator, in which case the animation
* will modify these properties in native. By then no JNI hopping would be necessary for VD
* during animation, and these setters and getters will be obsolete.
*/
// Setters and getters during animation.
@LayoutlibDelegate
static float nGetRotation(long groupPtr) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getRotation();
}
@LayoutlibDelegate
static void nSetRotation(long groupPtr, float rotation) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setRotation(rotation);
}
@LayoutlibDelegate
static float nGetPivotX(long groupPtr) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getPivotX();
}
@LayoutlibDelegate
static void nSetPivotX(long groupPtr, float pivotX) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setPivotX(pivotX);
}
@LayoutlibDelegate
static float nGetPivotY(long groupPtr) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getPivotY();
}
@LayoutlibDelegate
static void nSetPivotY(long groupPtr, float pivotY) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setPivotY(pivotY);
}
@LayoutlibDelegate
static float nGetScaleX(long groupPtr) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getScaleX();
}
@LayoutlibDelegate
static void nSetScaleX(long groupPtr, float scaleX) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setScaleX(scaleX);
}
@LayoutlibDelegate
static float nGetScaleY(long groupPtr) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getScaleY();
}
@LayoutlibDelegate
static void nSetScaleY(long groupPtr, float scaleY) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setScaleY(scaleY);
}
@LayoutlibDelegate
static float nGetTranslateX(long groupPtr) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getTranslateX();
}
@LayoutlibDelegate
static void nSetTranslateX(long groupPtr, float translateX) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setTranslateX(translateX);
}
@LayoutlibDelegate
static float nGetTranslateY(long groupPtr) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getTranslateY();
}
@LayoutlibDelegate
static void nSetTranslateY(long groupPtr, float translateY) {
VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setTranslateY(translateY);
}
@LayoutlibDelegate
static void nSetPathData(long pathPtr, long pathDataPtr) {
VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setPathData(PathParser_Delegate.getDelegate(pathDataPtr).getPathDataNodes());
}
@LayoutlibDelegate
static float nGetStrokeWidth(long pathPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getStrokeWidth();
}
@LayoutlibDelegate
static void nSetStrokeWidth(long pathPtr, float width) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeWidth(width);
}
@LayoutlibDelegate
static int nGetStrokeColor(long pathPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getStrokeColor();
}
@LayoutlibDelegate
static void nSetStrokeColor(long pathPtr, int strokeColor) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeColor(strokeColor);
}
@LayoutlibDelegate
static float nGetStrokeAlpha(long pathPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getStrokeAlpha();
}
@LayoutlibDelegate
static void nSetStrokeAlpha(long pathPtr, float alpha) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeAlpha(alpha);
}
@LayoutlibDelegate
static int nGetFillColor(long pathPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getFillColor();
}
@LayoutlibDelegate
static void nSetFillColor(long pathPtr, int fillColor) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setFillColor(fillColor);
}
@LayoutlibDelegate
static float nGetFillAlpha(long pathPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getFillAlpha();
}
@LayoutlibDelegate
static void nSetFillAlpha(long pathPtr, float fillAlpha) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setFillAlpha(fillAlpha);
}
@LayoutlibDelegate
static float nGetTrimPathStart(long pathPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getTrimPathStart();
}
@LayoutlibDelegate
static void nSetTrimPathStart(long pathPtr, float trimPathStart) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setTrimPathStart(trimPathStart);
}
@LayoutlibDelegate
static float nGetTrimPathEnd(long pathPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getTrimPathEnd();
}
@LayoutlibDelegate
static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setTrimPathEnd(trimPathEnd);
}
@LayoutlibDelegate
static float nGetTrimPathOffset(long pathPtr) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getTrimPathOffset();
}
@LayoutlibDelegate
static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) {
VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setTrimPathOffset(trimPathOffset);
}
/**
* Base class for all the internal Delegates that does two functions:
* <ol>
* <li>Serves as base class to store all the delegates in one {@link DelegateManager}
* <li>Provides setName for all the classes. {@link VPathRenderer_Delegate} does actually
* not need it
* </ol>
*/
abstract static class VNativeObject {
long mNativePtr = 0;
@NonNull
static <T> T getDelegate(long nativePtr) {
//noinspection unchecked
T vNativeObject = (T) sPathManager.getDelegate(nativePtr);
assert vNativeObject != null;
return vNativeObject;
}
abstract void setName(String name);
void setNativePtr(long nativePtr) {
mNativePtr = nativePtr;
}
/**
* Method to explicitly dispose native objects
*/
void dispose() {
}
}
private static class VClipPath_Delegate extends VPath_Delegate {
private VClipPath_Delegate() {
// Empty constructor.
}
private VClipPath_Delegate(VClipPath_Delegate copy) {
super(copy);
}
@Override
public boolean isClipPath() {
return true;
}
}
static class VFullPath_Delegate extends VPath_Delegate {
// These constants need to be kept in sync with their values in VectorDrawable.VFullPath
private static final int STROKE_WIDTH_INDEX = 0;
private static final int STROKE_COLOR_INDEX = 1;
private static final int STROKE_ALPHA_INDEX = 2;
private static final int FILL_COLOR_INDEX = 3;
private static final int FILL_ALPHA_INDEX = 4;
private static final int TRIM_PATH_START_INDEX = 5;
private static final int TRIM_PATH_END_INDEX = 6;
private static final int TRIM_PATH_OFFSET_INDEX = 7;
private static final int STROKE_LINE_CAP_INDEX = 8;
private static final int STROKE_LINE_JOIN_INDEX = 9;
private static final int STROKE_MITER_LIMIT_INDEX = 10;
private static final int FILL_TYPE_INDEX = 11;
private static final int LINECAP_BUTT = 0;
private static final int LINECAP_ROUND = 1;
private static final int LINECAP_SQUARE = 2;
private static final int LINEJOIN_MITER = 0;
private static final int LINEJOIN_ROUND = 1;
private static final int LINEJOIN_BEVEL = 2;
@NonNull
public Consumer<Float> getFloatPropertySetter(int propertyIdx) {
switch (propertyIdx) {
case STROKE_WIDTH_INDEX:
return this::setStrokeWidth;
case STROKE_ALPHA_INDEX:
return this::setStrokeAlpha;
case FILL_ALPHA_INDEX:
return this::setFillAlpha;
case TRIM_PATH_START_INDEX:
return this::setTrimPathStart;
case TRIM_PATH_END_INDEX:
return this::setTrimPathEnd;
case TRIM_PATH_OFFSET_INDEX:
return this::setTrimPathOffset;
}
assert false : ("Invalid VFullPath_Delegate property index " + propertyIdx);
return t -> {};
}
@NonNull
public Consumer<Integer> getIntPropertySetter(int propertyIdx) {
switch (propertyIdx) {
case STROKE_COLOR_INDEX:
return this::setStrokeColor;
case FILL_COLOR_INDEX:
return this::setFillColor;
}
assert false : ("Invalid VFullPath_Delegate property index " + propertyIdx);
return t -> {};
}
/////////////////////////////////////////////////////
// Variables below need to be copied (deep copy if applicable) for mutation.
int mStrokeColor = Color.TRANSPARENT;
float mStrokeWidth = 0;
int mFillColor = Color.TRANSPARENT;
long mStrokeGradient = 0;
long mFillGradient = 0;
float mStrokeAlpha = 1.0f;
float mFillAlpha = 1.0f;
float mTrimPathStart = 0;
float mTrimPathEnd = 1;
float mTrimPathOffset = 0;
Cap mStrokeLineCap = BUTT;
Join mStrokeLineJoin = MITER;
float mStrokeMiterlimit = 4;
int mFillType = 0; // WINDING(0) is the default value. See Path.FillType
private VFullPath_Delegate() {
// Empty constructor.
}
private VFullPath_Delegate(VFullPath_Delegate copy) {
super(copy);
mStrokeColor = copy.mStrokeColor;
mStrokeWidth = copy.mStrokeWidth;
mStrokeAlpha = copy.mStrokeAlpha;
mFillColor = copy.mFillColor;
mFillAlpha = copy.mFillAlpha;
mTrimPathStart = copy.mTrimPathStart;
mTrimPathEnd = copy.mTrimPathEnd;
mTrimPathOffset = copy.mTrimPathOffset;
mStrokeLineCap = copy.mStrokeLineCap;
mStrokeLineJoin = copy.mStrokeLineJoin;
mStrokeMiterlimit = copy.mStrokeMiterlimit;
mStrokeGradient = copy.mStrokeGradient;
mFillGradient = copy.mFillGradient;
mFillType = copy.mFillType;
}
private int getStrokeLineCap() {
switch (mStrokeLineCap) {
case BUTT:
return LINECAP_BUTT;
case ROUND:
return LINECAP_ROUND;
case SQUARE:
return LINECAP_SQUARE;
default:
assert false;
}
return -1;
}
private void setStrokeLineCap(int cap) {
switch (cap) {
case LINECAP_BUTT:
mStrokeLineCap = BUTT;
break;
case LINECAP_ROUND:
mStrokeLineCap = ROUND;
break;
case LINECAP_SQUARE:
mStrokeLineCap = SQUARE;
break;
default:
assert false;
}
}
private int getStrokeLineJoin() {
switch (mStrokeLineJoin) {
case MITER:
return LINEJOIN_MITER;
case ROUND:
return LINEJOIN_ROUND;
case BEVEL:
return LINEJOIN_BEVEL;
default:
assert false;
}
return -1;
}
private void setStrokeLineJoin(int join) {
switch (join) {
case LINEJOIN_BEVEL:
mStrokeLineJoin = BEVEL;
break;
case LINEJOIN_MITER:
mStrokeLineJoin = MITER;
break;
case LINEJOIN_ROUND:
mStrokeLineJoin = Join.ROUND;
break;
default:
assert false;
}
}
private int getStrokeColor() {
return mStrokeColor;
}
private void setStrokeColor(int strokeColor) {
mStrokeColor = strokeColor;
}
private float getStrokeWidth() {
return mStrokeWidth;
}
private void setStrokeWidth(float strokeWidth) {
mStrokeWidth = strokeWidth;
}
private float getStrokeAlpha() {
return mStrokeAlpha;
}
private void setStrokeAlpha(float strokeAlpha) {
mStrokeAlpha = strokeAlpha;
}
private int getFillColor() {
return mFillColor;
}
private void setFillColor(int fillColor) {
mFillColor = fillColor;
}
private float getFillAlpha() {
return mFillAlpha;
}
private void setFillAlpha(float fillAlpha) {
mFillAlpha = fillAlpha;
}
private float getTrimPathStart() {
return mTrimPathStart;
}
private void setTrimPathStart(float trimPathStart) {
mTrimPathStart = trimPathStart;
}
private float getTrimPathEnd() {
return mTrimPathEnd;
}
private void setTrimPathEnd(float trimPathEnd) {
mTrimPathEnd = trimPathEnd;
}
private float getTrimPathOffset() {
return mTrimPathOffset;
}
private void setTrimPathOffset(float trimPathOffset) {
mTrimPathOffset = trimPathOffset;
}
private void setStrokeMiterlimit(float limit) {
mStrokeMiterlimit = limit;
}
private float getStrokeMiterlimit() {
return mStrokeMiterlimit;
}
private void setStrokeGradient(long gradientPtr) {
mStrokeGradient = gradientPtr;
}
private void setFillGradient(long gradientPtr) {
mFillGradient = gradientPtr;
}
private void setFillType(int fillType) {
mFillType = fillType;
}
private int getFillType() {
return mFillType;
}
}
static class VGroup_Delegate extends VNativeObject {
// This constants need to be kept in sync with their definitions in VectorDrawable.Group
private static final int ROTATE_INDEX = 0;
private static final int PIVOT_X_INDEX = 1;
private static final int PIVOT_Y_INDEX = 2;
private static final int SCALE_X_INDEX = 3;
private static final int SCALE_Y_INDEX = 4;
private static final int TRANSLATE_X_INDEX = 5;
private static final int TRANSLATE_Y_INDEX = 6;
public Consumer<Float> getPropertySetter(int propertyIdx) {
switch (propertyIdx) {
case ROTATE_INDEX:
return this::setRotation;
case PIVOT_X_INDEX:
return this::setPivotX;
case PIVOT_Y_INDEX:
return this::setPivotY;
case SCALE_X_INDEX:
return this::setScaleX;
case SCALE_Y_INDEX:
return this::setScaleY;
case TRANSLATE_X_INDEX:
return this::setTranslateX;
case TRANSLATE_Y_INDEX:
return this::setTranslateY;
}
assert false : ("Invalid VGroup_Delegate property index " + propertyIdx);
return t -> {};
}
/////////////////////////////////////////////////////
// Variables below need to be copied (deep copy if applicable) for mutation.
final ArrayList<Object> mChildren = new ArrayList<>();
// mStackedMatrix is only used temporarily when drawing, it combines all
// the parents' local matrices with the current one.
private final Matrix mStackedMatrix = new Matrix();
// mLocalMatrix is updated based on the update of transformation information,
// either parsed from the XML or by animation.
private final Matrix mLocalMatrix = new Matrix();
private float mRotate = 0;
private float mPivotX = 0;
private float mPivotY = 0;
private float mScaleX = 1;
private float mScaleY = 1;
private float mTranslateX = 0;
private float mTranslateY = 0;
private int mChangingConfigurations;
private String mGroupName = null;
private VGroup_Delegate(VGroup_Delegate copy, ArrayMap<String, Object> targetsMap) {
mRotate = copy.mRotate;
mPivotX = copy.mPivotX;
mPivotY = copy.mPivotY;
mScaleX = copy.mScaleX;
mScaleY = copy.mScaleY;
mTranslateX = copy.mTranslateX;
mTranslateY = copy.mTranslateY;
mGroupName = copy.mGroupName;
mChangingConfigurations = copy.mChangingConfigurations;
if (mGroupName != null) {
targetsMap.put(mGroupName, this);
}
mLocalMatrix.set(copy.mLocalMatrix);
}
private VGroup_Delegate() {
}
private void updateLocalMatrix() {
// The order we apply is the same as the
// RenderNode.cpp::applyViewPropertyTransforms().
mLocalMatrix.reset();
mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
mLocalMatrix.postScale(mScaleX, mScaleY);
mLocalMatrix.postRotate(mRotate, 0, 0);
mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
}
/* Setters and Getters, used by animator from AnimatedVectorDrawable. */
private float getRotation() {
return mRotate;
}
private void setRotation(float rotation) {
if (rotation != mRotate) {
mRotate = rotation;
updateLocalMatrix();
}
}
private float getPivotX() {
return mPivotX;
}
private void setPivotX(float pivotX) {
if (pivotX != mPivotX) {
mPivotX = pivotX;
updateLocalMatrix();
}
}
private float getPivotY() {
return mPivotY;
}
private void setPivotY(float pivotY) {
if (pivotY != mPivotY) {
mPivotY = pivotY;
updateLocalMatrix();
}
}
private float getScaleX() {
return mScaleX;
}
private void setScaleX(float scaleX) {
if (scaleX != mScaleX) {
mScaleX = scaleX;
updateLocalMatrix();
}
}
private float getScaleY() {
return mScaleY;
}
private void setScaleY(float scaleY) {
if (scaleY != mScaleY) {
mScaleY = scaleY;
updateLocalMatrix();
}
}
private float getTranslateX() {
return mTranslateX;
}
private void setTranslateX(float translateX) {
if (translateX != mTranslateX) {
mTranslateX = translateX;
updateLocalMatrix();
}
}
private float getTranslateY() {
return mTranslateY;
}
private void setTranslateY(float translateY) {
if (translateY != mTranslateY) {
mTranslateY = translateY;
updateLocalMatrix();
}
}
@Override
public void setName(String name) {
mGroupName = name;
}
@Override
protected void dispose() {
mChildren.stream().filter(child -> child instanceof VNativeObject).forEach(child
-> {
VNativeObject nativeObject = (VNativeObject) child;
if (nativeObject.mNativePtr != 0) {
sPathManager.removeJavaReferenceFor(nativeObject.mNativePtr);
nativeObject.mNativePtr = 0;
}
nativeObject.dispose();
});
mChildren.clear();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
}
}
public static class VPath_Delegate extends VNativeObject {
protected PathParser_Delegate.PathDataNode[] mNodes = null;
String mPathName;
int mChangingConfigurations;
public VPath_Delegate() {
// Empty constructor.
}
public VPath_Delegate(VPath_Delegate copy) {
mPathName = copy.mPathName;
mChangingConfigurations = copy.mChangingConfigurations;
mNodes = copy.mNodes != null ? PathParser_Delegate.deepCopyNodes(copy.mNodes) : null;
}
public void toPath(Path path) {
path.reset();
if (mNodes != null) {
PathParser_Delegate.PathDataNode.nodesToPath(mNodes,
Path_Delegate.getDelegate(path.mNativePath));
}
}
@Override
public void setName(String name) {
mPathName = name;
}
public boolean isClipPath() {
return false;
}
private void setPathData(PathParser_Delegate.PathDataNode[] nodes) {
if (!PathParser_Delegate.canMorph(mNodes, nodes)) {
// This should not happen in the middle of animation.
mNodes = PathParser_Delegate.deepCopyNodes(nodes);
} else {
PathParser_Delegate.updateNodes(mNodes, nodes);
}
}
@Override
void dispose() {
mNodes = null;
}
}
static class VPathRenderer_Delegate extends VNativeObject {
/* Right now the internal data structure is organized as a tree.
* Each node can be a group node, or a path.
* A group node can have groups or paths as children, but a path node has
* no children.
* One example can be:
* Root Group
* / | \
* Group Path Group
* / \ |
* Path Path Path
*
*/
// Variables that only used temporarily inside the draw() call, so there
// is no need for deep copying.
private final Path mPath;
private final Path mRenderPath;
private final Matrix mFinalPathMatrix = new Matrix();
private final long mRootGroupPtr;
private float mViewportWidth = 0;
private float mViewportHeight = 0;
private float mRootAlpha = 1.0f;
private Paint mStrokePaint;
private Paint mFillPaint;
private PathMeasure mPathMeasure;
private boolean mAntiAlias = true;
private VPathRenderer_Delegate(long rootGroupPtr) {
mRootGroupPtr = rootGroupPtr;
mPath = new Path();
mRenderPath = new Path();
}
private VPathRenderer_Delegate(VPathRenderer_Delegate rendererToCopy,
long rootGroupPtr) {
this(rootGroupPtr);
mViewportWidth = rendererToCopy.mViewportWidth;
mViewportHeight = rendererToCopy.mViewportHeight;
mRootAlpha = rendererToCopy.mRootAlpha;
}
private float getRootAlpha() {
return mRootAlpha;
}
void setRootAlpha(float alpha) {
mRootAlpha = alpha;
}
private void drawGroupTree(VGroup_Delegate currentGroup, Matrix currentMatrix,
long canvasPtr, int w, int h, long filterPtr) {
// Calculate current group's matrix by preConcat the parent's and
// and the current one on the top of the stack.
// Basically the Mfinal = Mviewport * M0 * M1 * M2;
// Mi the local matrix at level i of the group tree.
currentGroup.mStackedMatrix.set(currentMatrix);
currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
// Save the current clip information, which is local to this group.
Canvas_Delegate.nSave(canvasPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
// Draw the group tree in the same order as the XML file.
for (int i = 0; i < currentGroup.mChildren.size(); i++) {
Object child = currentGroup.mChildren.get(i);
if (child instanceof VGroup_Delegate) {
VGroup_Delegate childGroup = (VGroup_Delegate) child;
drawGroupTree(childGroup, currentGroup.mStackedMatrix,
canvasPtr, w, h, filterPtr);
} else if (child instanceof VPath_Delegate) {
VPath_Delegate childPath = (VPath_Delegate) child;
drawPath(currentGroup, childPath, canvasPtr, w, h, filterPtr);
}
}
Canvas_Delegate.nRestore(canvasPtr);
}
public void draw(long canvasPtr, long filterPtr, int w, int h) {
// Traverse the tree in pre-order to draw.
drawGroupTree(VNativeObject.getDelegate(mRootGroupPtr), Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr);
}
private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr,
int w,
int h,
long filterPtr) {
final float scaleX = w / mViewportWidth;
final float scaleY = h / mViewportHeight;
final float minScale = Math.min(scaleX, scaleY);
final Matrix groupStackedMatrix = VGroup.mStackedMatrix;
mFinalPathMatrix.set(groupStackedMatrix);
mFinalPathMatrix.postScale(scaleX, scaleY);
final float matrixScale = getMatrixScale(groupStackedMatrix);
if (matrixScale == 0) {
// When either x or y is scaled to 0, we don't need to draw anything.
return;
}
VPath.toPath(mPath);
final Path path = mPath;
mRenderPath.reset();
if (VPath.isClipPath()) {
mRenderPath.setFillType(FillType.WINDING);
mRenderPath.addPath(path, mFinalPathMatrix);
Canvas_Delegate.nClipPath(canvasPtr, mRenderPath.mNativePath, Op
.INTERSECT.nativeInt);
} else {
VFullPath_Delegate fullPath = (VFullPath_Delegate) VPath;
if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
if (mPathMeasure == null) {
mPathMeasure = new PathMeasure();
}
mPathMeasure.setPath(mPath, false);
float len = mPathMeasure.getLength();
start = start * len;
end = end * len;
path.reset();
if (start > end) {
mPathMeasure.getSegment(start, len, path, true);
mPathMeasure.getSegment(0f, end, path, true);
} else {
mPathMeasure.getSegment(start, end, path, true);
}
path.rLineTo(0, 0); // fix bug in measure
}
mRenderPath.addPath(path, mFinalPathMatrix);
if (fullPath.mFillColor != Color.TRANSPARENT) {
if (mFillPaint == null) {
mFillPaint = new Paint();
mFillPaint.setStyle(Style.FILL);
mFillPaint.setAntiAlias(mAntiAlias);
}
final Paint fillPaint = mFillPaint;
fillPaint.setColor(applyAlpha(applyAlpha(fullPath.mFillColor, fullPath
.mFillAlpha), getRootAlpha()));
Paint_Delegate fillPaintDelegate = Paint_Delegate.getDelegate(fillPaint
.getNativeInstance());
// mFillPaint can not be null at this point so we will have a delegate
assert fillPaintDelegate != null;
fillPaintDelegate.setColorFilter(filterPtr);
Shader_Delegate shaderDelegate =
Shader_Delegate.getDelegate(fullPath.mFillGradient);
if (shaderDelegate != null) {
// If there is a shader, apply the local transformation to make sure
// the gradient is transformed to match the viewport
shaderDelegate.setLocalMatrix(mFinalPathMatrix.native_instance);
shaderDelegate.setAlpha(fullPath.mFillAlpha);
}
fillPaintDelegate.setShader(fullPath.mFillGradient);
Path_Delegate.nSetFillType(mRenderPath.mNativePath, fullPath.mFillType);
BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
.getNativeInstance());
if (shaderDelegate != null) {
// Remove the local matrix
shaderDelegate.setLocalMatrix(0);
}
}
if (fullPath.mStrokeColor != Color.TRANSPARENT) {
if (mStrokePaint == null) {
mStrokePaint = new Paint();
mStrokePaint.setStyle(Style.STROKE);
mStrokePaint.setAntiAlias(mAntiAlias);
}
final Paint strokePaint = mStrokePaint;
if (fullPath.mStrokeLineJoin != null) {
strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
}
if (fullPath.mStrokeLineCap != null) {
strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
}
strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
strokePaint.setColor(applyAlpha(applyAlpha(fullPath.mStrokeColor, fullPath
.mStrokeAlpha), getRootAlpha()));
Paint_Delegate strokePaintDelegate = Paint_Delegate.getDelegate(strokePaint
.getNativeInstance());
// mStrokePaint can not be null at this point so we will have a delegate
assert strokePaintDelegate != null;
strokePaintDelegate.setColorFilter(filterPtr);
final float finalStrokeScale = minScale * matrixScale;
strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
Shader_Delegate strokeShaderDelegate =
Shader_Delegate.getDelegate(fullPath.mStrokeGradient);
if (strokeShaderDelegate != null) {
strokeShaderDelegate.setAlpha(fullPath.mStrokeAlpha);
}
strokePaintDelegate.setShader(fullPath.mStrokeGradient);
BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
.getNativeInstance());
}
}
}
private float getMatrixScale(Matrix groupStackedMatrix) {
// Given unit vectors A = (0, 1) and B = (1, 0).
// After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
// Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
// which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
// If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
//
// For non-skew case, which is most of the cases, matrix scale is computing exactly the
// scale on x and y axis, and take the minimal of these two.
// For skew case, an unit square will mapped to a parallelogram. And this function will
// return the minimal height of the 2 bases.
float[] unitVectors = new float[]{0, 1, 1, 0};
groupStackedMatrix.mapVectors(unitVectors);
float scaleX = MathUtils.mag(unitVectors[0], unitVectors[1]);
float scaleY = MathUtils.mag(unitVectors[2], unitVectors[3]);
float crossProduct = MathUtils.cross(unitVectors[0], unitVectors[1],
unitVectors[2], unitVectors[3]);
float maxScale = MathUtils.max(scaleX, scaleY);
float matrixScale = 0;
if (maxScale > 0) {
matrixScale = MathUtils.abs(crossProduct) / maxScale;
}
if (DBG_VECTOR_DRAWABLE) {
Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
}
return matrixScale;
}
private void setAntiAlias(boolean aa) {
mAntiAlias = aa;
}
@Override
public void setName(String name) {
}
@Override
protected void finalize() throws Throwable {
// The mRootGroupPtr is not explicitly freed by anything in the VectorDrawable so we
// need to free it here.
VNativeObject nativeObject = sPathManager.getDelegate(mRootGroupPtr);
sPathManager.removeJavaReferenceFor(mRootGroupPtr);
assert nativeObject != null;
nativeObject.dispose();
super.finalize();
}
}
}