Skip to content

Commit 28b2fed

Browse files
committed
feat: Introduce RCTUIImage
1 parent 5406504 commit 28b2fed

File tree

9 files changed

+73
-36
lines changed

9 files changed

+73
-36
lines changed

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ UIKIT_STATIC_INLINE void UIBezierPathAppendPath(UIBezierPath *path, UIBezierPath
6161
#define RCTUIScrollView UIScrollView
6262
#define RCTPlatformImage UIImage
6363

64-
6564
UIKIT_STATIC_INLINE RCTPlatformView *RCTUIViewHitTestWithEvent(RCTPlatformView *view, CGPoint point, __unused UIEvent *__nullable event)
6665
{
6766
return [view hitTest:point withEvent:event];
@@ -268,7 +267,6 @@ extern "C" {
268267

269268
// UIGraphics.h
270269
CGContextRef UIGraphicsGetCurrentContext(void);
271-
CGImageRef UIImageGetCGImageRef(NSImage *image);
272270

273271
#ifdef __cplusplus
274272
}
@@ -334,18 +332,30 @@ NS_INLINE NSEdgeInsets UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat botto
334332
#define UIApplication NSApplication
335333

336334
// UIImage
335+
// RCTUIImage is a subclass of NSImage that caches its CGImage representation.
336+
// This is needed because NSImage's CGImageForProposedRect: returns a new autoreleased
337+
// CGImage each time, which causes issues when used with CALayer.contents.
338+
@interface RCTUIImage : NSImage
339+
@property (nonatomic, readonly, nullable) CGImageRef CGImage;
340+
@property (nonatomic, readonly) CGFloat scale;
341+
@end
342+
337343
typedef NS_ENUM(NSInteger, UIImageRenderingMode) {
338344
UIImageRenderingModeAlwaysOriginal,
339345
UIImageRenderingModeAlwaysTemplate,
340346
};
341347

342348
#ifdef __cplusplus
343-
extern "C"
349+
extern "C" {
344350
#endif
345-
CGFloat UIImageGetScale(NSImage *image);
346351

352+
CGFloat UIImageGetScale(NSImage *image);
347353
CGImageRef UIImageGetCGImageRef(NSImage *image);
348354

355+
#ifdef __cplusplus
356+
}
357+
#endif
358+
349359
NS_INLINE NSImage *UIImageWithContentsOfFile(NSString *filePath)
350360
{
351361
return [[NSImage alloc] initWithContentsOfFile:filePath];
@@ -626,7 +636,7 @@ typedef void (^RCTUIGraphicsImageDrawingActions)(RCTUIGraphicsImageRendererConte
626636

627637
- (instancetype)initWithSize:(CGSize)size;
628638
- (instancetype)initWithSize:(CGSize)size format:(RCTUIGraphicsImageRendererFormat *)format;
629-
- (NSImage *)imageWithActions:(NS_NOESCAPE RCTUIGraphicsImageDrawingActions)actions;
639+
- (RCTUIImage *)imageWithActions:(NS_NOESCAPE RCTUIGraphicsImageDrawingActions)actions;
630640

631641
@end
632642
NS_ASSUME_NONNULL_END

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

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,43 @@ CGFloat UIImageGetScale(NSImage *image)
7676
return 1.0;
7777
}
7878

79+
// RCTUIImage - NSImage subclass with cached CGImage
80+
81+
@implementation RCTUIImage {
82+
CGImageRef _cachedCGImage;
83+
}
84+
85+
- (void)dealloc {
86+
if (_cachedCGImage != NULL) {
87+
CGImageRelease(_cachedCGImage);
88+
}
89+
}
90+
91+
- (CGImageRef)CGImage {
92+
if (_cachedCGImage == NULL) {
93+
CGImageRef cgImage = [self CGImageForProposedRect:NULL context:NULL hints:NULL];
94+
if (cgImage != NULL) {
95+
_cachedCGImage = CGImageRetain(cgImage);
96+
}
97+
}
98+
return _cachedCGImage;
99+
}
100+
101+
- (CGFloat)scale {
102+
return UIImageGetScale(self);
103+
}
104+
105+
@end
106+
79107
CGImageRef __nullable UIImageGetCGImageRef(NSImage *image)
80108
{
109+
// If it's an RCTUIImage, use the cached CGImage property
110+
if ([image isKindOfClass:[RCTUIImage class]]) {
111+
return ((RCTUIImage *)image).CGImage;
112+
}
113+
114+
// Otherwise, fall back to the standard NSImage method
115+
// Note: This returns an autoreleased CGImageRef
81116
return [image CGImageForProposedRect:NULL context:NULL hints:NULL];
82117
}
83118

@@ -825,11 +860,10 @@ - (nonnull instancetype)initWithSize:(CGSize)size format:(nonnull RCTUIGraphicsI
825860
return self;
826861
}
827862

828-
- (nonnull NSImage *)imageWithActions:(NS_NOESCAPE RCTUIGraphicsImageDrawingActions)actions {
829-
830-
NSImage *image = [NSImage imageWithSize:_size
831-
flipped:YES
832-
drawingHandler:^BOOL(NSRect dstRect) {
863+
- (nonnull RCTUIImage *)imageWithActions:(NS_NOESCAPE RCTUIGraphicsImageDrawingActions)actions {
864+
RCTUIImage *image = [RCTUIImage imageWithSize:_size
865+
flipped:YES
866+
drawingHandler:^BOOL(NSRect dstRect) {
833867

834868
RCTUIGraphicsImageRendererContext *context = [NSGraphicsContext currentContext];
835869
if (self->_format.opaque) {
@@ -838,6 +872,12 @@ - (nonnull NSImage *)imageWithActions:(NS_NOESCAPE RCTUIGraphicsImageDrawingActi
838872
actions(context);
839873
return YES;
840874
}];
875+
876+
// Calling these in succession forces the image to render its contents immediately,
877+
// rather than deferring until later.
878+
[image lockFocus];
879+
[image unlockFocus];
880+
841881
return image;
842882
}
843883

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,7 @@ static void RCTAddContourEffectToLayer(
839839
const UIEdgeInsets &contourInsets,
840840
const RCTBorderStyle &contourStyle)
841841
{
842-
RCTPlatformImage *image = RCTGetBorderImage( // [macOS]
842+
RCTUIImage *image = RCTGetBorderImage( // [macOS]
843843
contourStyle, layer.bounds.size, cornerRadii, contourInsets, contourColors, [RCTUIColor clearColor], NO); // [macOS]
844844

845845
if (image == nil) {
@@ -850,13 +850,8 @@ static void RCTAddContourEffectToLayer(
850850
CGRect contentsCenter = CGRect{
851851
CGPoint{imageCapInsets.left / imageSize.width, imageCapInsets.top / imageSize.height},
852852
CGSize{(CGFloat)1.0 / imageSize.width, (CGFloat)1.0 / imageSize.height}};
853-
#if !TARGET_OS_OSX // [macOS]
854853
layer.contents = (id)image.CGImage;
855854
layer.contentsScale = image.scale;
856-
#else // [macOS
857-
layer.contents = (__bridge id) UIImageGetCGImageRef(image);
858-
layer.contentsScale = UIImageGetScale(image);
859-
#endif // macOS]
860855

861856
BOOL isResizable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
862857
if (isResizable) {
@@ -1292,17 +1287,13 @@ - (void)invalidateLayer
12921287
_boxShadowLayer.zPosition = _borderLayer.zPosition;
12931288
_boxShadowLayer.frame = RCTGetBoundingRect(_props->boxShadow, self.layer.bounds.size);
12941289

1295-
RCTPlatformImage *boxShadowImage = RCTGetBoxShadowImage( // [macOS]
1290+
RCTUIImage *boxShadowImage = RCTGetBoxShadowImage( // [macOS]
12961291
_props->boxShadow,
12971292
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
12981293
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
12991294
self.layer.bounds.size);
13001295

1301-
#if !TARGET_OS_OSX // [macOS]
13021296
_boxShadowLayer.contents = (id)boxShadowImage.CGImage;
1303-
#else // [macOS
1304-
_boxShadowLayer.contents = (__bridge id)UIImageGetCGImageRef(boxShadowImage);
1305-
#endif // macOS]
13061297
}
13071298

13081299
// clipping

packages/react-native/React/Fabric/Utils/RCTBoxShadow.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#import <React/RCTUIKit.h>
1313
#import <react/renderer/graphics/BoxShadow.h>
1414

15-
RCT_EXTERN RCTPlatformImage *RCTGetBoxShadowImage( // [macOS]
15+
RCT_EXTERN RCTUIImage *RCTGetBoxShadowImage( // [macOS]
1616
const std::vector<facebook::react::BoxShadow> &shadows,
1717
RCTCornerRadii cornerRadii,
1818
UIEdgeInsets edgeInsets,

packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ static void renderInsetShadows(
281281
CGContextRestoreGState(context);
282282
}
283283

284-
RCTPlatformImage *RCTGetBoxShadowImage( // [macOS]
284+
RCTUIImage *RCTGetBoxShadowImage( // [macOS]
285285
const std::vector<BoxShadow> &shadows,
286286
RCTCornerRadii cornerRadii,
287287
UIEdgeInsets edgeInsets,
@@ -293,7 +293,7 @@ static void renderInsetShadows(
293293
RCTUIGraphicsImageRenderer *const renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:boundingRect.size
294294
format:rendererFormat];
295295
// macOS]
296-
RCTPlatformImage *const boxShadowImage = // [macOS]
296+
RCTUIImage *const boxShadowImage = // [macOS]
297297
[renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
298298
auto [outsetShadows, insetShadows] = splitBoxShadowsByInset(shadows);
299299
const CGContextRef context = rendererContext.CGContext;

packages/react-native/React/Fabric/Utils/RCTLinearGradient.mm

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ + (CALayer *)gradientLayerWithSize:(CGSize)size gradient:(const LinearGradient &
2020
{
2121
RCTUIGraphicsImageRenderer *renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:size]; // [macOS]
2222
const auto &direction = gradient.direction;
23-
RCTPlatformImage *gradientImage = [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
23+
RCTUIImage *gradientImage = [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
2424
CGPoint startPoint;
2525
CGPoint endPoint;
2626

@@ -65,11 +65,7 @@ + (CALayer *)gradientLayerWithSize:(CGSize)size gradient:(const LinearGradient &
6565
}];
6666

6767
CALayer *gradientLayer = [CALayer layer];
68-
#if !TARGET_OS_OSX // [macOS]
69-
gradientLayer.contents = (__bridge id)gradientImage.CGImage;
70-
#else // [macOS
71-
gradientLayer.contents = (__bridge id)UIImageGetCGImageRef(gradientImage);
72-
#endif // macOS]
68+
gradientLayer.contents = (id)gradientImage.CGImage;
7369

7470
return gradientLayer;
7571
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ RCTPathCreateWithRoundedRect(CGRect bounds, RCTCornerInsets cornerInsets, const
6262
* `borderInsets` defines the border widths for each edge.
6363
* `scaleFactor` defines the backing scale factor of the device for supporting high-resolution drawing. // [macOS]
6464
*/
65-
RCT_EXTERN RCTPlatformImage *RCTGetBorderImage( // [macOS]
65+
RCT_EXTERN RCTUIImage *RCTGetBorderImage( // [macOS]
6666
RCTBorderStyle borderStyle,
6767
CGSize viewSize,
6868
RCTCornerRadii cornerRadii,

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
191191
return renderer;
192192
}
193193

194-
static RCTPlatformImage *RCTGetSolidBorderImage( // [macOS]
194+
static RCTUIImage *RCTGetSolidBorderImage( // [macOS]
195195
RCTCornerRadii cornerRadii,
196196
CGSize viewSize,
197197
UIEdgeInsets borderInsets,
@@ -231,7 +231,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
231231

232232
RCTUIGraphicsImageRenderer *const imageRenderer =
233233
RCTMakeUIGraphicsImageRenderer(size, backgroundColor, hasCornerRadii, drawToEdge);
234-
RCTPlatformImage *image = [imageRenderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
234+
RCTUIImage *image = [imageRenderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
235235
const CGContextRef context = rendererContext.CGContext;
236236
const CGRect rect = {.size = size};
237237
CGPathRef path = RCTPathCreateOuterOutline(drawToEdge, rect, cornerRadii);
@@ -461,7 +461,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
461461
// of gradients _along_ a path (NB: clipping a path and drawing a linear gradient
462462
// is _not_ equivalent).
463463

464-
static RCTPlatformImage *RCTGetDashedOrDottedBorderImage( // [macOS]
464+
static RCTUIImage *RCTGetDashedOrDottedBorderImage( // [macOS]
465465
RCTBorderStyle borderStyle,
466466
RCTCornerRadii cornerRadii,
467467
CGSize viewSize,
@@ -525,7 +525,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
525525
}];
526526
}
527527

528-
RCTPlatformImage *RCTGetBorderImage( // [macOS]
528+
RCTUIImage *RCTGetBorderImage( // [macOS]
529529
RCTBorderStyle borderStyle,
530530
CGSize viewSize,
531531
RCTCornerRadii cornerRadii,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1269,7 +1269,7 @@ - (void)displayLayer:(CALayer *)layer
12691269
return;
12701270
}
12711271

1272-
RCTPlatformImage *image = RCTGetBorderImage( // [macOS]
1272+
RCTUIImage *image = RCTGetBorderImage( // [macOS]
12731273
_borderStyle, layer.bounds.size, cornerRadii, borderInsets, borderColors, backgroundColor, self.clipsToBounds);
12741274

12751275
layer.backgroundColor = NULL;

0 commit comments

Comments
 (0)