Skip to content

Commit 2d866c8

Browse files
authored
feat(RCTUIKit): Shim RCTUIGraphicsImageRenderer (#2209)
* feat(RCTUIKit): Shim RCTUIGraphicsImageRenderer * Update RCTBorderDrawing.m * `[RCTUIGraphicsImageRendererFormat defaultFormat]` doesn't return a singleton * `[NSImage lockFocus]` is deprecated. Use a newer API. * fix typo * Implement `format.opaque`
1 parent 3ebf897 commit 2d866c8

File tree

6 files changed

+100
-126
lines changed

6 files changed

+100
-126
lines changed

packages/react-native/Libraries/Image/RCTImageBlurUtils.mm

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,22 @@
2525

2626
// convert to ARGB if it isn't
2727
if (CGImageGetBitsPerPixel(imageRef) != 32 || !((CGImageGetBitmapInfo(imageRef) & kCGBitmapAlphaInfoMask))) {
28-
#if !TARGET_OS_OSX // [macOS]
29-
UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat];
30-
rendererFormat.scale = inputImage.scale;
31-
UIGraphicsImageRenderer *const renderer = [[UIGraphicsImageRenderer alloc] initWithSize:inputImage.size
32-
format:rendererFormat];
28+
RCTUIGraphicsImageRendererFormat *const rendererFormat = [RCTUIGraphicsImageRendererFormat defaultFormat]; // [macOS]
29+
rendererFormat.scale = UIImageGetScale(inputImage); // [macOS]
30+
RCTUIGraphicsImageRenderer *const renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:inputImage.size // [macOS]
31+
format:rendererFormat];
3332

33+
#if !TARGET_OS_OSX // [macOS]
3434
imageRef = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull context) {
3535
[inputImage drawAtPoint:CGPointZero];
3636
}].CGImage;
3737
#else // [macOS
38-
UIGraphicsBeginImageContextWithOptions(inputImage.size, NO, imageScale);
39-
[inputImage drawAtPoint:CGPointZero fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
40-
imageRef = (CGImageRef)CFAutorelease(CGBitmapContextCreateImage(UIGraphicsGetCurrentContext()));
41-
UIGraphicsEndImageContext();
38+
NSImage *image = [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull context) {
39+
[inputImage drawAtPoint:CGPointZero fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
40+
}];
41+
imageRef = UIImageGetCGImageRef(image);
4242
#endif // macOS]
4343
}
44-
4544
vImage_Buffer buffer1, buffer2;
4645
buffer1.width = buffer2.width = CGImageGetWidth(imageRef);
4746
buffer1.height = buffer2.height = CGImageGetHeight(imageRef);

packages/react-native/Libraries/Image/RCTImageUtils.mm

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -382,25 +382,19 @@ BOOL RCTUpscalingRequired(
382382
}
383383

384384
BOOL opaque = !RCTUIImageHasAlpha(image); // [macOS]
385-
#if !TARGET_OS_OSX // [macOS]
386-
UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat];
385+
RCTUIGraphicsImageRendererFormat *const rendererFormat = [RCTUIGraphicsImageRendererFormat defaultFormat]; // [macOS]
387386
rendererFormat.opaque = opaque;
388387
rendererFormat.scale = destScale;
389-
UIGraphicsImageRenderer *const renderer = [[UIGraphicsImageRenderer alloc] initWithSize:destSize
388+
RCTUIGraphicsImageRenderer *const renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:destSize // [macOS]
390389
format:rendererFormat];
391-
return [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull context) {
390+
return [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull context) { // [macOS]
392391
CGContextConcatCTM(context.CGContext, transform);
392+
#if !TARGET_OS_OSX // [macOS]
393393
[image drawAtPoint:CGPointZero];
394-
}];
395394
#else // [macOS
396-
UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
397-
CGContextRef currentContext = UIGraphicsGetCurrentContext();
398-
CGContextConcatCTM(currentContext, transform);
399-
[image drawAtPoint:CGPointZero fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
400-
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
401-
UIGraphicsEndImageContext();
402-
return result;
395+
[image drawAtPoint:CGPointZero fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
403396
#endif // macOS]
397+
}];
404398
}
405399

406400
BOOL RCTImageHasAlpha(CGImageRef image)

packages/react-native/React/Base/RCTUIKit.h

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,6 @@ extern "C" {
269269

270270
// UIGraphics.h
271271
CGContextRef UIGraphicsGetCurrentContext(void);
272-
void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
273-
NSImage *UIGraphicsGetImageFromCurrentImageContext(void);
274-
void UIGraphicsEndImageContext(void);
275272
CGImageRef UIImageGetCGImageRef(NSImage *image);
276273

277274
#ifdef __cplusplus
@@ -641,3 +638,31 @@ NS_ASSUME_NONNULL_BEGIN
641638
NS_ASSUME_NONNULL_END
642639
@end
643640
#endif
641+
642+
#if !TARGET_OS_OSX
643+
typedef UIGraphicsImageRendererContext RCTUIGraphicsImageRendererContext;
644+
typedef UIGraphicsImageDrawingActions RCTUIGraphicsImageDrawingActions;
645+
typedef UIGraphicsImageRendererFormat RCTUIGraphicsImageRendererFormat;
646+
typedef UIGraphicsImageRenderer RCTUIGraphicsImageRenderer;
647+
#else
648+
NS_ASSUME_NONNULL_BEGIN
649+
typedef NSGraphicsContext RCTUIGraphicsImageRendererContext;
650+
typedef void (^RCTUIGraphicsImageDrawingActions)(RCTUIGraphicsImageRendererContext *rendererContext);
651+
652+
@interface RCTUIGraphicsImageRendererFormat : NSObject
653+
654+
+ (instancetype)defaultFormat;
655+
656+
@property (nonatomic) CGFloat scale;
657+
@property (nonatomic) BOOL opaque;
658+
659+
@end
660+
661+
@interface RCTUIGraphicsImageRenderer : NSObject
662+
663+
- (instancetype)initWithSize:(CGSize)size format:(RCTUIGraphicsImageRendererFormat *)format;
664+
- (NSImage *)imageWithActions:(NS_NOESCAPE RCTUIGraphicsImageDrawingActions)actions;
665+
666+
@end
667+
NS_ASSUME_NONNULL_END
668+
#endif

packages/react-native/React/Base/macOS/RCTUIKit.m

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -32,37 +32,6 @@ CGContextRef UIGraphicsGetCurrentContext(void)
3232
return [[NSGraphicsContext currentContext] CGContext];
3333
}
3434

35-
void UIGraphicsBeginImageContextWithOptions(CGSize size, __unused BOOL opaque, CGFloat scale)
36-
{
37-
if (scale == 0.0)
38-
{
39-
// TODO: Assert. We can't assume a display scale on macOS
40-
scale = 1.0;
41-
}
42-
43-
size_t width = ceilf(size.width * scale);
44-
size_t height = ceilf(size.height * scale);
45-
46-
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
47-
CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, 8/*bitsPerComponent*/, width * 4/*bytesPerRow*/, colorSpace, kCGImageAlphaPremultipliedFirst);
48-
CGColorSpaceRelease(colorSpace);
49-
50-
if (ctx != NULL)
51-
{
52-
// flip the context (top left at 0, 0) and scale it
53-
CGContextTranslateCTM(ctx, 0.0, height);
54-
CGContextScaleCTM(ctx, scale, -scale);
55-
56-
NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
57-
objc_setAssociatedObject(graphicsContext, &RCTGraphicsContextSizeKey, [NSValue valueWithSize:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
58-
59-
[NSGraphicsContext saveGraphicsState];
60-
[NSGraphicsContext setCurrentContext:graphicsContext];
61-
62-
CFRelease(ctx);
63-
}
64-
}
65-
6635
NSImage *UIGraphicsGetImageFromCurrentImageContext(void)
6736
{
6837
NSImage *image = nil;
@@ -83,12 +52,6 @@ void UIGraphicsBeginImageContextWithOptions(CGSize size, __unused BOOL opaque, C
8352
return image;
8453
}
8554

86-
void UIGraphicsEndImageContext(void)
87-
{
88-
RCTAssert(objc_getAssociatedObject([NSGraphicsContext currentContext], &RCTGraphicsContextSizeKey), @"The current graphics context is not a React image context!");
89-
[NSGraphicsContext restoreGraphicsState];
90-
}
91-
9255
//
9356
// functionally equivalent types
9457
//
@@ -1040,4 +1003,45 @@ - (void)setImage:(UIImage *)image
10401003

10411004
@end
10421005

1006+
@implementation RCTUIGraphicsImageRendererFormat
1007+
1008+
+ (nonnull instancetype)defaultFormat {
1009+
RCTUIGraphicsImageRendererFormat *format = [RCTUIGraphicsImageRendererFormat new];
1010+
return format;
1011+
}
1012+
1013+
@end
1014+
1015+
@implementation RCTUIGraphicsImageRenderer
1016+
{
1017+
CGSize _size;
1018+
RCTUIGraphicsImageRendererFormat *_format;
1019+
}
1020+
1021+
- (nonnull instancetype)initWithSize:(CGSize)size format:(nonnull RCTUIGraphicsImageRendererFormat *)format {
1022+
if (self = [super init]) {
1023+
self->_size = size;
1024+
self->_format = format;
1025+
}
1026+
return self;
1027+
}
1028+
1029+
- (nonnull NSImage *)imageWithActions:(NS_NOESCAPE RCTUIGraphicsImageDrawingActions)actions {
1030+
1031+
NSImage *image = [NSImage imageWithSize:_size
1032+
flipped:YES
1033+
drawingHandler:^BOOL(NSRect dstRect) {
1034+
1035+
RCTUIGraphicsImageRendererContext *context = [NSGraphicsContext currentContext];
1036+
if (self->_format.opaque) {
1037+
CGContextSetAlpha([context CGContext], 1.0);
1038+
}
1039+
actions(context);
1040+
return YES;
1041+
}];
1042+
return image;
1043+
}
1044+
1045+
@end
1046+
10431047
#endif

packages/react-native/React/Views/RCTBorderDrawing.m

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -171,27 +171,16 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
171171
return RCTPathCreateWithRoundedRect(rect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
172172
}
173173

174-
#if !TARGET_OS_OSX // [macOS]
175-
static UIGraphicsImageRenderer *
176-
RCTUIGraphicsImageRenderer(CGSize size, CGColorRef backgroundColor, BOOL hasCornerRadii, BOOL drawToEdge)
174+
static RCTUIGraphicsImageRenderer * // [macOS]
175+
RCTMakeUIGraphicsImageRenderer(CGSize size, CGColorRef backgroundColor, BOOL hasCornerRadii, BOOL drawToEdge) // [macOS]
177176
{
178177
const CGFloat alpha = CGColorGetAlpha(backgroundColor);
179178
const BOOL opaque = (drawToEdge || !hasCornerRadii) && alpha == 1.0;
180-
UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat];
179+
RCTUIGraphicsImageRendererFormat *const rendererFormat = [RCTUIGraphicsImageRendererFormat defaultFormat]; // [macOS]
181180
rendererFormat.opaque = opaque;
182-
UIGraphicsImageRenderer *const renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:rendererFormat];
181+
RCTUIGraphicsImageRenderer *const renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:size format:rendererFormat]; // [macOS]
183182
return renderer;
184183
}
185-
#else // [macOS
186-
static CGContextRef
187-
RCTUIGraphicsBeginImageContext(CGSize size, CGColorRef backgroundColor, BOOL hasCornerRadii, BOOL drawToEdge, CGFloat scaleFactor)
188-
{
189-
const CGFloat alpha = CGColorGetAlpha(backgroundColor);
190-
const BOOL opaque = (drawToEdge || !hasCornerRadii) && alpha == 1.0;
191-
UIGraphicsBeginImageContextWithOptions(size, opaque, scaleFactor);
192-
return UIGraphicsGetCurrentContext();
193-
}
194-
#endif // macOS]
195184

196185
static UIImage *RCTGetSolidBorderImage(
197186
RCTCornerRadii cornerRadii,
@@ -237,16 +226,11 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
237226
return nil;
238227
} // macOS]
239228

240-
#if !TARGET_OS_OSX // [macOS]
241-
UIGraphicsImageRenderer *const imageRenderer =
242-
RCTUIGraphicsImageRenderer(size, backgroundColor, hasCornerRadii, drawToEdge);
243-
UIImage *image = [imageRenderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
229+
RCTUIGraphicsImageRenderer *const imageRenderer =
230+
RCTMakeUIGraphicsImageRenderer(size, backgroundColor, hasCornerRadii, drawToEdge); // [macOS]
231+
CGColorRetain(backgroundColor); // [macOS] CGColorRefs are not atuomtically retained when passed into a block
232+
UIImage *image = [imageRenderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
244233
const CGContextRef context = rendererContext.CGContext;
245-
#else // [macOS
246-
CGContextRef context = RCTUIGraphicsBeginImageContext(size, backgroundColor, hasCornerRadii, drawToEdge, scaleFactor);
247-
// Add extra braces for scope to match the indentation level of the iOS block
248-
{
249-
#endif // macOS]
250234
const CGRect rect = {.size = size};
251235
CGPathRef path = RCTPathCreateOuterOutline(drawToEdge, rect, cornerRadii);
252236

@@ -255,6 +239,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
255239
CGContextAddPath(context, path);
256240
CGContextFillPath(context);
257241
}
242+
CGColorRelease(backgroundColor); // [macOS]
258243

259244
CGContextAddPath(context, path);
260245
CGPathRelease(path);
@@ -402,13 +387,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
402387
}
403388

404389
CGPathRelease(insetPath);
405-
#if !TARGET_OS_OSX // [macOS]
406390
}];
407-
#else // [macOS
408-
}
409-
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
410-
UIGraphicsEndImageContext();
411-
#endif // macOS]
412391

413392
if (makeStretchable) {
414393
#if !TARGET_OS_OSX // [macOS]
@@ -509,16 +488,10 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
509488
} // macOS]
510489

511490
const BOOL hasCornerRadii = RCTCornerRadiiAreAboveThreshold(cornerRadii);
512-
#if !TARGET_OS_OSX // [macOS]
513-
UIGraphicsImageRenderer *const imageRenderer =
514-
RCTUIGraphicsImageRenderer(viewSize, backgroundColor, hasCornerRadii, drawToEdge);
515-
return [imageRenderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
491+
RCTUIGraphicsImageRenderer *const imageRenderer = // [macOS]
492+
RCTMakeUIGraphicsImageRenderer(viewSize, backgroundColor, hasCornerRadii, drawToEdge); // [macOS]
493+
return [imageRenderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
516494
const CGContextRef context = rendererContext.CGContext;
517-
#else // [macOS
518-
CGContextRef context = RCTUIGraphicsBeginImageContext(viewSize, backgroundColor, hasCornerRadii, drawToEdge, scaleFactor);
519-
// Add extra braces for scope to match the indentation level of the iOS block
520-
{
521-
#endif // macOS]
522495
const CGRect rect = {.size = viewSize};
523496

524497
if (backgroundColor) {
@@ -549,14 +522,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
549522
CGContextStrokePath(context);
550523

551524
CGPathRelease(path);
552-
#if !TARGET_OS_OSX // [macOS]
553525
}];
554-
#else // [macOS
555-
}
556-
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
557-
UIGraphicsEndImageContext();
558-
return image;
559-
#endif // macOS]
560526
}
561527

562528
UIImage *RCTGetBorderImage(

packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Diff.m

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,13 @@ - (UIImage *)diffWithImage:(UIImage *)image
1616
}
1717
CGSize imageSize = CGSizeMake(MAX(self.size.width, image.size.width), MAX(self.size.height, image.size.height));
1818

19-
#if !TARGET_OS_OSX // [macOS]
20-
UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat];
19+
RCTUIGraphicsImageRendererFormat *const rendererFormat = [RCTUIGraphicsImageRendererFormat defaultFormat]; // [macOS]
2120
rendererFormat.opaque = YES;
22-
UIGraphicsImageRenderer *const renderer = [[UIGraphicsImageRenderer alloc] initWithSize:imageSize
23-
format:rendererFormat];
21+
RCTUIGraphicsImageRenderer *const renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:imageSize // [macOS]
22+
format:rendererFormat];
2423

25-
return [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
24+
return [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
2625
const CGContextRef context = rendererContext.CGContext;
27-
#else // [macOS
28-
UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0.0);
29-
CGContextRef context = UIGraphicsGetCurrentContext();
30-
// Add extra braces for scope to match the indentation level of the iOS block
31-
{
32-
#endif // macOS]
3326
[self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
3427
CGContextSetAlpha(context, 0.5f);
3528
CGContextBeginTransparencyLayer(context, NULL);
@@ -38,14 +31,7 @@ - (UIImage *)diffWithImage:(UIImage *)image
3831
CGContextSetFillColorWithColor(context, [RCTUIColor whiteColor].CGColor);
3932
CGContextFillRect(context, CGRectMake(0, 0, self.size.width, self.size.height));
4033
CGContextEndTransparencyLayer(context);
41-
#if !TARGET_OS_OSX // [macOS]
4234
}];
43-
#else // [macOS
44-
}
45-
UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext();
46-
UIGraphicsEndImageContext();
47-
return returnImage;
48-
#endif // macOS]
4935
}
5036

5137
@end

0 commit comments

Comments
 (0)