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 коммит произвёл facebook-github-bot-3
Родитель abb81eb270
Коммит b115277d00
6 изменённых файлов: 77 добавлений и 22 удалений

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 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];