diff --git a/Example/ios/Podfile.lock b/Example/ios/Podfile.lock index 72688179..ff9c8ef9 100644 --- a/Example/ios/Podfile.lock +++ b/Example/ios/Podfile.lock @@ -513,7 +513,7 @@ PODS: - React-RCTText - ReactCommon/turbomodule/core - Yoga - - RNSVG (13.10.0): + - RNSVG (13.11.0): - React-Core - SocketRocket (0.6.1) - Yoga (1.14.0) @@ -741,7 +741,7 @@ SPEC CHECKSUMS: React-utils: 0a70ea97d4e2749f336b450c082905be1d389435 ReactCommon: e593d19c9e271a6da4d0bd7f13b28cfeae5d164b RNReanimated: 726395a2fa2f04cea340274ba57a4e659bc0d9c1 - RNSVG: ee7e4ae98adade9ad8a12e7f9276504e71bd3ef7 + RNSVG: 791907c36f290562750132f8d274730c3aa529f6 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: 65286bb6a07edce5e4fe8c90774da977ae8fc009 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/Example/src/App.tsx b/Example/src/App.tsx index 05695289..45ecb312 100644 --- a/Example/src/App.tsx +++ b/Example/src/App.tsx @@ -115,6 +115,7 @@ const names: (keyof typeof examples)[] = [ 'Reanimated', 'Transforms', 'Markers', + 'Mask', ]; const initialState = { diff --git a/Example/src/examples.tsx b/Example/src/examples.tsx index dab7226d..22b7d34c 100644 --- a/Example/src/examples.tsx +++ b/Example/src/examples.tsx @@ -18,6 +18,7 @@ import * as PanResponder from './examples/PanResponder'; import * as Reanimated from './examples/Reanimated'; import * as Transforms from './examples/Transforms'; import * as Markers from './examples/Markers'; +import * as Mask from './examples/Mask'; export { Svg, @@ -40,4 +41,5 @@ export { Reanimated, Transforms, Markers, + Mask, }; diff --git a/Example/src/examples/Mask.tsx b/Example/src/examples/Mask.tsx new file mode 100644 index 00000000..a8ca125d --- /dev/null +++ b/Example/src/examples/Mask.tsx @@ -0,0 +1,148 @@ +import React, {Component} from 'react'; +import {StyleSheet, View} from 'react-native'; +import { + Svg, + Circle, + Path, + Rect, + Mask, + Polygon, + Defs, + LinearGradient, + Stop, + Text, +} from 'react-native-svg'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + height: 100, + width: 200, + }, + svg: { + flex: 1, + alignSelf: 'stretch', + }, +}); + +class SimpleMask extends Component { + static title = 'Simple svg with mask'; + render() { + return ( + + + + + + + + + + + + ); + } +} + +class AnotherMask extends Component { + static title = 'Another svg with mask'; + render() { + return ( + + + + + + + + + + + + ); + } +} + +class MaskWithText extends Component { + static title = 'Svg with with text and a mask with gradient'; + render() { + return ( + + + + + + + + + + + + + {'This text is under the rectangle'} + + + + + ); + } +} + +const icon = ( + + + + + + + + + +); + +const samples = [SimpleMask, AnotherMask, MaskWithText]; + +export {icon, samples}; diff --git a/FabricExample/ios/Podfile.lock b/FabricExample/ios/Podfile.lock index 122ad31b..6d63263f 100644 --- a/FabricExample/ios/Podfile.lock +++ b/FabricExample/ios/Podfile.lock @@ -1087,7 +1087,7 @@ PODS: - React-RCTText - ReactCommon/turbomodule/core - Yoga - - RNSVG (13.10.0): + - RNSVG (13.11.0): - hermes-engine - RCT-Folly (= 2021.07.22.00) - RCTRequired @@ -1102,9 +1102,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNSVG/common (= 13.10.0) + - RNSVG/common (= 13.11.0) - Yoga - - RNSVG/common (13.10.0): + - RNSVG/common (13.11.0): - hermes-engine - RCT-Folly (= 2021.07.22.00) - RCTRequired @@ -1359,7 +1359,7 @@ SPEC CHECKSUMS: React-utils: 0a70ea97d4e2749f336b450c082905be1d389435 ReactCommon: e593d19c9e271a6da4d0bd7f13b28cfeae5d164b RNReanimated: 5008fe999d57038a1c5c1163044854d453f41b98 - RNSVG: b677ab45318fca9f50dc361c1e3fd7c558dd0963 + RNSVG: e3b83203f24f5d275aa71ed85390222a6eb51a48 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: 65286bb6a07edce5e4fe8c90774da977ae8fc009 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/FabricExample/src/App.tsx b/FabricExample/src/App.tsx index 05695289..45ecb312 100644 --- a/FabricExample/src/App.tsx +++ b/FabricExample/src/App.tsx @@ -115,6 +115,7 @@ const names: (keyof typeof examples)[] = [ 'Reanimated', 'Transforms', 'Markers', + 'Mask', ]; const initialState = { diff --git a/FabricExample/src/examples.tsx b/FabricExample/src/examples.tsx index dab7226d..22b7d34c 100644 --- a/FabricExample/src/examples.tsx +++ b/FabricExample/src/examples.tsx @@ -18,6 +18,7 @@ import * as PanResponder from './examples/PanResponder'; import * as Reanimated from './examples/Reanimated'; import * as Transforms from './examples/Transforms'; import * as Markers from './examples/Markers'; +import * as Mask from './examples/Mask'; export { Svg, @@ -40,4 +41,5 @@ export { Reanimated, Transforms, Markers, + Mask, }; diff --git a/FabricExample/src/examples/Mask.tsx b/FabricExample/src/examples/Mask.tsx new file mode 100644 index 00000000..a8ca125d --- /dev/null +++ b/FabricExample/src/examples/Mask.tsx @@ -0,0 +1,148 @@ +import React, {Component} from 'react'; +import {StyleSheet, View} from 'react-native'; +import { + Svg, + Circle, + Path, + Rect, + Mask, + Polygon, + Defs, + LinearGradient, + Stop, + Text, +} from 'react-native-svg'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + height: 100, + width: 200, + }, + svg: { + flex: 1, + alignSelf: 'stretch', + }, +}); + +class SimpleMask extends Component { + static title = 'Simple svg with mask'; + render() { + return ( + + + + + + + + + + + + ); + } +} + +class AnotherMask extends Component { + static title = 'Another svg with mask'; + render() { + return ( + + + + + + + + + + + + ); + } +} + +class MaskWithText extends Component { + static title = 'Svg with with text and a mask with gradient'; + render() { + return ( + + + + + + + + + + + + + {'This text is under the rectangle'} + + + + + ); + } +} + +const icon = ( + + + + + + + + + +); + +const samples = [SimpleMask, AnotherMask, MaskWithText]; + +export {icon, samples}; diff --git a/RNSVG.podspec b/RNSVG.podspec index 1f40f05c..88afeb13 100644 --- a/RNSVG.podspec +++ b/RNSVG.podspec @@ -28,7 +28,7 @@ Pod::Spec.new do |s| ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/common/cpp\"" } end else - s.platforms = { :osx => "10.14", :ios => "9.0", :tvos => "9.2" } + s.platforms = { :osx => "10.14", :ios => "10.0", :tvos => "9.2" } s.exclude_files = 'apple/Utils/RNSVGFabricConversions.h' s.dependency 'React-Core' end diff --git a/apple/Elements/RNSVGSvgView.h b/apple/Elements/RNSVGSvgView.h index d3c88ffd..e589dad3 100644 --- a/apple/Elements/RNSVGSvgView.h +++ b/apple/Elements/RNSVGSvgView.h @@ -63,9 +63,7 @@ - (RNSVGNode *)getDefinedMask:(NSString *)maskName; -- (NSString *)getDataURL; - -- (NSString *)getDataURLwithBounds:(CGRect)bounds; +- (NSString *)getDataURLWithBounds:(CGRect)bounds; - (CGRect)getContextBounds; diff --git a/apple/Elements/RNSVGSvgView.mm b/apple/Elements/RNSVGSvgView.mm index 51fa1ee9..e5da830c 100644 --- a/apple/Elements/RNSVGSvgView.mm +++ b/apple/Elements/RNSVGSvgView.mm @@ -321,29 +321,20 @@ using namespace facebook::react; return nil; } -- (NSString *)getDataURL +- (NSString *)getDataURLWithBounds:(CGRect)bounds { - UIGraphicsBeginImageContextWithOptions(_boundingBox.size, NO, 0); - [self clearChildCache]; - [self drawRect:_boundingBox]; - [self clearChildCache]; - [self invalidate]; - NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()); - NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; - UIGraphicsEndImageContext(); - return base64; -} + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:bounds.size]; -- (NSString *)getDataURLwithBounds:(CGRect)bounds -{ - UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 1); - [self clearChildCache]; - [self drawRect:bounds]; - [self clearChildCache]; - [self invalidate]; - NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()); + UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) { + [self clearChildCache]; + [self drawRect:bounds]; + [self clearChildCache]; + [self invalidate]; + }]; + + NSData *imageData = UIImagePNGRepresentation(image); NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; - UIGraphicsEndImageContext(); + return base64; } diff --git a/apple/RNSVGRenderable.mm b/apple/RNSVGRenderable.mm index d69b9745..20b73787 100644 --- a/apple/RNSVGRenderable.mm +++ b/apple/RNSVGRenderable.mm @@ -304,35 +304,30 @@ UInt32 saturate(CGFloat value) CGContextRelease(bcontext); free(pixels); - // Render content of current SVG Renderable to image - UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0); - CGContextRef newContext = UIGraphicsGetCurrentContext(); - CGContextTranslateCTM(newContext, 0.0, height); - CGContextScaleCTM(newContext, 1.0, -1.0); - [self renderLayerTo:newContext rect:rect]; - CGImageRef contentImage = CGBitmapContextCreateImage(newContext); - UIGraphicsEndImageContext(); + UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat]; + format.scale = scale; + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:boundsSize format:format]; + + // Get the content image + UIImage *contentImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) { + CGContextTranslateCTM(rendererContext.CGContext, 0.0, height); + CGContextScaleCTM(rendererContext.CGContext, 1.0, -1.0); + [self renderLayerTo:rendererContext.CGContext rect:rect]; + }]; // Blend current element and mask - UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0); - newContext = UIGraphicsGetCurrentContext(); - CGContextTranslateCTM(newContext, 0.0, height); - CGContextScaleCTM(newContext, 1.0, -1.0); - - CGContextSetBlendMode(newContext, kCGBlendModeCopy); - CGContextDrawImage(newContext, drawBounds, maskImage); - CGImageRelease(maskImage); - - CGContextSetBlendMode(newContext, kCGBlendModeSourceIn); - CGContextDrawImage(newContext, drawBounds, contentImage); - CGImageRelease(contentImage); - - CGImageRef blendedImage = CGBitmapContextCreateImage(newContext); - UIGraphicsEndImageContext(); + UIImage *blendedImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) { + CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeCopy); + CGContextDrawImage(rendererContext.CGContext, drawBounds, maskImage); + CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeSourceIn); + CGContextDrawImage(rendererContext.CGContext, drawBounds, contentImage.CGImage); + }]; // Render blended result into current render context - CGContextDrawImage(context, drawBounds, blendedImage); - CGImageRelease(blendedImage); + [blendedImage drawInRect:drawBounds]; + + // Render blended result into current render context + CGImageRelease(maskImage); } else { [self renderLayerTo:context rect:rect]; } diff --git a/apple/RNSVGSvgViewModule.mm b/apple/RNSVGSvgViewModule.mm index 231940e7..3d869615 100644 --- a/apple/RNSVGSvgViewModule.mm +++ b/apple/RNSVGSvgViewModule.mm @@ -39,7 +39,7 @@ RCT_EXPORT_MODULE() if ([view isKindOfClass:[RNSVGSvgView class]]) { RNSVGSvgView *svg = view; if (options == nil) { - b64 = [svg getDataURL]; + b64 = [svg getDataURLWithBounds:svg.boundingBox]; } else { id width = [options objectForKey:@"width"]; id height = [options objectForKey:@"height"]; @@ -53,7 +53,7 @@ RCT_EXPORT_MODULE() NSInteger hi = (NSInteger)[h intValue]; CGRect bounds = CGRectMake(0, 0, wi, hi); - b64 = [svg getDataURLwithBounds:bounds]; + b64 = [svg getDataURLWithBounds:bounds]; } } else { RCTLogError(@"Invalid svg returned from registry, expecting RNSVGSvgView, got: %@", view);