From 69534a33733ae83b6977002d219a6cc4ec127578 Mon Sep 17 00:00:00 2001 From: mattds Date: Wed, 13 Apr 2016 07:41:36 -0700 Subject: [PATCH] Added support for Image corner radii in Android Summary:Split out from PR #4252 - kmagiera I've made the changes to how the radii arrays are allocated, is the approach I've taken correct? also it looks like ImageStylePropTypes are needed so I left them in for the moment. I suppose this pull request will only be valid if iOS supports image corner radii, but at least it's here if/when needed. Attached an image of how it handles the existing case: ![screen shot 2016-01-08 at 4 21 25 pm](https://cloud.githubusercontent.com/assets/1407729/12200126/d3caceac-b625-11e5-8281-06274732a281.png) Closes https://github.com/facebook/react-native/pull/5197 Differential Revision: D3138725 Pulled By: mkonicek fb-gh-sync-id: df772fd07fe85386ae4c681f9e79a19d2316d38b fbshipit-source-id: df772fd07fe85386ae4c681f9e79a19d2316d38b --- Libraries/Image/ImageStylePropTypes.js | 6 ++ .../java/com/facebook/react/views/image/BUCK | 1 + .../react/views/image/ReactImageManager.java | 23 ++++- .../react/views/image/ReactImageView.java | 87 ++++++++++++++++--- 4 files changed, 101 insertions(+), 16 deletions(-) diff --git a/Libraries/Image/ImageStylePropTypes.js b/Libraries/Image/ImageStylePropTypes.js index 159079e0d..d6fbb6404 100644 --- a/Libraries/Image/ImageStylePropTypes.js +++ b/Libraries/Image/ImageStylePropTypes.js @@ -55,6 +55,12 @@ var ImageStylePropTypes = { * @platform android */ overlayColor: ReactPropTypes.string, + + // Android-Specific styles + borderTopLeftRadius: ReactPropTypes.number, + borderTopRightRadius: ReactPropTypes.number, + borderBottomLeftRadius: ReactPropTypes.number, + borderBottomRightRadius: ReactPropTypes.number, }; module.exports = ImageStylePropTypes; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK index abafb1b59..4ab1fc6f5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK @@ -6,6 +6,7 @@ android_library( deps = [ react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), + react_native_target('java/com/facebook/csslayout:csslayout'), react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), react_native_dep('libraries/fresco/fresco-react-native:fbcore'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java index 6ebd2e8c8..eaee5053a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java @@ -16,10 +16,13 @@ import java.util.Map; import android.graphics.Color; import android.graphics.PorterDuff.Mode; +import com.facebook.csslayout.CSSConstants; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.annotations.ReactPropGroup; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewProps; @@ -106,9 +109,23 @@ public class ReactImageManager extends SimpleViewManager { view.setBorderWidth(borderWidth); } - @ReactProp(name = "borderRadius") - public void setBorderRadius(ReactImageView view, float borderRadius) { - view.setBorderRadius(borderRadius); + @ReactPropGroup(names = { + ViewProps.BORDER_RADIUS, + ViewProps.BORDER_TOP_LEFT_RADIUS, + ViewProps.BORDER_TOP_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_LEFT_RADIUS + }, defaultFloat = CSSConstants.UNDEFINED) + public void setBorderRadius(ReactImageView view, int index, float borderRadius) { + if (!CSSConstants.isUndefined(borderRadius)) { + borderRadius = PixelUtil.toPixelFromDIP(borderRadius); + } + + if (index == 0) { + view.setBorderRadius(borderRadius); + } else { + view.setBorderRadius(borderRadius, index - 1); + } } @ReactProp(name = ViewProps.RESIZE_MODE) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index c45c0403b..7096fc3ac 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -18,6 +18,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; @@ -26,6 +27,8 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import com.facebook.common.util.UriUtil; +import com.facebook.csslayout.CSSConstants; +import com.facebook.csslayout.FloatUtil; import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; import com.facebook.drawee.controller.BaseControllerListener; import com.facebook.drawee.controller.ControllerListener; @@ -48,6 +51,8 @@ import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.EventDispatcher; +import java.util.Arrays; + /** * Wrapper class around Fresco's GenericDraweeView, enabling persisting props across multiple view * update and consistent processing of both static and network images. @@ -56,6 +61,8 @@ public class ReactImageView extends GenericDraweeView { public static final int REMOTE_IMAGE_FADE_DURATION_MS = 300; + private static float[] sComputedCornerRadii = new float[4]; + /* * Implementation note re rounded corners: * @@ -72,7 +79,7 @@ public class ReactImageView extends GenericDraweeView { private class RoundedCornerPostprocessor extends BasePostprocessor { - float getRadius(Bitmap source) { + void getRadii(Bitmap source, float[] computedCornerRadii, float[] mappedRadii) { ScalingUtils.getTransform( sMatrix, new Rect(0, 0, source.getWidth(), source.getHeight()), @@ -82,13 +89,29 @@ public class ReactImageView extends GenericDraweeView { 0.0f, mScaleType); sMatrix.invert(sInverse); - return sInverse.mapRadius(mBorderRadius); + + mappedRadii[0] = sInverse.mapRadius(computedCornerRadii[0]); + mappedRadii[1] = mappedRadii[0]; + + mappedRadii[2] = sInverse.mapRadius(computedCornerRadii[1]); + mappedRadii[3] = mappedRadii[2]; + + mappedRadii[4] = sInverse.mapRadius(computedCornerRadii[2]); + mappedRadii[5] = mappedRadii[4]; + + mappedRadii[6] = sInverse.mapRadius(computedCornerRadii[3]); + mappedRadii[7] = mappedRadii[6]; } @Override public void process(Bitmap output, Bitmap source) { + cornerRadii(sComputedCornerRadii); + output.setHasAlpha(true); - if (mBorderRadius < 0.01f) { + if (FloatUtil.floatsEqual(sComputedCornerRadii[0], 0f) && + FloatUtil.floatsEqual(sComputedCornerRadii[1], 0f) && + FloatUtil.floatsEqual(sComputedCornerRadii[2], 0f) && + FloatUtil.floatsEqual(sComputedCornerRadii[3], 0f)) { super.process(output, source); return; } @@ -96,12 +119,19 @@ public class ReactImageView extends GenericDraweeView { paint.setAntiAlias(true); paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); Canvas canvas = new Canvas(output); - float radius = getRadius(source); - canvas.drawRoundRect( + + float[] radii = new float[8]; + + getRadii(source, sComputedCornerRadii, radii); + + Path pathForBorderRadius = new Path(); + + pathForBorderRadius.addRoundRect( new RectF(0, 0, source.getWidth(), source.getHeight()), - radius, - radius, - paint); + radii, + Path.Direction.CW); + + canvas.drawPath(pathForBorderRadius, paint); } } @@ -110,7 +140,8 @@ public class ReactImageView extends GenericDraweeView { private int mBorderColor; private int mOverlayColor; private float mBorderWidth; - private float mBorderRadius; + private float mBorderRadius = CSSConstants.UNDEFINED; + private @Nullable float[] mBorderCornerRadii; private ScalingUtils.ScaleType mScaleType; private boolean mIsDirty; private boolean mIsLocalImage; @@ -198,8 +229,22 @@ public class ReactImageView extends GenericDraweeView { } public void setBorderRadius(float borderRadius) { - mBorderRadius = PixelUtil.toPixelFromDIP(borderRadius); - mIsDirty = true; + if (!FloatUtil.floatsEqual(mBorderRadius, borderRadius)) { + mBorderRadius = borderRadius; + mIsDirty = true; + } + } + + public void setBorderRadius(float borderRadius, int position) { + if (mBorderCornerRadii == null) { + mBorderCornerRadii = new float[4]; + Arrays.fill(mBorderCornerRadii, CSSConstants.UNDEFINED); + } + + if (!FloatUtil.floatsEqual(mBorderCornerRadii[position], borderRadius)) { + mBorderCornerRadii[position] = borderRadius; + mIsDirty = true; + } } public void setScaleType(ScalingUtils.ScaleType scaleType) { @@ -250,6 +295,15 @@ public class ReactImageView extends GenericDraweeView { // no worth marking as dirty if it already rendered.. } + private void cornerRadii(float[] computedCorners) { + float defaultBorderRadius = !CSSConstants.isUndefined(mBorderRadius) ? mBorderRadius : 0; + + computedCorners[0] = mBorderCornerRadii != null && !CSSConstants.isUndefined(mBorderCornerRadii[0]) ? mBorderCornerRadii[0] : defaultBorderRadius; + computedCorners[1] = mBorderCornerRadii != null && !CSSConstants.isUndefined(mBorderCornerRadii[1]) ? mBorderCornerRadii[1] : defaultBorderRadius; + computedCorners[2] = mBorderCornerRadii != null && !CSSConstants.isUndefined(mBorderCornerRadii[2]) ? mBorderCornerRadii[2] : defaultBorderRadius; + computedCorners[3] = mBorderCornerRadii != null && !CSSConstants.isUndefined(mBorderCornerRadii[3]) ? mBorderCornerRadii[3] : defaultBorderRadius; + } + public void maybeUpdateView() { if (!mIsDirty) { return; @@ -271,10 +325,17 @@ public class ReactImageView extends GenericDraweeView { boolean usePostprocessorScaling = mScaleType != ScalingUtils.ScaleType.CENTER_CROP && mScaleType != ScalingUtils.ScaleType.FOCUS_CROP; - float hierarchyRadius = usePostprocessorScaling ? 0 : mBorderRadius; RoundingParams roundingParams = hierarchy.getRoundingParams(); - roundingParams.setCornersRadius(hierarchyRadius); + + if (usePostprocessorScaling) { + roundingParams.setCornersRadius(0); + } else { + cornerRadii(sComputedCornerRadii); + + roundingParams.setCornersRadii(sComputedCornerRadii[0], sComputedCornerRadii[1], sComputedCornerRadii[2], sComputedCornerRadii[3]); + } + roundingParams.setBorder(mBorderColor, mBorderWidth); if (mOverlayColor != Color.TRANSPARENT) { roundingParams.setOverlayColor(mOverlayColor);