Add preserveAspectRatio prop for Image

Add preserveAspectRatio prop for Image.
Fix touch events for Image on Android.
Fix viewBox slice bug on Android.
This commit is contained in:
Horcrux
2016-08-13 14:03:08 +08:00
parent a4c0c60b7f
commit d1afb78da0
20 changed files with 251 additions and 42 deletions
@@ -12,10 +12,16 @@ package com.horcrux.svg;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.net.Uri;
import android.util.Log;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.common.logging.FLog;
import com.facebook.common.references.CloseableReference;
@@ -45,6 +51,9 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
private String mW;
private String mH;
private Uri mUri;
private float mImageRatio;
private String mAlign;
private int mMeetOrSlice;
private AtomicBoolean mLoading = new AtomicBoolean(false);
@ReactProp(name = "x")
@@ -81,12 +90,29 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
return;
}
mImageRatio = (float)src.getInt("width") / (float)src.getInt("height");
mUri = Uri.parse(uriString);
}
}
@ReactProp(name = "align")
public void setAlign(String align) {
mAlign = align;
markUpdated();
}
@ReactProp(name = "meetOrSlice")
public void setMeetOrSlice(int meetOrSlice) {
mMeetOrSlice = meetOrSlice;
markUpdated();
}
@Override
public void draw(final Canvas canvas, final Paint paint, final float opacity) {
mPath = new Path();
mPath.addRect(new RectF(getRect()), Path.Direction.CW);
if (!mLoading.get()) {
final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(mUri).build();
@@ -140,14 +166,70 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
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);
canvas.concat(mMatrix);
Paint alphaPaint = new Paint();
alphaPaint.setAlpha((int) (opacity * 255));
canvas.drawBitmap(bitmap, null, getRect(), alphaPaint);
// apply viewBox transform on Image render.
Rect rect = getRect();
float rectWidth = (float)rect.width();
float rectHeight = (float)rect.height();
float rectX = (float)rect.left;
float rectY = (float)rect.top;
float rectRatio = rectWidth / rectHeight;
RectF renderRect;
if (mImageRatio == rectRatio) {
renderRect = new RectF(rect);
} else if (mImageRatio < rectRatio) {
renderRect = new RectF(0, 0, (int)(rectHeight * mImageRatio), (int)rectHeight);
} else {
renderRect = new RectF(0, 0, (int)rectWidth, (int)(rectWidth / mImageRatio));
}
RNSVGViewBoxShadowNode viewBox = new RNSVGViewBoxShadowNode();
viewBox.setMinX("0");
viewBox.setMinY("0");
viewBox.setVbWidth(renderRect.width() / mScale + "");
viewBox.setVbHeight(renderRect.height() / mScale + "");
viewBox.setWidth(rectWidth / mScale);
viewBox.setHeight(rectHeight / mScale);
viewBox.setAlign(mAlign);
viewBox.setMeetOrSlice(mMeetOrSlice);
viewBox.setupDimensions(new Rect(0, 0, (int) rectWidth, (int) rectHeight));
Matrix transform = viewBox.getTransform();
transform.mapRect(renderRect);
Matrix translation = new Matrix();
translation.postTranslate(rectX, rectY);
translation.mapRect(renderRect);
Path clip = new Path();
Path clipPath = getClipPath(canvas, paint);
if (clipPath != null) {
// clip by the common area of clipPath and mPath
clip.setFillType(Path.FillType.INVERSE_EVEN_ODD);
Path inverseWindingPath = new Path();
inverseWindingPath.setFillType(Path.FillType.INVERSE_WINDING);
inverseWindingPath.addPath(mPath);
inverseWindingPath.addPath(clipPath);
Path evenOddPath = new Path();
evenOddPath.setFillType(Path.FillType.EVEN_ODD);
evenOddPath.addPath(mPath);
evenOddPath.addPath(clipPath);
canvas.clipPath(evenOddPath, Region.Op.DIFFERENCE);
canvas.clipPath(inverseWindingPath, Region.Op.DIFFERENCE);
} else {
canvas.clipPath(mPath, Region.Op.REPLACE);
}
canvas.drawBitmap(bitmap, null, renderRect, alphaPaint);
restoreCanvas(canvas, count);
markUpdateSeen();
}
@@ -103,7 +103,7 @@ public class RNSVGRectShadowNode extends RNSVGPathShadowNode {
}
path.addRoundRect(new RectF(x, y, x + w, y + h), rx, ry, Path.Direction.CW);
} else {
path.addRect(x, y, x + w, y + h, Path.Direction.CW);
path.addRect(x, y, x + w, y + h, Path.Direction.CW);
}
return path;
}
@@ -10,6 +10,7 @@
package com.horcrux.svg;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import com.facebook.react.bridge.ReadableArray;
@@ -85,8 +86,13 @@ public class RNSVGViewBoxShadowNode extends RNSVGGroupShadowNode {
@Override
public void draw(Canvas canvas, Paint paint, float opacity) {
// based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform
setupDimensions(canvas);
mMatrix = getTransform();
super.draw(canvas, paint, opacity);
}
public Matrix getTransform() {
// based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform
// Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively.
float vbX = PropHelper.fromPercentageToFloat(mMinX, mCanvasWidth, 0, mScale);
@@ -134,7 +140,7 @@ public class RNSVGViewBoxShadowNode extends RNSVGGroupShadowNode {
if (!mAlign.equals("none") && mMeetOrSlice == MOS_MEET) {
scaleX = scaleY = Math.min(scaleX, scaleY);
} else if (!mAlign.equals("none") && mMeetOrSlice == MOS_SLICE) {
scaleX = scaleY = Math.min(scaleX, scaleY);
scaleX = scaleY = Math.max(scaleX, scaleY);
}
// If align contains 'xMid', minus (e-width / scale-x - vb-width) / 2 from transform-x.
@@ -161,10 +167,10 @@ public class RNSVGViewBoxShadowNode extends RNSVGGroupShadowNode {
// The transform applied to content contained by the element is given by
// translate(translate-x, translate-y) scale(scale-x, scale-y).
mMatrix.reset();
mMatrix.postTranslate(-translateX * (mFromSymbol ? scaleX : 1), -translateY * (mFromSymbol ? scaleY : 1));
mMatrix.postScale(scaleX, scaleY);
super.draw(canvas, paint, opacity);
Matrix transform = new Matrix();
transform.postTranslate(-translateX * (mFromSymbol ? scaleX : 1), -translateY * (mFromSymbol ? scaleY : 1));
transform.postScale(scaleX, scaleY);
return transform;
}
@Override
@@ -236,16 +236,21 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode {
}
}
protected void clip(Canvas canvas, Paint paint) {
protected @Nullable Path getClipPath(Canvas canvas, Paint paint) {
Path clip = mClipPath;
if (clip == null && mClipPathRef != null) {
RNSVGVirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPathRef);
clip = node.getPath(canvas, paint);
}
return clip;
}
protected void clip(Canvas canvas, Paint paint) {
Path clip = getClipPath(canvas, paint);
if (clip != null) {
canvas.clipPath(clip, Region.Op.REPLACE);
canvas.saveLayer(0f, 0f, 0f, 0f, paint, Canvas.CLIP_SAVE_FLAG);
}
}
@@ -286,6 +291,13 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode {
mCanvasHeight = canvas.getHeight();
}
protected void setupDimensions(Rect rect) {
mCanvasX = rect.left;
mCanvasY = rect.top;
mCanvasWidth = rect.width();
mCanvasHeight = rect.height();
}
protected void saveDefinition() {
if (mName != null) {
getSvgShadowNode().defineTemplate(this, mName);