Skip to content

Commit

Permalink
fix: mask and filters rendering issues (#2345)
Browse files Browse the repository at this point in the history
# Summary

Fixed rendering issues on macOS and iOS after merging Mask and Filter
changes together.

## Test Plan

Example app
  • Loading branch information
jakex7 authored Jul 11, 2024
1 parent 08e9207 commit d548750
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 72 deletions.
26 changes: 2 additions & 24 deletions apple/Filters/RNSVGFilter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -106,30 +106,8 @@ - (CIImage *)applyFilter:(CIImage *)img
}
}

// 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()
Expand Down
88 changes: 41 additions & 47 deletions apple/RNSVGRenderable.mm
Original file line number Diff line number Diff line change
Expand Up @@ -251,20 +251,13 @@ - (void)renderTo:(CGContextRef)context rect:(CGRect)rect
[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;
Expand All @@ -273,10 +266,8 @@ - (void)renderTo:(CGContextRef)context rect:(CGRect)rect
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
Expand All @@ -299,7 +290,17 @@ - (void)renderTo:(CGContextRef)context rect:(CGRect)rect

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

Expand All @@ -326,17 +327,20 @@ - (void)renderTo:(CGContextRef)context rect:(CGRect)rect
#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
Expand Down Expand Up @@ -369,56 +373,46 @@ - (void)renderTo:(CGContextRef)context rect:(CGRect)rect
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);
// Blend current element and mask
UIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
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);
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 {
Expand Down
1 change: 1 addition & 0 deletions apple/Utils/RNSVGRenderUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@interface RNSVGRenderUtils : NSObject

+ (CIContext *)sharedCIContext;
+ (CGFloat)getScreenScale;
+ (CGImage *)renderToImage:(RNSVGRenderable *)renderable
ctm:(CGAffineTransform)ctm
rect:(CGRect)rect
Expand Down
22 changes: 21 additions & 1 deletion apple/Utils/RNSVGRenderUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,34 @@ + (CIContext *)sharedCIContext
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) {
Expand Down

0 comments on commit d548750

Please sign in to comment.