Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(RCTUIKit): Shim RCTUIGraphicsImageRenderer #2209

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading