Skip to content

Commit

Permalink
feat(RCTUIKit): Shim RCTUIGraphicsImageRenderer (#2209)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
Saadnajmi authored Oct 3, 2024
1 parent 3ebf897 commit 2d866c8
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 126 deletions.
19 changes: 9 additions & 10 deletions packages/react-native/Libraries/Image/RCTImageBlurUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,22 @@

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

#if !TARGET_OS_OSX // [macOS]
imageRef = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull context) {
[inputImage drawAtPoint:CGPointZero];
}].CGImage;
#else // [macOS
UIGraphicsBeginImageContextWithOptions(inputImage.size, NO, imageScale);
[inputImage drawAtPoint:CGPointZero fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
imageRef = (CGImageRef)CFAutorelease(CGBitmapContextCreateImage(UIGraphicsGetCurrentContext()));
UIGraphicsEndImageContext();
NSImage *image = [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull context) {
[inputImage drawAtPoint:CGPointZero fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
}];
imageRef = UIImageGetCGImageRef(image);
#endif // macOS]
}

vImage_Buffer buffer1, buffer2;
buffer1.width = buffer2.width = CGImageGetWidth(imageRef);
buffer1.height = buffer2.height = CGImageGetHeight(imageRef);
Expand Down
18 changes: 6 additions & 12 deletions packages/react-native/Libraries/Image/RCTImageUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -382,25 +382,19 @@ BOOL RCTUpscalingRequired(
}

BOOL opaque = !RCTUIImageHasAlpha(image); // [macOS]
#if !TARGET_OS_OSX // [macOS]
UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat];
RCTUIGraphicsImageRendererFormat *const rendererFormat = [RCTUIGraphicsImageRendererFormat defaultFormat]; // [macOS]
rendererFormat.opaque = opaque;
rendererFormat.scale = destScale;
UIGraphicsImageRenderer *const renderer = [[UIGraphicsImageRenderer alloc] initWithSize:destSize
RCTUIGraphicsImageRenderer *const renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:destSize // [macOS]
format:rendererFormat];
return [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull context) {
return [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull context) { // [macOS]
CGContextConcatCTM(context.CGContext, transform);
#if !TARGET_OS_OSX // [macOS]
[image drawAtPoint:CGPointZero];
}];
#else // [macOS
UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextConcatCTM(currentContext, transform);
[image drawAtPoint:CGPointZero fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
[image drawAtPoint:CGPointZero fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
#endif // macOS]
}];
}

BOOL RCTImageHasAlpha(CGImageRef image)
Expand Down
31 changes: 28 additions & 3 deletions packages/react-native/React/Base/RCTUIKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,6 @@ extern "C" {

// UIGraphics.h
CGContextRef UIGraphicsGetCurrentContext(void);
void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
NSImage *UIGraphicsGetImageFromCurrentImageContext(void);
void UIGraphicsEndImageContext(void);
CGImageRef UIImageGetCGImageRef(NSImage *image);

#ifdef __cplusplus
Expand Down Expand Up @@ -641,3 +638,31 @@ NS_ASSUME_NONNULL_BEGIN
NS_ASSUME_NONNULL_END
@end
#endif

#if !TARGET_OS_OSX
typedef UIGraphicsImageRendererContext RCTUIGraphicsImageRendererContext;
typedef UIGraphicsImageDrawingActions RCTUIGraphicsImageDrawingActions;
typedef UIGraphicsImageRendererFormat RCTUIGraphicsImageRendererFormat;
typedef UIGraphicsImageRenderer RCTUIGraphicsImageRenderer;
#else
NS_ASSUME_NONNULL_BEGIN
typedef NSGraphicsContext RCTUIGraphicsImageRendererContext;
typedef void (^RCTUIGraphicsImageDrawingActions)(RCTUIGraphicsImageRendererContext *rendererContext);

@interface RCTUIGraphicsImageRendererFormat : NSObject

+ (instancetype)defaultFormat;

@property (nonatomic) CGFloat scale;
@property (nonatomic) BOOL opaque;

@end

@interface RCTUIGraphicsImageRenderer : NSObject

- (instancetype)initWithSize:(CGSize)size format:(RCTUIGraphicsImageRendererFormat *)format;
- (NSImage *)imageWithActions:(NS_NOESCAPE RCTUIGraphicsImageDrawingActions)actions;

@end
NS_ASSUME_NONNULL_END
#endif
78 changes: 41 additions & 37 deletions packages/react-native/React/Base/macOS/RCTUIKit.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,6 @@ CGContextRef UIGraphicsGetCurrentContext(void)
return [[NSGraphicsContext currentContext] CGContext];
}

void UIGraphicsBeginImageContextWithOptions(CGSize size, __unused BOOL opaque, CGFloat scale)
{
if (scale == 0.0)
{
// TODO: Assert. We can't assume a display scale on macOS
scale = 1.0;
}

size_t width = ceilf(size.width * scale);
size_t height = ceilf(size.height * scale);

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, 8/*bitsPerComponent*/, width * 4/*bytesPerRow*/, colorSpace, kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);

if (ctx != NULL)
{
// flip the context (top left at 0, 0) and scale it
CGContextTranslateCTM(ctx, 0.0, height);
CGContextScaleCTM(ctx, scale, -scale);

NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
objc_setAssociatedObject(graphicsContext, &RCTGraphicsContextSizeKey, [NSValue valueWithSize:size], OBJC_ASSOCIATION_COPY_NONATOMIC);

[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:graphicsContext];

CFRelease(ctx);
}
}

NSImage *UIGraphicsGetImageFromCurrentImageContext(void)
{
NSImage *image = nil;
Expand All @@ -83,12 +52,6 @@ void UIGraphicsBeginImageContextWithOptions(CGSize size, __unused BOOL opaque, C
return image;
}

void UIGraphicsEndImageContext(void)
{
RCTAssert(objc_getAssociatedObject([NSGraphicsContext currentContext], &RCTGraphicsContextSizeKey), @"The current graphics context is not a React image context!");
[NSGraphicsContext restoreGraphicsState];
}

//
// functionally equivalent types
//
Expand Down Expand Up @@ -1040,4 +1003,45 @@ - (void)setImage:(UIImage *)image

@end

@implementation RCTUIGraphicsImageRendererFormat

+ (nonnull instancetype)defaultFormat {
RCTUIGraphicsImageRendererFormat *format = [RCTUIGraphicsImageRendererFormat new];
return format;
}

@end

@implementation RCTUIGraphicsImageRenderer
{
CGSize _size;
RCTUIGraphicsImageRendererFormat *_format;
}

- (nonnull instancetype)initWithSize:(CGSize)size format:(nonnull RCTUIGraphicsImageRendererFormat *)format {
if (self = [super init]) {
self->_size = size;
self->_format = format;
}
return self;
}

- (nonnull NSImage *)imageWithActions:(NS_NOESCAPE RCTUIGraphicsImageDrawingActions)actions {

NSImage *image = [NSImage imageWithSize:_size
flipped:YES
drawingHandler:^BOOL(NSRect dstRect) {

RCTUIGraphicsImageRendererContext *context = [NSGraphicsContext currentContext];
if (self->_format.opaque) {
CGContextSetAlpha([context CGContext], 1.0);
}
actions(context);
return YES;
}];
return image;
}

@end

#endif
58 changes: 12 additions & 46 deletions packages/react-native/React/Views/RCTBorderDrawing.m
Original file line number Diff line number Diff line change
Expand Up @@ -171,27 +171,16 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
return RCTPathCreateWithRoundedRect(rect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
}

#if !TARGET_OS_OSX // [macOS]
static UIGraphicsImageRenderer *
RCTUIGraphicsImageRenderer(CGSize size, CGColorRef backgroundColor, BOOL hasCornerRadii, BOOL drawToEdge)
static RCTUIGraphicsImageRenderer * // [macOS]
RCTMakeUIGraphicsImageRenderer(CGSize size, CGColorRef backgroundColor, BOOL hasCornerRadii, BOOL drawToEdge) // [macOS]
{
const CGFloat alpha = CGColorGetAlpha(backgroundColor);
const BOOL opaque = (drawToEdge || !hasCornerRadii) && alpha == 1.0;
UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat];
RCTUIGraphicsImageRendererFormat *const rendererFormat = [RCTUIGraphicsImageRendererFormat defaultFormat]; // [macOS]
rendererFormat.opaque = opaque;
UIGraphicsImageRenderer *const renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:rendererFormat];
RCTUIGraphicsImageRenderer *const renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:size format:rendererFormat]; // [macOS]
return renderer;
}
#else // [macOS
static CGContextRef
RCTUIGraphicsBeginImageContext(CGSize size, CGColorRef backgroundColor, BOOL hasCornerRadii, BOOL drawToEdge, CGFloat scaleFactor)
{
const CGFloat alpha = CGColorGetAlpha(backgroundColor);
const BOOL opaque = (drawToEdge || !hasCornerRadii) && alpha == 1.0;
UIGraphicsBeginImageContextWithOptions(size, opaque, scaleFactor);
return UIGraphicsGetCurrentContext();
}
#endif // macOS]

static UIImage *RCTGetSolidBorderImage(
RCTCornerRadii cornerRadii,
Expand Down Expand Up @@ -237,16 +226,11 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
return nil;
} // macOS]

#if !TARGET_OS_OSX // [macOS]
UIGraphicsImageRenderer *const imageRenderer =
RCTUIGraphicsImageRenderer(size, backgroundColor, hasCornerRadii, drawToEdge);
UIImage *image = [imageRenderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
RCTUIGraphicsImageRenderer *const imageRenderer =
RCTMakeUIGraphicsImageRenderer(size, backgroundColor, hasCornerRadii, drawToEdge); // [macOS]
CGColorRetain(backgroundColor); // [macOS] CGColorRefs are not atuomtically retained when passed into a block
UIImage *image = [imageRenderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
const CGContextRef context = rendererContext.CGContext;
#else // [macOS
CGContextRef context = RCTUIGraphicsBeginImageContext(size, backgroundColor, hasCornerRadii, drawToEdge, scaleFactor);
// Add extra braces for scope to match the indentation level of the iOS block
{
#endif // macOS]
const CGRect rect = {.size = size};
CGPathRef path = RCTPathCreateOuterOutline(drawToEdge, rect, cornerRadii);

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

CGContextAddPath(context, path);
CGPathRelease(path);
Expand Down Expand Up @@ -402,13 +387,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
}

CGPathRelease(insetPath);
#if !TARGET_OS_OSX // [macOS]
}];
#else // [macOS
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
#endif // macOS]

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

const BOOL hasCornerRadii = RCTCornerRadiiAreAboveThreshold(cornerRadii);
#if !TARGET_OS_OSX // [macOS]
UIGraphicsImageRenderer *const imageRenderer =
RCTUIGraphicsImageRenderer(viewSize, backgroundColor, hasCornerRadii, drawToEdge);
return [imageRenderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
RCTUIGraphicsImageRenderer *const imageRenderer = // [macOS]
RCTMakeUIGraphicsImageRenderer(viewSize, backgroundColor, hasCornerRadii, drawToEdge); // [macOS]
return [imageRenderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
const CGContextRef context = rendererContext.CGContext;
#else // [macOS
CGContextRef context = RCTUIGraphicsBeginImageContext(viewSize, backgroundColor, hasCornerRadii, drawToEdge, scaleFactor);
// Add extra braces for scope to match the indentation level of the iOS block
{
#endif // macOS]
const CGRect rect = {.size = viewSize};

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

CGPathRelease(path);
#if !TARGET_OS_OSX // [macOS]
}];
#else // [macOS
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
#endif // macOS]
}

UIImage *RCTGetBorderImage(
Expand Down
22 changes: 4 additions & 18 deletions packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Diff.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,13 @@ - (UIImage *)diffWithImage:(UIImage *)image
}
CGSize imageSize = CGSizeMake(MAX(self.size.width, image.size.width), MAX(self.size.height, image.size.height));

#if !TARGET_OS_OSX // [macOS]
UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat];
RCTUIGraphicsImageRendererFormat *const rendererFormat = [RCTUIGraphicsImageRendererFormat defaultFormat]; // [macOS]
rendererFormat.opaque = YES;
UIGraphicsImageRenderer *const renderer = [[UIGraphicsImageRenderer alloc] initWithSize:imageSize
format:rendererFormat];
RCTUIGraphicsImageRenderer *const renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:imageSize // [macOS]
format:rendererFormat];

return [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
return [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
const CGContextRef context = rendererContext.CGContext;
#else // [macOS
UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
// Add extra braces for scope to match the indentation level of the iOS block
{
#endif // macOS]
[self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
CGContextSetAlpha(context, 0.5f);
CGContextBeginTransparencyLayer(context, NULL);
Expand All @@ -38,14 +31,7 @@ - (UIImage *)diffWithImage:(UIImage *)image
CGContextSetFillColorWithColor(context, [RCTUIColor whiteColor].CGColor);
CGContextFillRect(context, CGRectMake(0, 0, self.size.width, self.size.height));
CGContextEndTransparencyLayer(context);
#if !TARGET_OS_OSX // [macOS]
}];
#else // [macOS
}
UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return returnImage;
#endif // macOS]
}

@end

0 comments on commit 2d866c8

Please sign in to comment.