Skip to content

Commit

Permalink
Anti-alias rounded borders on overflow: hidden views
Browse files Browse the repository at this point in the history
Summary:
When a view is rendered with a combination of `overflow: hidden` and `borderRadius: >0`, the `ReactViewGroup` would apply the border radius using Canvas `clipPath`. In Android graphics, clipPath is not an anti-aliased operation and caused aliasing artifacts.

Changing the method to a bitmask using the `PorterDuff` method results in the same functionality, but with hardware accelerated antialiasing.

#24486

Changelog:
[Android][Change] - Views with overflow: hidden and borderRadius: >0 now render anti-aliased borders.

Reviewed By: javache

Differential Revision: D38914878

fbshipit-source-id: 45ac7e4aece7a76c4216412175e49d3d73b6f391
  • Loading branch information
Harrison Spain authored and facebook-github-bot committed Aug 25, 2022
1 parent 18542b6 commit 7708cdc
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,10 @@ public class ReactFeatureFlags {

/** Temporary flag to allow execution of mount items up to 15ms earlier than normal. */
public static boolean enableEarlyScheduledMountItemExecution = false;

/**
* Use a bitmap mask instead of clipPath for rounding corners so that they are antialiased in
* Android
*/
public static boolean antiAliasRoundedOverflowCorners = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
Expand Down Expand Up @@ -129,6 +132,7 @@ public void onLayoutChange(
private boolean mNeedsOffscreenAlphaCompositing;
private @Nullable ViewGroupDrawingOrderHelper mDrawingOrderHelper;
private @Nullable Path mPath;
private @Nullable Paint mPaint;
private int mLayoutDirection;
private float mBackfaceOpacity;
private String mBackfaceVisibility;
Expand Down Expand Up @@ -159,6 +163,7 @@ private void initView() {
mNeedsOffscreenAlphaCompositing = false;
mDrawingOrderHelper = null;
mPath = null;
mPaint = null;
mLayoutDirection = 0; // set when background is created
mBackfaceOpacity = 1.f;
mBackfaceVisibility = "visible";
Expand Down Expand Up @@ -806,8 +811,16 @@ public Rect getOverflowInset() {
@Override
protected void dispatchDraw(Canvas canvas) {
try {
dispatchOverflowDraw(canvas);
super.dispatchDraw(canvas);
if (ReactFeatureFlags.antiAliasRoundedOverflowCorners && hasRoundedOverflow()) {
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
super.dispatchDraw(canvas);
dispatchOverflowDraw(canvas);
canvas.restoreToCount(saveCount);
} else {
dispatchOverflowDraw(canvas);
super.dispatchDraw(canvas);
}

} catch (NullPointerException | StackOverflowError e) {
// Adding special exception management for StackOverflowError for logging purposes.
// This will be removed in the future.
Expand Down Expand Up @@ -860,7 +873,7 @@ private void dispatchOverflowDraw(Canvas canvas) {

boolean hasClipPath = false;

if (mReactBackgroundDrawable != null) {
if (mReactBackgroundDrawable != null && mReactBackgroundDrawable.hasRoundedBorders()) {
final RectF borderWidth = mReactBackgroundDrawable.getDirectionAwareBorderInsets();

if (borderWidth.top > 0
Expand Down Expand Up @@ -980,7 +993,21 @@ private void dispatchOverflowDraw(Canvas canvas) {
Math.max(bottomLeftBorderRadius - borderWidth.bottom, 0),
},
Path.Direction.CW);
canvas.clipPath(mPath);

if (ReactFeatureFlags.antiAliasRoundedOverflowCorners) {
mPath.setFillType(Path.FillType.INVERSE_WINDING);

if (mPaint == null) {
mPaint = new Paint();
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(getContext().getColor(android.R.color.white));
}

canvas.drawPath(mPath, mPaint);
} else {
canvas.clipPath(mPath);
}
hasClipPath = true;
}
}
Expand All @@ -995,6 +1022,13 @@ private void dispatchOverflowDraw(Canvas canvas) {
}
}

private boolean hasRoundedOverflow() {
return mOverflow != null
&& (mOverflow.equals(ViewProps.HIDDEN.toString())
|| mOverflow.equals(ViewProps.SCROLL.toString()))
&& (mReactBackgroundDrawable != null && mReactBackgroundDrawable.hasRoundedBorders());
}

public void setOpacityIfPossible(float opacity) {
mBackfaceOpacity = opacity;
setBackfaceVisibilityDependantOpacity();
Expand Down

0 comments on commit 7708cdc

Please sign in to comment.