diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawBorder.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawBorder.java index fa620ba9d..126479096 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawBorder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawBorder.java @@ -34,6 +34,11 @@ import com.facebook.react.uimanager.Spacing; private static final int BORDER_BOTTOM_COLOR_SET = 1 << 4; private static final int BORDER_PATH_EFFECT_DIRTY = 1 << 5; + // ~0 == 0xFFFFFFFF, all bits set to 1. + private static final int ALL_BITS_SET = ~0; + // 0 == 0x00000000, all bits set to 0. + private static final int ALL_BITS_UNSET = 0; + private float mBorderLeftWidth; private float mBorderTopWidth; private float mBorderRightWidth; @@ -212,14 +217,32 @@ import com.facebook.react.uimanager.Spacing; } /** - * @return true when border colors differs where two border sides meet (e.g. right and top border - * colors differ) + * Quickly determine if all the set border colors are equal. Bitwise AND all the set colors + * together, then OR them all together. If the AND and the OR are the same, then the colors + * are compatible, so return this color. + * + * Used to avoid expensive path creation and expensive calls to canvas.drawPath + * + * @return A compatible border color, or zero if the border colors are not compatible. */ - private boolean isBorderColorDifferentAtIntersectionPoints() { - return isFlagSet(BORDER_TOP_COLOR_SET) || - isFlagSet(BORDER_BOTTOM_COLOR_SET) || - isFlagSet(BORDER_LEFT_COLOR_SET) || - isFlagSet(BORDER_RIGHT_COLOR_SET); + private static int fastBorderCompatibleColorOrZero( + float borderLeft, + float borderTop, + float borderRight, + float borderBottom, + int colorLeft, + int colorTop, + int colorRight, + int colorBottom) { + int andSmear = (borderLeft > 0 ? colorLeft : ALL_BITS_SET) & + (borderTop > 0 ? colorTop : ALL_BITS_SET) & + (borderRight > 0 ? colorRight : ALL_BITS_SET) & + (borderBottom > 0 ? colorBottom : ALL_BITS_SET); + int orSmear = (borderLeft > 0 ? colorLeft : ALL_BITS_UNSET) | + (borderTop > 0 ? colorTop : ALL_BITS_UNSET) | + (borderRight > 0 ? colorRight : ALL_BITS_UNSET) | + (borderBottom > 0 ? colorBottom : ALL_BITS_UNSET); + return andSmear == orSmear ? andSmear : 0; } private void drawRectangularBorders(Canvas canvas) { @@ -246,23 +269,63 @@ import com.facebook.react.uimanager.Spacing; float rightInset = right - borderRight; int rightColor = resolveBorderColor(BORDER_RIGHT_COLOR_SET, mBorderRightColor, defaultColor); - boolean isDrawPathRequired = isBorderColorDifferentAtIntersectionPoints(); - if (isDrawPathRequired && mPathForBorder == null) { - mPathForBorder = new Path(); - } + // Check for fast path to border drawing. + int fastBorderColor = fastBorderCompatibleColorOrZero( + borderLeft, + borderTop, + borderRight, + borderBottom, + leftColor, + topColor, + rightColor, + bottomColor); + if (fastBorderColor != 0) { + // Fast border color draw. + if (Color.alpha(fastBorderColor) != 0) { + // Border color is not transparent. - // Draw center. The border might be opaque, so we need to draw this. - if (Color.alpha(mBackgroundColor) != 0) { - PAINT.setColor(mBackgroundColor); - canvas.drawRect(left, top, right, bottom, PAINT); - } + // Draw center. + if (Color.alpha(mBackgroundColor) != 0) { + PAINT.setColor(mBackgroundColor); + if (Color.alpha(fastBorderColor) == 255) { + // The border will draw over the edges, so only draw the inset background. + canvas.drawRect(leftInset, topInset, rightInset, bottomInset, PAINT); + } else { + // The border is opaque, so we have to draw the entire background color. + canvas.drawRect(left, top, right, bottom, PAINT); + } + } - // Draw top. - if (borderTop != 0 && Color.alpha(topColor) != 0) { - PAINT.setColor(topColor); + PAINT.setColor(fastBorderColor); + if (borderLeft > 0) { + canvas.drawRect(left, top, leftInset, bottom - borderBottom, PAINT); + } + if (borderTop > 0) { + canvas.drawRect(left + borderLeft, top, right, topInset, PAINT); + } + if (borderRight > 0) { + canvas.drawRect(rightInset, top + borderTop, right, bottom, PAINT); + } + if (borderBottom > 0) { + canvas.drawRect(left, bottomInset, right - borderRight, bottom, PAINT); + } + } + } else { + if (mPathForBorder == null) { + mPathForBorder = new Path(); + } - if (isDrawPathRequired) { + // Draw center. Any of the borders might be opaque or transparent, so we need to draw this. + if (Color.alpha(mBackgroundColor) != 0) { + PAINT.setColor(mBackgroundColor); + canvas.drawRect(left, top, right, bottom, PAINT); + } + + // Draw top. + if (borderTop != 0 && Color.alpha(topColor) != 0) { + PAINT.setColor(topColor); updatePathForTopBorder( + mPathForBorder, top, topInset, left, @@ -270,17 +333,13 @@ import com.facebook.react.uimanager.Spacing; right, rightInset); canvas.drawPath(mPathForBorder, PAINT); - } else { - canvas.drawRect(left, top, right, topInset, PAINT); } - } - // Draw bottom. - if (borderBottom != 0 && Color.alpha(bottomColor) != 0) { - PAINT.setColor(bottomColor); - - if (isDrawPathRequired) { + // Draw bottom. + if (borderBottom != 0 && Color.alpha(bottomColor) != 0) { + PAINT.setColor(bottomColor); updatePathForBottomBorder( + mPathForBorder, bottom, bottomInset, left, @@ -288,17 +347,13 @@ import com.facebook.react.uimanager.Spacing; right, rightInset); canvas.drawPath(mPathForBorder, PAINT); - } else { - canvas.drawRect(left, bottomInset, right, bottom, PAINT); } - } - // Draw left. - if (borderLeft != 0 && Color.alpha(leftColor) != 0) { - PAINT.setColor(leftColor); - - if (isDrawPathRequired) { + // Draw left. + if (borderLeft != 0 && Color.alpha(leftColor) != 0) { + PAINT.setColor(leftColor); updatePathForLeftBorder( + mPathForBorder, top, topInset, bottom, @@ -306,17 +361,13 @@ import com.facebook.react.uimanager.Spacing; left, leftInset); canvas.drawPath(mPathForBorder, PAINT); - } else { - canvas.drawRect(left, topInset, leftInset, bottomInset, PAINT); } - } - // Draw right. - if (borderRight != 0 && Color.alpha(rightColor) != 0) { - PAINT.setColor(rightColor); - - if (isDrawPathRequired) { + // Draw right. + if (borderRight != 0 && Color.alpha(rightColor) != 0) { + PAINT.setColor(rightColor); updatePathForRightBorder( + mPathForBorder, top, topInset, bottom, @@ -324,82 +375,72 @@ import com.facebook.react.uimanager.Spacing; right, rightInset); canvas.drawPath(mPathForBorder, PAINT); - } else { - canvas.drawRect(rightInset, topInset, right, bottomInset, PAINT); } } } - private void updatePathForTopBorder( + private static void updatePathForTopBorder( + Path path, float top, float topInset, float left, float leftInset, float right, float rightInset) { - if (mPathForBorder == null) { - mPathForBorder = new Path(); - } - mPathForBorder.reset(); - mPathForBorder.moveTo(left, top); - mPathForBorder.lineTo(leftInset, topInset); - mPathForBorder.lineTo(rightInset, topInset); - mPathForBorder.lineTo(right, top); - mPathForBorder.lineTo(left, top); + path.reset(); + path.moveTo(left, top); + path.lineTo(leftInset, topInset); + path.lineTo(rightInset, topInset); + path.lineTo(right, top); + path.lineTo(left, top); } - private void updatePathForBottomBorder( + private static void updatePathForBottomBorder( + Path path, float bottom, float bottomInset, float left, float leftInset, float right, float rightInset) { - if (mPathForBorder == null) { - mPathForBorder = new Path(); - } - mPathForBorder.reset(); - mPathForBorder.moveTo(left, bottom); - mPathForBorder.lineTo(right, bottom); - mPathForBorder.lineTo(rightInset, bottomInset); - mPathForBorder.lineTo(leftInset, bottomInset); - mPathForBorder.lineTo(left, bottom); + path.reset(); + path.moveTo(left, bottom); + path.lineTo(right, bottom); + path.lineTo(rightInset, bottomInset); + path.lineTo(leftInset, bottomInset); + path.lineTo(left, bottom); } - private void updatePathForLeftBorder( + private static void updatePathForLeftBorder( + Path path, float top, float topInset, float bottom, float bottomInset, float left, float leftInset) { - if (mPathForBorder == null) { - mPathForBorder = new Path(); - } - mPathForBorder.reset(); - mPathForBorder.moveTo(left, top); - mPathForBorder.lineTo(leftInset, topInset); - mPathForBorder.lineTo(leftInset, bottomInset); - mPathForBorder.lineTo(left, bottom); - mPathForBorder.lineTo(left, top); + path.reset(); + path.moveTo(left, top); + path.lineTo(leftInset, topInset); + path.lineTo(leftInset, bottomInset); + path.lineTo(left, bottom); + path.lineTo(left, top); } - private void updatePathForRightBorder( + private static void updatePathForRightBorder( + Path path, float top, float topInset, float bottom, float bottomInset, float right, float rightInset) { - if (mPathForBorder == null) { - mPathForBorder = new Path(); - } - mPathForBorder.reset(); - mPathForBorder.moveTo(right, top); - mPathForBorder.lineTo(right, bottom); - mPathForBorder.lineTo(rightInset, bottomInset); - mPathForBorder.lineTo(rightInset, topInset); - mPathForBorder.lineTo(right, top); + path.reset(); + path.moveTo(right, top); + path.lineTo(right, bottom); + path.lineTo(rightInset, bottomInset); + path.lineTo(rightInset, topInset); + path.lineTo(right, top); } private int resolveBorderColor(int flag, int color, int defaultColor) {