Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native animation, ClipPath spec conformance, Pattern element, Mask element, cache invalidation #757

Merged
merged 26 commits into from
Sep 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f3ea54c
Use TextureView instead of Bitmap in SvgView on Android
msand Feb 23, 2018
fbd6591
Experiment to allow animating fillOpacity of Rect using native driver.
msand Feb 24, 2018
3a59dfb
Simplify shadow node lookup and drawing after update transaction.
msand Mar 4, 2018
a3c9aa2
Add remaining properties to view managers.
msand Mar 4, 2018
7deabf8
Add RenderableView, fix names. Allow percentages in nested SVG (iOS).
msand Mar 17, 2018
0eb501d
Alternative approach without TextureView.
msand Mar 18, 2018
3879c90
Support native animation of fill and stroke
msand Aug 17, 2018
34d4051
Fix early predicate.
msand Aug 18, 2018
211afec
Fix null exception.
msand Aug 19, 2018
e98b0fe
Merge branch 'master' into NativeAnimation
msand Aug 19, 2018
41134b8
Improve width and height calculation
msand Aug 19, 2018
0de4213
Rename regex to be namespaced
msand Aug 19, 2018
ce602c1
Fix dynamic null handling
msand Aug 22, 2018
d0a9cba
Fix caching of text
msand Aug 24, 2018
8e0420f
[android] Fix caching of text
msand Aug 24, 2018
f1f0e2f
[ios] Fix clipping of images
msand Aug 25, 2018
a1097b8
Fix spec conformance of clipping path with multiple child elements.
msand Aug 31, 2018
11a5752
[ios] Fix clipped bounds calculation for gradients
msand Sep 1, 2018
7bb1e74
[android] Fix clipping path on Android < Kitkat (API 19)
msand Sep 1, 2018
fc63635
Implement basic support for Pattern element
msand Sep 2, 2018
46307ec
Implement basic support for Mask element
msand Sep 11, 2018
b88cba8
[iOS] Fix transform in mask handling
msand Sep 11, 2018
926bb71
[iOS] Fix invalidation of text elements
msand Sep 12, 2018
c0f51d8
[iOS] More extensive invalidation logic
msand Sep 12, 2018
7e42a99
Clear cached data in tree when changing properties on root svg element.
msand Sep 12, 2018
c39b326
[android] Only render once per frame when animating several svg nodes.
msand Sep 15, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions android/src/main/java/com/horcrux/svg/Brush.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

package com.horcrux.svg;

import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
Expand All @@ -23,19 +26,29 @@
import com.facebook.react.common.ReactConstants;

class Brush {
private BrushType mType = BrushType.LINEAR_GRADIENT;
private BrushType mType;
private final ReadableArray mPoints;
private ReadableArray mColors;
private final boolean mUseObjectBoundingBox;
private boolean mUseContentObjectBoundingBox;
private Matrix mMatrix;
private Rect mUserSpaceBoundingBox;
PatternShadowNode mPattern;

Brush(BrushType type, ReadableArray points, BrushUnits units) {
mType = type;
mPoints = points;
mUseObjectBoundingBox = units == BrushUnits.OBJECT_BOUNDING_BOX;
}

void setContentUnits(BrushUnits units) {
mUseContentObjectBoundingBox = units == BrushUnits.OBJECT_BOUNDING_BOX;
}

void setPattern(PatternShadowNode pattern) {
mPattern = pattern;
}

enum BrushType {
LINEAR_GRADIENT(0),
RADIAL_GRADIENT(1),
Expand Down Expand Up @@ -105,6 +118,35 @@ void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float opacity)
float offsetX = rect.left;
float offsetY = rect.top;

if (mType == BrushType.PATTERN) {
double x = PropHelper.fromRelative(mPoints.getString(0), width, offsetX, scale, paint.getTextSize());
double y = PropHelper.fromRelative(mPoints.getString(1), height, offsetY, scale, paint.getTextSize());
double w = PropHelper.fromRelative(mPoints.getString(2), width, offsetX, scale, paint.getTextSize());
double h = PropHelper.fromRelative(mPoints.getString(3), height, offsetY, scale, paint.getTextSize());

RectF vbRect = mPattern.getViewBox();
RectF eRect = new RectF((float)x, (float)y, (float)w, (float)h);
Matrix mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mPattern.mAlign, mPattern.mMeetOrSlice);

Bitmap bitmap = Bitmap.createBitmap(
(int) w,
(int) h,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.concat(mViewBoxMatrix);
mPattern.draw(canvas, new Paint(), opacity);

Matrix patternMatrix = new Matrix();
if (mMatrix != null) {
patternMatrix.preConcat(mMatrix);
}

BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
bitmapShader.setLocalMatrix(patternMatrix);
paint.setShader(bitmapShader);
return;
}

int stopsCount = mColors.size() / 5;
int[] stopsColors = new int[stopsCount];
float[] stops = new float[stopsCount];
Expand Down Expand Up @@ -170,12 +212,5 @@ void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float opacity)
radialGradient.setLocalMatrix(radialMatrix);
paint.setShader(radialGradient);
}
// else {
// todo: pattern support

//Shader mShader1 = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
//paint.setShader(mShader1);
//bitmap.recycle();
// }
}
}
61 changes: 59 additions & 2 deletions android/src/main/java/com/horcrux/svg/GroupShadowNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@
package com.horcrux.svg;

import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Build;
import android.support.annotation.RequiresApi;

import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ReactShadowNode;
Expand Down Expand Up @@ -79,7 +84,7 @@ public void run(ReactShadowNode lNode) {
}

int count = node.saveAndSetupCanvas(canvas);
node.draw(canvas, paint, opacity * mOpacity);
node.render(canvas, paint, opacity * mOpacity);
RectF r = node.getClientRect();
if (r != null) {
groupRect.union(r);
Expand Down Expand Up @@ -119,14 +124,66 @@ protected Path getPath(final Canvas canvas, final Paint paint) {
traverseChildren(new NodeRunnable() {
public void run(ReactShadowNode node) {
if (node instanceof VirtualNode) {
path.addPath(((VirtualNode)node).getPath(canvas, paint));
VirtualNode n = (VirtualNode)node;
Matrix transform = n.mMatrix;
path.addPath(n.getPath(canvas, paint), transform);
}
}
});

return path;
}

protected Path getPath(final Canvas canvas, final Paint paint, final Region.Op op) {
final Path path = new Path();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
final Path.Op pop = Path.Op.valueOf(op.name());
traverseChildren(new NodeRunnable() {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void run(ReactShadowNode node) {
if (node instanceof VirtualNode) {
VirtualNode n = (VirtualNode)node;
Matrix transform = n.mMatrix;
Path p2;
if (n instanceof GroupShadowNode) {
p2 = ((GroupShadowNode)n).getPath(canvas, paint, op);
} else {
p2 = n.getPath(canvas, paint);
}
p2.transform(transform);
path.op(p2, pop);
}
}
});
} else {
Rect clipBounds = canvas.getClipBounds();
final Region bounds = new Region(clipBounds);
final Region r = new Region();
traverseChildren(new NodeRunnable() {
public void run(ReactShadowNode node) {
if (node instanceof VirtualNode) {
VirtualNode n = (VirtualNode)node;
Matrix transform = n.mMatrix;
Path p2;
if (n instanceof GroupShadowNode) {
p2 = ((GroupShadowNode)n).getPath(canvas, paint, op);
} else {
p2 = n.getPath(canvas, paint);
}
p2.transform(transform);
Region r2 = new Region();
r2.setPath(p2, bounds);
r.op(r2, op);
}
}
});
path.addPath(r.getBoundaryPath());
}

return path;
}

@Override
public int hitTest(final float[] src) {
if (!mInvertible) {
Expand Down
4 changes: 2 additions & 2 deletions android/src/main/java/com/horcrux/svg/ImageShadowNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ public void setY(String y) {
markUpdated();
}

@ReactProp(name = "width")
@ReactProp(name = "imagewidth")
public void setWidth(String width) {
mW = width;
markUpdated();
}

@ReactProp(name = "height")
@ReactProp(name = "imageheight")
public void seHeight(String height) {
mH = height;
markUpdated();
Expand Down
116 changes: 116 additions & 0 deletions android/src/main/java/com/horcrux/svg/MaskShadowNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright (c) 2015-present, Horcrux.
* All rights reserved.
*
* This source code is licensed under the MIT-style license found in the
* LICENSE file in the root directory of this source tree.
*/


package com.horcrux.svg;

import android.graphics.Matrix;

import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.annotations.ReactProp;

import javax.annotation.Nullable;

/**
* Shadow node for virtual Mask definition view
*/
class MaskShadowNode extends GroupShadowNode {

String mX;
String mY;
String mWidth;
String mHeight;
Brush.BrushUnits mMaskUnits;
Brush.BrushUnits mMaskContentUnits;

private static final float[] sRawMatrix = new float[]{
1, 0, 0,
0, 1, 0,
0, 0, 1
};
private Matrix mMatrix = null;

@ReactProp(name = "x")
public void setX(String x) {
mX = x;
markUpdated();
}

@ReactProp(name = "y")
public void setY(String y) {
mY = y;
markUpdated();
}

@ReactProp(name = "maskwidth")
public void setWidth(String width) {
mWidth = width;
markUpdated();
}

@ReactProp(name = "maskheight")
public void setHeight(String height) {
mHeight = height;
markUpdated();
}

@ReactProp(name = "maskUnits")
public void setMaskUnits(int maskUnits) {
switch (maskUnits) {
case 0:
mMaskUnits = Brush.BrushUnits.OBJECT_BOUNDING_BOX;
break;
case 1:
mMaskUnits = Brush.BrushUnits.USER_SPACE_ON_USE;
break;
}
markUpdated();
}

@ReactProp(name = "maskContentUnits")
public void setMaskContentUnits(int maskContentUnits) {
switch (maskContentUnits) {
case 0:
mMaskContentUnits = Brush.BrushUnits.OBJECT_BOUNDING_BOX;
break;
case 1:
mMaskContentUnits = Brush.BrushUnits.USER_SPACE_ON_USE;
break;
}
markUpdated();
}

@ReactProp(name = "maskTransform")
public void setMaskTransform(@Nullable ReadableArray matrixArray) {
if (matrixArray != null) {
int matrixSize = PropHelper.toMatrixData(matrixArray, sRawMatrix, mScale);
if (matrixSize == 6) {
if (mMatrix == null) {
mMatrix = new Matrix();
}
mMatrix.setValues(sRawMatrix);
} else if (matrixSize != -1) {
FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6");
}
} else {
mMatrix = null;
}

markUpdated();
}

@Override
protected void saveDefinition() {
if (mName != null) {
SvgViewShadowNode svg = getSvgShadowNode();
svg.defineMask(this, mName);
}
}
}
Loading