Add basic DisplayP3 color support (#42830)

Summary:
This adds initial support for wide gamut (DisplayP3) colors to React Native iOS per the [RFC](https://github.com/react-native-community/discussions-and-proposals/pull/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: https://github.com/facebook/react-native/pull/42830

Test Plan:
![Screenshot_20240131-100112](https://github.com/facebook/react-native/assets/1944151/bbd011b1-dab0-47d6-b341-74fa8fac6757)

Follow test steps from https://github.com/facebook/react-native/issues/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
This commit is contained in:
Ryan Linton
2024-02-27 05:16:34 -08:00
committed by Facebook GitHub Bot
parent 3b93f0e3c1
commit a40bd8e34a
11 changed files with 180 additions and 7 deletions
@@ -6,6 +6,7 @@
*/
#import <React/RCTBridgeDelegate.h>
#import <React/RCTConvert.h>
#import <UIKit/UIKit.h>
@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
@@ -6,11 +6,13 @@
*/
#import "RCTAppDelegate.h"
#import <React/RCTColorSpaceUtils.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <React/RCTUtils.h>
#import <react/renderer/graphics/ColorComponents.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
#import "RCTAppDelegate+Protected.h"
#import "RCTAppSetupUtils.h"
@@ -75,6 +77,7 @@ static NSDictionary *updateInitialProps(NSDictionary *initialProps, BOOL isFabri
- (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 @@ static NSDictionary *updateInitialProps(NSDictionary *initialProps, BOOL isFabri
}
}
- (RCTColorSpace)defaultColorSpace
{
return RCTColorSpaceSRGB;
}
#pragma mark - New Arch Enabled settings
- (BOOL)newArchEnabled
@@ -17,6 +17,15 @@
#import <React/RCTTextDecorationLineType.h>
#import <yoga/Yoga.h>
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;
+56 -6
View File
@@ -879,6 +879,48 @@ static NSString *RCTSemanticColorNames(void)
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 @@ static NSString *RCTSemanticColorNames(void)
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];
@@ -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 <React/RCTConvert.h>
@interface RCTColorSpaceUtils : NSObject
+ (void)applyDefaultColorSpace:(RCTColorSpace)colorSpace;
@end
@@ -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 <react/renderer/graphics/ColorComponents.h>
@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
@@ -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 <mutex>
namespace facebook::react {
static ColorSpace defaultColorSpace = ColorSpace::sRGB;
ColorSpace getDefaultColorSpace() {
return defaultColorSpace;
}
void setDefaultColorSpace(ColorSpace newColorSpace) {
defaultColorSpace = newColorSpace;
}
} // namespace facebook::react
@@ -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
@@ -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"
@@ -44,6 +44,24 @@ inline void fromRawValueShared(
result = colorFromComponents(colorComponents);
} else {
if (value.hasType<std::unordered_map<std::string, RawValue>>()) {
const auto& items = (std::unordered_map<std::string, RawValue>)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);
}
}
@@ -87,6 +87,12 @@ int32_t ColorFromUIColor(const std::shared_ptr<void> &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