feat: remove UIGraphicsBeginImageContextWithOptions from repo (#2117)

Since UIGraphicsBeginImageContextWithOptions will be depracated in iOS 17 (developer.apple.com/documentation/uikit/1623912-uigraphicsbeginimagecontextwitho), I changed the implementation to not use it and use UIGraphicsImageRenderer instead.

Also added Mask examples to be able to test it.
This commit is contained in:
Wojciech Lewicki
2023-08-21 11:35:50 +02:00
committed by GitHub
parent 9176c4c95a
commit f5503e2c9b
13 changed files with 343 additions and 57 deletions
+2 -2
View File
@@ -513,7 +513,7 @@ PODS:
- React-RCTText - React-RCTText
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- RNSVG (13.10.0): - RNSVG (13.11.0):
- React-Core - React-Core
- SocketRocket (0.6.1) - SocketRocket (0.6.1)
- Yoga (1.14.0) - Yoga (1.14.0)
@@ -741,7 +741,7 @@ SPEC CHECKSUMS:
React-utils: 0a70ea97d4e2749f336b450c082905be1d389435 React-utils: 0a70ea97d4e2749f336b450c082905be1d389435
ReactCommon: e593d19c9e271a6da4d0bd7f13b28cfeae5d164b ReactCommon: e593d19c9e271a6da4d0bd7f13b28cfeae5d164b
RNReanimated: 726395a2fa2f04cea340274ba57a4e659bc0d9c1 RNReanimated: 726395a2fa2f04cea340274ba57a4e659bc0d9c1
RNSVG: ee7e4ae98adade9ad8a12e7f9276504e71bd3ef7 RNSVG: 791907c36f290562750132f8d274730c3aa529f6
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Yoga: 65286bb6a07edce5e4fe8c90774da977ae8fc009 Yoga: 65286bb6a07edce5e4fe8c90774da977ae8fc009
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
+1
View File
@@ -115,6 +115,7 @@ const names: (keyof typeof examples)[] = [
'Reanimated', 'Reanimated',
'Transforms', 'Transforms',
'Markers', 'Markers',
'Mask',
]; ];
const initialState = { const initialState = {
+2
View File
@@ -18,6 +18,7 @@ import * as PanResponder from './examples/PanResponder';
import * as Reanimated from './examples/Reanimated'; import * as Reanimated from './examples/Reanimated';
import * as Transforms from './examples/Transforms'; import * as Transforms from './examples/Transforms';
import * as Markers from './examples/Markers'; import * as Markers from './examples/Markers';
import * as Mask from './examples/Mask';
export { export {
Svg, Svg,
@@ -40,4 +41,5 @@ export {
Reanimated, Reanimated,
Transforms, Transforms,
Markers, Markers,
Mask,
}; };
+148
View File
@@ -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 (
<View style={styles.container}>
<Svg viewBox="-10 -10 120 120">
<Rect x={-10} y={-10} width={120} height={120} fill="blue" />
<Mask id="myMask">
<Rect x={0} y={0} width={100} height={100} fill="white" />
<Path
d="M10,35 A20,20,0,0,1,50,35 A20,20,0,0,1,90,35 Q90,65,50,95 Q10,65,10,35 Z"
fill="black"
/>
</Mask>
<Polygon points="-10,110 110,110 110,-10" fill="orange" />
<Circle cx={50} cy={50} r={50} fill="purple" mask="url(#myMask)" />
</Svg>
</View>
);
}
}
class AnotherMask extends Component {
static title = 'Another svg with mask';
render() {
return (
<View style={styles.container}>
<Svg width={500} height={120}>
<Defs>
<Mask id="mask1" x={0} y={0} width={100} height={100}>
<Rect
x={0}
y={0}
width={100}
height={50}
stroke="none"
fill="#ffffff"
/>
</Mask>
</Defs>
<Rect
x={1}
y={1}
width={100}
height={100}
stroke="none"
fill="#0000ff"
mask="url(#mask1)"
/>
<Rect
x={1}
y={1}
width={100}
height={100}
stroke="#000000"
fill="none"
/>
</Svg>
</View>
);
}
}
class MaskWithText extends Component {
static title = 'Svg with with text and a mask with gradient';
render() {
return (
<View style={styles.container}>
<Svg width={500} height={120}>
<Defs>
<LinearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
<Stop offset="0%" stopColor="#ffffff" stopOpacity={1} />
<Stop offset="100%" stopColor="#000000" stopOpacity={1} />
</LinearGradient>
<Mask id="mask4" x={0} y={0} width={200} height={100}>
<Rect
x={0}
y={0}
width={200}
height={100}
stroke="none"
fill="url(#gradient1)"
/>
</Mask>
</Defs>
<Text x={10} y={55} stroke="none" fill="#000000">
{'This text is under the rectangle'}
</Text>
<Rect
x={1}
y={1}
width={200}
height={100}
stroke="none"
fill="#0000ff"
mask="url(#mask4)"
/>
</Svg>
</View>
);
}
}
const icon = (
<Svg width="30" height="30" viewBox="-10 -10 120 120">
<Rect x={-10} y={-10} width={120} height={120} fill="blue" />
<Mask id="myMask">
<Rect x={0} y={0} width={100} height={100} fill="white" />
<Path
d="M10,35 A20,20,0,0,1,50,35 A20,20,0,0,1,90,35 Q90,65,50,95 Q10,65,10,35 Z"
fill="black"
/>
</Mask>
<Polygon points="-10,110 110,110 110,-10" fill="orange" />
<Circle cx={50} cy={50} r={50} fill="purple" mask="url(#myMask)" />
</Svg>
);
const samples = [SimpleMask, AnotherMask, MaskWithText];
export {icon, samples};
+4 -4
View File
@@ -1087,7 +1087,7 @@ PODS:
- React-RCTText - React-RCTText
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- RNSVG (13.10.0): - RNSVG (13.11.0):
- hermes-engine - hermes-engine
- RCT-Folly (= 2021.07.22.00) - RCT-Folly (= 2021.07.22.00)
- RCTRequired - RCTRequired
@@ -1102,9 +1102,9 @@ PODS:
- React-utils - React-utils
- ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- RNSVG/common (= 13.10.0) - RNSVG/common (= 13.11.0)
- Yoga - Yoga
- RNSVG/common (13.10.0): - RNSVG/common (13.11.0):
- hermes-engine - hermes-engine
- RCT-Folly (= 2021.07.22.00) - RCT-Folly (= 2021.07.22.00)
- RCTRequired - RCTRequired
@@ -1359,7 +1359,7 @@ SPEC CHECKSUMS:
React-utils: 0a70ea97d4e2749f336b450c082905be1d389435 React-utils: 0a70ea97d4e2749f336b450c082905be1d389435
ReactCommon: e593d19c9e271a6da4d0bd7f13b28cfeae5d164b ReactCommon: e593d19c9e271a6da4d0bd7f13b28cfeae5d164b
RNReanimated: 5008fe999d57038a1c5c1163044854d453f41b98 RNReanimated: 5008fe999d57038a1c5c1163044854d453f41b98
RNSVG: b677ab45318fca9f50dc361c1e3fd7c558dd0963 RNSVG: e3b83203f24f5d275aa71ed85390222a6eb51a48
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Yoga: 65286bb6a07edce5e4fe8c90774da977ae8fc009 Yoga: 65286bb6a07edce5e4fe8c90774da977ae8fc009
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
+1
View File
@@ -115,6 +115,7 @@ const names: (keyof typeof examples)[] = [
'Reanimated', 'Reanimated',
'Transforms', 'Transforms',
'Markers', 'Markers',
'Mask',
]; ];
const initialState = { const initialState = {
+2
View File
@@ -18,6 +18,7 @@ import * as PanResponder from './examples/PanResponder';
import * as Reanimated from './examples/Reanimated'; import * as Reanimated from './examples/Reanimated';
import * as Transforms from './examples/Transforms'; import * as Transforms from './examples/Transforms';
import * as Markers from './examples/Markers'; import * as Markers from './examples/Markers';
import * as Mask from './examples/Mask';
export { export {
Svg, Svg,
@@ -40,4 +41,5 @@ export {
Reanimated, Reanimated,
Transforms, Transforms,
Markers, Markers,
Mask,
}; };
+148
View File
@@ -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 (
<View style={styles.container}>
<Svg viewBox="-10 -10 120 120">
<Rect x={-10} y={-10} width={120} height={120} fill="blue" />
<Mask id="myMask">
<Rect x={0} y={0} width={100} height={100} fill="white" />
<Path
d="M10,35 A20,20,0,0,1,50,35 A20,20,0,0,1,90,35 Q90,65,50,95 Q10,65,10,35 Z"
fill="black"
/>
</Mask>
<Polygon points="-10,110 110,110 110,-10" fill="orange" />
<Circle cx={50} cy={50} r={50} fill="purple" mask="url(#myMask)" />
</Svg>
</View>
);
}
}
class AnotherMask extends Component {
static title = 'Another svg with mask';
render() {
return (
<View style={styles.container}>
<Svg width={500} height={120}>
<Defs>
<Mask id="mask1" x={0} y={0} width={100} height={100}>
<Rect
x={0}
y={0}
width={100}
height={50}
stroke="none"
fill="#ffffff"
/>
</Mask>
</Defs>
<Rect
x={1}
y={1}
width={100}
height={100}
stroke="none"
fill="#0000ff"
mask="url(#mask1)"
/>
<Rect
x={1}
y={1}
width={100}
height={100}
stroke="#000000"
fill="none"
/>
</Svg>
</View>
);
}
}
class MaskWithText extends Component {
static title = 'Svg with with text and a mask with gradient';
render() {
return (
<View style={styles.container}>
<Svg width={500} height={120}>
<Defs>
<LinearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
<Stop offset="0%" stopColor="#ffffff" stopOpacity={1} />
<Stop offset="100%" stopColor="#000000" stopOpacity={1} />
</LinearGradient>
<Mask id="mask4" x={0} y={0} width={200} height={100}>
<Rect
x={0}
y={0}
width={200}
height={100}
stroke="none"
fill="url(#gradient1)"
/>
</Mask>
</Defs>
<Text x={10} y={55} stroke="none" fill="#000000">
{'This text is under the rectangle'}
</Text>
<Rect
x={1}
y={1}
width={200}
height={100}
stroke="none"
fill="#0000ff"
mask="url(#mask4)"
/>
</Svg>
</View>
);
}
}
const icon = (
<Svg width="30" height="30" viewBox="-10 -10 120 120">
<Rect x={-10} y={-10} width={120} height={120} fill="blue" />
<Mask id="myMask">
<Rect x={0} y={0} width={100} height={100} fill="white" />
<Path
d="M10,35 A20,20,0,0,1,50,35 A20,20,0,0,1,90,35 Q90,65,50,95 Q10,65,10,35 Z"
fill="black"
/>
</Mask>
<Polygon points="-10,110 110,110 110,-10" fill="orange" />
<Circle cx={50} cy={50} r={50} fill="purple" mask="url(#myMask)" />
</Svg>
);
const samples = [SimpleMask, AnotherMask, MaskWithText];
export {icon, samples};
+1 -1
View File
@@ -28,7 +28,7 @@ Pod::Spec.new do |s|
ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/common/cpp\"" } ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/common/cpp\"" }
end end
else 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.exclude_files = 'apple/Utils/RNSVGFabricConversions.h'
s.dependency 'React-Core' s.dependency 'React-Core'
end end
+1 -3
View File
@@ -63,9 +63,7 @@
- (RNSVGNode *)getDefinedMask:(NSString *)maskName; - (RNSVGNode *)getDefinedMask:(NSString *)maskName;
- (NSString *)getDataURL; - (NSString *)getDataURLWithBounds:(CGRect)bounds;
- (NSString *)getDataURLwithBounds:(CGRect)bounds;
- (CGRect)getContextBounds; - (CGRect)getContextBounds;
+11 -20
View File
@@ -321,29 +321,20 @@ using namespace facebook::react;
return nil; return nil;
} }
- (NSString *)getDataURL - (NSString *)getDataURLWithBounds:(CGRect)bounds
{ {
UIGraphicsBeginImageContextWithOptions(_boundingBox.size, NO, 0); UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:bounds.size];
[self clearChildCache];
[self drawRect:_boundingBox];
[self clearChildCache];
[self invalidate];
NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext());
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
UIGraphicsEndImageContext();
return base64;
}
- (NSString *)getDataURLwithBounds:(CGRect)bounds UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
{ [self clearChildCache];
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 1); [self drawRect:bounds];
[self clearChildCache]; [self clearChildCache];
[self drawRect:bounds]; [self invalidate];
[self clearChildCache]; }];
[self invalidate];
NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()); NSData *imageData = UIImagePNGRepresentation(image);
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
UIGraphicsEndImageContext();
return base64; return base64;
} }
+20 -25
View File
@@ -304,35 +304,30 @@ UInt32 saturate(CGFloat value)
CGContextRelease(bcontext); CGContextRelease(bcontext);
free(pixels); free(pixels);
// Render content of current SVG Renderable to image UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat];
UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0); format.scale = scale;
CGContextRef newContext = UIGraphicsGetCurrentContext(); UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:boundsSize format:format];
CGContextTranslateCTM(newContext, 0.0, height);
CGContextScaleCTM(newContext, 1.0, -1.0); // Get the content image
[self renderLayerTo:newContext rect:rect]; UIImage *contentImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
CGImageRef contentImage = CGBitmapContextCreateImage(newContext); CGContextTranslateCTM(rendererContext.CGContext, 0.0, height);
UIGraphicsEndImageContext(); CGContextScaleCTM(rendererContext.CGContext, 1.0, -1.0);
[self renderLayerTo:rendererContext.CGContext rect:rect];
}];
// Blend current element and mask // Blend current element and mask
UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0); UIImage *blendedImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
newContext = UIGraphicsGetCurrentContext(); CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeCopy);
CGContextTranslateCTM(newContext, 0.0, height); CGContextDrawImage(rendererContext.CGContext, drawBounds, maskImage);
CGContextScaleCTM(newContext, 1.0, -1.0); CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeSourceIn);
CGContextDrawImage(rendererContext.CGContext, drawBounds, contentImage.CGImage);
CGContextSetBlendMode(newContext, kCGBlendModeCopy); }];
CGContextDrawImage(newContext, drawBounds, maskImage);
CGImageRelease(maskImage);
CGContextSetBlendMode(newContext, kCGBlendModeSourceIn);
CGContextDrawImage(newContext, drawBounds, contentImage);
CGImageRelease(contentImage);
CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
UIGraphicsEndImageContext();
// Render blended result into current render context // Render blended result into current render context
CGContextDrawImage(context, drawBounds, blendedImage); [blendedImage drawInRect:drawBounds];
CGImageRelease(blendedImage);
// Render blended result into current render context
CGImageRelease(maskImage);
} else { } else {
[self renderLayerTo:context rect:rect]; [self renderLayerTo:context rect:rect];
} }
+2 -2
View File
@@ -39,7 +39,7 @@ RCT_EXPORT_MODULE()
if ([view isKindOfClass:[RNSVGSvgView class]]) { if ([view isKindOfClass:[RNSVGSvgView class]]) {
RNSVGSvgView *svg = view; RNSVGSvgView *svg = view;
if (options == nil) { if (options == nil) {
b64 = [svg getDataURL]; b64 = [svg getDataURLWithBounds:svg.boundingBox];
} else { } else {
id width = [options objectForKey:@"width"]; id width = [options objectForKey:@"width"];
id height = [options objectForKey:@"height"]; id height = [options objectForKey:@"height"];
@@ -53,7 +53,7 @@ RCT_EXPORT_MODULE()
NSInteger hi = (NSInteger)[h intValue]; NSInteger hi = (NSInteger)[h intValue];
CGRect bounds = CGRectMake(0, 0, wi, hi); CGRect bounds = CGRectMake(0, 0, wi, hi);
b64 = [svg getDataURLwithBounds:bounds]; b64 = [svg getDataURLWithBounds:bounds];
} }
} else { } else {
RCTLogError(@"Invalid svg returned from registry, expecting RNSVGSvgView, got: %@", view); RCTLogError(@"Invalid svg returned from registry, expecting RNSVGSvgView, got: %@", view);