WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
NEW
232731
spreadMethod is not supported for radial gradient
https://bugs.webkit.org/show_bug.cgi?id=232731
Summary
spreadMethod is not supported for radial gradient
Said Abou-Hallawa
Reported
2021-11-04 15:52:50 PDT
Created
attachment 443347
[details]
test case Open the attached test case. The radial gradient is not displayed correctly if spreadMethod is equal to "repeat" or "reflect"
Attachments
test case
(1.58 KB, image/svg+xml)
2021-11-04 15:52 PDT
,
Said Abou-Hallawa
no flags
Details
View All
Add attachment
proposed patch, testcase, etc.
Said Abou-Hallawa
Comment 1
2021-11-04 15:54:01 PDT
rdar://85014588
Ahmad Saleem
Comment 2
2025-12-25 00:50:32 PST
``` switch (m_spreadMethod) { case GradientSpreadMethod::Repeat: case GradientSpreadMethod::Reflect: { CGContextStateSaver saveState(platformContext); CGGradientDrawingOptions extendOptions = 0; CGRect boundingBox = CGContextGetClipBoundingBox(platformContext); if (CGRectIsInfinite(boundingBox) || CGRectIsEmpty(boundingBox)) break; CGFloat gradientRadius = data.endRadius - data.startRadius; if (gradientRadius <= 0) { CGGradientDrawingOptions padOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; m_platformRenderer->drawRadialGradient(platformContext, data.point0, data.startRadius, data.point1, data.endRadius, padOptions); break; } // Calculate focal point and center. FloatPoint focalPoint = data.point0; FloatPoint centerPoint = data.point1; // Vector from focal point to center. FloatSize focalToCenter = centerPoint - focalPoint; // Calculate distance from focal point to each corner. auto distanceFromPoint = [](const FloatPoint& point, CGFloat x, CGFloat y) -> CGFloat { CGFloat dx = x - point.x(); CGFloat dy = y - point.y(); return std::sqrt(dx * dx + dy * dy); }; // Find the maximum distance from the focal point to any corner. CGFloat maxDistance = std::max({ distanceFromPoint(focalPoint, CGRectGetMinX(boundingBox), CGRectGetMinY(boundingBox)), distanceFromPoint(focalPoint, CGRectGetMaxX(boundingBox), CGRectGetMinY(boundingBox)), distanceFromPoint(focalPoint, CGRectGetMinX(boundingBox), CGRectGetMaxY(boundingBox)), distanceFromPoint(focalPoint, CGRectGetMaxX(boundingBox), CGRectGetMaxY(boundingBox)), distanceFromPoint(centerPoint, CGRectGetMinX(boundingBox), CGRectGetMinY(boundingBox)), distanceFromPoint(centerPoint, CGRectGetMaxX(boundingBox), CGRectGetMinY(boundingBox)), distanceFromPoint(centerPoint, CGRectGetMinX(boundingBox), CGRectGetMaxY(boundingBox)), distanceFromPoint(centerPoint, CGRectGetMaxX(boundingBox), CGRectGetMaxY(boundingBox)) }); // Add extra buffer to ensure we fully cover corners. int repetitions = static_cast<int>(std::ceil((maxDistance - data.startRadius) / gradientRadius)) + 5; repetitions = std::min(repetitions, 100); bool flip = false; for (int i = 0; i <= repetitions; ++i) { CGFloat innerRadius = data.startRadius + i * gradientRadius; CGFloat outerRadius = data.startRadius + (i + 1) * gradientRadius; // Calculate the start and end points for this ring. CGFloat innerScale = innerRadius / data.endRadius; CGFloat outerScale = outerRadius / data.endRadius; FloatPoint ringStart = focalPoint + (focalToCenter * innerScale); FloatPoint ringEnd = focalPoint + (focalToCenter * outerScale); if (m_spreadMethod == GradientSpreadMethod::Reflect && flip) { // For reflect mode, swap start and end to reverse the gradient. m_platformRenderer->drawRadialGradient(platformContext, ringEnd, outerRadius, ringStart, innerRadius, extendOptions); } else { // Normal gradient direction. m_platformRenderer->drawRadialGradient(platformContext, ringStart, innerRadius, ringEnd, outerRadius, extendOptions); } if (m_spreadMethod == GradientSpreadMethod::Reflect) flip = !flip; } // Handle the area before startRadius if needed. if (data.startRadius > 0) { CGContextSaveGState(platformContext); CGContextBeginPath(platformContext); CGContextAddEllipseInRect(platformContext, CGRectMake( focalPoint.x() - data.startRadius, focalPoint.y() - data.startRadius, data.startRadius * 2, data.startRadius * 2)); CGContextClip(platformContext); if (!m_stops.stops().isEmpty()) { auto firstStop = m_stops.stops().first(); CGContextSetFillColorWithColor(platformContext, cachedSDRCGColorForColorspace(firstStop.color, colorSpace.value_or(DestinationColorSpace::SRGB())).get()); CGContextFillRect(platformContext, boundingBox); } CGContextRestoreGState(platformContext); } break; } case GradientSpreadMethod::Pad: { CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; m_platformRenderer->drawRadialGradient(platformContext, data.point0, data.startRadius, data.point1, data.endRadius, extendOptions); break; } } ``` In GradientCG.cpp (void Gradient::paint(CGContextRef platformContext, std::optional<DestinationColorSpace> colorSpace)). Above fixes all known bugzilla issues for me.
Ahmad Saleem
Comment 3
2025-12-29 03:12:18 PST
Dumping full code, which compiles to make it less mysterious on where to fit switch statement: ``` [&] (const RadialData& data) { bool needScaling = data.aspectRatio != 1; if (needScaling) { CGContextSaveGState(platformContext); // Scale from the center of the gradient. We only ever scale non-deprecated gradients, // for which point0 == point1. ASSERT(data.point0 == data.point1); CGContextTranslateCTM(platformContext, data.point0.x(), data.point0.y()); CGContextScaleCTM(platformContext, 1, 1 / data.aspectRatio); CGContextTranslateCTM(platformContext, -data.point0.x(), -data.point0.y()); } switch (m_spreadMethod) { case GradientSpreadMethod::Repeat: case GradientSpreadMethod::Reflect: { CGContextStateSaver saveState(platformContext); CGGradientDrawingOptions extendOptions = 0; CGRect boundingBox = CGContextGetClipBoundingBox(platformContext); if (CGRectIsInfinite(boundingBox) || CGRectIsEmpty(boundingBox)) break; CGFloat gradientRadius = data.endRadius - data.startRadius; if (gradientRadius <= 0) { CGGradientDrawingOptions padOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; m_platformRenderer->drawRadialGradient(platformContext, data.point0, data.startRadius, data.point1, data.endRadius, padOptions); break; } // Calculate focal point and center. FloatPoint focalPoint = data.point0; FloatPoint centerPoint = data.point1; // Vector from focal point to center. FloatSize focalToCenter = centerPoint - focalPoint; // Calculate distance from focal point to each corner. auto distanceFromPoint = [](const FloatPoint& point, CGFloat x, CGFloat y) -> CGFloat { CGFloat dx = x - point.x(); CGFloat dy = y - point.y(); return std::sqrt(dx * dx + dy * dy); }; // Find the maximum distance from the focal point to any corner. CGFloat maxDistance = std::max({ distanceFromPoint(focalPoint, CGRectGetMinX(boundingBox), CGRectGetMinY(boundingBox)), distanceFromPoint(focalPoint, CGRectGetMaxX(boundingBox), CGRectGetMinY(boundingBox)), distanceFromPoint(focalPoint, CGRectGetMinX(boundingBox), CGRectGetMaxY(boundingBox)), distanceFromPoint(focalPoint, CGRectGetMaxX(boundingBox), CGRectGetMaxY(boundingBox)), distanceFromPoint(centerPoint, CGRectGetMinX(boundingBox), CGRectGetMinY(boundingBox)), distanceFromPoint(centerPoint, CGRectGetMaxX(boundingBox), CGRectGetMinY(boundingBox)), distanceFromPoint(centerPoint, CGRectGetMinX(boundingBox), CGRectGetMaxY(boundingBox)), distanceFromPoint(centerPoint, CGRectGetMaxX(boundingBox), CGRectGetMaxY(boundingBox)) }); // Add extra buffer to ensure we fully cover corners. int repetitions = static_cast<int>(std::ceil((maxDistance - data.startRadius) / gradientRadius)) + 5; repetitions = std::min(repetitions, 100); bool flip = false; for (int i = 0; i <= repetitions; ++i) { CGFloat innerRadius = data.startRadius + i * gradientRadius; CGFloat outerRadius = data.startRadius + (i + 1) * gradientRadius; // Calculate the start and end points for this ring. CGFloat innerScale = innerRadius / data.endRadius; CGFloat outerScale = outerRadius / data.endRadius; FloatPoint ringStart = focalPoint + (focalToCenter * innerScale); FloatPoint ringEnd = focalPoint + (focalToCenter * outerScale); if (m_spreadMethod == GradientSpreadMethod::Reflect && flip) { // For reflect mode, swap start and end to reverse the gradient. m_platformRenderer->drawRadialGradient(platformContext, ringEnd, outerRadius, ringStart, innerRadius, extendOptions); } else { // Normal gradient direction. m_platformRenderer->drawRadialGradient(platformContext, ringStart, innerRadius, ringEnd, outerRadius, extendOptions); } if (m_spreadMethod == GradientSpreadMethod::Reflect) flip = !flip; } // Handle the area before startRadius if needed. if (data.startRadius > 0) { CGContextSaveGState(platformContext); CGContextBeginPath(platformContext); CGContextAddEllipseInRect(platformContext, CGRectMake( focalPoint.x() - data.startRadius, focalPoint.y() - data.startRadius, data.startRadius * 2, data.startRadius * 2)); CGContextClip(platformContext); if (!m_stops.stops().isEmpty()) { auto firstStop = m_stops.stops().first(); CGContextSetFillColorWithColor(platformContext, cachedSDRCGColorForColorspace(firstStop.color, colorSpace.value_or(DestinationColorSpace::SRGB())).get()); CGContextFillRect(platformContext, boundingBox); } CGContextRestoreGState(platformContext); } break; } case GradientSpreadMethod::Pad: { CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; m_platformRenderer->drawRadialGradient(platformContext, data.point0, data.startRadius, data.point1, data.endRadius, extendOptions); break; } } if (needScaling) CGContextRestoreGState(platformContext); }, ```
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug