Bug 691187 - Prune zero-length segments for canvas strokes. r=lsalzman

Differential Revision: https://phabricator.services.mozilla.com/D179462
This commit is contained in:
Andrew Osmond 2023-06-02 12:48:31 +00:00
Родитель 19abba01c7
Коммит 9975cde84f
23 изменённых файлов: 140 добавлений и 100 удалений

Просмотреть файл

@ -2955,7 +2955,7 @@ void CanvasRenderingContext2D::BeginPath() {
void CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding) {
EnsureUserSpacePath(aWinding);
if (!mPath) {
if (!mPath || mPath->IsEmpty()) {
return;
}
@ -2991,7 +2991,7 @@ void CanvasRenderingContext2D::Fill(const CanvasPath& aPath,
}
RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
if (!gfxpath) {
if (!gfxpath || gfxpath->IsEmpty()) {
return;
}
@ -3022,7 +3022,7 @@ void CanvasRenderingContext2D::Fill(const CanvasPath& aPath,
void CanvasRenderingContext2D::Stroke() {
EnsureUserSpacePath();
if (!mPath) {
if (!mPath || mPath->IsEmpty()) {
return;
}
@ -3065,7 +3065,7 @@ void CanvasRenderingContext2D::Stroke(const CanvasPath& aPath) {
RefPtr<gfx::Path> gfxpath =
aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
if (!gfxpath) {
if (!gfxpath || gfxpath->IsEmpty()) {
return;
}
@ -3219,6 +3219,10 @@ void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
Point p1(aX1, aY1);
Point p2(aX2, aY2);
if (!p1.IsFinite() || !p2.IsFinite() || !std::isfinite(aRadius)) {
return;
}
// Execute these calculations in double precision to avoid cumulative
// rounding errors.
double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx,
@ -3226,7 +3230,7 @@ void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
bool anticlockwise;
if (p0 == p1 || p1 == p2 || aRadius == 0) {
LineTo(p1.x, p1.y);
LineTo(p1);
return;
}
@ -3234,7 +3238,7 @@ void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
dir = (p2.x.value - p1.x.value) * (p0.y.value - p1.y.value) +
(p2.y.value - p1.y.value) * (p1.x.value - p0.x.value);
if (dir == 0) {
LineTo(p1.x, p1.y);
LineTo(p1);
return;
}
@ -3285,8 +3289,16 @@ void CanvasRenderingContext2D::Rect(double aX, double aY, double aW,
double aH) {
EnsureWritablePath();
if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) ||
!std::isfinite(aH)) {
return;
}
if (mPathBuilder) {
mPathBuilder->MoveTo(Point(aX, aY));
if (aW == 0 && aH == 0) {
return;
}
mPathBuilder->LineTo(Point(aX + aW, aY));
mPathBuilder->LineTo(Point(aX + aW, aY + aH));
mPathBuilder->LineTo(Point(aX, aY + aH));
@ -3294,6 +3306,9 @@ void CanvasRenderingContext2D::Rect(double aX, double aY, double aW,
} else {
mDSPathBuilder->MoveTo(
mTarget->GetTransform().TransformPoint(Point(aX, aY)));
if (aW == 0 && aH == 0) {
return;
}
mDSPathBuilder->LineTo(
mTarget->GetTransform().TransformPoint(Point(aX + aW, aY)));
mDSPathBuilder->LineTo(
@ -6310,21 +6325,30 @@ void CanvasPath::ClosePath() {
void CanvasPath::MoveTo(double aX, double aY) {
EnsurePathBuilder();
mPathBuilder->MoveTo(Point(ToFloat(aX), ToFloat(aY)));
Point pos(ToFloat(aX), ToFloat(aY));
if (!pos.IsFinite()) {
return;
}
mPathBuilder->MoveTo(pos);
}
void CanvasPath::LineTo(double aX, double aY) {
EnsurePathBuilder();
mPathBuilder->LineTo(Point(ToFloat(aX), ToFloat(aY)));
LineTo(Point(ToFloat(aX), ToFloat(aY)));
}
void CanvasPath::QuadraticCurveTo(double aCpx, double aCpy, double aX,
double aY) {
EnsurePathBuilder();
mPathBuilder->QuadraticBezierTo(gfx::Point(ToFloat(aCpx), ToFloat(aCpy)),
gfx::Point(ToFloat(aX), ToFloat(aY)));
Point cp1(ToFloat(aCpx), ToFloat(aCpy));
Point cp2(ToFloat(aX), ToFloat(aY));
if (!cp1.IsFinite() || !cp2.IsFinite() ||
(cp1 == mPathBuilder->CurrentPoint() && cp1 == cp2)) {
return;
}
mPathBuilder->QuadraticBezierTo(cp1, cp2);
}
void CanvasPath::BezierCurveTo(double aCp1x, double aCp1y, double aCp2x,
@ -6347,6 +6371,10 @@ void CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2,
Point p1(aX1, aY1);
Point p2(aX2, aY2);
if (!p1.IsFinite() || !p2.IsFinite() || !std::isfinite(aRadius)) {
return;
}
// Execute these calculations in double precision to avoid cumulative
// rounding errors.
double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx,
@ -6354,7 +6382,7 @@ void CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2,
bool anticlockwise;
if (p0 == p1 || p1 == p2 || aRadius == 0) {
LineTo(p1.x, p1.y);
LineTo(p1);
return;
}
@ -6362,7 +6390,7 @@ void CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2,
dir = (p2.x.value - p1.x.value) * (p0.y.value - p1.y.value) +
(p2.y.value - p1.y.value) * (p1.x.value - p0.x.value);
if (dir == 0) {
LineTo(p1.x, p1.y);
LineTo(p1);
return;
}
@ -6397,7 +6425,17 @@ void CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2,
}
void CanvasPath::Rect(double aX, double aY, double aW, double aH) {
EnsurePathBuilder();
if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) ||
!std::isfinite(aH)) {
return;
}
MoveTo(aX, aY);
if (aW == 0 && aH == 0) {
return;
}
LineTo(aX + aW, aY);
LineTo(aX + aW, aY + aH);
LineTo(aX, aY + aH);
@ -6443,6 +6481,10 @@ void CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY,
void CanvasPath::LineTo(const gfx::Point& aPoint) {
EnsurePathBuilder();
if (!aPoint.IsFinite() || aPoint == mPathBuilder->CurrentPoint()) {
return;
}
mPathBuilder->LineTo(aPoint);
}
@ -6450,6 +6492,11 @@ void CanvasPath::BezierTo(const gfx::Point& aCP1, const gfx::Point& aCP2,
const gfx::Point& aCP3) {
EnsurePathBuilder();
if (!aCP1.IsFinite() || !aCP2.IsFinite() || !aCP3.IsFinite() ||
(aCP1 == mPathBuilder->CurrentPoint() && aCP1 == aCP2 && aCP1 == aCP3)) {
return;
}
mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
}

Просмотреть файл

@ -337,11 +337,17 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
void MoveTo(double aX, double aY) override {
EnsureWritablePath();
mozilla::gfx::Point pos(ToFloat(aX), ToFloat(aY));
if (!pos.IsFinite()) {
return;
}
if (mPathBuilder) {
mPathBuilder->MoveTo(mozilla::gfx::Point(ToFloat(aX), ToFloat(aY)));
mPathBuilder->MoveTo(pos);
} else {
mDSPathBuilder->MoveTo(mTarget->GetTransform().TransformPoint(
mozilla::gfx::Point(ToFloat(aX), ToFloat(aY))));
mozilla::gfx::Point transformedPos =
mTarget->GetTransform().TransformPoint(pos);
mDSPathBuilder->MoveTo(transformedPos);
}
}
@ -355,17 +361,25 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
double aY) override {
EnsureWritablePath();
mozilla::gfx::Point cp1(ToFloat(aCpx), ToFloat(aCpy));
mozilla::gfx::Point cp2(ToFloat(aX), ToFloat(aY));
if (!cp1.IsFinite() || !cp2.IsFinite()) {
return;
}
if (mPathBuilder) {
mPathBuilder->QuadraticBezierTo(
mozilla::gfx::Point(ToFloat(aCpx), ToFloat(aCpy)),
mozilla::gfx::Point(ToFloat(aX), ToFloat(aY)));
if (cp1 == mPathBuilder->CurrentPoint() && cp1 == cp2) {
return;
}
mPathBuilder->QuadraticBezierTo(cp1, cp2);
} else {
mozilla::gfx::Matrix transform = mTarget->GetTransform();
mDSPathBuilder->QuadraticBezierTo(
transform.TransformPoint(
mozilla::gfx::Point(ToFloat(aCpx), ToFloat(aCpy))),
transform.TransformPoint(
mozilla::gfx::Point(ToFloat(aX), ToFloat(aY))));
mozilla::gfx::Point transformedPos = transform.TransformPoint(cp1);
if (transformedPos == mDSPathBuilder->CurrentPoint() && cp1 == cp2) {
return;
}
mDSPathBuilder->QuadraticBezierTo(transformedPos,
transform.TransformPoint(cp2));
}
}
@ -501,22 +515,45 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
enum class Style : uint8_t { STROKE = 0, FILL, MAX };
void LineTo(const mozilla::gfx::Point& aPoint) {
if (!aPoint.IsFinite()) {
return;
}
if (mPathBuilder) {
if (mPathBuilder->CurrentPoint() == aPoint) {
return;
}
mPathBuilder->LineTo(aPoint);
} else {
mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(aPoint));
mozilla::gfx::Point transformedPt =
mTarget->GetTransform().TransformPoint(aPoint);
if (mDSPathBuilder->CurrentPoint() == transformedPt) {
return;
}
mDSPathBuilder->LineTo(transformedPt);
}
}
void BezierTo(const mozilla::gfx::Point& aCP1,
const mozilla::gfx::Point& aCP2,
const mozilla::gfx::Point& aCP3) {
if (!aCP1.IsFinite() || !aCP2.IsFinite() || !aCP3.IsFinite()) {
return;
}
if (mPathBuilder) {
if (aCP1 == mPathBuilder->CurrentPoint() && aCP1 == aCP2 &&
aCP1 == aCP3) {
return;
}
mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
} else {
mozilla::gfx::Matrix transform = mTarget->GetTransform();
mDSPathBuilder->BezierTo(transform.TransformPoint(aCP1),
transform.TransformPoint(aCP2),
mozilla::gfx::Point transformedPos = transform.TransformPoint(aCP1);
if (transformedPos == mDSPathBuilder->CurrentPoint() && aCP1 == aCP2 &&
aCP1 == aCP3) {
return;
}
mDSPathBuilder->BezierTo(transformedPos, transform.TransformPoint(aCP2),
transform.TransformPoint(aCP3));
}
}

Просмотреть файл

@ -14252,7 +14252,7 @@ ctx.lineTo(50, 25);
ctx.closePath();
ctx.stroke();
todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
isPixel(ctx, 50,25, 0,255,0,255, 0);
}
</script>
@ -14324,7 +14324,7 @@ ctx.moveTo(50, 25);
ctx.bezierCurveTo(50, 25, 50, 25, 50, 25);
ctx.stroke();
todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
isPixel(ctx, 50,25, 0,255,0,255, 0);
}
</script>
@ -14356,7 +14356,7 @@ ctx.moveTo(50, 25);
ctx.lineTo(50, 25);
ctx.stroke();
todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
isPixel(ctx, 50,25, 0,255,0,255, 0);
}
</script>
@ -14389,7 +14389,7 @@ ctx.stroke();
ctx.strokeRect(50, 25, 0, 0);
todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
isPixel(ctx, 50,25, 0,255,0,255, 0);
}
</script>

Просмотреть файл

@ -1007,6 +1007,8 @@ class Path : public external::AtomicRefCounted<Path> {
virtual Point ComputePointAtLength(Float aLength, Point* aTangent = nullptr);
virtual bool IsEmpty() const { return false; }
protected:
Path();
void EnsureFlattenedPath();

Просмотреть файл

@ -134,6 +134,7 @@ void PathBuilderD2D::LineTo(const Point& aPoint) {
mSink->AddLine(D2DPoint(aPoint));
mCurrentPoint = aPoint;
mFigureEmpty = false;
}
void PathBuilderD2D::BezierTo(const Point& aCP1, const Point& aCP2,
@ -143,6 +144,7 @@ void PathBuilderD2D::BezierTo(const Point& aCP1, const Point& aCP2,
D2D1::BezierSegment(D2DPoint(aCP1), D2DPoint(aCP2), D2DPoint(aCP3)));
mCurrentPoint = aCP3;
mFigureEmpty = false;
}
void PathBuilderD2D::QuadraticBezierTo(const Point& aCP1, const Point& aCP2) {
@ -151,6 +153,7 @@ void PathBuilderD2D::QuadraticBezierTo(const Point& aCP1, const Point& aCP2) {
D2D1::QuadraticBezierSegment(D2DPoint(aCP1), D2DPoint(aCP2)));
mCurrentPoint = aCP2;
mFigureEmpty = false;
}
void PathBuilderD2D::Close() {
@ -248,6 +251,7 @@ void PathBuilderD2D::Arc(const Point& aOrigin, Float aRadius, Float aStartAngle,
}
mCurrentPoint = endPoint;
mFigureEmpty = false;
}
void PathBuilderD2D::EnsureActive(const Point& aPoint) {
@ -269,8 +273,8 @@ already_AddRefed<Path> PathBuilderD2D::Finish() {
return nullptr;
}
return MakeAndAddRef<PathD2D>(mGeometry, mFigureActive, mCurrentPoint,
mFillRule, mBackendType);
return MakeAndAddRef<PathD2D>(mGeometry, mFigureActive, mFigureEmpty,
mCurrentPoint, mFillRule, mBackendType);
}
already_AddRefed<PathBuilder> PathD2D::CopyToBuilder(FillRule aFillRule) const {

Просмотреть файл

@ -56,6 +56,7 @@ class PathBuilderD2D : public PathBuilder {
RefPtr<ID2D1PathGeometry> mGeometry;
bool mFigureActive;
bool mFigureEmpty = true;
FillRule mFillRule;
BackendType mBackendType;
};
@ -63,10 +64,11 @@ class PathBuilderD2D : public PathBuilder {
class PathD2D : public Path {
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathD2D, override)
PathD2D(ID2D1PathGeometry* aGeometry, bool aEndedActive,
PathD2D(ID2D1PathGeometry* aGeometry, bool aEndedActive, bool aIsEmpty,
const Point& aEndPoint, FillRule aFillRule, BackendType aBackendType)
: mGeometry(aGeometry),
mEndedActive(aEndedActive),
mIsEmpty(aIsEmpty),
mEndPoint(aEndPoint),
mFillRule(aFillRule),
mBackendType(aBackendType) {}
@ -93,6 +95,8 @@ class PathD2D : public Path {
virtual FillRule GetFillRule() const { return mFillRule; }
bool IsEmpty() const override { return mIsEmpty; }
ID2D1Geometry* GetGeometry() { return mGeometry; }
private:
@ -101,6 +105,7 @@ class PathD2D : public Path {
mutable RefPtr<ID2D1PathGeometry> mGeometry;
bool mEndedActive;
bool mIsEmpty;
Point mEndPoint;
FillRule mFillRule;
BackendType mBackendType;

Просмотреть файл

@ -7,6 +7,7 @@
#include "PathSkia.h"
#include "HelpersSkia.h"
#include "PathHelpers.h"
#include "mozilla/UniquePtr.h"
#include "skia/include/core/SkPathUtils.h"
#include "skia/src/core/SkGeometry.h"
@ -270,4 +271,11 @@ Maybe<Rect> PathSkia::AsRect() const {
}
return Nothing();
}
bool PathSkia::IsEmpty() const {
// Move/Close/Done segments are not included in the mask so as long as any
// flag is set, we know that the path is non-empty.
return mPath.getSegmentMasks() != 0;
}
} // namespace mozilla::gfx

Просмотреть файл

@ -95,6 +95,8 @@ class PathSkia : public Path {
const Matrix& aTransform, SkPath& aFillPath,
const Maybe<Rect>& aClipRect = Nothing()) const;
bool IsEmpty() const override;
private:
friend class DrawTargetSkia;

Просмотреть файл

@ -1,5 +0,0 @@
[2d.path.stroke.prune.arc.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Zero-length line segments from arcTo and arc are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,5 +0,0 @@
[2d.path.stroke.prune.closed.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Zero-length line segments from closed paths are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,5 +0,0 @@
[2d.path.stroke.prune.curve.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Zero-length line segments from quadraticCurveTo and bezierCurveTo are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,5 +0,0 @@
[2d.path.stroke.prune.line.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Zero-length line segments from lineTo are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,5 +0,0 @@
[2d.path.stroke.prune.rect.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Zero-length line segments from rect and strokeRect are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.arc.html]
[Zero-length line segments from arcTo and arc are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.arc.worker.html]
[Zero-length line segments from arcTo and arc are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.closed.html]
[Zero-length line segments from closed paths are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.closed.worker.html]
[Zero-length line segments from closed paths are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.curve.html]
[Zero-length line segments from quadraticCurveTo and bezierCurveTo are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.curve.worker.html]
[Zero-length line segments from quadraticCurveTo and bezierCurveTo are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.line.html]
[Zero-length line segments from lineTo are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.line.worker.html]
[Zero-length line segments from lineTo are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.rect.html]
[Zero-length line segments from rect and strokeRect are removed before stroking]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[2d.path.stroke.prune.rect.worker.html]
[Zero-length line segments from rect and strokeRect are removed before stroking]
expected: FAIL