refactor gradients on Android merge #86 into branch

This commit is contained in:
Horcrux
2016-07-26 17:17:48 +08:00
parent 9888a1adf2
commit 28fa1cd091
25 changed files with 463 additions and 203 deletions

View File

@@ -48,6 +48,7 @@ class ClipImage extends Component{
width="90%" width="90%"
height="90%" height="90%"
href={require('../image.jpg')} href={require('../image.jpg')}
opacity="0.6"
clipPath="url(#clip)" clipPath="url(#clip)"
/> />
<Text <Text

View File

@@ -9,6 +9,13 @@
package com.horcrux.svg; package com.horcrux.svg;
import android.graphics.Color;
import android.graphics.RectF;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.LinearGradient;
import android.graphics.Shader;
import android.graphics.Matrix;
import android.util.Log; import android.util.Log;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -29,7 +36,10 @@ import java.util.regex.Pattern;
* @return a {@code float[]} if converted successfully, or {@code null} if {@param value} was * @return a {@code float[]} if converted successfully, or {@code null} if {@param value} was
* {@code null}. * {@code null}.
*/ */
/*package*/ static @Nullable float[] toFloatArray(@Nullable ReadableArray value) { /*package*/
static
@Nullable
float[] toFloatArray(@Nullable ReadableArray value) {
if (value != null) { if (value != null) {
float[] result = new float[value.size()]; float[] result = new float[value.size()];
toFloatArray(value, result); toFloatArray(value, result);
@@ -45,10 +55,11 @@ import java.util.regex.Pattern;
* will not be converted. * will not be converted.
* *
* @param value input array * @param value input array
* @param into output array * @param into output array
* @return number of items copied from input to the output array * @return number of items copied from input to the output array
*/ */
/*package*/ static int toFloatArray(ReadableArray value, float[] into) { /*package*/
static int toFloatArray(ReadableArray value, float[] into) {
int length = value.size() > into.length ? into.length : value.size(); int length = value.size() > into.length ? into.length : value.size();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
into[i] = (float) value.getDouble(i); into[i] = (float) value.getDouble(i);
@@ -62,18 +73,20 @@ import java.util.regex.Pattern;
* Converts percentage string into actual based on a relative number * Converts percentage string into actual based on a relative number
* *
* @param percentage percentage string * @param percentage percentage string
* @param relative relative number * @param relative relative number
* @param offset offset number * @param offset offset number
* @return actual float based on relative number * @return actual float based on relative number
*/ */
/*package*/ static float fromPercentageToFloat(String percentage, float relative, float offset, float scale) { /*package*/
Matcher matched = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$").matcher(percentage); static float fromPercentageToFloat(String percentage, float relative, float offset, float scale) {
Matcher matched = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$").matcher(percentage);
if (matched.matches()) { if (matched.matches()) {
return Float.valueOf(matched.group(1)) / 100 * relative + offset; return Float.valueOf(matched.group(1)) / 100 * relative + offset;
} else { } else {
return Float.valueOf(percentage) * scale; return Float.valueOf(percentage) * scale;
} }
} }
/** /**
* Judge given string is a percentage-like string or not. * Judge given string is a percentage-like string or not.
* *
@@ -81,8 +94,101 @@ import java.util.regex.Pattern;
* @return string is percentage-like or not. * @return string is percentage-like or not.
*/ */
/*package*/ static boolean isPercentage(String string) { /*package*/
static boolean isPercentage(String string) {
Pattern pattern = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$"); Pattern pattern = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$");
return pattern.matcher(string).matches(); return pattern.matcher(string).matches();
} }
/**
*
*/
/*package*/ static class RNSVGBrush {
private GradientType mType = GradientType.LINEAR_GRADIENT;
private ReadableArray mPoints;
private ReadableArray mColors;
public RNSVGBrush(GradientType type, ReadableArray points, ReadableArray colors) {
mType = type;
mPoints = points;
mColors = colors;
}
public enum GradientType {
LINEAR_GRADIENT(0),
RADIAL_GRADIENT(1);
GradientType(int ni) {
nativeInt = ni;
}
final int nativeInt;
}
private static void parseGradientStops(ReadableArray value, int stopsCount, float[] stops, int[] stopsColors) {
int startStops = value.size() - stopsCount;
for (int i = 0; i < stopsCount; i++) {
stops[i] = (float) value.getDouble(startStops + i);
stopsColors[i] = Color.argb(
(int) (value.getDouble(i * 4 + 3) * 255),
(int) (value.getDouble(i * 4) * 255),
(int) (value.getDouble(i * 4 + 1) * 255),
(int) (value.getDouble(i * 4 + 2) * 255));
}
}
public void setupPaint(Paint paint, RectF box, float mScale) {
float height = box.height();
float width = box.width();
float midX = box.centerX();
float midY = box.centerY();
float offsetX = (midX - width / 2);
float offsetY = (midY - height / 2);
int stopsCount = mColors.size() / 5;
int[] stopsColors = new int[stopsCount];
float[] stops = new float[stopsCount];
parseGradientStops(mColors, stopsCount, stops, stopsColors);
if (mType == GradientType.LINEAR_GRADIENT) {
float x1 = PropHelper.fromPercentageToFloat(mPoints.getString(0), width, offsetX, mScale);
float y1 = PropHelper.fromPercentageToFloat(mPoints.getString(1), height, offsetY, mScale);
float x2 = PropHelper.fromPercentageToFloat(mPoints.getString(2), width, offsetX, mScale);
float y2 = PropHelper.fromPercentageToFloat(mPoints.getString(3), height, offsetY, mScale);
paint.setShader(
new LinearGradient(
x1,
y1,
x2,
y2,
stopsColors,
stops,
Shader.TileMode.CLAMP));
} else {
float rx = PropHelper.fromPercentageToFloat(mPoints.getString(2), width, 0f, mScale);
float ry = PropHelper.fromPercentageToFloat(mPoints.getString(3), height, 0f, mScale);
float cx = PropHelper.fromPercentageToFloat(mPoints.getString(4), width, offsetX, mScale);
float cy = PropHelper.fromPercentageToFloat(mPoints.getString(5), height, offsetY, mScale) / (ry / rx);
// TODO: support focus point.
//float fx = PropHelper.fromPercentageToFloat(mPoints.getString(0), width, offsetX) * mScale;
//float fy = PropHelper.fromPercentageToFloat(mPoints.getString(1), height, offsetY) * mScale / (ry / rx);
Shader radialGradient = new RadialGradient(
cx,
cy,
rx,
stopsColors,
stops,
Shader.TileMode.CLAMP
);
Matrix radialMatrix = new Matrix();
radialMatrix.preScale(1f, ry / rx);
radialGradient.setLocalMatrix(radialMatrix);
paint.setShader(radialGradient);
}
}
}
} }

View File

@@ -9,14 +9,8 @@
package com.horcrux.svg; package com.horcrux.svg;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point; import android.graphics.Point;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.uimanager.annotations.ReactProp;
/** /**
* Shadow node for virtual RNSVGClipPath view * Shadow node for virtual RNSVGClipPath view

View File

@@ -0,0 +1,45 @@
/**
* 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.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.view.View;
import com.facebook.react.bridge.ReadableArray;
/**
* Shadow node for virtual Definition type views
*/
public class RNSVGDefinitionShadowNode extends RNSVGVirtualNode {
public void draw(Canvas canvas, Paint paint, float opacity) {}
@Override
public boolean isResponsible() {
return false;
}
protected Path getPath(Canvas canvas, Paint paint) {
return null;
}
public int hitTest(Point point, View view) {
return -1;
}
public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList, boolean inherited) {}
public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList) {}
public void resetProperties() {}
}

View File

@@ -11,20 +11,11 @@ package com.horcrux.svg;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.view.View;
import android.util.Log;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.ArrayList;
/** /**
* Shadow node for virtual RNSVGPath view * Shadow node for virtual RNSVGPath view
*/ */
public class RNSVGDefsShadowNode extends RNSVGVirtualNode { public class RNSVGDefsShadowNode extends RNSVGDefinitionShadowNode {
@Override @Override
public void draw(Canvas canvas, Paint paint, float opacity) { public void draw(Canvas canvas, Paint paint, float opacity) {
@@ -38,27 +29,4 @@ public class RNSVGDefsShadowNode extends RNSVGVirtualNode {
restoreCanvas(canvas, count); restoreCanvas(canvas, count);
} }
@Override
public boolean isResponsible() {
return false;
}
protected Path getPath(Canvas canvas, Paint paint) {
return new Path();
}
@Override
public int hitTest(Point point, View view) {
return -1;
}
@Override
public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList, boolean inherited) {}
@Override
public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList) {}
@Override
public void resetProperties() {}
} }

View File

@@ -23,11 +23,8 @@ import com.facebook.react.uimanager.annotations.ReactProp;
public class RNSVGEllipseShadowNode extends RNSVGPathShadowNode { public class RNSVGEllipseShadowNode extends RNSVGPathShadowNode {
private String mCx; private String mCx;
private String mCy; private String mCy;
private String mRx; private String mRx;
private String mRy; private String mRy;
@ReactProp(name = "cx") @ReactProp(name = "cx")

View File

@@ -19,8 +19,6 @@ import android.view.ViewGroup;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import java.util.ArrayList;
/** /**
* Shadow node for virtual RNSVGGroup view * Shadow node for virtual RNSVGGroup view
*/ */

View File

@@ -17,7 +17,8 @@ import android.graphics.PorterDuff;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri; import android.net.Uri;
import android.util.Log; import android.util.Log;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.common.logging.FLog;
import com.facebook.common.executors.CallerThreadExecutor; import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference; import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource; import com.facebook.datasource.DataSource;
@@ -25,12 +26,14 @@ import com.facebook.common.logging.FLog;
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline; import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.ReactConstants; import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
@@ -43,7 +46,6 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
private String mW; private String mW;
private String mH; private String mH;
private Uri mUri; private Uri mUri;
private Bitmap mBitmap;
private boolean mLoading; private boolean mLoading;
@ReactProp(name = "x") @ReactProp(name = "x")
@@ -85,40 +87,30 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
} }
@Override @Override
public void draw(Canvas canvas, Paint paint, float opacity) { public void draw(final Canvas canvas, final Paint paint, final float opacity) {
RNSVGSvgViewShadowNode node = getSvgShadowNode(); final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(mUri).build();
if (mBitmap != null) { final boolean inMemoryCache = Fresco.getImagePipeline().isInBitmapMemoryCache(request);
int count = saveAndSetupCanvas(canvas);
clip(canvas, paint);
float x = PropHelper.fromPercentageToFloat(mX, mCanvasWidth, 0, mScale);
float y = PropHelper.fromPercentageToFloat(mY, mCanvasHeight, 0, mScale);
float w = PropHelper.fromPercentageToFloat(mW, mCanvasWidth, 0, mScale);
float h = PropHelper.fromPercentageToFloat(mH, mCanvasHeight, 0, mScale);
canvas.drawBitmap(mBitmap, null, new Rect((int) x, (int) y, (int) (x + w), (int)(y + h)), null);
restoreCanvas(canvas, count); if (inMemoryCache) {
markUpdateSeen(); tryRender(request, canvas, paint, opacity * mOpacity);
} else if (!mLoading) { } else if (!mLoading) {
mLoading = true; loadBitmap(request, canvas, paint);
loadBitmap(canvas, paint, node);
} }
} }
public void loadBitmap(final Canvas canvas, final Paint paint, final RNSVGSvgViewShadowNode node) { private void loadBitmap(@Nonnull final ImageRequest request, @Nonnull final Canvas canvas, @Nonnull final Paint paint) {
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(mUri).build(); final DataSource<CloseableReference<CloseableImage>> dataSource
= Fresco.getImagePipeline().fetchDecodedImage(request, getThemedContext());
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>>
dataSource = imagePipeline.fetchDecodedImage(request, getThemedContext());
dataSource.subscribe(new BaseBitmapDataSubscriber() { dataSource.subscribe(new BaseBitmapDataSubscriber() {
@Override @Override
public void onNewResultImpl(@Nullable Bitmap bitmap) { public void onNewResultImpl(@Nullable Bitmap bitmap) {
if (bitmap != null) { if (bitmap != null) {
mBitmap = bitmap;
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
paint.reset(); paint.reset();
node.drawChildren(canvas, paint); mLoading = false;
getSvgShadowNode().drawChildren(canvas, paint);
} }
} }
@@ -126,9 +118,51 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
public void onFailureImpl(DataSource dataSource) { public void onFailureImpl(DataSource dataSource) {
// No cleanup required here. // No cleanup required here.
// TODO: more details about this failure // TODO: more details about this failure
FLog.w(ReactConstants.TAG, "RNSVG: load Image load failed!:"); mLoading = false;
FLog.w(ReactConstants.TAG, dataSource.getFailureCause(), "RNSVG: fetchDecodedImage failed!");
} }
}, },
CallerThreadExecutor.getInstance()); UiThreadImmediateExecutorService.getInstance()
);
}
private void doRender(@Nonnull final Canvas canvas, @Nonnull final Paint paint, @Nonnull final Bitmap bitmap, final float opacity) {
final int count = saveAndSetupCanvas(canvas);
clip(canvas, paint);
float x = PropHelper.fromPercentageToFloat(mX, mCanvasWidth, 0, mScale);
float y = PropHelper.fromPercentageToFloat(mY, mCanvasHeight, 0, mScale);
float w = PropHelper.fromPercentageToFloat(mW, mCanvasWidth, 0, mScale);
float h = PropHelper.fromPercentageToFloat(mH, mCanvasHeight, 0, mScale);
Paint alphaPaint = new Paint();
alphaPaint.setAlpha((int) (opacity * 255));
canvas.drawBitmap(bitmap, null, new Rect((int) x, (int) y, (int) (x + w), (int) (y + h)), alphaPaint);
restoreCanvas(canvas, count);
}
private void tryRender(@Nonnull final ImageRequest request, @Nonnull final Canvas canvas, @Nonnull final Paint paint, final float opacity) {
final DataSource<CloseableReference<CloseableImage>> dataSource
= Fresco.getImagePipeline().fetchImageFromBitmapCache(request, getThemedContext());
try {
final CloseableReference<CloseableImage> imageReference = dataSource.getResult();
if (imageReference != null) {
try {
if (imageReference.get() instanceof CloseableBitmap) {
final Bitmap bitmap = ((CloseableBitmap) imageReference.get()).getUnderlyingBitmap();
if (bitmap != null) {
doRender(canvas, paint, bitmap, opacity);
}
}
} finally {
CloseableReference.closeSafely(imageReference);
}
}
} finally {
dataSource.close();
}
} }
} }

View File

@@ -12,12 +12,6 @@ package com.horcrux.svg;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import javax.annotation.Nullable; import javax.annotation.Nullable;

View File

@@ -0,0 +1,81 @@
/**
* 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 com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.ArrayList;
/**
* Shadow node for virtual LinearGradient definition view
*/
public class RNSVGLinearGradientShadowNode extends RNSVGDefinitionShadowNode {
private String mX1;
private String mY1;
private String mX2;
private String mY2;
private ReadableArray mGradient;
@ReactProp(name = "x1")
public void setX1(String x1) {
mX1 = x1;
markUpdated();
}
@ReactProp(name = "y1")
public void setCx(String y1) {
mY1 = y1;
markUpdated();
}
@ReactProp(name = "x2")
public void setX2(String x2) {
mX2 = x2;
markUpdated();
}
@ReactProp(name = "y2")
public void setY2(String y2) {
mY2 = y2;
markUpdated();
}
@ReactProp(name = "gradient")
public void setGradient(ReadableArray gradient) {
mGradient = gradient;
markUpdated();
}
@Override
protected void saveDefinition() {
if (mName != null) {
WritableArray points = new JavaOnlyArray();
points.pushString(mX1);
points.pushString(mY1);
points.pushString(mX2);
points.pushString(mY2);
PropHelper.RNSVGBrush brush = new PropHelper.RNSVGBrush(PropHelper.RNSVGBrush.GradientType.LINEAR_GRADIENT, points, mGradient);
getSvgShadowNode().defineBrush(brush, mName);
}
}
@Override
protected void removeDefinition() {
if (mName != null) {
getSvgShadowNode().removeBrush(mName);
}
}
}

View File

@@ -17,18 +17,11 @@ import android.graphics.DashPathEffect;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.Matrix;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import com.facebook.common.logging.FLog; import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
@@ -78,7 +71,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
private ArrayList<String> mChangedList; private ArrayList<String> mChangedList;
private ArrayList<Object> mOriginProperties; private ArrayList<Object> mOriginProperties;
protected ReadableArray mPropList = new JavaOnlyArray();; protected ReadableArray mPropList = new JavaOnlyArray();
@ReactProp(name = "d") @ReactProp(name = "d")
public void setPath(@Nullable ReadableArray shapePath) { public void setPath(@Nullable ReadableArray shapePath) {
@@ -281,10 +274,11 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
* if the stroke should be drawn, {@code false} if not. * if the stroke should be drawn, {@code false} if not.
*/ */
protected boolean setupStrokePaint(Paint paint, float opacity, @Nullable RectF box) { protected boolean setupStrokePaint(Paint paint, float opacity, @Nullable RectF box) {
paint.reset();
if (mStrokeWidth == 0 || mStroke == null || mStroke.size() == 0) { if (mStrokeWidth == 0 || mStroke == null || mStroke.size() == 0) {
return false; return false;
} }
paint.reset();
paint.setFlags(Paint.ANTI_ALIAS_FLAG); paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(mStrokeLinecap); paint.setStrokeCap(mStrokeLinecap);
@@ -310,62 +304,13 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
(int) (colors.getDouble(1) * 255), (int) (colors.getDouble(1) * 255),
(int) (colors.getDouble(2) * 255), (int) (colors.getDouble(2) * 255),
(int) (colors.getDouble(3) * 255)); (int) (colors.getDouble(3) * 255));
} else if (colorType == 1 || colorType == 2) { } else if (colorType == 1) {
if (box == null) { if (box == null) {
box = new RectF(); box = new RectF();
mPath.computeBounds(box, true); mPath.computeBounds(box, true);
} }
PropHelper.RNSVGBrush brush = getSvgShadowNode().getDefinedBrush(colors.getString(1));
int startColorsPosition = colorType == 1 ? 5 : 7; brush.setupPaint(paint, box, mScale);
int stopsCount = (colors.size() - startColorsPosition) / 5;
int [] stopsColors = new int [stopsCount];
float [] stops = new float[stopsCount];
float height = box.height();
float width = box.width();
float midX = box.centerX();
float midY = box.centerY();
float offsetX = (midX - width / 2);
float offsetY = (midY - height / 2);
parseGradientStops(colors, stopsCount, stops, stopsColors, startColorsPosition);
if (colorType == 1) {
float x1 = PropHelper.fromPercentageToFloat(colors.getString(1), width, offsetX, mScale);
float y1 = PropHelper.fromPercentageToFloat(colors.getString(2), height, offsetY, mScale);
float x2 = PropHelper.fromPercentageToFloat(colors.getString(3), width, offsetX, mScale);
float y2 = PropHelper.fromPercentageToFloat(colors.getString(4), height, offsetY, mScale);
paint.setShader(
new LinearGradient(
x1,
y1,
x2,
y2,
stopsColors,
stops,
Shader.TileMode.CLAMP));
} else {
float rx = PropHelper.fromPercentageToFloat(colors.getString(3), width, 0f, mScale);
float ry = PropHelper.fromPercentageToFloat(colors.getString(4), height, 0f, mScale);
float cx = PropHelper.fromPercentageToFloat(colors.getString(5), width, offsetX, mScale);
float cy = PropHelper.fromPercentageToFloat(colors.getString(6), height, offsetY, mScale) / (ry / rx);
// TODO: do not support focus point.
//float fx = PropHelper.fromPercentageToFloat(colors.getString(1), width, offsetX) * mScale;
//float fy = PropHelper.fromPercentageToFloat(colors.getString(2), height, offsetY) * mScale / (ry / rx);
Shader radialGradient = new RadialGradient(
cx,
cy,
rx,
stopsColors,
stops,
Shader.TileMode.CLAMP
);
Matrix radialMatrix = new Matrix();
radialMatrix.preScale(1f, ry / rx);
radialGradient.setLocalMatrix(radialMatrix);
paint.setShader(radialGradient);
}
} else { } else {
// TODO: Support pattern. // TODO: Support pattern.
FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!"); FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
@@ -455,7 +400,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
for (int i = 0, size = mergeList.size(); i < size; i++) { for (int i = 0, size = mergeList.size(); i < size; i++) {
try { try {
String fieldName = mergeList.getString(i); String fieldName = mergeList.getString(i);
Field field = target.getClass().getField(fieldName); Field field = getClass().getField(fieldName);
Object value = field.get(target); Object value = field.get(target);
if (inherited) { if (inherited) {
@@ -464,9 +409,9 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
propList.pushString(fieldName); propList.pushString(fieldName);
} }
} else { } else {
field.set(this, value); mOriginProperties.add(field.get(this));
mOriginProperties.add(value);
mChangedList.add(fieldName); mChangedList.add(fieldName);
field.set(this, value);
} }
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
@@ -497,6 +442,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
} }
mChangedList = null; mChangedList = null;
mOriginProperties = null;
} }
} }

View File

@@ -0,0 +1,94 @@
/**
* 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 com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.uimanager.annotations.ReactProp;
/**
* Shadow node for virtual LinearGradient definition view
*/
public class RNSVGRadialGradientShadowNode extends RNSVGDefinitionShadowNode {
private String mFx;
private String mFy;
private String mRx;
private String mRy;
private String mCx;
private String mCy;
private ReadableArray mGradient;
@ReactProp(name = "fx")
public void setFX(String fx) {
mFx = fx;
markUpdated();
}
@ReactProp(name = "fy")
public void setFy(String fy) {
mFy = fy;
markUpdated();
}
@ReactProp(name = "rx")
public void setRx(String rx) {
mRx = rx;
markUpdated();
}
@ReactProp(name = "ry")
public void setRy(String ry) {
mRy = ry;
markUpdated();
}
@ReactProp(name = "cx")
public void setCx(String cx) {
mCx = cx;
markUpdated();
}
@ReactProp(name = "cy")
public void setCy(String cy) {
mCy = cy;
markUpdated();
}
@ReactProp(name = "gradient")
public void setGradient(ReadableArray gradient) {
mGradient = gradient;
markUpdated();
}
@Override
protected void saveDefinition() {
if (mName != null) {
WritableArray points = new JavaOnlyArray();
points.pushString(mFx);
points.pushString(mFy);
points.pushString(mRx);
points.pushString(mRy);
points.pushString(mCx);
points.pushString(mCy);
PropHelper.RNSVGBrush brush = new PropHelper.RNSVGBrush(PropHelper.RNSVGBrush.GradientType.RADIAL_GRADIENT, points, mGradient);
getSvgShadowNode().defineBrush(brush, mName);
}
}
@Override
protected void removeDefinition() {
if (mName != null) {
getSvgShadowNode().removeBrush(mName);
}
}
}

View File

@@ -12,12 +12,7 @@ package com.horcrux.svg;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import javax.annotation.Nullable; import javax.annotation.Nullable;

View File

@@ -10,24 +10,9 @@
package com.horcrux.svg; package com.horcrux.svg;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.common.SystemClock;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.TouchEvent;
import com.facebook.react.uimanager.events.TouchEventType;
import javax.annotation.Nullable;
// NativeGestureUtil.notifyNativeGestureStarted // NativeGestureUtil.notifyNativeGestureStarted
/** /**

View File

@@ -11,7 +11,6 @@ package com.horcrux.svg;
import android.view.ViewGroup; import android.view.ViewGroup;
//import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewGroupManager;
@@ -34,6 +33,8 @@ public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
/* package */ static final String CLASS_DEFS = "RNSVGDefs"; /* package */ static final String CLASS_DEFS = "RNSVGDefs";
/* package */ static final String CLASS_USE = "RNSVGUse"; /* package */ static final String CLASS_USE = "RNSVGUse";
/* package */ static final String CLASS_VIEW_BOX = "RNSVGViewBox"; /* package */ static final String CLASS_VIEW_BOX = "RNSVGViewBox";
/* package */ static final String CLASS_LINEAR_GRADIENT = "RNSVGLinearGradient";
/* package */ static final String CLASS_RADIAL_GRADIENT = "RNSVGRadialGradient";
private final String mClassName; private final String mClassName;
@@ -87,6 +88,14 @@ public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
return new RNSVGRenderableViewManager(CLASS_VIEW_BOX); return new RNSVGRenderableViewManager(CLASS_VIEW_BOX);
} }
public static RNSVGRenderableViewManager createRNSVGLinearGradientManager() {
return new RNSVGRenderableViewManager(CLASS_LINEAR_GRADIENT);
}
public static RNSVGRenderableViewManager createRNSVGRadialGradientManager() {
return new RNSVGRenderableViewManager(CLASS_RADIAL_GRADIENT);
}
private RNSVGRenderableViewManager(String className) { private RNSVGRenderableViewManager(String className) {
mClassName = className; mClassName = className;
} }
@@ -135,6 +144,12 @@ public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
case CLASS_VIEW_BOX: case CLASS_VIEW_BOX:
mVirtualNode = new RNSVGViewBoxShadowNode(); mVirtualNode = new RNSVGViewBoxShadowNode();
break; break;
case CLASS_LINEAR_GRADIENT:
mVirtualNode = new RNSVGLinearGradientShadowNode();
break;
case CLASS_RADIAL_GRADIENT:
mVirtualNode = new RNSVGRadialGradientShadowNode();
break;
default: default:
throw new IllegalStateException("Unexpected type " + mClassName); throw new IllegalStateException("Unexpected type " + mClassName);
} }
@@ -170,6 +185,10 @@ public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
return RNSVGUseShadowNode.class; return RNSVGUseShadowNode.class;
case CLASS_VIEW_BOX: case CLASS_VIEW_BOX:
return RNSVGViewBoxShadowNode.class; return RNSVGViewBoxShadowNode.class;
case CLASS_LINEAR_GRADIENT:
return RNSVGLinearGradientShadowNode.class;
case CLASS_RADIAL_GRADIENT:
return RNSVGRadialGradientShadowNode.class;
default: default:
throw new IllegalStateException("Unexpected type " + mClassName); throw new IllegalStateException("Unexpected type " + mClassName);
} }

View File

@@ -19,21 +19,15 @@ import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.facebook.infer.annotation.Assertions; import com.facebook.infer.annotation.Assertions;
import com.facebook.react.common.SystemClock; import com.facebook.react.common.SystemClock;
import com.facebook.react.touch.OnInterceptTouchEventListener;
import com.facebook.react.touch.ReactInterceptingViewGroup;
import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.TouchTargetHelper;
import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.TouchEvent; import com.facebook.react.uimanager.events.TouchEvent;
import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper; import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper;
import com.facebook.react.uimanager.events.TouchEventType; import com.facebook.react.uimanager.events.TouchEventType;
import com.facebook.react.views.view.ReactClippingViewGroup;
import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.NativeGestureUtil;
// NativeGestureUtil.notifyNativeGestureStarted // NativeGestureUtil.notifyNativeGestureStarted
/** /**

View File

@@ -11,9 +11,7 @@ package com.horcrux.svg;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point; import android.graphics.Point;
import android.util.Log; import android.util.Log;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -30,10 +28,9 @@ import java.util.Map;
public class RNSVGSvgViewShadowNode extends LayoutShadowNode { public class RNSVGSvgViewShadowNode extends LayoutShadowNode {
private boolean mResponsible = false; private boolean mResponsible = false;
private static final Map<String, RNSVGVirtualNode> mDefinedClipPaths = new HashMap<>(); private static final Map<String, RNSVGVirtualNode> mDefinedClipPaths = new HashMap<>();
private static final Map<String, RNSVGVirtualNode> mDefinedTemplates = new HashMap<>(); private static final Map<String, RNSVGVirtualNode> mDefinedTemplates = new HashMap<>();
private static final Map<String, PropHelper.RNSVGBrush> mDefinedBrushes = new HashMap<>();
@Override @Override
public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) { public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) {
@@ -54,7 +51,17 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode {
return bitmap; return bitmap;
} }
public void drawChildren(Canvas canvas, Paint paint) { /**
* Draw all of the child nodes of this root node
*
* This method is synchronized since
* {@link com.horcrux.svg.RNSVGImageShadowNode#loadImage(ImageRequest, Canvas, Paint)} calls it
* asynchronously after images have loaded and are ready to be drawn.
*
* @param canvas
* @param paint
*/
public synchronized void drawChildren(Canvas canvas, Paint paint) {
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i); RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
child.setupDimensions(canvas); child.setupDimensions(canvas);
@@ -114,4 +121,15 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode {
return mDefinedTemplates.get(templateRef); return mDefinedTemplates.get(templateRef);
} }
public void defineBrush(PropHelper.RNSVGBrush brush, String brushRef) {
mDefinedBrushes.put(brushRef, brush);
}
public void removeBrush(String brushRef) {
mDefinedBrushes.remove(brushRef);
}
public PropHelper.RNSVGBrush getDefinedBrush(String brushRef) {
return mDefinedBrushes.get(brushRef);
}
} }

View File

@@ -13,6 +13,7 @@ import javax.annotation.Nullable;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Point; import android.graphics.Point;
@@ -22,7 +23,6 @@ import android.graphics.Typeface;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
@@ -102,6 +102,9 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
if (mPath == null) { if (mPath == null) {
canvas.drawText(text, 0, -paint.ascent(), paint); canvas.drawText(text, 0, -paint.ascent(), paint);
} else { } else {
Matrix matrix = new Matrix();
matrix.setTranslate(0, -paint.getTextSize() * 1.1f);
mPath.transform(matrix);
canvas.drawTextOnPath(text, mPath, 0, -paint.ascent(), paint); canvas.drawTextOnPath(text, mPath, 0, -paint.ascent(), paint);
} }
} }
@@ -186,6 +189,7 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
if (setupFillPaint(paint, 1.0f, getBox(paint, text))) { if (setupFillPaint(paint, 1.0f, getBox(paint, text))) {
applyTextPropertiesToPaint(paint); applyTextPropertiesToPaint(paint);
paint.getTextPath(text, 0, text.length(), 0, -paint.ascent(), path); paint.getTextPath(text, 0, text.length(), 0, -paint.ascent(), path);
path.transform(mMatrix);
} }
return path; return path;

View File

@@ -11,15 +11,12 @@ package com.horcrux.svg;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path;
import android.util.Log; import android.util.Log;
import com.facebook.common.logging.FLog; import com.facebook.common.logging.FLog;
import com.facebook.react.common.ReactConstants; import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.ArrayList;
/** /**
* Shadow node for virtual RNSVGPath view * Shadow node for virtual RNSVGPath view
*/ */

View File

@@ -10,9 +10,7 @@
package com.horcrux.svg; package com.horcrux.svg;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path;
import android.util.Log; import android.util.Log;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;

View File

@@ -11,33 +11,23 @@ package com.horcrux.svg;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region; import android.graphics.Region;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ReactShadowNode;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* Base class for RNSVGView virtual nodes: {@link RNSVGGroupShadowNode}, {@link RNSVGPathShadowNode} and * Base class for RNSVGView virtual nodes: {@link RNSVGGroupShadowNode}, {@link RNSVGPathShadowNode} and
@@ -76,7 +66,7 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode {
private RNSVGSvgViewShadowNode mSvgShadowNode; private RNSVGSvgViewShadowNode mSvgShadowNode;
public RNSVGVirtualNode() { public RNSVGVirtualNode() {
mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density; mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density;
} }
public abstract void draw(Canvas canvas, Paint paint, float opacity); public abstract void draw(Canvas canvas, Paint paint, float opacity);

View File

@@ -37,6 +37,8 @@ public class RNSvgPackage implements ReactPackage {
RNSVGRenderableViewManager.createRNSVGDefsViewManager(), RNSVGRenderableViewManager.createRNSVGDefsViewManager(),
RNSVGRenderableViewManager.createRNSVGUseViewManager(), RNSVGRenderableViewManager.createRNSVGUseViewManager(),
RNSVGRenderableViewManager.createRNSVGViewBoxViewManager(), RNSVGRenderableViewManager.createRNSVGViewBoxViewManager(),
RNSVGRenderableViewManager.createRNSVGLinearGradientManager(),
RNSVGRenderableViewManager.createRNSVGRadialGradientManager(),
new RNSVGSvgViewManager()); new RNSVGSvgViewManager());
} }

View File

@@ -17,6 +17,6 @@
- (void) drawLinearGradient:(CGContextRef)context; - (void) drawLinearGradient:(CGContextRef)context;
- (void)drawRidialGradient:(CGContextRef)context; - (void) drawRidialGradient:(CGContextRef)context;
@end @end

View File

@@ -313,10 +313,10 @@
10ABC7381D43982B006CCF6E /* RNSVGVBMOS.h */, 10ABC7381D43982B006CCF6E /* RNSVGVBMOS.h */,
10ABC7371D439779006CCF6E /* RNSVGCGFCRule.h */, 10ABC7371D439779006CCF6E /* RNSVGCGFCRule.h */,
1039D2AE1CE72F27001E90A8 /* RNSVGPercentageConverter.h */, 1039D2AE1CE72F27001E90A8 /* RNSVGPercentageConverter.h */,
1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */,
1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */, 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */,
1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */, 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */,
1039D29E1CE72177001E90A8 /* RNSVGCGFloatArray.h */, 1039D29E1CE72177001E90A8 /* RNSVGCGFloatArray.h */,
1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */,
); );
name = Utils; name = Utils;
sourceTree = "<group>"; sourceTree = "<group>";