diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm index 99104f0ec8fc..49a2b2d42a18 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm @@ -31,7 +31,6 @@ - (instancetype)initWithFrame:(CGRect)frame _props = defaultProps; _imageView = [RCTUIImageViewAnimated new]; - _imageView.clipsToBounds = YES; _imageView.contentMode = RCTContentModeFromImageResizeMode(defaultProps->resizeMode); _imageView.layer.minificationFilter = kCAFilterTrilinear; _imageView.layer.magnificationFilter = kCAFilterTrilinear; diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 5571b5350bff..a1e82d07969c 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -995,6 +995,7 @@ - (void)invalidateLayer } const auto borderMetrics = _props->resolveBorderMetrics(_layoutMetrics); + BOOL const borderRadiiCircular = areBorderRadiiCircular(borderMetrics.borderRadii); // Stage 1. Shadow Path BOOL const layerHasShadow = layer.shadowOpacity > 0 && CGColorGetAlpha(layer.shadowColor) > 0; @@ -1044,7 +1045,7 @@ - (void)invalidateLayer const bool useCoreAnimationBorderRendering = borderMetrics.borderColors.isUniform() && borderMetrics.borderWidths.isUniform() && borderMetrics.borderStyles.isUniform() && borderMetrics.borderStyles.left == BorderStyle::Solid && - areBorderRadiiCircular(borderMetrics.borderRadii) && + borderRadiiCircular && ( // iOS draws borders in front of the content whereas CSS draws them behind // the content. For this reason, only use iOS border drawing when clipping @@ -1126,7 +1127,7 @@ - (void)invalidateLayer _outlineLayer.frame = CGRectInset( layer.bounds, -_props->outlineOffset - _props->outlineWidth, -_props->outlineOffset - _props->outlineWidth); - if (areBorderRadiiCircular(borderMetrics.borderRadii) && borderMetrics.borderRadii.topLeft.horizontal == 0) { + if (borderRadiiCircular && borderMetrics.borderRadii.topLeft.horizontal == 0) { UIColor *outlineColor = RCTUIColorFromSharedColor(_props->outlineColor); _outlineLayer.borderWidth = _props->outlineWidth; _outlineLayer.borderColor = outlineColor.CGColor; @@ -1302,7 +1303,7 @@ - (void)invalidateLayer if (self.currentContainerView.clipsToBounds) { BOOL clipToPaddingBox = ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox(); if (!clipToPaddingBox) { - if (areBorderRadiiCircular(borderMetrics.borderRadii)) { + if (borderRadiiCircular) { self.currentContainerView.layer.cornerRadius = borderMetrics.borderRadii.topLeft.horizontal; } else { CALayer *maskLayer = @@ -1312,20 +1313,26 @@ - (void)invalidateLayer self.currentContainerView.layer.mask = maskLayer; } - for (UIView *subview in self.currentContainerView.subviews) { - if ([subview isKindOfClass:[UIImageView class]]) { - RCTCornerInsets cornerInsets = RCTGetCornerInsets( - RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), - RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths)); - - // If the subview is an image view, we have to apply the mask directly to the image view's layer, - // otherwise the image might overflow with the border radius. - subview.layer.mask = [self createMaskLayer:subview.bounds cornerInsets:cornerInsets]; + if (!borderRadiiCircular && + (borderMetrics.borderColors.left || borderMetrics.borderColors.right || borderMetrics.borderColors.top || + borderMetrics.borderColors.bottom)) { + for (UIView *subview in self.currentContainerView.subviews) { + if ([subview isKindOfClass:[UIImageView class]]) { + RCTCornerInsets cornerInsets = RCTGetCornerInsets( + RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), + RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths)); + + // If the subview is an image view, we have to apply the mask directly to the image view's layer, + // otherwise the image might overflow with the border radius. + // Applying a mask is rendering wise expensive so we only apply it when needed, which is only + // for none uniform border radii (that are actually visible by color). + subview.layer.mask = [self createMaskLayer:subview.bounds cornerInsets:cornerInsets]; + } } } } else if ( !borderMetrics.borderWidths.isUniform() || borderMetrics.borderWidths.left != 0 || - !areBorderRadiiCircular(borderMetrics.borderRadii)) { + !borderRadiiCircular) { CALayer *maskLayer = [self createMaskLayer:RCTCGRectFromRect(_layoutMetrics.getPaddingFrame()) cornerInsets:RCTGetCornerInsets( RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 55d4a8e7da26..b9cb2f08b609 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -1051,6 +1051,13 @@ const styles = StyleSheet.create({ flex: { flex: 1, }, + imageWithUniformButNoneCircularBorderRadius: { + width: 200, + height: 100, + borderRadius: '50%', + borderWidth: 4, + borderColor: 'blue', + }, imageWithBorderRadius: { borderRadius: 5, }, @@ -1420,6 +1427,10 @@ exports.examples = [ render: function (): React.Node { return ( +