mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-06 00:12:21 +00:00
fix: mask and filters rendering issues (#2345)
# Summary Fixed rendering issues on macOS and iOS after merging Mask and Filter changes together. ## Test Plan Example app
This commit is contained in:
@@ -106,30 +106,8 @@ using namespace facebook::react;
|
||||
}
|
||||
}
|
||||
|
||||
// Crop results to filter bounds
|
||||
CIFilter *crop = [CIFilter filterWithName:@"CICrop"];
|
||||
[crop setDefaults];
|
||||
[crop setValue:result forKey:@"inputImage"];
|
||||
|
||||
CGFloat scaleX = ctm.a, scaleY = fabs(ctm.d);
|
||||
CGFloat x, y, width, height;
|
||||
if (self.filterUnits == kRNSVGUnitsUserSpaceOnUse) {
|
||||
x = [self relativeOn:self.x relative:canvasBounds.size.width / scaleX];
|
||||
y = [self relativeOn:self.y relative:canvasBounds.size.height / scaleY];
|
||||
width = [self relativeOn:self.width relative:canvasBounds.size.width / scaleX];
|
||||
height = [self relativeOn:self.height relative:canvasBounds.size.height / scaleY];
|
||||
} else { // kRNSVGUnitsObjectBoundingBox
|
||||
x = renderableBounds.origin.x + [self relativeOnFraction:self.x relative:renderableBounds.size.width];
|
||||
y = renderableBounds.origin.y + [self relativeOnFraction:self.y relative:renderableBounds.size.height];
|
||||
width = [self relativeOnFraction:self.width relative:renderableBounds.size.width];
|
||||
height = [self relativeOnFraction:self.height relative:renderableBounds.size.height];
|
||||
}
|
||||
CGRect cropCGRect = CGRectMake(x, y, width, height);
|
||||
cropCGRect = CGRectApplyAffineTransform(cropCGRect, ctm);
|
||||
CIVector *cropRect = [CIVector vectorWithCGRect:cropCGRect];
|
||||
[crop setValue:cropRect forKey:@"inputRectangle"];
|
||||
|
||||
return [crop valueForKey:@"outputImage"];
|
||||
return result;
|
||||
// TODO: Crop element to filter's x, y, width, height
|
||||
}
|
||||
|
||||
static CIFilter *sourceAlphaFilter()
|
||||
|
||||
+42
-48
@@ -251,20 +251,13 @@ UInt32 saturate(CGFloat value)
|
||||
[self beginTransparencyLayer:context];
|
||||
|
||||
if (self.mask || self.filter) {
|
||||
CGRect bounds = CGContextGetClipBoundingBox(context);
|
||||
// Get current context transformations for offscreenContext
|
||||
CGAffineTransform currentCTM = CGContextGetCTM(context);
|
||||
|
||||
CGFloat height = rect.size.height;
|
||||
CGFloat width = rect.size.width;
|
||||
CGFloat scale = 0.0;
|
||||
#if TARGET_OS_OSX
|
||||
scale = [[NSScreen mainScreen] backingScaleFactor];
|
||||
#else
|
||||
if (@available(iOS 13.0, *)) {
|
||||
scale = [UITraitCollection currentTraitCollection].displayScale;
|
||||
} else {
|
||||
#if !TARGET_OS_VISION
|
||||
scale = [[UIScreen mainScreen] scale];
|
||||
#endif
|
||||
}
|
||||
#endif // TARGET_OS_OSX
|
||||
CGFloat scale = [RNSVGRenderUtils getScreenScale];
|
||||
NSUInteger iheight = (NSUInteger)height;
|
||||
NSUInteger iwidth = (NSUInteger)width;
|
||||
NSUInteger iscale = (NSUInteger)scale;
|
||||
@@ -273,10 +266,8 @@ UInt32 saturate(CGFloat value)
|
||||
NSUInteger npixels = scaledHeight * scaledWidth;
|
||||
CGAffineTransform screenScaleCTM = CGAffineTransformMake(scale, 0, 0, scale, 0, 0);
|
||||
CGRect scaledRect = CGRectApplyAffineTransform(rect, screenScaleCTM);
|
||||
// Get current context transformations for offscreenContext
|
||||
CGAffineTransform currentCTM = CGContextGetCTM(context);
|
||||
|
||||
CGImage *contentImage = [RNSVGRenderUtils renderToImage:self ctm:currentCTM rect:scaledRect clip:nil];
|
||||
CGImage *contentImage = [RNSVGRenderUtils renderToImage:self ctm:currentCTM rect:rect clip:nil];
|
||||
|
||||
if (self.filter) {
|
||||
// https://www.w3.org/TR/SVG11/filters.html#FilterElement
|
||||
@@ -299,7 +290,17 @@ UInt32 saturate(CGFloat value)
|
||||
|
||||
if (!self.mask) {
|
||||
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
|
||||
|
||||
// On macOS the currentCTM is inverted, so we need to transform it again
|
||||
// https://stackoverflow.com/a/13358085
|
||||
#if TARGET_OS_OSX
|
||||
CGContextTranslateCTM(context, 0.0, rect.size.height);
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
CGContextDrawImage(context, rect, contentImage);
|
||||
#else
|
||||
CGContextDrawImage(context, scaledRect, contentImage);
|
||||
#endif
|
||||
|
||||
CGContextConcatCTM(context, currentCTM);
|
||||
}
|
||||
|
||||
@@ -326,17 +327,20 @@ UInt32 saturate(CGFloat value)
|
||||
#if TARGET_OS_OSX // [macOS]
|
||||
// on macOS currentCTM is not scaled properly with screen scale so we need to scale it manually
|
||||
CGContextConcatCTM(bcontext, screenScaleCTM);
|
||||
CGContextTranslateCTM(bcontext, 0, rect.size.height);
|
||||
CGContextScaleCTM(bcontext, 1, -1);
|
||||
|
||||
#endif // [macOS]
|
||||
CGContextConcatCTM(bcontext, currentCTM);
|
||||
|
||||
// Clip to mask bounds and render the mask
|
||||
CGFloat x = [self relativeOn:[_maskNode x] relative:width];
|
||||
CGFloat y = [self relativeOn:[_maskNode y] relative:height];
|
||||
CGFloat w = [self relativeOn:[_maskNode maskwidth] relative:width];
|
||||
CGFloat h = [self relativeOn:[_maskNode maskheight] relative:height];
|
||||
CGSize currentBoundsSize = self.pathBounds.size;
|
||||
CGFloat x = [self relativeOnFraction:[_maskNode x] relative:currentBoundsSize.width];
|
||||
CGFloat y = [self relativeOnFraction:[_maskNode y] relative:currentBoundsSize.height];
|
||||
CGFloat w = [self relativeOnFraction:[_maskNode maskwidth] relative:currentBoundsSize.width];
|
||||
CGFloat h = [self relativeOnFraction:[_maskNode maskheight] relative:currentBoundsSize.height];
|
||||
CGRect maskBounds = CGRectApplyAffineTransform(CGRectMake(x, y, w, h), screenScaleCTM);
|
||||
CGContextClipToRect(bcontext, maskBounds);
|
||||
[_maskNode renderLayerTo:bcontext rect:scaledRect];
|
||||
[_maskNode renderLayerTo:bcontext rect:bounds];
|
||||
|
||||
// Apply luminanceToAlpha filter primitive
|
||||
// https://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement
|
||||
@@ -369,56 +373,46 @@ UInt32 saturate(CGFloat value)
|
||||
UIImage *blendedImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
|
||||
CGContextConcatCTM(
|
||||
rendererContext.CGContext, CGAffineTransformInvert(CGContextGetCTM(rendererContext.CGContext)));
|
||||
CGContextTranslateCTM(rendererContext.CGContext, 0.0, scaledHeight);
|
||||
CGContextScaleCTM(rendererContext.CGContext, 1.0, -1.0);
|
||||
CGContextConcatCTM(rendererContext.CGContext, screenScaleCTM);
|
||||
|
||||
CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeCopy);
|
||||
CGContextDrawImage(rendererContext.CGContext, scaledRect, maskImage);
|
||||
CGContextDrawImage(rendererContext.CGContext, rect, maskImage);
|
||||
CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeSourceIn);
|
||||
CGContextDrawImage(rendererContext.CGContext, scaledRect, contentImage);
|
||||
CGContextDrawImage(rendererContext.CGContext, rect, contentImage);
|
||||
}];
|
||||
|
||||
// Render blended result into current render context
|
||||
// Invert the CTM and apply transformations to draw image in 1:1
|
||||
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
|
||||
[blendedImage drawInRect:scaledRect];
|
||||
CGContextConcatCTM(context, currentCTM);
|
||||
CGContextTranslateCTM(context, 0.0, scaledRect.size.height);
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
|
||||
// Render blended result into current render context
|
||||
CGImageRelease(maskImage);
|
||||
[blendedImage drawInRect:scaledRect];
|
||||
#else // [macOS
|
||||
// Render content of current SVG Renderable to image
|
||||
UIGraphicsBeginImageContextWithOptions(scaledRect.size, NO, 1.0);
|
||||
CGContextRef newContext = UIGraphicsGetCurrentContext();
|
||||
CGContextConcatCTM(newContext, CGAffineTransformInvert(CGContextGetCTM(newContext)));
|
||||
CGContextConcatCTM(newContext, screenScaleCTM);
|
||||
CGContextConcatCTM(newContext, currentCTM);
|
||||
[self renderLayerTo:newContext rect:scaledRect];
|
||||
CGImageRef contentImage = CGBitmapContextCreateImage(newContext);
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
// Blend current element and mask
|
||||
UIGraphicsBeginImageContextWithOptions(scaledRect.size, NO, 0.0);
|
||||
newContext = UIGraphicsGetCurrentContext();
|
||||
CGContextTranslateCTM(newContext, 0.0, height);
|
||||
CGContextScaleCTM(newContext, 1.0, -1.0);
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
|
||||
CGContextRef newContext = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGContextConcatCTM(newContext, CGAffineTransformInvert(CGContextGetCTM(newContext)));
|
||||
|
||||
CGContextSetBlendMode(newContext, kCGBlendModeCopy);
|
||||
CGContextDrawImage(newContext, scaledRect, maskImage);
|
||||
CGImageRelease(maskImage);
|
||||
|
||||
CGContextSetBlendMode(newContext, kCGBlendModeSourceIn);
|
||||
CGContextDrawImage(newContext, scaledRect, contentImage);
|
||||
CGImageRelease(contentImage);
|
||||
|
||||
CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
// Render blended result into current render context
|
||||
// Invert the CTM and apply transformations to draw image in 1:1
|
||||
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
|
||||
CGContextTranslateCTM(context, 0.0, rect.size.height);
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
|
||||
// Render blended result into current render context
|
||||
CGContextDrawImage(context, rect, blendedImage);
|
||||
CGContextConcatCTM(context, currentCTM);
|
||||
CGImageRelease(blendedImage);
|
||||
#endif // macOS]
|
||||
CGImageRelease(maskImage);
|
||||
}
|
||||
CGImageRelease(contentImage);
|
||||
} else {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@interface RNSVGRenderUtils : NSObject
|
||||
|
||||
+ (CIContext *)sharedCIContext;
|
||||
+ (CGFloat)getScreenScale;
|
||||
+ (CGImage *)renderToImage:(RNSVGRenderable *)renderable
|
||||
ctm:(CGAffineTransform)ctm
|
||||
rect:(CGRect)rect
|
||||
|
||||
@@ -13,14 +13,34 @@
|
||||
return sharedCIContext;
|
||||
}
|
||||
|
||||
+ (CGFloat)getScreenScale
|
||||
{
|
||||
CGFloat scale = 0.0;
|
||||
#if TARGET_OS_OSX
|
||||
scale = [[NSScreen mainScreen] backingScaleFactor];
|
||||
#else
|
||||
if (@available(iOS 13.0, *)) {
|
||||
scale = [UITraitCollection currentTraitCollection].displayScale;
|
||||
} else {
|
||||
#if !TARGET_OS_VISION
|
||||
scale = [[UIScreen mainScreen] scale];
|
||||
#endif
|
||||
}
|
||||
#endif // TARGET_OS_OSX
|
||||
return scale;
|
||||
}
|
||||
|
||||
+ (CGImage *)renderToImage:(RNSVGRenderable *)renderable
|
||||
ctm:(CGAffineTransform)ctm
|
||||
rect:(CGRect)rect
|
||||
clip:(CGRect *)clip
|
||||
{
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1.0);
|
||||
CGFloat scale = [self getScreenScale];
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
|
||||
CGContextRef cgContext = UIGraphicsGetCurrentContext();
|
||||
#if !TARGET_OS_OSX
|
||||
CGContextConcatCTM(cgContext, CGAffineTransformInvert(CGContextGetCTM(cgContext)));
|
||||
#endif
|
||||
CGContextConcatCTM(cgContext, ctm);
|
||||
|
||||
if (clip) {
|
||||
|
||||
Reference in New Issue
Block a user