Skip to content
Permalink
Browse files
ConicGradient angle should start at the x-axis, not at the top
https://bugs.webkit.org/show_bug.cgi?id=244254
rdar://problem/99041207

Reviewed by Said Abou-Hallawa and Aditya Keerthi.

* LayoutTests/fast/canvas/canvas-conic-gradient-angle-expected.html:
* LayoutTests/fast/canvas/canvas-conic-gradient-center-expected.html:
* LayoutTests/imported/w3c/web-platform-tests/html/canvas/element/fill-and-stroke-styles/2d.gradient.conic.negative.rotation-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/canvas/element/fill-and-stroke-styles/2d.gradient.conic.negative.rotation.html:
* LayoutTests/imported/w3c/web-platform-tests/html/canvas/element/fill-and-stroke-styles/2d.gradient.conic.positive.rotation-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/canvas/element/fill-and-stroke-styles/2d.gradient.conic.positive.rotation.html:
* LayoutTests/inspector/canvas/recording-html-2d-expected.txt:
Updating tests for conic gradient with angle starting from the x-axis.

* LayoutTests/platform/gtk-wk2/imported/w3c/web-platform-tests/html/canvas/element/fill-and-stroke-styles/2d.gradient.conic.negative.rotation-expected.txt: Added.
* LayoutTests/platform/gtk-wk2/imported/w3c/web-platform-tests/html/canvas/element/fill-and-stroke-styles/2d.gradient.conic.positive.rotation-expected.txt: Added.
* LayoutTests/platform/gtk-wk2/imported/w3c/web-platform-tests/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.conic.negative.rotation-expected.txt: Added.
* LayoutTests/platform/gtk-wk2/imported/w3c/web-platform-tests/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.conic.negative.rotation.worker-expected.txt: Added.
* LayoutTests/platform/gtk-wk2/imported/w3c/web-platform-tests/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.conic.positive.rotation-expected.txt: Added.
* LayoutTests/platform/gtk-wk2/imported/w3c/web-platform-tests/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.conic.positive.rotation.worker-expected.txt: Added.
Most of the related gtk-wk2 are passing now.

* Source/WTF/wtf/MathExtras.h:
Adding radians per turn constants.

* Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:
(WebCore::CanvasRenderingContext2DBase::createConicGradient):
For consistency with other canvas methods, startAngle should start from x-axis:
https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createconicgradient
Therefore, we are adding 90 deg (pi/2 rad) to startAngle.

* Source/WebCore/platform/graphics/GeometryUtilities.cpp:
(WebCore::normalizeAngleInRadians): Added
* Source/WebCore/platform/graphics/GeometryUtilities.h:
* Source/WebCore/platform/graphics/Path.cpp:
(WebCore::computeArcBounds):
Moving radian angle normalization logic from Path.cpp to GeometryUtilities.cpp so it can be reused.

Canonical link: https://commits.webkit.org/254038@main
  • Loading branch information
vitorroriz authored and nt1m committed Sep 1, 2022
1 parent a5a7844 commit cf0a117f4760fd9c36ba296a7724e524bf4d3027
Show file tree
Hide file tree
Showing 18 changed files with 78 additions and 35 deletions.
@@ -16,13 +16,13 @@
ctx.fillRect(x, y, width, height);
}

fillCanvasWithSolidColor("c1", 50, 0, 50, 50);
fillCanvasWithSolidColor("c1", 0, 50, 50, 50);
fillCanvasWithSolidColor("c2", 0, 0, 50, 50);
fillCanvasWithSolidColor("c2", 50, 50, 50, 50);
fillCanvasWithSolidColor("c3", 0, 50, 50, 50);
fillCanvasWithSolidColor("c3", 50, 0, 50, 50);
fillCanvasWithSolidColor("c4", 0, 0, 50, 50);
fillCanvasWithSolidColor("c4", 50, 50, 50, 50);
fillCanvasWithSolidColor("c1", 0, 0, 50, 50);
fillCanvasWithSolidColor("c1", 50, 50, 50, 50);
fillCanvasWithSolidColor("c2", 50, 0, 50, 50);
fillCanvasWithSolidColor("c2", 0, 50, 50, 50);
fillCanvasWithSolidColor("c3", 0, 0, 50, 50);
fillCanvasWithSolidColor("c3", 50, 50, 50, 50);
fillCanvasWithSolidColor("c4", 0, 50, 50, 50);
fillCanvasWithSolidColor("c4", 50, 0, 50, 50);
</script>
</body>
@@ -16,13 +16,13 @@
ctx.fillRect(x, y, width, height);
}

fillCanvasWithSolidColor("c1", 20, 0, 80, 20);
fillCanvasWithSolidColor("c1", 0, 20, 20, 80);
fillCanvasWithSolidColor("c2", 80, 0, 20, 20);
fillCanvasWithSolidColor("c2", 0, 20, 80, 80);
fillCanvasWithSolidColor("c3", 20, 0, 80, 80);
fillCanvasWithSolidColor("c3", 0, 80, 20, 20);
fillCanvasWithSolidColor("c4", 80, 0, 20, 80);
fillCanvasWithSolidColor("c4", 0, 80, 80, 20);
fillCanvasWithSolidColor("c1", 20, 20, 80, 80);
fillCanvasWithSolidColor("c1", 0, 0, 20, 20);
fillCanvasWithSolidColor("c2", 0, 0, 80, 20);
fillCanvasWithSolidColor("c2", 80, 20, 20, 80);
fillCanvasWithSolidColor("c3", 0, 0, 20, 80);
fillCanvasWithSolidColor("c3", 20, 80, 80, 20);
fillCanvasWithSolidColor("c4", 0, 0, 80, 80);
fillCanvasWithSolidColor("c4", 80, 80, 20, 20);
</script>
</body>
@@ -4,5 +4,5 @@ Actual output:
Expected output:


FAIL Conic gradient with negative rotation assert_equals: Red channel of the pixel at (25, 15) expected 255 but got 195
PASS Conic gradient with negative rotation

@@ -27,8 +27,8 @@ <h1>2d.gradient.conic.negative.rotation</h1>
g.addColorStop(0.75, "#f00");
ctx.fillStyle = g;
ctx.fillRect(0, 0, 100, 50);
_assertPixel(canvas, 25,15, 255,0,0,255, "25,15", "255,0,0,255");
_assertPixel(canvas, 75,40, 0,255,0,255, "75,40", "0,255,0,255");
_assertPixelApprox(canvas, 25,15, 255,0,0,255, "25,15", "255,0,0,255", 3);
_assertPixelApprox(canvas, 75,40, 0,255,0,255, "75,40", "0,255,0,255", 3);


});
@@ -4,5 +4,5 @@ Actual output:
Expected output:


FAIL Conic gradient with positive rotation assert_equals: Red channel of the pixel at (25, 15) expected 255 but got 195
PASS Conic gradient with positive rotation

@@ -27,8 +27,8 @@ <h1>2d.gradient.conic.positive.rotation</h1>
g.addColorStop(0.75, "#f00");
ctx.fillStyle = g;
ctx.fillRect(0, 0, 100, 50);
_assertPixel(canvas, 25,15, 255,0,0,255, "25,15", "255,0,0,255");
_assertPixel(canvas, 75,40, 0,255,0,255, "75,40", "0,255,0,255");
_assertPixelApprox(canvas, 25,15, 255,0,0,255, "25,15", "255,0,0,255", 3);
_assertPixelApprox(canvas, 75,40, 0,255,0,255, "75,40", "0,255,0,255", 3);


});
@@ -223,7 +223,7 @@ rebuildPath2D(3, "");
rebuildPath2D(4, "");
rebuildCanvasGradient(5, {"type":"linear-gradient","points":[1,2,3,4],"stops":[{"offset":1,"color":"rgb(255, 0, 0)"},{"offset":1,"color":"rgb(0, 0, 255)"}]});
rebuildCanvasGradient(6, {"type":"radial-gradient","points":[1,2,3,4,5,6],"stops":[]});
rebuildCanvasGradient(7, {"type":"conic-gradient","points":[2,3,1],"stops":[{"offset":1,"color":"rgb(0, 128, 0)"}]});
rebuildCanvasGradient(7, {"type":"conic-gradient","points":[2,3,2.570796489715576],"stops":[{"offset":1,"color":"rgb(0, 128, 0)"}]});
rebuildCanvasPattern(8, {"image":<filtered>,"repeat":"no-repeat"});
rebuildImage(9, <filtered>);
rebuildImageData(10, {"data":[0,0,0,0,0,0,0,0],"width":1,"height":2});
@@ -0,0 +1,8 @@
2d.gradient.conic.negative.rotation
Conic gradient with negative rotation
Actual output:
Expected output:


PASS Conic gradient with negative rotation

@@ -0,0 +1,8 @@
2d.gradient.conic.positive.rotation
Conic gradient with positive rotation
Actual output:
Expected output:


PASS Conic gradient with positive rotation

@@ -0,0 +1,7 @@
2d.gradient.conic.negative.rotation

Conic gradient with negative rotation


PASS Conic gradient with negative rotation

@@ -0,0 +1,3 @@

PASS Conic gradient with negative rotation

@@ -0,0 +1,7 @@
2d.gradient.conic.positive.rotation

Conic gradient with positive rotation


PASS Conic gradient with positive rotation

@@ -0,0 +1,3 @@

PASS Conic gradient with positive rotation

@@ -122,6 +122,7 @@ constexpr float gradientsPerDegreeFloat= 400.0f / 360.0f;
constexpr float degreesPerGradientFloat = 360.0f / 400.0f;
constexpr float turnsPerDegreeFloat = 1.0f / 360.0f;
constexpr float degreesPerTurnFloat = 360.0f;
constexpr float radiansPerTurnFloat = 2.0f * piFloat;

constexpr inline float deg2rad(float d) { return d * radiansPerDegreeFloat; }
constexpr inline float rad2deg(float r) { return r * degreesPerRadianFloat; }
@@ -49,6 +49,7 @@
#include "DisplayListRecorder.h"
#include "DisplayListReplayer.h"
#include "FloatQuad.h"
#include "GeometryUtilities.h"
#include "Gradient.h"
#include "HTMLCanvasElement.h"
#include "HTMLImageElement.h"
@@ -1934,7 +1935,9 @@ ExceptionOr<Ref<CanvasGradient>> CanvasRenderingContext2DBase::createConicGradie
if (!std::isfinite(angleInRadians) || !std::isfinite(x) || !std::isfinite(y))
return Exception { NotSupportedError };

return CanvasGradient::create(FloatPoint(x, y), angleInRadians, *this);
// Angle starts from x-axis for consistency within canvas methods. See https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createconicgradient
angleInRadians = normalizeAngleInRadians(angleInRadians) + piOverTwoFloat;
return CanvasGradient::create(FloatPoint(x, y), angleInRadians , *this);
}

ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2DBase::createPattern(CanvasImageSource&& image, const String& repetition)
@@ -346,4 +346,10 @@ float angleOfPointToSideOfIntersection(const FloatRect& boundingRect, const std:
return side == BoxSide::Top || side == BoxSide::Bottom ? angle : 90 - angle;
}

float normalizeAngleInRadians(float radians)
{
float circles = radians / radiansPerTurnFloat;
return radiansPerTurnFloat * (circles - floor(circles));
}

}
@@ -98,6 +98,8 @@ std::array<FloatPoint, 4> verticesForBox(const FloatRect&, const FloatPoint);
float toPositiveAngle(float angle);
float toRelatedAcuteAngle(float angle);

float normalizeAngleInRadians(float radians);

struct RotatedRect {
FloatPoint center;
FloatSize size;
@@ -32,6 +32,7 @@
#include "FloatPoint.h"
#include "FloatRect.h"
#include "FloatRoundedRect.h"
#include "GeometryUtilities.h"
#include "PathTraversalState.h"
#include "RoundedRect.h"
#include <math.h>
@@ -454,23 +455,17 @@ static FloatRect computeArcBounds(const FloatPoint& center, float radius, float
if (clockwise)
std::swap(start, end);

constexpr float fullCircle = 2 * piFloat;
if (end - start >= fullCircle) {
if (end - start >= radiansPerTurnFloat) {
auto diameter = radius * 2;
return { center.x() - radius, center.y() - radius, diameter, diameter };
}

auto normalize = [&] (float radians) {
double circles = radians / fullCircle;
return fullCircle * (circles - floor(circles));
};

start = normalize(start);
end = normalize(end);
start = normalizeAngleInRadians(start);
end = normalizeAngleInRadians(end);

auto lengthInRadians = end - start;
if (start > end)
lengthInRadians += fullCircle;
lengthInRadians += radiansPerTurnFloat;

FloatPoint startPoint { center.x() + radius * cos(start), center.y() + radius * sin(start) };
FloatPoint endPoint { center.x() + radius * cos(end), center.y() + radius * sin(end) };
@@ -479,7 +474,7 @@ static FloatRect computeArcBounds(const FloatPoint& center, float radius, float

auto contains = [&] (float angleToCheck) {
return (start < angleToCheck && start + lengthInRadians > angleToCheck)
|| (start > angleToCheck && start + lengthInRadians > angleToCheck + fullCircle);
|| (start > angleToCheck && start + lengthInRadians > angleToCheck + radiansPerTurnFloat);
};

if (contains(0))

0 comments on commit cf0a117

Please sign in to comment.