mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Use CA shadow apis for box shadow (#50636)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50636 A while ago someone on GitHub reported that box shadow on iOS was causing frame drops when animating a large, pretty blurry shadow: https://github.com/facebook/react-native/issues/49128. This week I finally got around to fixing this! The slowness was happening since we were using CG to draw this shadow, which is very CPU intensive and, to my knowledge, does not take advantage of GPUs to do anything. Couple that with an animating, large, blurry shadow and we have frame issues. These shadows were taking very long to draw, to get the image of the shadow (which then needs to be copied into some texture 3 times as big, composited, put on the screen etc) took 12-14ms :o, thats very slow. To fix this I figured out how to get CA's shadow APIs working, which take advantage of the GPU. The enable inset shadows and spread you have to get creative with a mix of `shadowPath` and `mask` with a `CAShapeLayer`, but we got it done! Things are much faster, I am not sure how to time this but using a real device shows no frame drops :D Changelog: [iOS][Fixed] - Box shadows on iOS are faster Reviewed By: lenaic Differential Revision: D72823334 fbshipit-source-id: 460339c9d77e7423ce59a1a9178b6b3ad527e4b0
This commit is contained in:
committed by
Facebook GitHub Bot
parent
c5e9ef53ae
commit
52173ab701
+22
-19
@@ -38,7 +38,7 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
|
||||
CALayer *_backgroundColorLayer;
|
||||
__weak CALayer *_borderLayer;
|
||||
CALayer *_outlineLayer;
|
||||
CALayer *_boxShadowLayer;
|
||||
NSMutableArray<CALayer *> *_boxShadowLayers;
|
||||
CALayer *_filterLayer;
|
||||
NSMutableArray<CALayer *> *_backgroundImageLayers;
|
||||
BOOL _needsInvalidateLayer;
|
||||
@@ -828,7 +828,7 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
|
||||
// If view has a solid background color, calculate shadow path from border.
|
||||
const RCTCornerInsets cornerInsets =
|
||||
RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero);
|
||||
CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil);
|
||||
CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil, NO);
|
||||
layer.shadowPath = shadowPath;
|
||||
CGPathRelease(shadowPath);
|
||||
} else {
|
||||
@@ -852,9 +852,9 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
|
||||
// rendering incorrectly on iOS, iOS apps in compatibility mode on visionOS, but not on visionOS.
|
||||
// To work around this, for iOS, we can calculate the border path based on `view.frame` (the
|
||||
// superview's coordinate space) instead of view.bounds.
|
||||
CGPathRef borderPath = RCTPathCreateWithRoundedRect(self.frame, cornerInsets, NULL);
|
||||
CGPathRef borderPath = RCTPathCreateWithRoundedRect(self.frame, cornerInsets, NULL, NO);
|
||||
#else // TARGET_OS_VISION
|
||||
CGPathRef borderPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, NULL);
|
||||
CGPathRef borderPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, NULL, NO);
|
||||
#endif
|
||||
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:borderPath];
|
||||
CGPathRelease(borderPath);
|
||||
@@ -1016,21 +1016,24 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
|
||||
}
|
||||
|
||||
// box shadow
|
||||
[_boxShadowLayer removeFromSuperlayer];
|
||||
_boxShadowLayer = nil;
|
||||
for (CALayer *boxShadowLayer in _boxShadowLayers) {
|
||||
[boxShadowLayer removeFromSuperlayer];
|
||||
}
|
||||
[_boxShadowLayers removeAllObjects];
|
||||
if (!_props->boxShadow.empty()) {
|
||||
_boxShadowLayer = [CALayer layer];
|
||||
[self.layer addSublayer:_boxShadowLayer];
|
||||
_boxShadowLayer.zPosition = _borderLayer.zPosition;
|
||||
_boxShadowLayer.frame = RCTGetBoundingRect(_props->boxShadow, self.layer.bounds.size);
|
||||
|
||||
UIImage *boxShadowImage = RCTGetBoxShadowImage(
|
||||
_props->boxShadow,
|
||||
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
|
||||
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
|
||||
self.layer.bounds.size);
|
||||
|
||||
_boxShadowLayer.contents = (id)boxShadowImage.CGImage;
|
||||
if (!_boxShadowLayers) {
|
||||
_boxShadowLayers = [NSMutableArray new];
|
||||
}
|
||||
for (auto it = _props->boxShadow.rbegin(); it != _props->boxShadow.rend(); ++it) {
|
||||
CALayer *shadowLayer = RCTGetBoxShadowLayer(
|
||||
*it,
|
||||
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
|
||||
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
|
||||
self.layer.bounds.size);
|
||||
shadowLayer.zPosition = _borderLayer.zPosition;
|
||||
[self.layer addSublayer:shadowLayer];
|
||||
[_boxShadowLayers addObject:shadowLayer];
|
||||
}
|
||||
}
|
||||
|
||||
// clipping
|
||||
@@ -1095,7 +1098,7 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
|
||||
|
||||
- (CAShapeLayer *)createMaskLayer:(CGRect)bounds cornerInsets:(RCTCornerInsets)cornerInsets
|
||||
{
|
||||
CGPathRef path = RCTPathCreateWithRoundedRect(bounds, cornerInsets, nil);
|
||||
CGPathRef path = RCTPathCreateWithRoundedRect(bounds, cornerInsets, nil, NO);
|
||||
CAShapeLayer *maskLayer = [CAShapeLayer layer];
|
||||
maskLayer.path = path;
|
||||
CGPathRelease(path);
|
||||
|
||||
@@ -12,10 +12,8 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <react/renderer/graphics/BoxShadow.h>
|
||||
|
||||
RCT_EXTERN UIImage *RCTGetBoxShadowImage(
|
||||
const std::vector<facebook::react::BoxShadow> &shadows,
|
||||
RCT_EXTERN CALayer *RCTGetBoxShadowLayer(
|
||||
const facebook::react::BoxShadow &shadow,
|
||||
RCTCornerRadii cornerRadii,
|
||||
UIEdgeInsets edgeInsets,
|
||||
CGSize layerSize);
|
||||
|
||||
RCT_EXTERN CGRect RCTGetBoundingRect(const std::vector<facebook::react::BoxShadow> &boxShadows, CGSize layerSize);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#import "RCTBoxShadow.h"
|
||||
|
||||
#import <CoreImage/CoreImage.h>
|
||||
#import <React/RCTConversions.h>
|
||||
|
||||
#import <react/renderer/graphics/Color.h>
|
||||
@@ -42,46 +41,6 @@ static RCTCornerRadii cornerRadiiForBoxShadow(RCTCornerRadii cornerRadii, CGFloa
|
||||
adjustedCornerRadius(cornerRadii.bottomRightVertical, spreadDistance)};
|
||||
}
|
||||
|
||||
// Returns the smallest CGRect that will contain all shadows and the layer itself.
|
||||
// The origin represents the location of this box relative to the layer the shadows
|
||||
// are attached to.
|
||||
CGRect RCTGetBoundingRect(const std::vector<BoxShadow> &boxShadows, CGSize layerSize)
|
||||
{
|
||||
CGFloat smallestX = 0;
|
||||
CGFloat smallestY = 0;
|
||||
CGFloat largestX = layerSize.width;
|
||||
CGFloat largestY = layerSize.height;
|
||||
for (const auto &boxShadow : boxShadows) {
|
||||
if (!boxShadow.inset) {
|
||||
CGFloat negativeXExtent = boxShadow.offsetX - boxShadow.spreadDistance - boxShadow.blurRadius;
|
||||
smallestX = MIN(smallestX, negativeXExtent);
|
||||
CGFloat negativeYExtent = boxShadow.offsetY - boxShadow.spreadDistance - boxShadow.blurRadius;
|
||||
smallestY = MIN(smallestY, negativeYExtent);
|
||||
CGFloat positiveXExtent = boxShadow.offsetX + boxShadow.spreadDistance + boxShadow.blurRadius + layerSize.width;
|
||||
largestX = MAX(largestX, positiveXExtent);
|
||||
CGFloat positiveYExtent = boxShadow.offsetY + boxShadow.spreadDistance + boxShadow.blurRadius + layerSize.height;
|
||||
largestY = MAX(largestY, positiveYExtent);
|
||||
}
|
||||
}
|
||||
|
||||
return CGRectMake(smallestX, smallestY, largestX - smallestX, largestY - smallestY);
|
||||
}
|
||||
|
||||
static std::pair<std::vector<BoxShadow>, std::vector<BoxShadow>> splitBoxShadowsByInset(
|
||||
const std::vector<BoxShadow> &allShadows)
|
||||
{
|
||||
std::vector<BoxShadow> outsetShadows, insetShadows;
|
||||
|
||||
std::partition_copy(
|
||||
allShadows.begin(),
|
||||
allShadows.end(),
|
||||
std::back_inserter(insetShadows),
|
||||
std::back_inserter(outsetShadows),
|
||||
[](BoxShadow shadow) { return shadow.inset; });
|
||||
|
||||
return std::make_pair(outsetShadows, insetShadows);
|
||||
}
|
||||
|
||||
static CGRect insetRect(CGRect rect, CGFloat left, CGFloat top, CGFloat right, CGFloat bottom)
|
||||
{
|
||||
return CGRectMake(
|
||||
@@ -94,208 +53,117 @@ static CGColorRef colorRefFromSharedColor(const SharedColor &color)
|
||||
return colorRef ? colorRef : [UIColor blackColor].CGColor;
|
||||
}
|
||||
|
||||
// Core graphics has support for shadows that looks similar to web and are very
|
||||
// fast to apply. The only issue is that this shadow does not take a spread
|
||||
// radius like on web. To get around this, we draw the shadow rect (the rect
|
||||
// that casts the shadow, not the shadow itself) offscreen. This shadow rect
|
||||
// is correctly sized to account for spread radius. Then, when setting the
|
||||
// shadow itself, we modify the offsetX/Y to account for the fact that our
|
||||
// shadow rect is offscreen and position it where it needs to be in the image.
|
||||
static void renderOutsetShadows(
|
||||
std::vector<BoxShadow> &outsetShadows,
|
||||
RCTCornerRadii cornerRadii,
|
||||
CGSize layerSize,
|
||||
CGRect boundingRect,
|
||||
CGContextRef context)
|
||||
static CALayer *initBoxShadowLayer(const BoxShadow &shadow, CGSize layerSize)
|
||||
{
|
||||
if (outsetShadows.empty()) {
|
||||
return;
|
||||
}
|
||||
// Save state before doing any work so that we can restore it after we have
|
||||
// drawn all of our shadows. This ensures that we do not need to worry about
|
||||
// graphical state carrying over after this function returns
|
||||
CGContextSaveGState(context);
|
||||
// Reverse iterator as shadows are stacked back to front
|
||||
for (auto it = outsetShadows.rbegin(); it != outsetShadows.rend(); ++it) {
|
||||
CGContextSaveGState(context);
|
||||
CGFloat offsetX = it->offsetX;
|
||||
CGFloat offsetY = it->offsetY;
|
||||
CGFloat blurRadius = it->blurRadius;
|
||||
CGFloat spreadDistance = it->spreadDistance;
|
||||
CGColorRef color = colorRefFromSharedColor(it->color);
|
||||
CALayer *shadowLayer = [CALayer layer];
|
||||
shadowLayer.frame = CGRectMake(0, 0, layerSize.width, layerSize.height);
|
||||
shadowLayer.shadowColor = colorRefFromSharedColor(shadow.color);
|
||||
// Default is (0, -3) believe it or not
|
||||
shadowLayer.shadowOffset = CGSizeMake(0, 0);
|
||||
shadowLayer.shadowOpacity = 1;
|
||||
// Apple's blur is not quite what we want and seems to be a bit overbearing
|
||||
// with the radius. This is an eyeballed adjustment that has the blur looking
|
||||
// more like the web.
|
||||
shadowLayer.shadowRadius = shadow.blurRadius / 2;
|
||||
shadowLayer.contentsScale = [UIScreen mainScreen].scale;
|
||||
|
||||
// First, define the shadow rect. This is the rect that will be filled
|
||||
// and _cast_ the shadow. As a result, the size does not incorporate
|
||||
// the blur radius since this rect is not the shadow itself.
|
||||
const RCTCornerInsets shadowRectCornerInsets =
|
||||
RCTGetCornerInsets(cornerRadiiForBoxShadow(cornerRadii, spreadDistance), UIEdgeInsetsZero);
|
||||
CGSize shadowRectSize =
|
||||
CGSizeMake(fmax(layerSize.width + 2 * spreadDistance, 0), fmax(layerSize.height + 2 * spreadDistance, 0));
|
||||
// Ensure this is drawn offscreen and will not show in the image
|
||||
CGRect shadowRect = CGRectMake(-shadowRectSize.width, 0, shadowRectSize.width, shadowRectSize.height);
|
||||
CGPathRef shadowRectPath = RCTPathCreateWithRoundedRect(shadowRect, shadowRectCornerInsets, nil);
|
||||
|
||||
// Second, set the shadow as graphics state so that when we fill our
|
||||
// shadow rect it will actually cast a shadow. The offset of this
|
||||
// shadow needs to compensate for the fact that the shadow rect is
|
||||
// offscreen. Additionally, we take away the spread radius since spread
|
||||
// grows in all directions but the origin of our shadow rect is just
|
||||
// the negative width, which accounts for 2*spread radius.
|
||||
CGContextSetShadowWithColor(
|
||||
context,
|
||||
CGSizeMake(
|
||||
offsetX - boundingRect.origin.x - spreadDistance - shadowRect.origin.x,
|
||||
offsetY - boundingRect.origin.y - spreadDistance),
|
||||
blurRadius,
|
||||
color);
|
||||
|
||||
// Third, the Core Graphics functions to actually draw the shadow rect
|
||||
// and thus the shadow itself.
|
||||
CGContextAddPath(context, shadowRectPath);
|
||||
// Color here does not matter, we just need something that has 1 for
|
||||
// alpha so that the shadow is visible. The rect is purposely rendered
|
||||
// outside of the context so it should not be visible.
|
||||
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
|
||||
CGContextFillPath(context);
|
||||
|
||||
CGPathRelease(shadowRectPath);
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
// Lastly, clear out the region inside the view so that the shadows do
|
||||
// not cover its content
|
||||
const RCTCornerInsets layerCornerInsets = RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero);
|
||||
CGPathRef shadowPathAlignedWithLayer = RCTPathCreateWithRoundedRect(
|
||||
CGRectMake(-boundingRect.origin.x, -boundingRect.origin.y, layerSize.width, layerSize.height),
|
||||
layerCornerInsets,
|
||||
nil);
|
||||
CGContextAddPath(context, shadowPathAlignedWithLayer);
|
||||
CGContextSetBlendMode(context, kCGBlendModeClear);
|
||||
CGContextFillPath(context);
|
||||
|
||||
CGPathRelease(shadowPathAlignedWithLayer);
|
||||
CGContextRestoreGState(context);
|
||||
return shadowLayer;
|
||||
}
|
||||
|
||||
// Just like with outset shadows, we need to draw inset shadow rects offscreen
|
||||
// then offset the shadow it casts to the proper place. We can replicate the shape
|
||||
// of the inset shadow by using 2 rects. One is the same size as the view it is
|
||||
// attached to, plus some padding. The other represents a cropping region of the
|
||||
// first rect, with the exact postion and size of this region depending on the
|
||||
// shadow's params. We make this cropping region by using the EO fill pattern so
|
||||
// that the interection of the 2 rects is clear, and thus no shadow is cast.
|
||||
static void renderInsetShadows(
|
||||
std::vector<BoxShadow> &insetShadows,
|
||||
RCTCornerRadii cornerRadii,
|
||||
UIEdgeInsets edgeInsets,
|
||||
CGSize layerSize,
|
||||
CGRect boundingRect,
|
||||
CGContextRef context)
|
||||
static CALayer *
|
||||
RCTGetOutsetBoxShadowLayer(const facebook::react::BoxShadow &shadow, RCTCornerRadii cornerRadii, CGSize layerSize)
|
||||
{
|
||||
if (insetShadows.empty()) {
|
||||
return;
|
||||
}
|
||||
// Save state before doing any work so that we can restore it after we have
|
||||
// drawn all of our shadows. This ensures that we do not need to worry about
|
||||
// graphical state carrying over after this function returns
|
||||
CGContextSaveGState(context);
|
||||
CALayer *shadowLayer = initBoxShadowLayer(shadow, layerSize);
|
||||
|
||||
CGRect layerFrameRelativeToBoundingRect =
|
||||
CGRectMake(-boundingRect.origin.x, -boundingRect.origin.y, layerSize.width, layerSize.height);
|
||||
CGRect shadowFrame =
|
||||
insetRect(layerFrameRelativeToBoundingRect, edgeInsets.left, edgeInsets.top, edgeInsets.right, edgeInsets.bottom);
|
||||
const RCTCornerInsets shadowRectCornerInsets =
|
||||
RCTGetCornerInsets(cornerRadiiForBoxShadow(cornerRadii, shadow.spreadDistance), UIEdgeInsetsZero);
|
||||
|
||||
// First, create a clipping area so we only draw within the view's bounds.
|
||||
// If we do not do this, blur artifacts will show up outside the view.
|
||||
CGRect outerClippingRect = CGRectMake(0, 0, boundingRect.size.width, boundingRect.size.height);
|
||||
// Add the path twice so we only draw inside the view with the EO crop rule
|
||||
CGContextAddRect(context, outerClippingRect);
|
||||
CGContextAddRect(context, outerClippingRect);
|
||||
const RCTCornerInsets cornerInsetsForLayer = RCTGetCornerInsets(cornerRadii, edgeInsets);
|
||||
CGPathRef layerPath = RCTPathCreateWithRoundedRect(shadowFrame, cornerInsetsForLayer, nil);
|
||||
CGContextAddPath(context, layerPath);
|
||||
CGContextEOClip(context);
|
||||
CGRect shadowRect = CGRectInset(shadowLayer.bounds, -shadow.spreadDistance, -shadow.spreadDistance);
|
||||
shadowRect = CGRectOffset(shadowRect, shadow.offsetX, shadow.offsetY);
|
||||
CGPathRef shadowRectPath = RCTPathCreateWithRoundedRect(shadowRect, shadowRectCornerInsets, nil, NO);
|
||||
shadowLayer.shadowPath = shadowRectPath;
|
||||
|
||||
CAShapeLayer *mask = [CAShapeLayer new];
|
||||
[mask setContentsScale:[UIScreen mainScreen].scale];
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathRef layerPath =
|
||||
RCTPathCreateWithRoundedRect(shadowLayer.bounds, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), nil, NO);
|
||||
CGPathAddPath(path, NULL, layerPath);
|
||||
CGPathRef paddedShadowRectPath = RCTPathCreateWithRoundedRect(
|
||||
CGRectInset(shadowRect, -2 * (shadow.blurRadius + 1), -2 * (shadow.blurRadius + 1)),
|
||||
shadowRectCornerInsets,
|
||||
nil,
|
||||
NO);
|
||||
CGPathAddPath(path, NULL, paddedShadowRectPath);
|
||||
mask.fillRule = kCAFillRuleEvenOdd;
|
||||
mask.path = path;
|
||||
shadowLayer.mask = mask;
|
||||
|
||||
CGPathRelease(path);
|
||||
CGPathRelease(shadowRectPath);
|
||||
CGPathRelease(layerPath);
|
||||
CGPathRelease(paddedShadowRectPath);
|
||||
|
||||
// Reverse iterator as shadows are stacked back to front
|
||||
for (auto it = insetShadows.rbegin(); it != insetShadows.rend(); ++it) {
|
||||
CGContextSaveGState(context);
|
||||
CGFloat offsetX = it->offsetX;
|
||||
CGFloat offsetY = it->offsetY;
|
||||
CGFloat blurRadius = it->blurRadius;
|
||||
CGFloat spreadDistance = it->spreadDistance;
|
||||
CGColorRef color = colorRefFromSharedColor(it->color);
|
||||
|
||||
// Second, create the two offscreen rects we will use to create the correct
|
||||
// inset shadow shape. shadowRect has an originX such that it AND the clear
|
||||
// region are both guaranteed to be offscreen. We do not want some combination
|
||||
// of shadow params to allow the clear region to showup inside our context.
|
||||
// We also pad the size of the shadow rect by the blur radius so that the
|
||||
// edges of the shadow remain a solid color and do not blend with outside
|
||||
// of the view.
|
||||
CGRect shadowCastingRect = CGRectInset(shadowFrame, -blurRadius, -blurRadius);
|
||||
CGRect clearRegionRect = CGRectInset(shadowFrame, spreadDistance, spreadDistance);
|
||||
// This happens if the spread causes the height/width to be negative. A null
|
||||
// rect breaks a lot of the logic, so lets just keep it as a point
|
||||
if (CGRectIsNull(clearRegionRect)) {
|
||||
clearRegionRect = CGRectMake(0, 0, 0, 0);
|
||||
}
|
||||
CGPoint offsetToMoveOffscreen = CGPointMake(
|
||||
-fmax(
|
||||
shadowCastingRect.size.width,
|
||||
clearRegionRect.size.width + offsetX + (clearRegionRect.origin.x - shadowCastingRect.origin.x)) -
|
||||
shadowCastingRect.origin.x,
|
||||
0);
|
||||
shadowCastingRect = CGRectOffset(shadowCastingRect, offsetToMoveOffscreen.x, offsetToMoveOffscreen.y);
|
||||
clearRegionRect =
|
||||
CGRectOffset(clearRegionRect, offsetToMoveOffscreen.x + offsetX, offsetToMoveOffscreen.y + offsetY);
|
||||
CGContextAddRect(context, shadowCastingRect);
|
||||
|
||||
const RCTCornerInsets cornerInsetsForClearRegion =
|
||||
RCTGetCornerInsets(cornerRadiiForBoxShadow(cornerRadii, -spreadDistance), edgeInsets);
|
||||
CGPathRef clearRegionPath = RCTPathCreateWithRoundedRect(clearRegionRect, cornerInsetsForClearRegion, nil);
|
||||
CGContextAddPath(context, clearRegionPath);
|
||||
|
||||
// Third, set the shadow graphics state with the appropriate offset such that
|
||||
// it is positioned on top of the view. We subtract blurRadius because the
|
||||
// shadow rect is padded.
|
||||
CGContextSetShadowWithColor(
|
||||
context, CGSizeMake(-offsetToMoveOffscreen.x, -offsetToMoveOffscreen.y), blurRadius, color);
|
||||
|
||||
// Fourth, the Core Graphics functions to actually draw the shadow rect
|
||||
// and thus the shadow itself. Note we use an EO fill path so that the
|
||||
// intersection between our two rects will be clear. The disjoint parts of
|
||||
// the rect will be colored, but because of the clipping area, we only see
|
||||
// the shadow projected from the shadow rect, not the clear region rect.
|
||||
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
|
||||
CGContextEOFillPath(context);
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
return shadowLayer;
|
||||
}
|
||||
|
||||
UIImage *RCTGetBoxShadowImage(
|
||||
const std::vector<BoxShadow> &shadows,
|
||||
static CALayer *RCTGetInsetBoxShadowLayer(
|
||||
const facebook::react::BoxShadow &shadow,
|
||||
RCTCornerRadii cornerRadii,
|
||||
UIEdgeInsets edgeInsets,
|
||||
CGSize layerSize)
|
||||
{
|
||||
CGRect boundingRect = RCTGetBoundingRect(shadows, layerSize);
|
||||
UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat];
|
||||
UIGraphicsImageRenderer *const renderer = [[UIGraphicsImageRenderer alloc] initWithSize:boundingRect.size
|
||||
format:rendererFormat];
|
||||
UIImage *const boxShadowImage =
|
||||
[renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
|
||||
auto [outsetShadows, insetShadows] = splitBoxShadowsByInset(shadows);
|
||||
const CGContextRef context = rendererContext.CGContext;
|
||||
// Outset shadows should be before inset shadows since outset needs to
|
||||
// clear out a region in the view so we do not block its contents.
|
||||
// Inset shadows could draw over those outset shadows but if the shadow
|
||||
// colors have alpha < 1 then we will have inaccurate alpha compositing
|
||||
renderOutsetShadows(outsetShadows, cornerRadii, layerSize, boundingRect, context);
|
||||
renderInsetShadows(insetShadows, cornerRadii, edgeInsets, layerSize, boundingRect, context);
|
||||
}];
|
||||
CALayer *shadowLayer = initBoxShadowLayer(shadow, layerSize);
|
||||
|
||||
return boxShadowImage;
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
// This shadow is padded by the blur to make sure blur artifacts can be cast
|
||||
// if the clear region is right on the border of the layer
|
||||
CGRect shadowRect =
|
||||
insetRect(shadowLayer.bounds, edgeInsets.left, edgeInsets.top, edgeInsets.right, edgeInsets.bottom);
|
||||
if (CGRectIsNull(shadowRect)) {
|
||||
shadowRect = CGRectMake(0, 0, 0, 0);
|
||||
}
|
||||
CGPathRef shadowPath =
|
||||
RCTPathCreateWithRoundedRect(CGRectInset(shadowRect, -shadow.blurRadius, -shadow.blurRadius), {}, nil, NO);
|
||||
|
||||
CGPathRef layerPath = RCTPathCreateWithRoundedRect(shadowRect, RCTGetCornerInsets(cornerRadii, edgeInsets), nil, NO);
|
||||
CGPathAddPath(path, NULL, shadowPath);
|
||||
|
||||
CGRect clearRegionRect = CGRectOffset(shadowRect, shadow.offsetX, shadow.offsetY);
|
||||
clearRegionRect = CGRectInset(clearRegionRect, shadow.spreadDistance, shadow.spreadDistance);
|
||||
if (CGRectIsNull(clearRegionRect)) {
|
||||
clearRegionRect = CGRectMake(0, 0, 0, 0);
|
||||
}
|
||||
CGPathRef clearRegionPath = RCTPathCreateWithRoundedRect(
|
||||
clearRegionRect,
|
||||
RCTGetCornerInsets(cornerRadiiForBoxShadow(cornerRadii, -shadow.spreadDistance), edgeInsets),
|
||||
nil,
|
||||
YES);
|
||||
CGPathAddPath(path, NULL, clearRegionPath);
|
||||
|
||||
shadowLayer.shadowPath = path;
|
||||
|
||||
CAShapeLayer *mask = [CAShapeLayer new];
|
||||
mask.path = layerPath;
|
||||
shadowLayer.mask = mask;
|
||||
|
||||
CGPathRelease(path);
|
||||
CGPathRelease(layerPath);
|
||||
CGPathRelease(shadowPath);
|
||||
CGPathRelease(clearRegionPath);
|
||||
|
||||
return shadowLayer;
|
||||
}
|
||||
|
||||
CALayer *RCTGetBoxShadowLayer(
|
||||
const facebook::react::BoxShadow &shadow,
|
||||
RCTCornerRadii cornerRadii,
|
||||
UIEdgeInsets edgeInsets,
|
||||
CGSize layerSize)
|
||||
{
|
||||
if (shadow.inset) {
|
||||
return RCTGetInsetBoxShadowLayer(shadow, cornerRadii, edgeInsets, layerSize);
|
||||
} else {
|
||||
return RCTGetOutsetBoxShadowLayer(shadow, cornerRadii, layerSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,11 @@ RCT_EXTERN RCTCornerInsets RCTGetCornerInsets(RCTCornerRadii cornerRadii, UIEdge
|
||||
* Create a CGPath representing a rounded rectangle with the specified bounds
|
||||
* and corner insets. Note that the CGPathRef must be released by the caller.
|
||||
*/
|
||||
RCT_EXTERN CGPathRef
|
||||
RCTPathCreateWithRoundedRect(CGRect bounds, RCTCornerInsets cornerInsets, const CGAffineTransform *transform);
|
||||
RCT_EXTERN CGPathRef RCTPathCreateWithRoundedRect(
|
||||
CGRect bounds,
|
||||
RCTCornerInsets cornerInsets,
|
||||
const CGAffineTransform *transform,
|
||||
const BOOL inverted);
|
||||
|
||||
/**
|
||||
* Draw a CSS-compliant border as an image. You can determine if it's scalable
|
||||
|
||||
@@ -95,7 +95,11 @@ static void RCTPathAddEllipticArc(
|
||||
CGPathAddArc(path, &t, 0, 0, radius, startAngle, endAngle, clockwise);
|
||||
}
|
||||
|
||||
CGPathRef RCTPathCreateWithRoundedRect(CGRect bounds, RCTCornerInsets cornerInsets, const CGAffineTransform *transform)
|
||||
CGPathRef RCTPathCreateWithRoundedRect(
|
||||
CGRect bounds,
|
||||
RCTCornerInsets cornerInsets,
|
||||
const CGAffineTransform *transform,
|
||||
const BOOL inverted)
|
||||
{
|
||||
const CGFloat minX = CGRectGetMinX(bounds);
|
||||
const CGFloat minY = CGRectGetMinY(bounds);
|
||||
@@ -120,14 +124,25 @@ CGPathRef RCTPathCreateWithRoundedRect(CGRect bounds, RCTCornerInsets cornerInse
|
||||
};
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){minX + topLeft.width, minY + topLeft.height}, topLeft, M_PI, 3 * M_PI_2, NO);
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){maxX - topRight.width, minY + topRight.height}, topRight, 3 * M_PI_2, 0, NO);
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){maxX - bottomRight.width, maxY - bottomRight.height}, bottomRight, 0, M_PI_2, NO);
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){minX + bottomLeft.width, maxY - bottomLeft.height}, bottomLeft, M_PI_2, M_PI, NO);
|
||||
if (inverted) {
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){minX + bottomLeft.width, maxY - bottomLeft.height}, bottomLeft, M_PI, M_PI_2, YES);
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){maxX - bottomRight.width, maxY - bottomRight.height}, bottomRight, M_PI_2, 0, YES);
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){maxX - topRight.width, minY + topRight.height}, topRight, 0, 3 * M_PI_2, YES);
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){minX + topLeft.width, minY + topLeft.height}, topLeft, 3 * M_PI_2, M_PI, YES);
|
||||
} else {
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){minX + topLeft.width, minY + topLeft.height}, topLeft, M_PI, 3 * M_PI_2, NO);
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){maxX - topRight.width, minY + topRight.height}, topRight, 3 * M_PI_2, 0, NO);
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){maxX - bottomRight.width, maxY - bottomRight.height}, bottomRight, 0, M_PI_2, NO);
|
||||
RCTPathAddEllipticArc(
|
||||
path, transform, (CGPoint){minX + bottomLeft.width, maxY - bottomLeft.height}, bottomLeft, M_PI_2, M_PI, NO);
|
||||
}
|
||||
CGPathCloseSubpath(path);
|
||||
return path;
|
||||
}
|
||||
@@ -178,7 +193,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
|
||||
return CGPathCreateWithRect(rect, NULL);
|
||||
}
|
||||
|
||||
return RCTPathCreateWithRoundedRect(rect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
|
||||
return RCTPathCreateWithRoundedRect(rect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL, NO);
|
||||
}
|
||||
|
||||
static UIGraphicsImageRenderer *
|
||||
@@ -247,7 +262,8 @@ static UIImage *RCTGetSolidBorderImage(
|
||||
CGContextAddPath(context, path);
|
||||
CGPathRelease(path);
|
||||
|
||||
CGPathRef insetPath = RCTPathCreateWithRoundedRect(UIEdgeInsetsInsetRect(rect, borderInsets), cornerInsets, NULL);
|
||||
CGPathRef insetPath =
|
||||
RCTPathCreateWithRoundedRect(UIEdgeInsetsInsetRect(rect, borderInsets), cornerInsets, NULL, NO);
|
||||
|
||||
CGContextAddPath(context, insetPath);
|
||||
CGContextEOClip(context);
|
||||
@@ -500,7 +516,8 @@ static UIImage *RCTGetDashedOrDottedBorderImage(
|
||||
// perpendicular to the path, that's why we inset by half the width, so that it
|
||||
// reaches the edge of the rect.
|
||||
CGRect pathRect = CGRectInset(rect, lineWidth / 2.0, lineWidth / 2.0);
|
||||
CGPathRef path = RCTPathCreateWithRoundedRect(pathRect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
|
||||
CGPathRef path =
|
||||
RCTPathCreateWithRoundedRect(pathRect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL, NO);
|
||||
|
||||
CGFloat dashLengths[2];
|
||||
dashLengths[0] = dashLengths[1] = (borderStyle == RCTBorderStyleDashed ? 3 : 1) * lineWidth;
|
||||
|
||||
@@ -883,7 +883,7 @@ static void RCTUpdateShadowPathForView(RCTView *view)
|
||||
// If view has a solid background color, calculate shadow path from border
|
||||
const RCTCornerRadii cornerRadii = [view cornerRadii];
|
||||
const RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero);
|
||||
CGPathRef shadowPath = RCTPathCreateWithRoundedRect(view.bounds, cornerInsets, NULL);
|
||||
CGPathRef shadowPath = RCTPathCreateWithRoundedRect(view.bounds, cornerInsets, NULL, NO);
|
||||
view.layer.shadowPath = shadowPath;
|
||||
CGPathRelease(shadowPath);
|
||||
|
||||
@@ -913,9 +913,9 @@ static void RCTUpdateHoverStyleForView(RCTView *view)
|
||||
// Due to an Apple bug, it seems on iOS, `[UIShape shapeWithBezierPath:]` needs to
|
||||
// be calculated in the superviews' coordinate space (view.frame). This is not true
|
||||
// on other platforms like visionOS.
|
||||
CGPathRef borderPath = RCTPathCreateWithRoundedRect(view.frame, cornerInsets, NULL);
|
||||
CGPathRef borderPath = RCTPathCreateWithRoundedRect(view.frame, cornerInsets, NULL, NO);
|
||||
#else // TARGET_OS_VISION
|
||||
CGPathRef borderPath = RCTPathCreateWithRoundedRect(view.bounds, cornerInsets, NULL);
|
||||
CGPathRef borderPath = RCTPathCreateWithRoundedRect(view.bounds, cornerInsets, NULL, NO);
|
||||
#endif
|
||||
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:borderPath];
|
||||
CGPathRelease(borderPath);
|
||||
@@ -941,7 +941,7 @@ static void RCTUpdateHoverStyleForView(RCTView *view)
|
||||
} else {
|
||||
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
|
||||
CGPathRef path =
|
||||
RCTPathCreateWithRoundedRect(self.bounds, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
|
||||
RCTPathCreateWithRoundedRect(self.bounds, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL, NO);
|
||||
shapeLayer.path = path;
|
||||
CGPathRelease(path);
|
||||
mask = shapeLayer;
|
||||
|
||||
Reference in New Issue
Block a user