Skip to content

Commit d1a4d6a

Browse files
cleanup
1 parent d86af94 commit d1a4d6a

File tree

4 files changed

+125
-115
lines changed

4 files changed

+125
-115
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ NS_ASSUME_NONNULL_BEGIN
1515
@interface RCTGradientUtils : NSObject
1616

1717
+ (std::vector<ProcessedColorStop>)getFixedColorStops:(const std::vector<ColorStop> &)colorStops gradientLineLength:(CGFloat)gradientLineLength;
18-
+ (std::vector<ProcessedColorStop>)processColorTransitionHints:(const std::vector<ProcessedColorStop> &)originalStops;
1918
@end
2019

2120
NS_ASSUME_NONNULL_END

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

Lines changed: 70 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -26,82 +26,12 @@
2626
return std::nullopt;
2727
}
2828

29-
@implementation RCTGradientUtils
30-
// https://drafts.csswg.org/css-images-4/#color-stop-fixup
31-
+ (std::vector<ProcessedColorStop>)getFixedColorStops:(const std::vector<ColorStop> &)colorStops gradientLineLength:(CGFloat)gradientLineLength
32-
{
33-
std::vector<ProcessedColorStop> fixedColorStops(colorStops.size());
34-
bool hasNullPositions = false;
35-
auto maxPositionSoFar = resolveColorStopPosition(colorStops[0].position, gradientLineLength);
36-
if (!maxPositionSoFar.has_value()) {
37-
maxPositionSoFar = 0.0f;
38-
}
39-
40-
for (size_t i = 0; i < colorStops.size(); i++) {
41-
const auto &colorStop = colorStops[i];
42-
auto newPosition = resolveColorStopPosition(colorStop.position, gradientLineLength);
43-
44-
if (!newPosition.has_value()) {
45-
// Step 1:
46-
// If the first color stop does not have a position,
47-
// set its position to 0%. If the last color stop does not have a position,
48-
// set its position to 100%.
49-
if (i == 0) {
50-
newPosition = 0.0f;
51-
} else if (i == colorStops.size() - 1) {
52-
newPosition = 1.0f;
53-
}
54-
}
55-
56-
// Step 2:
57-
// If a color stop or transition hint has a position
58-
// that is less than the specified position of any color stop or transition hint
59-
// before it in the list, set its position to be equal to the
60-
// largest specified position of any color stop or transition hint before it.
61-
if (newPosition.has_value()) {
62-
newPosition = std::max(newPosition.value(), maxPositionSoFar.value());
63-
fixedColorStops[i] = ProcessedColorStop{colorStop.color, newPosition};
64-
maxPositionSoFar = newPosition;
65-
} else {
66-
hasNullPositions = true;
67-
}
68-
}
69-
70-
// Step 3:
71-
// If any color stop still does not have a position,
72-
// then, for each run of adjacent color stops without positions,
73-
// set their positions so that they are evenly spaced between the preceding and
74-
// following color stops with positions.
75-
if (hasNullPositions) {
76-
size_t lastDefinedIndex = 0;
77-
for (size_t i = 1; i < fixedColorStops.size(); i++) {
78-
auto endPosition = fixedColorStops[i].position;
79-
if (endPosition.has_value()) {
80-
size_t unpositionedStops = i - lastDefinedIndex - 1;
81-
if (unpositionedStops > 0) {
82-
auto startPosition = fixedColorStops[lastDefinedIndex].position;
83-
if (startPosition.has_value()) {
84-
auto increment = (endPosition.value() - startPosition.value()) / (unpositionedStops + 1);
85-
for (size_t j = 1; j <= unpositionedStops; j++) {
86-
fixedColorStops[lastDefinedIndex + j] =
87-
ProcessedColorStop{colorStops[lastDefinedIndex + j].color, startPosition.value() + increment * j};
88-
}
89-
}
90-
}
91-
lastDefinedIndex = i;
92-
}
93-
}
94-
}
95-
96-
return fixedColorStops;
97-
}
98-
9929

10030
// Spec: https://drafts.csswg.org/css-images-4/#coloring-gradient-line (Refer transition hint section)
10131
// Browsers add 9 intermediate color stops when a transition hint is present
10232
// Algorithm is referred from Blink engine
10333
// [source](https://github.com/chromium/chromium/blob/a296b1bad6dc1ed9d751b7528f7ca2134227b828/third_party/blink/renderer/core/css/css_gradient_value.cc#L240).
104-
+ (std::vector<ProcessedColorStop>)processColorTransitionHints:(const std::vector<ProcessedColorStop> &)originalStops
34+
static std::vector<ProcessedColorStop> processColorTransitionHints(const std::vector<ProcessedColorStop>& originalStops)
10535
{
10636
auto colorStops = std::vector<ProcessedColorStop>(originalStops);
10737
int indexOffset = 0;
@@ -203,5 +133,74 @@ @implementation RCTGradientUtils
203133
return colorStops;
204134
}
205135

136+
137+
@implementation RCTGradientUtils
138+
// https://drafts.csswg.org/css-images-4/#color-stop-fixup
139+
+ (std::vector<ProcessedColorStop>)getFixedColorStops:(const std::vector<ColorStop> &)colorStops gradientLineLength:(CGFloat)gradientLineLength
140+
{
141+
std::vector<ProcessedColorStop> fixedColorStops(colorStops.size());
142+
bool hasNullPositions = false;
143+
auto maxPositionSoFar = resolveColorStopPosition(colorStops[0].position, gradientLineLength);
144+
if (!maxPositionSoFar.has_value()) {
145+
maxPositionSoFar = 0.0f;
146+
}
147+
148+
for (size_t i = 0; i < colorStops.size(); i++) {
149+
const auto &colorStop = colorStops[i];
150+
auto newPosition = resolveColorStopPosition(colorStop.position, gradientLineLength);
151+
152+
if (!newPosition.has_value()) {
153+
// Step 1:
154+
// If the first color stop does not have a position,
155+
// set its position to 0%. If the last color stop does not have a position,
156+
// set its position to 100%.
157+
if (i == 0) {
158+
newPosition = 0.0f;
159+
} else if (i == colorStops.size() - 1) {
160+
newPosition = 1.0f;
161+
}
162+
}
163+
164+
// Step 2:
165+
// If a color stop or transition hint has a position
166+
// that is less than the specified position of any color stop or transition hint
167+
// before it in the list, set its position to be equal to the
168+
// largest specified position of any color stop or transition hint before it.
169+
if (newPosition.has_value()) {
170+
newPosition = std::max(newPosition.value(), maxPositionSoFar.value());
171+
fixedColorStops[i] = ProcessedColorStop{colorStop.color, newPosition};
172+
maxPositionSoFar = newPosition;
173+
} else {
174+
hasNullPositions = true;
175+
}
176+
}
177+
178+
// Step 3:
179+
// If any color stop still does not have a position,
180+
// then, for each run of adjacent color stops without positions,
181+
// set their positions so that they are evenly spaced between the preceding and
182+
// following color stops with positions.
183+
if (hasNullPositions) {
184+
size_t lastDefinedIndex = 0;
185+
for (size_t i = 1; i < fixedColorStops.size(); i++) {
186+
auto endPosition = fixedColorStops[i].position;
187+
if (endPosition.has_value()) {
188+
size_t unpositionedStops = i - lastDefinedIndex - 1;
189+
if (unpositionedStops > 0) {
190+
auto startPosition = fixedColorStops[lastDefinedIndex].position;
191+
if (startPosition.has_value()) {
192+
auto increment = (endPosition.value() - startPosition.value()) / (unpositionedStops + 1);
193+
for (size_t j = 1; j <= unpositionedStops; j++) {
194+
fixedColorStops[lastDefinedIndex + j] =
195+
ProcessedColorStop{colorStops[lastDefinedIndex + j].color, startPosition.value() + increment * j};
196+
}
197+
}
198+
}
199+
lastDefinedIndex = i;
200+
}
201+
}
202+
}
203+
return processColorTransitionHints(fixedColorStops);
204+
}
206205
@end
207206

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@ + (CALayer *)gradientLayerWithSize:(CGSize)size gradient:(const LinearGradient &
4141
CGFloat dx = endPoint.x - startPoint.x;
4242
CGFloat dy = endPoint.y - startPoint.y;
4343
CGFloat gradientLineLength = sqrt(dx * dx + dy * dy);
44-
const auto processedStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops gradientLineLength:gradientLineLength];
45-
const auto colorStops = [RCTGradientUtils processColorTransitionHints:processedStops];
46-
44+
const auto colorStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops gradientLineLength:gradientLineLength];
45+
4746
CGContextRef context = rendererContext.CGContext;
4847
NSMutableArray *colors = [NSMutableArray array];
4948
CGFloat locations[colorStops.size()];

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

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,56 +17,81 @@
1717
namespace {
1818
using RadiusVector = std::pair<CGFloat, CGFloat>;
1919

20-
static RadiusVector RadiusToSide(CGFloat px, CGFloat py, CGFloat width, CGFloat height,
21-
bool isCircle, bool (*compare)(CGFloat, CGFloat)) {
22-
CGFloat dx1 = std::abs(px);
23-
CGFloat dy1 = std::abs(py);
24-
CGFloat dx2 = std::abs(px - width);
25-
CGFloat dy2 = std::abs(py - height);
20+
static RadiusVector RadiusToSide(CGFloat centerX, CGFloat centerY, CGFloat width, CGFloat height,
21+
bool isCircle, RadialGradientSize::SizeKeyword size) {
22+
CGFloat radiusXFromLeftSide = std::abs(centerX);
23+
CGFloat radiusYFromTopSide = std::abs(centerY);
24+
CGFloat radiusXFromRightSide = std::abs(centerX - width);
25+
CGFloat radiusYFromBottomSide = std::abs(centerY - height);
26+
CGFloat radiusX;
27+
CGFloat radiusY;
2628

27-
CGFloat dx = compare(dx1, dx2) ? dx1 : dx2;
28-
CGFloat dy = compare(dy1, dy2) ? dy1 : dy2;
29+
if (size == RadialGradientSize::SizeKeyword::ClosestSide) {
30+
radiusX = std::min(radiusXFromLeftSide, radiusXFromRightSide);
31+
radiusY = std::min(radiusYFromTopSide, radiusYFromBottomSide);
32+
} else {
33+
radiusX = std::max(radiusXFromLeftSide, radiusXFromRightSide);
34+
radiusY = std::max(radiusYFromTopSide, radiusYFromBottomSide);
35+
}
2936

3037
if (isCircle) {
31-
CGFloat radius = compare(dx, dy) ? dx : dy;
38+
CGFloat radius;
39+
if (size == RadialGradientSize::SizeKeyword::ClosestSide) {
40+
radius = std::min(radiusX, radiusY);
41+
} else {
42+
radius = std::max(radiusX, radiusY);
43+
}
3244
return {radius, radius};
3345
}
3446

35-
return {dx, dy};
47+
return {radiusX, radiusY};
3648
}
3749

38-
// Calculate ellipse radius maintaining aspect ratio
3950
static RadiusVector EllipseRadius(CGFloat offsetX, CGFloat offsetY, CGFloat aspectRatio) {
4051
if (aspectRatio == 0 || std::isinf(aspectRatio) || std::isnan(aspectRatio)) {
4152
return {0, 0};
4253
}
43-
54+
// Ellipse that passes through a point formula: (x-h)^2/a^2 + (y-k)^2/b^2 = 1
55+
// a = semi major axis length
56+
// b = semi minor axis length = a / aspectRatio
57+
// x - h = offsetX
58+
// y - k = offsetY
4459
CGFloat a = std::sqrt(offsetX * offsetX + offsetY * offsetY * aspectRatio * aspectRatio);
4560
return {a, a / aspectRatio};
4661
}
4762

48-
static RadiusVector RadiusToCorner(CGFloat px, CGFloat py, CGFloat width, CGFloat height,
49-
bool isCircle, bool (*compare)(CGFloat, CGFloat)) {
63+
static RadiusVector RadiusToCorner(CGFloat centerX, CGFloat centerY, CGFloat width, CGFloat height,
64+
bool isCircle, RadialGradientSize::SizeKeyword keyword) {
5065
std::array<CGPoint, 4> corners = {{{0, 0}, {width, 0}, {width, height}, {0, height}}};
5166

5267
size_t cornerIndex = 0;
53-
CGFloat distance = hypot(px - corners[cornerIndex].x, py - corners[cornerIndex].y);
68+
CGFloat distance = hypot(centerX - corners[cornerIndex].x, centerY - corners[cornerIndex].y);
69+
bool isClosestCorner = keyword == RadialGradientSize::SizeKeyword::ClosestCorner;
5470

5571
for (size_t i = 1; i < corners.size(); ++i) {
56-
CGFloat newDistance = hypot(px - corners[i].x, py - corners[i].y);
57-
if (compare(newDistance, distance)) {
58-
cornerIndex = i;
59-
distance = newDistance;
72+
CGFloat newDistance = hypot(centerX - corners[i].x, centerY - corners[i].y);
73+
if (isClosestCorner) {
74+
if (newDistance < distance) {
75+
distance = newDistance;
76+
cornerIndex = i;
77+
}
78+
} else {
79+
if (newDistance > distance) {
80+
distance = newDistance;
81+
cornerIndex = i;
82+
}
6083
}
6184
}
6285

6386
if (isCircle) {
6487
return {distance, distance};
6588
}
6689

67-
const RadiusVector sideRadius = RadiusToSide(px, py, width, height, false, compare);
68-
return EllipseRadius(corners[cornerIndex].x - px,
69-
corners[cornerIndex].y - py,
90+
// https://www.w3.org/TR/css-images-3/#typedef-radial-size
91+
// Aspect ratio of corner size ellipse is same as the respective side size ellipse
92+
const RadiusVector sideRadius = RadiusToSide(centerX, centerY, width, height, false, isClosestCorner ? RadialGradientSize::SizeKeyword::ClosestSide : RadialGradientSize::SizeKeyword::FarthestSide);
93+
return EllipseRadius(corners[cornerIndex].x - centerX,
94+
corners[cornerIndex].y - centerY,
7095
sideRadius.first / sideRadius.second);
7196
}
7297

@@ -86,23 +111,17 @@ static RadiusVector GetRadialGradientRadius(bool isCircle, const RadialGradientS
86111

87112
if (std::holds_alternative<RadialGradientSize::SizeKeyword>(size.value)) {
88113
const auto& keyword = std::get<RadialGradientSize::SizeKeyword>(size.value);
89-
if (keyword == RadialGradientSize::SizeKeyword::ClosestSide) {
90-
return RadiusToSide(centerX, centerY, width, height, isCircle,
91-
[](CGFloat a, CGFloat b) { return a < b; });
92-
}
93-
if (keyword == RadialGradientSize::SizeKeyword::FarthestSide) {
94-
return RadiusToSide(centerX, centerY, width, height, isCircle,
95-
[](CGFloat a, CGFloat b) { return a > b; });
114+
if (keyword == RadialGradientSize::SizeKeyword::ClosestSide || keyword == RadialGradientSize::SizeKeyword::FarthestSide) {
115+
return RadiusToSide(centerX, centerY, width, height, isCircle, keyword);
96116
}
117+
97118
if (keyword == RadialGradientSize::SizeKeyword::ClosestCorner) {
98-
return RadiusToCorner(centerX, centerY, width, height, isCircle,
99-
[](CGFloat a, CGFloat b) { return a < b; });
119+
return RadiusToCorner(centerX, centerY, width, height, isCircle, keyword);
100120
}
101121
}
102122

103123
// defaults to farthest corner
104-
return RadiusToCorner(centerX, centerY, width, height, isCircle,
105-
[](CGFloat a, CGFloat b) { return a > b; });
124+
return RadiusToCorner(centerX, centerY, width, height, isCircle, RadialGradientSize::SizeKeyword::FarthestCorner);
106125
}
107126
} // namespace
108127

@@ -114,13 +133,11 @@ + (CALayer *)gradientLayerWithSize:(CGSize)size gradient:(const RadialGradient &
114133
UIImage *gradientImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
115134
CGContextRef context = rendererContext.CGContext;
116135

117-
// Calculate center point from position
118136
CGPoint centerPoint = CGPointMake(
119137
gradient.position.x.resolve(size.width),
120138
gradient.position.y.resolve(size.height)
121139
);
122140

123-
// Calculate radii using our helper functions
124141
bool isCircle = (gradient.shape == RadialGradientShape::Circle);
125142
auto [radiusX, radiusY] = GetRadialGradientRadius(
126143
isCircle,
@@ -131,22 +148,18 @@ + (CALayer *)gradientLayerWithSize:(CGSize)size gradient:(const RadialGradient &
131148
size.height
132149
);
133150

134-
// For elliptical gradients, scale the context
135151
CGFloat scale = 1.0;
136152
if (radiusX != radiusY && gradient.shape != RadialGradientShape::Circle) {
137153
scale = radiusX / radiusY;
138-
// Save the original context state
139154
CGContextSaveGState(context);
140155
// Scale the context to make the circular gradient appear elliptical
141156
CGContextTranslateCTM(context, centerPoint.x, centerPoint.y);
142157
CGContextScaleCTM(context, 1.0, 1.0/scale);
143158
CGContextTranslateCTM(context, -centerPoint.x, -centerPoint.y);
144-
// Use the larger radius for the gradient
145159
radiusX = std::max(radiusX, radiusY * scale);
146160
}
147161

148-
const auto processedStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops gradientLineLength:radiusX];
149-
const auto colorStops = [RCTGradientUtils processColorTransitionHints:processedStops];
162+
const auto colorStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops gradientLineLength:radiusX];
150163

151164
NSMutableArray *colors = [NSMutableArray array];
152165
CGFloat locations[colorStops.size()];

0 commit comments

Comments
 (0)