Fixed border smearing issue
Summary: public The iOS border rendering code did not follow the CSS spec in cases where the sum of adjacent border radii was greater than the width of the view, resulting in drawing glitches such as pixel smear and borders appearing stretched or squashed. This diff brings our implementation closer to spec-compliance in these cases. I also fixed a longstanding issue with ghostly diagonal lines appearing at the corners due to antialiasing rounding errors! Fixes https://github.com/facebook/react-native/issues/1572 https://github.com/facebook/react-native/issues/2089 https://github.com/facebook/react-native/issues/4604 Reviewed By: tadeuzagallo Differential Revision: D2811249 fb-gh-sync-id: c3dd2721e0a01a432fa4dc78daa05680595edd08
До Ширина: | Высота: | Размер: 73 KiB После Ширина: | Высота: | Размер: 128 KiB |
До Ширина: | Высота: | Размер: 87 KiB После Ширина: | Высота: | Размер: 147 KiB |
До Ширина: | Высота: | Размер: 277 KiB После Ширина: | Высота: | Размер: 360 KiB |
До Ширина: | Высота: | Размер: 153 KiB После Ширина: | Высота: | Размер: 153 KiB |
|
@ -96,10 +96,22 @@ CGPathRef RCTPathCreateWithRoundedRect(CGRect bounds,
|
|||
const CGFloat maxX = CGRectGetMaxX(bounds);
|
||||
const CGFloat maxY = CGRectGetMaxY(bounds);
|
||||
|
||||
const CGSize topLeft = cornerInsets.topLeft;
|
||||
const CGSize topRight = cornerInsets.topRight;
|
||||
const CGSize bottomLeft = cornerInsets.bottomLeft;
|
||||
const CGSize bottomRight = cornerInsets.bottomRight;
|
||||
const CGSize topLeft = {
|
||||
MAX(0, MIN(cornerInsets.topLeft.width, bounds.size.width - cornerInsets.topRight.width)),
|
||||
MAX(0, MIN(cornerInsets.topLeft.height, bounds.size.height - cornerInsets.bottomLeft.height)),
|
||||
};
|
||||
const CGSize topRight = {
|
||||
MAX(0, MIN(cornerInsets.topRight.width, bounds.size.width - cornerInsets.topLeft.width)),
|
||||
MAX(0, MIN(cornerInsets.topRight.height, bounds.size.height - cornerInsets.bottomRight.height)),
|
||||
};
|
||||
const CGSize bottomLeft = {
|
||||
MAX(0, MIN(cornerInsets.bottomLeft.width, bounds.size.width - cornerInsets.bottomRight.width)),
|
||||
MAX(0, MIN(cornerInsets.bottomLeft.height, bounds.size.height - cornerInsets.topLeft.height)),
|
||||
};
|
||||
const CGSize bottomRight = {
|
||||
MAX(0, MIN(cornerInsets.bottomRight.width, bounds.size.width - cornerInsets.bottomLeft.width)),
|
||||
MAX(0, MIN(cornerInsets.bottomRight.height, bounds.size.height - cornerInsets.topRight.height)),
|
||||
};
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
RCTPathAddEllipticArc(path, transform, (CGPoint){
|
||||
|
@ -174,6 +186,7 @@ static CGContextRef RCTUIGraphicsBeginImageContext(CGSize size, CGColorRef backg
|
|||
}
|
||||
|
||||
static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
|
||||
CGSize viewSize,
|
||||
UIEdgeInsets borderInsets,
|
||||
RCTBorderColors borderColors,
|
||||
CGColorRef backgroundColor,
|
||||
|
@ -182,6 +195,16 @@ static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
|
|||
const BOOL hasCornerRadii = RCTCornerRadiiAreAboveThreshold(cornerRadii);
|
||||
const RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, borderInsets);
|
||||
|
||||
const BOOL makeStretchable =
|
||||
(borderInsets.left + cornerInsets.topLeft.width +
|
||||
borderInsets.right + cornerInsets.bottomRight.width <= viewSize.width) &&
|
||||
(borderInsets.left + cornerInsets.bottomLeft.width +
|
||||
borderInsets.right + cornerInsets.topRight.width <= viewSize.width) &&
|
||||
(borderInsets.top + cornerInsets.topLeft.height +
|
||||
borderInsets.bottom + cornerInsets.bottomRight.height <= viewSize.height) &&
|
||||
(borderInsets.top + cornerInsets.topRight.height +
|
||||
borderInsets.bottom + cornerInsets.bottomLeft.height <= viewSize.height);
|
||||
|
||||
const UIEdgeInsets edgeInsets = (UIEdgeInsets){
|
||||
borderInsets.top + MAX(cornerInsets.topLeft.height, cornerInsets.topRight.height),
|
||||
borderInsets.left + MAX(cornerInsets.topLeft.width, cornerInsets.bottomLeft.width),
|
||||
|
@ -189,11 +212,11 @@ static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
|
|||
borderInsets.right + MAX(cornerInsets.bottomRight.width, cornerInsets.topRight.width)
|
||||
};
|
||||
|
||||
const CGSize size = (CGSize){
|
||||
const CGSize size = makeStretchable ? (CGSize){
|
||||
// 1pt for the middle stretchable area along each axis
|
||||
edgeInsets.left + 1 + edgeInsets.right,
|
||||
edgeInsets.top + 1 + edgeInsets.bottom
|
||||
};
|
||||
} : viewSize;
|
||||
|
||||
CGContextRef ctx = RCTUIGraphicsBeginImageContext(size, backgroundColor, hasCornerRadii, drawToEdge);
|
||||
const CGRect rect = {.size = size};
|
||||
|
@ -270,6 +293,8 @@ static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
|
|||
}
|
||||
}
|
||||
|
||||
CGColorRef currentColor = NULL;
|
||||
|
||||
// RIGHT
|
||||
if (borderInsets.right > 0) {
|
||||
|
||||
|
@ -280,9 +305,8 @@ static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
|
|||
(CGPoint){size.width, size.height},
|
||||
};
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, borderColors.right);
|
||||
currentColor = borderColors.right;
|
||||
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
|
||||
CGContextFillPath(ctx);
|
||||
}
|
||||
|
||||
// BOTTOM
|
||||
|
@ -295,9 +319,12 @@ static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
|
|||
(CGPoint){size.width, size.height},
|
||||
};
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, borderColors.bottom);
|
||||
if (!CGColorEqualToColor(currentColor, borderColors.bottom)) {
|
||||
CGContextSetFillColorWithColor(ctx, currentColor);
|
||||
CGContextFillPath(ctx);
|
||||
currentColor = borderColors.bottom;
|
||||
}
|
||||
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
|
||||
CGContextFillPath(ctx);
|
||||
}
|
||||
|
||||
// LEFT
|
||||
|
@ -310,9 +337,12 @@ static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
|
|||
(CGPoint){0, size.height},
|
||||
};
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, borderColors.left);
|
||||
if (!CGColorEqualToColor(currentColor, borderColors.left)) {
|
||||
CGContextSetFillColorWithColor(ctx, currentColor);
|
||||
CGContextFillPath(ctx);
|
||||
currentColor = borderColors.left;
|
||||
}
|
||||
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
|
||||
CGContextFillPath(ctx);
|
||||
}
|
||||
|
||||
// TOP
|
||||
|
@ -325,10 +355,16 @@ static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
|
|||
(CGPoint){size.width, 0},
|
||||
};
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, borderColors.top);
|
||||
if (!CGColorEqualToColor(currentColor, borderColors.top)) {
|
||||
CGContextSetFillColorWithColor(ctx, currentColor);
|
||||
CGContextFillPath(ctx);
|
||||
currentColor = borderColors.top;
|
||||
}
|
||||
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
|
||||
CGContextFillPath(ctx);
|
||||
}
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, currentColor);
|
||||
CGContextFillPath(ctx);
|
||||
}
|
||||
|
||||
CGPathRelease(insetPath);
|
||||
|
@ -336,7 +372,11 @@ static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
|
|||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return [image resizableImageWithCapInsets:edgeInsets];
|
||||
if (makeStretchable) {
|
||||
image = [image resizableImageWithCapInsets:edgeInsets];
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// Currently, the dashed / dotted implementation only supports a single colour +
|
||||
|
@ -469,7 +509,7 @@ UIImage *RCTGetBorderImage(RCTBorderStyle borderStyle,
|
|||
|
||||
switch (borderStyle) {
|
||||
case RCTBorderStyleSolid:
|
||||
return RCTGetSolidBorderImage(cornerRadii, borderInsets, borderColors, backgroundColor, drawToEdge);
|
||||
return RCTGetSolidBorderImage(cornerRadii, viewSize, borderInsets, borderColors, backgroundColor, drawToEdge);
|
||||
case RCTBorderStyleDashed:
|
||||
case RCTBorderStyleDotted:
|
||||
return RCTGetDashedOrDottedBorderImage(borderStyle, cornerRadii, viewSize, borderInsets, borderColors, backgroundColor, drawToEdge);
|
||||
|
|
|
@ -477,15 +477,26 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
|
|||
|
||||
- (RCTCornerRadii)cornerRadii
|
||||
{
|
||||
const CGRect bounds = self.bounds;
|
||||
const CGFloat maxRadius = MIN(bounds.size.height, bounds.size.width);
|
||||
// Get corner radii
|
||||
const CGFloat radius = MAX(0, _borderRadius);
|
||||
const CGFloat topLeftRadius = _borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius;
|
||||
const CGFloat topRightRadius = _borderTopRightRadius >= 0 ? _borderTopRightRadius : radius;
|
||||
const CGFloat bottomLeftRadius = _borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius;
|
||||
const CGFloat bottomRightRadius = _borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius;
|
||||
|
||||
// Get scale factors required to prevent radii from overlapping
|
||||
const CGSize size = self.bounds.size;
|
||||
const CGFloat topScaleFactor = MIN(1, size.width / (topLeftRadius + topRightRadius));
|
||||
const CGFloat bottomScaleFactor = MIN(1, size.width / (bottomLeftRadius + bottomRightRadius));
|
||||
const CGFloat rightScaleFactor = MIN(1, size.height / (topRightRadius + bottomRightRadius));
|
||||
const CGFloat leftScaleFactor = MIN(1, size.height / (topLeftRadius + bottomLeftRadius));
|
||||
|
||||
// Return scaled radii
|
||||
return (RCTCornerRadii){
|
||||
MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius),
|
||||
MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius),
|
||||
MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius),
|
||||
MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius),
|
||||
topLeftRadius * MIN(topScaleFactor, leftScaleFactor),
|
||||
topRightRadius * MIN(topScaleFactor, rightScaleFactor),
|
||||
bottomLeftRadius * MIN(bottomScaleFactor, leftScaleFactor),
|
||||
bottomRightRadius * MIN(bottomScaleFactor, rightScaleFactor),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -501,6 +512,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
|
|||
|
||||
- (void)displayLayer:(CALayer *)layer
|
||||
{
|
||||
if (CGSizeEqualToSize(layer.bounds.size, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const RCTCornerRadii cornerRadii = [self cornerRadii];
|
||||
const UIEdgeInsets borderInsets = [self bordersAsInsets];
|
||||
const RCTBorderColors borderColors = [self borderColors];
|
||||
|
|