1717namespace {
1818using 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
3950static 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