Bug 1923636 - Avoid copying paths when transforming. r=aosmond

TransformedCopyToBuilder is called any time in Canvas2D the transform
is modified and then a path op is used. On cases that repeatedly change
the transform while building paths, this leads to fairly significant
expense in repeatedly copying the path contents as well as allocating
and deallocating paths.

To work around this, a new primitive TransformedMoveToBuilder is provided
that allows in-place transforming of the path contents. Because paths that
are "finished" and referenced externally can't be safely modified, extra
static methods (i.e. ToBuilder) are added to guarantee that there are no
other refs to the path before modifying its contents.

One snag is that ArcParams could previously not handle transforms, so it
is modified to allow a transform to be supplied to an Arc op, which is
only flattened out when it is streamed to a sink.

Differential Revision: https://phabricator.services.mozilla.com/D225548
This commit is contained in:
Lee Salzman 2024-10-14 22:48:45 +00:00
parent edb3abd50c
commit 6fc4f6027b
11 changed files with 305 additions and 155 deletions

View File

@ -3874,7 +3874,7 @@ bool CanvasRenderingContext2D::EnsureWritablePath() {
if (!mPath) {
mPathBuilder = mTarget->CreatePathBuilder(fillRule);
} else {
mPathBuilder = mPath->CopyToBuilder(fillRule);
mPathBuilder = Path::ToBuilder(mPath.forget(), fillRule);
}
return true;
}
@ -3906,9 +3906,7 @@ void CanvasRenderingContext2D::EnsureUserSpacePath(
}
if (mPath && mPath->GetFillRule() != fillRule) {
mPathBuilder = mPath->CopyToBuilder(fillRule);
mPath = mPathBuilder->Finish();
mPathBuilder = nullptr;
Path::SetFillRule(mPath, fillRule);
}
NS_ASSERTION(mPath, "mPath should exist");
@ -3921,11 +3919,9 @@ void CanvasRenderingContext2D::TransformCurrentPath(const Matrix& aTransform) {
}
if (mPathBuilder) {
RefPtr<Path> path = mPathBuilder->Finish();
mPathBuilder = path->TransformedCopyToBuilder(aTransform);
mPathBuilder = Path::ToBuilder(mPathBuilder->Finish(), aTransform);
} else if (mPath) {
mPathBuilder = mPath->TransformedCopyToBuilder(aTransform);
mPath = nullptr;
mPathBuilder = Path::ToBuilder(mPath.forget(), aTransform);
}
}
@ -6359,8 +6355,7 @@ void CanvasRenderingContext2D::EnsureErrorTarget() {
void CanvasRenderingContext2D::FillRuleChanged() {
if (mPath) {
mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
mPath = nullptr;
mPathBuilder = Path::ToBuilder(mPath.forget(), CurrentState().fillRule);
}
}
@ -7004,9 +6999,7 @@ void CanvasPath::AddPath(CanvasPath& aCanvasPath, const DOMMatrix2DInit& aInit,
}
if (!transform.IsIdentity()) {
RefPtr<PathBuilder> tempBuilder =
tempPath->TransformedCopyToBuilder(transform, FillRule::FILL_WINDING);
tempPath = tempBuilder->Finish();
Path::TransformAndSetFillRule(tempPath, transform, FillRule::FILL_WINDING);
}
EnsurePathBuilder(); // in case a path is added to itself
@ -7046,8 +7039,7 @@ already_AddRefed<gfx::Path> CanvasPath::GetPath(
mPath->StreamToSink(tmpPathBuilder);
mPath = tmpPathBuilder->Finish();
} else if (mPath->GetFillRule() != fillRule) {
RefPtr<PathBuilder> tmpPathBuilder = mPath->CopyToBuilder(fillRule);
mPath = tmpPathBuilder->Finish();
Path::SetFillRule(mPath, fillRule);
}
RefPtr<gfx::Path> path(mPath);
@ -7061,8 +7053,7 @@ void CanvasPath::EnsurePathBuilder() const {
// if there is not pathbuilder, there must be a path
MOZ_ASSERT(mPath);
mPathBuilder = mPath->CopyToBuilder();
mPath = nullptr;
mPathBuilder = Path::ToBuilder(mPath.forget());
}
size_t BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext) {

View File

@ -207,9 +207,7 @@ bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) {
// We have non-scaling-stroke as well as a non-translation transform.
// We should transform the path first then apply the stroke on the
// transformed path to preserve the stroke-width.
RefPtr<PathBuilder> builder = path->TransformedCopyToBuilder(mat);
path = builder->Finish();
Path::Transform(path, mat);
point = mat.TransformPoint(point);
}
}
@ -281,8 +279,7 @@ float SVGGeometryElement::GetPathLengthScale(PathLengthScaleForType aFor) {
// we need to take that into account.
auto matrix = LocalTransform();
if (!matrix.IsIdentity()) {
RefPtr<PathBuilder> builder = path->TransformedCopyToBuilder(matrix);
path = builder->Finish();
Path::Transform(path, matrix);
}
}
return path->ComputeLength() / authorsPathLengthEstimate;

View File

@ -959,6 +959,72 @@ class Path : public external::AtomicRefCounted<Path> {
virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(
const Matrix& aTransform, FillRule aFillRule) const = 0;
protected:
/** This returns a PathBuilder object that may consume the contents of this
* path.
*/
virtual inline already_AddRefed<PathBuilder> MoveToBuilder(
FillRule aFillRule) {
return CopyToBuilder(aFillRule);
}
inline already_AddRefed<PathBuilder> MoveToBuilder() {
return MoveToBuilder(GetFillRule());
}
/** Like TransformedCopyToBuilder, but is allowed to consume the contents of
* the path when beneficial.
*/
virtual inline already_AddRefed<PathBuilder> TransformedMoveToBuilder(
const Matrix& aTransform, FillRule aFillRule) {
return TransformedCopyToBuilder(aTransform, aFillRule);
}
inline already_AddRefed<PathBuilder> TransformedMoveToBuilder(
const Matrix& aTransform) {
return TransformedMoveToBuilder(aTransform, GetFillRule());
}
public:
/** Move to a PathBuilder only if there are no other references to the path,
* otherwise copy.
*/
static inline already_AddRefed<PathBuilder> ToBuilder(
already_AddRefed<Path> aPath, FillRule aFillRule) {
RefPtr<Path> path = aPath;
return path->hasOneRef() ? path->MoveToBuilder(aFillRule)
: path->CopyToBuilder(aFillRule);
}
static inline already_AddRefed<PathBuilder> ToBuilder(
already_AddRefed<Path> aPath) {
RefPtr<Path> path = aPath;
FillRule fillRule = path->GetFillRule();
return ToBuilder(path.forget(), fillRule);
}
/** Transformed move to a PathBuilder only if there are no other references to
* the path, otherwise copy.
*/
static inline already_AddRefed<PathBuilder> ToBuilder(
already_AddRefed<Path> aPath, const Matrix& aTransform,
FillRule aFillRule) {
RefPtr<Path> path = aPath;
return path->hasOneRef()
? path->TransformedMoveToBuilder(aTransform, aFillRule)
: path->TransformedCopyToBuilder(aTransform, aFillRule);
}
static inline already_AddRefed<PathBuilder> ToBuilder(
already_AddRefed<Path> aPath, const Matrix& aTransform) {
RefPtr<Path> path = aPath;
FillRule fillRule = path->GetFillRule();
return ToBuilder(path.forget(), aTransform, fillRule);
}
/** Modifies an existing path in-place if it has no other references, or
* copies if it is used elsewhere.
*/
static void Transform(RefPtr<Path>& aPath, const Matrix& aTransform);
static void SetFillRule(RefPtr<Path>& aPath, FillRule aFillRule);
static void TransformAndSetFillRule(RefPtr<Path>& aPath,
const Matrix& aTransform,
FillRule aFillRule);
/** This function checks if a point lies within a path. It allows passing a
* transform that will transform the path to the coordinate space in which
* aPoint is given.
@ -1037,6 +1103,24 @@ class PathBuilder : public PathSink {
virtual bool IsActive() const = 0;
};
inline void Path::Transform(RefPtr<Path>& aPath, const Matrix& aTransform) {
RefPtr<PathBuilder> builder = Path::ToBuilder(aPath.forget(), aTransform);
aPath = builder->Finish();
}
inline void Path::SetFillRule(RefPtr<Path>& aPath, FillRule aFillRule) {
RefPtr<PathBuilder> builder = Path::ToBuilder(aPath.forget(), aFillRule);
aPath = builder->Finish();
}
inline void Path::TransformAndSetFillRule(RefPtr<Path>& aPath,
const Matrix& aTransform,
FillRule aFillRule) {
RefPtr<PathBuilder> builder =
Path::ToBuilder(aPath.forget(), aTransform, aFillRule);
aPath = builder->Finish();
}
struct Glyph {
uint32_t mIndex;
Point mPosition;

View File

@ -15,47 +15,6 @@
namespace mozilla {
namespace gfx {
struct PathOp {
~PathOp() = default;
enum OpType {
OP_MOVETO = 0,
OP_LINETO,
OP_BEZIERTO,
OP_QUADRATICBEZIERTO,
OP_ARC,
OP_CLOSE
};
OpType mType;
Point mP1;
#if (!defined(__GNUC__) || __GNUC__ >= 7) && defined(__clang__)
PathOp() {}
union {
struct {
Point mP2;
Point mP3;
};
struct {
float mRadius;
float mStartAngle;
float mEndAngle;
bool mAntiClockwise;
};
};
#else
PathOp() = default;
Point mP2;
Point mP3;
float mRadius;
float mStartAngle;
float mEndAngle;
bool mAntiClockwise;
#endif
};
const int32_t sPointCount[] = {1, 1, 3, 2, 0, 0};
// Kappa constant for 90-degree angle

View File

@ -11,6 +11,28 @@
namespace mozilla {
namespace gfx {
inline Maybe<float> PathOps::ArcParams::GetRadius() const {
if (!transform.HasNonAxisAlignedTransform() &&
FuzzyEqual(transform._11, transform._22) && transform._11 > 0.0f) {
return Some(transform._11);
}
auto scale = transform.ScaleFactors();
if (scale.AreScalesSame() && scale.xScale > 0.0f) {
return Some(scale.xScale);
}
return Nothing();
}
inline void PathOps::ArcParams::ToSink(PathSink& aPathSink,
bool aAntiClockwise) const {
if (Maybe<float> radius = GetRadius()) {
aPathSink.Arc(GetOrigin(), *radius, startAngle, endAngle, aAntiClockwise);
} else {
ArcToBezier(&aPathSink, Point(), Size(1.0f, 1.0f), startAngle, endAngle,
aAntiClockwise, 0.0f, transform);
}
}
#define NEXT_PARAMS(_type) \
const _type params = *reinterpret_cast<const _type*>(nextByte); \
nextByte += sizeof(_type);
@ -46,10 +68,10 @@ bool PathOps::StreamToSink(PathSink& aPathSink) const {
aPathSink.QuadraticBezierTo(params.p1, params.p2);
break;
}
case OpType::OP_ARC: {
case OpType::OP_ARC_CW:
case OpType::OP_ARC_CCW: {
NEXT_PARAMS(ArcParams)
aPathSink.Arc(params.origin, params.radius, params.startAngle,
params.endAngle, params.antiClockwise);
params.ToSink(aPathSink, opType == OpType::OP_ARC_CCW);
break;
}
case OpType::OP_CLOSE:
@ -108,10 +130,10 @@ bool PathOps::CheckedStreamToSink(PathSink& aPathSink) const {
aPathSink.QuadraticBezierTo(params.p1, params.p2);
break;
}
case OpType::OP_ARC: {
case OpType::OP_ARC_CW:
case OpType::OP_ARC_CCW: {
CHECKED_NEXT_PARAMS(ArcParams)
aPathSink.Arc(params.origin, params.radius, params.startAngle,
params.endAngle, params.antiClockwise);
params.ToSink(aPathSink, opType == OpType::OP_ARC_CCW);
break;
}
case OpType::OP_CLOSE:
@ -128,6 +150,7 @@ bool PathOps::CheckedStreamToSink(PathSink& aPathSink) const {
PathOps PathOps::TransformedCopy(const Matrix& aTransform) const {
PathOps newPathOps;
newPathOps.mPathData.reserve(mPathData.size());
const uint8_t* nextByte = mPathData.data();
const uint8_t* end = nextByte + mPathData.size();
while (nextByte < end) {
@ -157,11 +180,11 @@ PathOps PathOps::TransformedCopy(const Matrix& aTransform) const {
aTransform.TransformPoint(params.p2));
break;
}
case OpType::OP_ARC: {
case OpType::OP_ARC_CW:
case OpType::OP_ARC_CCW: {
NEXT_PARAMS(ArcParams)
ArcToBezier(&newPathOps, params.origin,
gfx::Size(params.radius, params.radius), params.startAngle,
params.endAngle, params.antiClockwise, 0.0f, aTransform);
newPathOps.Arc(params.transform * aTransform, params.startAngle,
params.endAngle, opType == OpType::OP_ARC_CCW);
break;
}
case OpType::OP_CLOSE:
@ -175,6 +198,54 @@ PathOps PathOps::TransformedCopy(const Matrix& aTransform) const {
return newPathOps;
}
#define MODIFY_NEXT_PARAMS(_type) \
_type& params = *reinterpret_cast<_type*>(nextByte); \
nextByte += sizeof(_type);
void PathOps::TransformInPlace(const Matrix& aTransform) {
uint8_t* nextByte = mPathData.data();
uint8_t* end = nextByte + mPathData.size();
while (nextByte < end) {
const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
nextByte += sizeof(OpType);
switch (opType) {
case OpType::OP_MOVETO: {
MODIFY_NEXT_PARAMS(Point)
params = aTransform.TransformPoint(params);
break;
}
case OpType::OP_LINETO: {
MODIFY_NEXT_PARAMS(Point)
params = aTransform.TransformPoint(params);
break;
}
case OpType::OP_BEZIERTO: {
MODIFY_NEXT_PARAMS(ThreePoints)
params.p1 = aTransform.TransformPoint(params.p1);
params.p2 = aTransform.TransformPoint(params.p2);
params.p3 = aTransform.TransformPoint(params.p3);
break;
}
case OpType::OP_QUADRATICBEZIERTO: {
MODIFY_NEXT_PARAMS(TwoPoints)
params.p1 = aTransform.TransformPoint(params.p1);
params.p2 = aTransform.TransformPoint(params.p2);
break;
}
case OpType::OP_ARC_CW:
case OpType::OP_ARC_CCW: {
MODIFY_NEXT_PARAMS(ArcParams)
params.transform *= aTransform;
break;
}
case OpType::OP_CLOSE:
break;
default:
MOZ_CRASH("We control mOpTypes, so this should never happen.");
}
}
}
Maybe<Circle> PathOps::AsCircle() const {
if (mPathData.empty()) {
return Nothing();
@ -184,21 +255,23 @@ Maybe<Circle> PathOps::AsCircle() const {
const uint8_t* end = nextByte + mPathData.size();
const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
nextByte += sizeof(OpType);
if (opType == OpType::OP_ARC) {
if (opType == OpType::OP_ARC_CW || opType == OpType::OP_ARC_CCW) {
NEXT_PARAMS(ArcParams)
if (fabs(fabs(params.startAngle - params.endAngle) - 2 * M_PI) < 1e-6) {
// we have a full circle
if (nextByte < end) {
const OpType nextOpType = *reinterpret_cast<const OpType*>(nextByte);
nextByte += sizeof(OpType);
if (nextOpType == OpType::OP_CLOSE) {
if (nextByte == end) {
return Some(Circle{params.origin, params.radius, true});
if (Maybe<float> radius = params.GetRadius()) {
// we have a full circle
if (nextByte < end) {
const OpType nextOpType = *reinterpret_cast<const OpType*>(nextByte);
nextByte += sizeof(OpType);
if (nextOpType == OpType::OP_CLOSE) {
if (nextByte == end) {
return Some(Circle{params.GetOrigin(), *radius, true});
}
}
} else {
// the circle wasn't closed
return Some(Circle{params.GetOrigin(), *radius, false});
}
} else {
// the circle wasn't closed
return Some(Circle{params.origin, params.radius, false});
}
}
}
@ -270,7 +343,8 @@ size_t PathOps::NumberOfOps() const {
case OpType::OP_QUADRATICBEZIERTO:
nextByte += sizeof(TwoPoints);
break;
case OpType::OP_ARC:
case OpType::OP_ARC_CW:
case OpType::OP_ARC_CCW:
nextByte += sizeof(ArcParams);
break;
case OpType::OP_CLOSE:
@ -394,5 +468,23 @@ already_AddRefed<PathBuilder> PathRecording::TransformedCopyToBuilder(
return recording.forget();
}
already_AddRefed<PathBuilder> PathRecording::MoveToBuilder(FillRule aFillRule) {
RefPtr<PathBuilderRecording> recording =
new PathBuilderRecording(mBackendType, std::move(mPathOps), aFillRule);
recording->SetCurrentPoint(mCurrentPoint);
recording->SetBeginPoint(mBeginPoint);
return recording.forget();
}
already_AddRefed<PathBuilder> PathRecording::TransformedMoveToBuilder(
const Matrix& aTransform, FillRule aFillRule) {
mPathOps.TransformInPlace(aTransform);
RefPtr<PathBuilderRecording> recording =
new PathBuilderRecording(mBackendType, std::move(mPathOps), aFillRule);
recording->SetCurrentPoint(aTransform.TransformPoint(mCurrentPoint));
recording->SetBeginPoint(aTransform.TransformPoint(mBeginPoint));
return recording.forget();
}
} // namespace gfx
} // namespace mozilla

View File

@ -50,57 +50,32 @@ class PathOps {
PathOps TransformedCopy(const Matrix& aTransform) const;
void TransformInPlace(const Matrix& aTransform);
size_t NumberOfOps() const;
void MoveTo(const Point& aPoint) { AppendPathOp(OpType::OP_MOVETO, aPoint); }
void LineTo(const Point& aPoint) { AppendPathOp(OpType::OP_LINETO, aPoint); }
void BezierTo(const Point& aCP1, const Point& aCP2, const Point& aCP3) {
AppendPathOp(OpType::OP_BEZIERTO, ThreePoints{aCP1, aCP2, aCP3});
}
void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) {
AppendPathOp(OpType::OP_QUADRATICBEZIERTO, TwoPoints{aCP1, aCP2});
}
void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
float aEndAngle, bool aAntiClockwise) {
AppendPathOp(OpType::OP_ARC, ArcParams{aOrigin, aRadius, aStartAngle,
aEndAngle, aAntiClockwise});
}
void Close() {
size_t oldSize = mPathData.size();
mPathData.resize(oldSize + sizeof(OpType));
*reinterpret_cast<OpType*>(mPathData.data() + oldSize) = OpType::OP_CLOSE;
}
Maybe<Circle> AsCircle() const;
Maybe<Line> AsLine() const;
bool IsActive() const { return !mPathData.empty(); }
bool IsEmpty() const;
private:
enum class OpType : uint32_t {
OP_MOVETO = 0,
OP_LINETO,
OP_BEZIERTO,
OP_QUADRATICBEZIERTO,
OP_ARC,
OP_ARC_CW,
OP_ARC_CCW,
OP_CLOSE,
OP_INVALID
};
template <typename T>
void AppendPathOp(const T& aOpData) {
mPathData.insert(mPathData.end(), (const uint8_t*)(&aOpData),
(const uint8_t*)(&aOpData + 1));
}
template <typename T>
void AppendPathOp(const OpType& aOpType, const T& aOpParams) {
size_t oldSize = mPathData.size();
mPathData.resize(oldSize + sizeof(OpType) + sizeof(T));
memcpy(mPathData.data() + oldSize, &aOpType, sizeof(OpType));
oldSize += sizeof(OpType);
memcpy(mPathData.data() + oldSize, &aOpParams, sizeof(T));
AppendPathOp(aOpType);
AppendPathOp(aOpParams);
}
struct TwoPoints {
@ -115,13 +90,51 @@ class PathOps {
};
struct ArcParams {
Point origin;
float radius;
Matrix transform;
float startAngle;
float endAngle;
bool antiClockwise;
Point GetOrigin() const { return transform.GetTranslation(); }
Maybe<float> GetRadius() const;
void ToSink(PathSink& aPathSink, bool aAntiClockwise) const;
};
public:
void MoveTo(const Point& aPoint) { AppendPathOp(OpType::OP_MOVETO, aPoint); }
void LineTo(const Point& aPoint) { AppendPathOp(OpType::OP_LINETO, aPoint); }
void BezierTo(const Point& aCP1, const Point& aCP2, const Point& aCP3) {
AppendPathOp(OpType::OP_BEZIERTO, ThreePoints{aCP1, aCP2, aCP3});
}
void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) {
AppendPathOp(OpType::OP_QUADRATICBEZIERTO, TwoPoints{aCP1, aCP2});
}
void Arc(const Matrix& aTransform, float aStartAngle, float aEndAngle,
bool aAntiClockwise) {
AppendPathOp(aAntiClockwise ? OpType::OP_ARC_CCW : OpType::OP_ARC_CW,
ArcParams{aTransform, aStartAngle, aEndAngle});
}
void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
float aEndAngle, bool aAntiClockwise) {
Arc(Matrix(aRadius, 0.0f, 0.0f, aRadius, aOrigin.x, aOrigin.y), aStartAngle,
aEndAngle, aAntiClockwise);
}
void Close() { AppendPathOp(OpType::OP_CLOSE); }
Maybe<Circle> AsCircle() const;
Maybe<Line> AsLine() const;
bool IsActive() const { return !mPathData.empty(); }
bool IsEmpty() const;
private:
std::vector<uint8_t> mPathData;
};
@ -200,6 +213,10 @@ class PathRecording final : public Path {
already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const final;
already_AddRefed<PathBuilder> TransformedCopyToBuilder(
const Matrix& aTransform, FillRule aFillRule) const final;
already_AddRefed<PathBuilder> MoveToBuilder(FillRule aFillRule) final;
already_AddRefed<PathBuilder> TransformedMoveToBuilder(
const Matrix& aTransform, FillRule aFillRule) final;
bool ContainsPoint(const Point& aPoint,
const Matrix& aTransform) const final {
EnsurePath();

View File

@ -17,13 +17,13 @@ already_AddRefed<PathBuilder> PathBuilderSkia::Create(FillRule aFillRule) {
return MakeAndAddRef<PathBuilderSkia>(aFillRule);
}
PathBuilderSkia::PathBuilderSkia(const Matrix& aTransform, const SkPath& aPath,
FillRule aFillRule)
PathBuilderSkia::PathBuilderSkia(SkPath&& aPath, FillRule aFillRule,
const Point& aCurrentPoint,
const Point& aBeginPoint)
: mPath(aPath) {
SkMatrix matrix;
GfxMatrixToSkiaMatrix(aTransform, matrix);
mPath.transform(matrix);
SetFillRule(aFillRule);
SetCurrentPoint(aCurrentPoint);
SetBeginPoint(aBeginPoint);
}
PathBuilderSkia::PathBuilderSkia(FillRule aFillRule) { SetFillRule(aFillRule); }
@ -96,18 +96,34 @@ void PathBuilderSkia::AppendPath(const SkPath& aPath) { mPath.addPath(aPath); }
already_AddRefed<PathBuilder> PathSkia::CopyToBuilder(
FillRule aFillRule) const {
return TransformedCopyToBuilder(Matrix(), aFillRule);
return MakeAndAddRef<PathBuilderSkia>(SkPath(mPath), aFillRule, mCurrentPoint,
mBeginPoint);
}
already_AddRefed<PathBuilder> PathSkia::TransformedCopyToBuilder(
const Matrix& aTransform, FillRule aFillRule) const {
RefPtr<PathBuilderSkia> builder =
MakeAndAddRef<PathBuilderSkia>(aTransform, mPath, aFillRule);
SkMatrix matrix;
GfxMatrixToSkiaMatrix(aTransform, matrix);
SkPath path(mPath);
path.transform(matrix);
return MakeAndAddRef<PathBuilderSkia>(
std::move(path), aFillRule, aTransform.TransformPoint(mCurrentPoint),
aTransform.TransformPoint(mBeginPoint));
}
builder->mCurrentPoint = aTransform.TransformPoint(mCurrentPoint);
builder->mBeginPoint = aTransform.TransformPoint(mBeginPoint);
already_AddRefed<PathBuilder> PathSkia::MoveToBuilder(FillRule aFillRule) {
return MakeAndAddRef<PathBuilderSkia>(std::move(mPath), aFillRule,
mCurrentPoint, mBeginPoint);
}
return builder.forget();
already_AddRefed<PathBuilder> PathSkia::TransformedMoveToBuilder(
const Matrix& aTransform, FillRule aFillRule) {
SkMatrix matrix;
GfxMatrixToSkiaMatrix(aTransform, matrix);
mPath.transform(matrix);
return MakeAndAddRef<PathBuilderSkia>(
std::move(mPath), aFillRule, aTransform.TransformPoint(mCurrentPoint),
aTransform.TransformPoint(mBeginPoint));
}
static bool SkPathContainsPoint(const SkPath& aPath, const Point& aPoint,

View File

@ -19,8 +19,8 @@ class PathBuilderSkia : public PathBuilder {
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderSkia, override)
PathBuilderSkia(const Matrix& aTransform, const SkPath& aPath,
FillRule aFillRule);
PathBuilderSkia(SkPath&& aPath, FillRule aFillRule,
const Point& aCurrentPoint, const Point& aBeginPoint);
explicit PathBuilderSkia(FillRule aFillRule);
void MoveTo(const Point& aPoint) override;
@ -68,6 +68,9 @@ class PathSkia : public Path {
FillRule aFillRule) const override;
already_AddRefed<PathBuilder> TransformedCopyToBuilder(
const Matrix& aTransform, FillRule aFillRule) const override;
already_AddRefed<PathBuilder> MoveToBuilder(FillRule aFillRule) override;
already_AddRefed<PathBuilder> TransformedMoveToBuilder(
const Matrix& aTransform, FillRule aFillRule) override;
bool ContainsPoint(const Point& aPoint,
const Matrix& aTransform) const override;

View File

@ -433,9 +433,7 @@ void gfxContext::EnsurePath() {
Matrix mat = mAzureState.transform;
mat.Invert();
mat = mPathTransform * mat;
mPathBuilder = mPath->TransformedCopyToBuilder(mat);
mPath = mPathBuilder->Finish();
mPathBuilder = nullptr;
Path::Transform(mPath, mat);
mTransformChanged = false;
}
@ -454,13 +452,12 @@ void gfxContext::EnsurePathBuilder() {
if (mPath) {
if (!mTransformChanged) {
mPathBuilder = mPath->CopyToBuilder();
mPath = nullptr;
mPathBuilder = Path::ToBuilder(mPath.forget());
} else {
Matrix invTransform = mAzureState.transform;
invTransform.Invert();
Matrix toNewUS = mPathTransform * invTransform;
mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS);
mPathBuilder = Path::ToBuilder(mPath.forget(), toNewUS);
}
return;
}
@ -495,7 +492,7 @@ void gfxContext::EnsurePathBuilder() {
gfxCriticalError()
<< "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish";
}
mPathBuilder = path->TransformedCopyToBuilder(toNewUS);
mPathBuilder = Path::ToBuilder(path.forget(), toNewUS);
}
mPathIsRect = false;

View File

@ -240,9 +240,7 @@ nsIFrame* SVGGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint) {
// Naturally we also need to transform the point into the same
// coordinate system in order to hit-test against the path.
point = ToMatrix(userToOuterSVG).TransformPoint(point);
RefPtr<PathBuilder> builder =
path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
path = builder->Finish();
Path::TransformAndSetFillRule(path, ToMatrix(userToOuterSVG), fillRule);
}
isHit = path->StrokeContainsPoint(stroke, point, {});
}
@ -625,9 +623,7 @@ void SVGGeometryFrame::Render(gfxContext* aContext, uint32_t aRenderComponents,
gfxMatrix outerSVGToUser = userToOuterSVG;
outerSVGToUser.Invert();
aContext->Multiply(outerSVGToUser);
RefPtr<PathBuilder> builder =
path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
path = builder->Finish();
Path::TransformAndSetFillRule(path, ToMatrix(userToOuterSVG), fillRule);
}
GeneralPattern strokePattern;
SVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, aImgParams,

View File

@ -2651,8 +2651,7 @@ void SVGTextDrawPathCallbacks::FillGeometry() {
RefPtr<Path> path = mContext.GetPath();
FillRule fillRule = SVGUtils::ToFillRule(mFrame->StyleSVG()->mFillRule);
if (fillRule != path->GetFillRule()) {
RefPtr<PathBuilder> builder = path->CopyToBuilder(fillRule);
path = builder->Finish();
Path::SetFillRule(path, fillRule);
}
mContext.GetDrawTarget()->Fill(path, fillPattern);
}
@ -4537,8 +4536,7 @@ already_AddRefed<Path> SVGTextFrame::GetTextPath(nsIFrame* aTextPathFrame) {
// Apply the geometry element's transform if appropriate.
auto matrix = geomElement->LocalTransform();
if (!matrix.IsIdentity()) {
RefPtr<PathBuilder> builder = path->TransformedCopyToBuilder(matrix);
path = builder->Finish();
Path::Transform(path, matrix);
}
return path.forget();