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
This commit is contained in:
Nick Lockwood
2016-01-07 09:48:15 -08:00
committed by facebook-github-bot-3
parent abb81eb270
commit b115277d00
6 changed files with 77 additions and 22 deletions

View File

@@ -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);

View File

@@ -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];