Skip to content

Commit a40bd8e

Browse files
ryanlntnfacebook-github-bot
authored andcommitted
Add basic DisplayP3 color support (#42830)
Summary: This adds initial support for wide gamut (DisplayP3) colors to React Native iOS per the [RFC](react-native-community/discussions-and-proposals#738). It provides the ability to set the default color space to sRGB or DisplayP3 and provides the native code necessary to support `color()` function syntax per the [W3C CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/#color-function) spec. It does _not_ yet support animations and requires additional JS code before fully supporting the `color()` function syntax. bypass-github-export-checks ## Changelog: [IOS] [ADDED] - Add basic DisplayP3 color support Pull Request resolved: #42830 Test Plan: ![Screenshot_20240131-100112](https://github.com/facebook/react-native/assets/1944151/bbd011b1-dab0-47d6-b341-74fa8fac6757) Follow test steps from #42831 to test support for `color()` function syntax. To globally change the default color space to DisplayP3 make the following changes to RNTester AppDelegate.mm: ```diff + #import <React/RCTConvert.h> - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // ... + RCTSetDefaultColorSpace(RCTColorSpaceDisplayP3); return [super application:application didFinishLaunchingWithOptions:launchOptions]; } ``` Reviewed By: javache Differential Revision: D53380407 Pulled By: cipolleschi fbshipit-source-id: 938523958f9021e8d98bdb1d4e254047e3ecdad7
1 parent 3b93f0e commit a40bd8e

File tree

11 files changed

+180
-7
lines changed

11 files changed

+180
-7
lines changed

packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
#import <React/RCTBridgeDelegate.h>
9+
#import <React/RCTConvert.h>
910
#import <UIKit/UIKit.h>
1011

1112
@class RCTBridge;
@@ -131,6 +132,11 @@ NS_ASSUME_NONNULL_BEGIN
131132
/// @return: `YES` to use RuntimeScheduler, `NO` to use JavaScript scheduler. The default value is `YES`.
132133
- (BOOL)runtimeSchedulerEnabled;
133134

135+
/**
136+
* The default `RCTColorSpace` for the app. It defaults to `RCTColorSpaceSRGB`.
137+
*/
138+
@property (nonatomic, readonly) RCTColorSpace defaultColorSpace;
139+
134140
@property (nonatomic, strong) RCTSurfacePresenterBridgeAdapter *bridgeAdapter;
135141

136142
/// This method returns a map of Component Descriptors and Components classes that needs to be registered in the

packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
*/
77

88
#import "RCTAppDelegate.h"
9+
#import <React/RCTColorSpaceUtils.h>
910
#import <React/RCTCxxBridgeDelegate.h>
1011
#import <React/RCTLog.h>
1112
#import <React/RCTRootView.h>
1213
#import <React/RCTSurfacePresenterBridgeAdapter.h>
1314
#import <React/RCTUtils.h>
15+
#import <react/renderer/graphics/ColorComponents.h>
1416
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
1517
#import "RCTAppDelegate+Protected.h"
1618
#import "RCTAppSetupUtils.h"
@@ -75,6 +77,7 @@ - (instancetype)init
7577
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
7678
{
7779
RCTSetNewArchEnabled([self newArchEnabled]);
80+
[RCTColorSpaceUtils applyDefaultColorSpace:self.defaultColorSpace];
7881
BOOL enableTM = self.turboModuleEnabled;
7982
BOOL fabricEnabled = self.fabricEnabled;
8083
BOOL enableBridgeless = self.bridgelessEnabled;
@@ -225,6 +228,11 @@ - (void)windowScene:(UIWindowScene *)windowScene
225228
}
226229
}
227230

231+
- (RCTColorSpace)defaultColorSpace
232+
{
233+
return RCTColorSpaceSRGB;
234+
}
235+
228236
#pragma mark - New Arch Enabled settings
229237

230238
- (BOOL)newArchEnabled

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717
#import <React/RCTTextDecorationLineType.h>
1818
#import <yoga/Yoga.h>
1919

20+
typedef NS_ENUM(NSInteger, RCTColorSpace) {
21+
RCTColorSpaceSRGB,
22+
RCTColorSpaceDisplayP3,
23+
};
24+
25+
// Change the default color space
26+
RCTColorSpace RCTGetDefaultColorSpace(void);
27+
RCT_EXTERN void RCTSetDefaultColorSpace(RCTColorSpace colorSpace);
28+
2029
/**
2130
* This class provides a collection of conversion functions for mapping
2231
* JSON objects to native types and classes. These are useful when writing
@@ -91,6 +100,13 @@ typedef NSURL RCTFileURL;
91100

92101
+ (CGAffineTransform)CGAffineTransform:(id)json;
93102

103+
+ (UIColor *)UIColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
104+
+ (UIColor *)UIColorWithRed:(CGFloat)red
105+
green:(CGFloat)green
106+
blue:(CGFloat)blue
107+
alpha:(CGFloat)alpha
108+
andColorSpace:(RCTColorSpace)colorSpace;
109+
+ (RCTColorSpace)RCTColorSpaceFromString:(NSString *)colorSpace;
94110
+ (UIColor *)UIColor:(id)json;
95111
+ (CGColorRef)CGColor:(id)json CF_RETURNS_NOT_RETAINED;
96112

packages/react-native/React/Base/RCTConvert.mm

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,48 @@ + (UIEdgeInsets)UIEdgeInsets:(id)json
879879
return names;
880880
}
881881

882+
// The iOS side is kept in synch with the C++ side by using the
883+
// RCTAppDelegate which, at startup, sets the default color space.
884+
// The usage of dispatch_once and of once_flag ensoure that those are
885+
// set only once when the app starts and that they can't change while
886+
// the app is running.
887+
static RCTColorSpace _defaultColorSpace = RCTColorSpaceSRGB;
888+
RCTColorSpace RCTGetDefaultColorSpace(void)
889+
{
890+
return _defaultColorSpace;
891+
}
892+
void RCTSetDefaultColorSpace(RCTColorSpace colorSpace)
893+
{
894+
_defaultColorSpace = colorSpace;
895+
}
896+
897+
+ (UIColor *)UIColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha
898+
{
899+
RCTColorSpace space = RCTGetDefaultColorSpace();
900+
return [self UIColorWithRed:red green:green blue:blue alpha:alpha andColorSpace:space];
901+
}
902+
+ (UIColor *)UIColorWithRed:(CGFloat)red
903+
green:(CGFloat)green
904+
blue:(CGFloat)blue
905+
alpha:(CGFloat)alpha
906+
andColorSpace:(RCTColorSpace)colorSpace
907+
{
908+
if (colorSpace == RCTColorSpaceDisplayP3) {
909+
return [UIColor colorWithDisplayP3Red:red green:green blue:blue alpha:alpha];
910+
}
911+
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
912+
}
913+
914+
+ (RCTColorSpace)RCTColorSpaceFromString:(NSString *)colorSpace
915+
{
916+
if ([colorSpace isEqualToString:@"display-p3"]) {
917+
return RCTColorSpaceDisplayP3;
918+
} else if ([colorSpace isEqualToString:@"srgb"]) {
919+
return RCTColorSpaceSRGB;
920+
}
921+
return RCTGetDefaultColorSpace();
922+
}
923+
882924
+ (UIColor *)UIColor:(id)json
883925
{
884926
if (!json) {
@@ -887,21 +929,29 @@ + (UIColor *)UIColor:(id)json
887929
if ([json isKindOfClass:[NSArray class]]) {
888930
NSArray *components = [self NSNumberArray:json];
889931
CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
890-
return [UIColor colorWithRed:[self CGFloat:components[0]]
891-
green:[self CGFloat:components[1]]
892-
blue:[self CGFloat:components[2]]
893-
alpha:alpha];
932+
return [self UIColorWithRed:[self CGFloat:components[0]]
933+
green:[self CGFloat:components[1]]
934+
blue:[self CGFloat:components[2]]
935+
alpha:alpha];
894936
} else if ([json isKindOfClass:[NSNumber class]]) {
895937
NSUInteger argb = [self NSUInteger:json];
896938
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
897939
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
898940
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
899941
CGFloat b = (argb & 0xFF) / 255.0;
900-
return [UIColor colorWithRed:r green:g blue:b alpha:a];
942+
return [self UIColorWithRed:r green:g blue:b alpha:a];
901943
} else if ([json isKindOfClass:[NSDictionary class]]) {
902944
NSDictionary *dictionary = json;
903945
id value = nil;
904-
if ((value = [dictionary objectForKey:@"semantic"])) {
946+
NSString *rawColorSpace = [dictionary objectForKey:@"space"];
947+
if ([rawColorSpace isEqualToString:@"display-p3"] || [rawColorSpace isEqualToString:@"srgb"]) {
948+
CGFloat r = [[dictionary objectForKey:@"r"] floatValue];
949+
CGFloat g = [[dictionary objectForKey:@"g"] floatValue];
950+
CGFloat b = [[dictionary objectForKey:@"b"] floatValue];
951+
CGFloat a = [[dictionary objectForKey:@"a"] floatValue];
952+
RCTColorSpace colorSpace = [self RCTColorSpaceFromString:rawColorSpace];
953+
return [self UIColorWithRed:r green:g blue:b alpha:a andColorSpace:colorSpace];
954+
} else if ((value = [dictionary objectForKey:@"semantic"])) {
905955
if ([value isKindOfClass:[NSString class]]) {
906956
NSString *semanticName = value;
907957
UIColor *color = [UIColor colorNamed:semanticName];
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#import <React/RCTConvert.h>
11+
12+
@interface RCTColorSpaceUtils : NSObject
13+
14+
+ (void)applyDefaultColorSpace:(RCTColorSpace)colorSpace;
15+
16+
@end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTColorSpaceUtils.h"
9+
10+
#import <react/renderer/graphics/ColorComponents.h>
11+
12+
@implementation RCTColorSpaceUtils
13+
14+
+ (void)applyDefaultColorSpace:(RCTColorSpace)colorSpace
15+
{
16+
facebook::react::ColorSpace cxxColorSpace =
17+
colorSpace == RCTColorSpaceSRGB ? facebook::react::ColorSpace::sRGB : facebook::react::ColorSpace::DisplayP3;
18+
19+
RCTSetDefaultColorSpace(colorSpace);
20+
facebook::react::setDefaultColorSpace(cxxColorSpace);
21+
}
22+
23+
@end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "ColorComponents.h"
9+
#include <mutex>
10+
11+
namespace facebook::react {
12+
13+
static ColorSpace defaultColorSpace = ColorSpace::sRGB;
14+
15+
ColorSpace getDefaultColorSpace() {
16+
return defaultColorSpace;
17+
}
18+
19+
void setDefaultColorSpace(ColorSpace newColorSpace) {
20+
defaultColorSpace = newColorSpace;
21+
}
22+
23+
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@
99

1010
namespace facebook::react {
1111

12+
enum class ColorSpace { sRGB, DisplayP3 };
13+
14+
ColorSpace getDefaultColorSpace();
15+
void setDefaultColorSpace(ColorSpace newColorSpace);
16+
1217
struct ColorComponents {
1318
float red{0};
1419
float green{0};
1520
float blue{0};
1621
float alpha{0};
22+
ColorSpace colorSpace{getDefaultColorSpace()};
1723
};
1824

1925
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ Pod::Spec.new do |s|
6161

6262
s.dependency "glog"
6363
s.dependency "RCT-Folly/Fabric", folly_version
64-
s.dependency "React-Core/Default", version
64+
s.dependency "React-jsi"
65+
s.dependency "React-jsiexecutor"
6566
s.dependency "React-utils"
6667
s.dependency "DoubleConversion"
6768
s.dependency "fmt", "9.1.0"

packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ inline void fromRawValueShared(
4444

4545
result = colorFromComponents(colorComponents);
4646
} else {
47+
if (value.hasType<std::unordered_map<std::string, RawValue>>()) {
48+
const auto& items = (std::unordered_map<std::string, RawValue>)value;
49+
if (items.find("space") != items.end()) {
50+
colorComponents.red = (float)items.at("r");
51+
colorComponents.green = (float)items.at("g");
52+
colorComponents.blue = (float)items.at("b");
53+
colorComponents.alpha = (float)items.at("a");
54+
colorComponents.colorSpace = getDefaultColorSpace();
55+
std::string space = (std::string)items.at("space");
56+
if (space == "display-p3") {
57+
colorComponents.colorSpace = ColorSpace::DisplayP3;
58+
} else if (space == "srgb") {
59+
colorComponents.colorSpace = ColorSpace::sRGB;
60+
}
61+
result = colorFromComponents(colorComponents);
62+
return;
63+
}
64+
}
4765
result = parsePlatformColor(contextContainer, surfaceId, value);
4866
}
4967
}

0 commit comments

Comments
 (0)