diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h index dd234fb7b1616d..d6954f6dbc2ef7 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h @@ -6,6 +6,7 @@ */ #import +#import #import @class RCTBridge; @@ -131,6 +132,11 @@ NS_ASSUME_NONNULL_BEGIN /// @return: `YES` to use RuntimeScheduler, `NO` to use JavaScript scheduler. The default value is `YES`. - (BOOL)runtimeSchedulerEnabled; +/** + * The default `RCTColorSpace` for the app. It defaults to `RCTColorSpaceSRGB`. + */ +@property (nonatomic, readonly) RCTColorSpace defaultColorSpace; + @property (nonatomic, strong) RCTSurfacePresenterBridgeAdapter *bridgeAdapter; /// This method returns a map of Component Descriptors and Components classes that needs to be registered in the diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index 702c4ab2888ad9..fc186499b8e3bc 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -6,11 +6,13 @@ */ #import "RCTAppDelegate.h" +#import #import #import #import #import #import +#import #import #import "RCTAppDelegate+Protected.h" #import "RCTAppSetupUtils.h" @@ -75,6 +77,7 @@ - (instancetype)init - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { RCTSetNewArchEnabled([self newArchEnabled]); + [RCTColorSpaceUtils applyDefaultColorSpace:self.defaultColorSpace]; BOOL enableTM = self.turboModuleEnabled; BOOL fabricEnabled = self.fabricEnabled; BOOL enableBridgeless = self.bridgelessEnabled; @@ -225,6 +228,11 @@ - (void)windowScene:(UIWindowScene *)windowScene } } +- (RCTColorSpace)defaultColorSpace +{ + return RCTColorSpaceSRGB; +} + #pragma mark - New Arch Enabled settings - (BOOL)newArchEnabled diff --git a/packages/react-native/React/Base/RCTConvert.h b/packages/react-native/React/Base/RCTConvert.h index 2d55e7c99acf12..e8db7a07623fa3 100644 --- a/packages/react-native/React/Base/RCTConvert.h +++ b/packages/react-native/React/Base/RCTConvert.h @@ -17,6 +17,15 @@ #import #import +typedef NS_ENUM(NSInteger, RCTColorSpace) { + RCTColorSpaceSRGB, + RCTColorSpaceDisplayP3, +}; + +// Change the default color space +RCTColorSpace RCTGetDefaultColorSpace(void); +RCT_EXTERN void RCTSetDefaultColorSpace(RCTColorSpace colorSpace); + /** * This class provides a collection of conversion functions for mapping * JSON objects to native types and classes. These are useful when writing @@ -91,6 +100,13 @@ typedef NSURL RCTFileURL; + (CGAffineTransform)CGAffineTransform:(id)json; ++ (UIColor *)UIColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; ++ (UIColor *)UIColorWithRed:(CGFloat)red + green:(CGFloat)green + blue:(CGFloat)blue + alpha:(CGFloat)alpha + andColorSpace:(RCTColorSpace)colorSpace; ++ (RCTColorSpace)RCTColorSpaceFromString:(NSString *)colorSpace; + (UIColor *)UIColor:(id)json; + (CGColorRef)CGColor:(id)json CF_RETURNS_NOT_RETAINED; diff --git a/packages/react-native/React/Base/RCTConvert.mm b/packages/react-native/React/Base/RCTConvert.mm index 1367d93a873291..2d2775f50f39bf 100644 --- a/packages/react-native/React/Base/RCTConvert.mm +++ b/packages/react-native/React/Base/RCTConvert.mm @@ -879,6 +879,48 @@ + (UIEdgeInsets)UIEdgeInsets:(id)json return names; } +// The iOS side is kept in synch with the C++ side by using the +// RCTAppDelegate which, at startup, sets the default color space. +// The usage of dispatch_once and of once_flag ensoure that those are +// set only once when the app starts and that they can't change while +// the app is running. +static RCTColorSpace _defaultColorSpace = RCTColorSpaceSRGB; +RCTColorSpace RCTGetDefaultColorSpace(void) +{ + return _defaultColorSpace; +} +void RCTSetDefaultColorSpace(RCTColorSpace colorSpace) +{ + _defaultColorSpace = colorSpace; +} + ++ (UIColor *)UIColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha +{ + RCTColorSpace space = RCTGetDefaultColorSpace(); + return [self UIColorWithRed:red green:green blue:blue alpha:alpha andColorSpace:space]; +} ++ (UIColor *)UIColorWithRed:(CGFloat)red + green:(CGFloat)green + blue:(CGFloat)blue + alpha:(CGFloat)alpha + andColorSpace:(RCTColorSpace)colorSpace +{ + if (colorSpace == RCTColorSpaceDisplayP3) { + return [UIColor colorWithDisplayP3Red:red green:green blue:blue alpha:alpha]; + } + return [UIColor colorWithRed:red green:green blue:blue alpha:alpha]; +} + ++ (RCTColorSpace)RCTColorSpaceFromString:(NSString *)colorSpace +{ + if ([colorSpace isEqualToString:@"display-p3"]) { + return RCTColorSpaceDisplayP3; + } else if ([colorSpace isEqualToString:@"srgb"]) { + return RCTColorSpaceSRGB; + } + return RCTGetDefaultColorSpace(); +} + + (UIColor *)UIColor:(id)json { if (!json) { @@ -887,21 +929,29 @@ + (UIColor *)UIColor:(id)json if ([json isKindOfClass:[NSArray class]]) { NSArray *components = [self NSNumberArray:json]; CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0; - return [UIColor colorWithRed:[self CGFloat:components[0]] - green:[self CGFloat:components[1]] - blue:[self CGFloat:components[2]] - alpha:alpha]; + return [self UIColorWithRed:[self CGFloat:components[0]] + green:[self CGFloat:components[1]] + blue:[self CGFloat:components[2]] + alpha:alpha]; } else if ([json isKindOfClass:[NSNumber class]]) { NSUInteger argb = [self NSUInteger:json]; CGFloat a = ((argb >> 24) & 0xFF) / 255.0; CGFloat r = ((argb >> 16) & 0xFF) / 255.0; CGFloat g = ((argb >> 8) & 0xFF) / 255.0; CGFloat b = (argb & 0xFF) / 255.0; - return [UIColor colorWithRed:r green:g blue:b alpha:a]; + return [self UIColorWithRed:r green:g blue:b alpha:a]; } else if ([json isKindOfClass:[NSDictionary class]]) { NSDictionary *dictionary = json; id value = nil; - if ((value = [dictionary objectForKey:@"semantic"])) { + NSString *rawColorSpace = [dictionary objectForKey:@"space"]; + if ([rawColorSpace isEqualToString:@"display-p3"] || [rawColorSpace isEqualToString:@"srgb"]) { + CGFloat r = [[dictionary objectForKey:@"r"] floatValue]; + CGFloat g = [[dictionary objectForKey:@"g"] floatValue]; + CGFloat b = [[dictionary objectForKey:@"b"] floatValue]; + CGFloat a = [[dictionary objectForKey:@"a"] floatValue]; + RCTColorSpace colorSpace = [self RCTColorSpaceFromString:rawColorSpace]; + return [self UIColorWithRed:r green:g blue:b alpha:a andColorSpace:colorSpace]; + } else if ((value = [dictionary objectForKey:@"semantic"])) { if ([value isKindOfClass:[NSString class]]) { NSString *semanticName = value; UIColor *color = [UIColor colorNamed:semanticName]; diff --git a/packages/react-native/React/Fabric/Utils/RCTColorSpaceUtils.h b/packages/react-native/React/Fabric/Utils/RCTColorSpaceUtils.h new file mode 100644 index 00000000000000..68456babda3ba8 --- /dev/null +++ b/packages/react-native/React/Fabric/Utils/RCTColorSpaceUtils.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#import + +@interface RCTColorSpaceUtils : NSObject + ++ (void)applyDefaultColorSpace:(RCTColorSpace)colorSpace; + +@end diff --git a/packages/react-native/React/Fabric/Utils/RCTColorSpaceUtils.mm b/packages/react-native/React/Fabric/Utils/RCTColorSpaceUtils.mm new file mode 100644 index 00000000000000..051547ecf595c4 --- /dev/null +++ b/packages/react-native/React/Fabric/Utils/RCTColorSpaceUtils.mm @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTColorSpaceUtils.h" + +#import + +@implementation RCTColorSpaceUtils + ++ (void)applyDefaultColorSpace:(RCTColorSpace)colorSpace +{ + facebook::react::ColorSpace cxxColorSpace = + colorSpace == RCTColorSpaceSRGB ? facebook::react::ColorSpace::sRGB : facebook::react::ColorSpace::DisplayP3; + + RCTSetDefaultColorSpace(colorSpace); + facebook::react::setDefaultColorSpace(cxxColorSpace); +} + +@end diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.cpp b/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.cpp new file mode 100644 index 00000000000000..428747a31948c1 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "ColorComponents.h" +#include + +namespace facebook::react { + +static ColorSpace defaultColorSpace = ColorSpace::sRGB; + +ColorSpace getDefaultColorSpace() { + return defaultColorSpace; +} + +void setDefaultColorSpace(ColorSpace newColorSpace) { + defaultColorSpace = newColorSpace; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.h b/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.h index 0a3887a3caeb7e..ce60f7a4074f4a 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.h @@ -9,11 +9,17 @@ namespace facebook::react { +enum class ColorSpace { sRGB, DisplayP3 }; + +ColorSpace getDefaultColorSpace(); +void setDefaultColorSpace(ColorSpace newColorSpace); + struct ColorComponents { float red{0}; float green{0}; float blue{0}; float alpha{0}; + ColorSpace colorSpace{getDefaultColorSpace()}; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec b/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec index b5faee3286fc0e..94743610da2fe2 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec +++ b/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec @@ -61,7 +61,8 @@ Pod::Spec.new do |s| s.dependency "glog" s.dependency "RCT-Folly/Fabric", folly_version - s.dependency "React-Core/Default", version + s.dependency "React-jsi" + s.dependency "React-jsiexecutor" s.dependency "React-utils" s.dependency "DoubleConversion" s.dependency "fmt", "9.1.0" diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.h b/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.h index fbc07288fa318b..87b8fcab8c6f31 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.h @@ -44,6 +44,24 @@ inline void fromRawValueShared( result = colorFromComponents(colorComponents); } else { + if (value.hasType>()) { + const auto& items = (std::unordered_map)value; + if (items.find("space") != items.end()) { + colorComponents.red = (float)items.at("r"); + colorComponents.green = (float)items.at("g"); + colorComponents.blue = (float)items.at("b"); + colorComponents.alpha = (float)items.at("a"); + colorComponents.colorSpace = getDefaultColorSpace(); + std::string space = (std::string)items.at("space"); + if (space == "display-p3") { + colorComponents.colorSpace = ColorSpace::DisplayP3; + } else if (space == "srgb") { + colorComponents.colorSpace = ColorSpace::sRGB; + } + result = colorFromComponents(colorComponents); + return; + } + } result = parsePlatformColor(contextContainer, surfaceId, value); } } diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm index 36cd4ac08f7b75..3d1040f1542569 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm @@ -87,6 +87,12 @@ int32_t ColorFromUIColor(const std::shared_ptr &uiColor) UIColor *_Nullable UIColorFromComponentsColor(const facebook::react::ColorComponents &components) { + if (components.colorSpace == ColorSpace::DisplayP3) { + return [UIColor colorWithDisplayP3Red:components.red + green:components.green + blue:components.blue + alpha:components.alpha]; + } return [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha]; } } // anonymous namespace