Skip to content

Instantly share code, notes, and snippets.

@jrmuizel
Last active June 12, 2023 14:52
Show Gist options
  • Save jrmuizel/d5560d35c46c102f20f4c681d537f713 to your computer and use it in GitHub Desktop.
Save jrmuizel/d5560d35c46c102f20f4c681d537f713 to your computer and use it in GitHub Desktop.
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index d84935f7c3f00..712ba15324623 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1054,21 +1054,20 @@ CanvasRenderingContext2D::CanvasRenderingContext2D(
mOpaqueAttrValue(false),
mContextAttributesHasAlpha(true),
mOpaque(false),
mResetLayer(true),
mIPC(false),
mHasPendingStableStateCallback(false),
mIsEntireFrameInvalid(false),
mPredictManyRedrawCalls(false),
mFrameCaptureState(FrameCaptureState::CLEAN,
"CanvasRenderingContext2D::mFrameCaptureState"),
- mPathTransformWillUpdate(false),
mInvalidateCount(0),
mWriteOnly(false) {
sNumLivingContexts.infallibleInit();
sErrorTarget.infallibleInit();
sNumLivingContexts.set(sNumLivingContexts.get() + 1);
}
CanvasRenderingContext2D::~CanvasRenderingContext2D() {
RemovePostRefreshObserver();
RemoveShutdownObserver();
@@ -1511,22 +1510,20 @@ bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
Redraw();
mFrameCaptureState = captureState;
return true;
}
void CanvasRenderingContext2D::SetInitialState() {
// Set up the initial canvas defaults
mPathBuilder = nullptr;
mPath = nullptr;
- mDSPathBuilder = nullptr;
- mPathTransformWillUpdate = false;
mStyleStack.Clear();
ContextState* state = mStyleStack.AppendElement();
state->globalAlpha = 1.0;
state->colorStyles[Style::FILL] = NS_RGB(0, 0, 0);
state->colorStyles[Style::STROKE] = NS_RGB(0, 0, 0);
state->shadowColor = NS_RGBA(0, 0, 0, 0);
}
@@ -1954,90 +1951,106 @@ void CanvasRenderingContext2D::Save() {
// reasonable code.
mStyleStack.RemoveElementAt(0);
}
}
void CanvasRenderingContext2D::Restore() {
if (MOZ_UNLIKELY(mStyleStack.Length() < 2)) {
return;
}
- TransformWillUpdate();
+ EnsureTarget();
if (!IsTargetValid()) {
return;
}
for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) {
if (clipOrTransform.IsClip()) {
mTarget->PopClip();
}
}
mStyleStack.RemoveLastElement();
- mTarget->SetTransform(CurrentState().transform);
+ Matrix newMatrix = CurrentState().transform;
+ Matrix adjustMatrix = mTarget->GetTransform();
+
+ Matrix inverse = newMatrix;
+ if (inverse.Invert()) {
+ adjustMatrix = adjustMatrix * inverse;
+ }
+ TransformWillUpdate(adjustMatrix);
+
+ mTarget->SetTransform(newMatrix);
}
//
// transformations
//
void CanvasRenderingContext2D::Scale(double aX, double aY,
ErrorResult& aError) {
- TransformWillUpdate();
+ EnsureTarget();
if (!IsTargetValid()) {
aError.Throw(NS_ERROR_FAILURE);
return;
}
+ TransformWillUpdate(Matrix::Scaling(1/aX, 1/aY));
Matrix newMatrix = mTarget->GetTransform();
newMatrix.PreScale(aX, aY);
SetTransformInternal(newMatrix);
}
void CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError) {
- TransformWillUpdate();
+ EnsureTarget();
if (!IsTargetValid()) {
aError.Throw(NS_ERROR_FAILURE);
return;
}
+ TransformWillUpdate(Matrix::Rotation(-aAngle));
Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform();
SetTransformInternal(newMatrix);
}
void CanvasRenderingContext2D::Translate(double aX, double aY,
ErrorResult& aError) {
- TransformWillUpdate();
+ EnsureTarget();
if (!IsTargetValid()) {
aError.Throw(NS_ERROR_FAILURE);
return;
}
+ TransformWillUpdate(Matrix::Translation(-aX, -aY));
Matrix newMatrix = mTarget->GetTransform();
newMatrix.PreTranslate(aX, aY);
SetTransformInternal(newMatrix);
}
void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21,
double aM22, double aDx, double aDy,
ErrorResult& aError) {
- TransformWillUpdate();
+ EnsureTarget();
if (!IsTargetValid()) {
aError.Throw(NS_ERROR_FAILURE);
return;
}
Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy);
+ Matrix inverse = newMatrix;
+ if (inverse.Invert()) {
+ TransformWillUpdate(inverse);
+ }
newMatrix *= mTarget->GetTransform();
SetTransformInternal(newMatrix);
}
already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform(
ErrorResult& aError) {
EnsureTarget();
if (!IsTargetValid()) {
aError.Throw(NS_ERROR_FAILURE);
@@ -2045,41 +2058,57 @@ already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform(
}
RefPtr<DOMMatrix> matrix =
new DOMMatrix(GetParentObject(), mTarget->GetTransform());
return matrix.forget();
}
void CanvasRenderingContext2D::SetTransform(double aM11, double aM12,
double aM21, double aM22,
double aDx, double aDy,
ErrorResult& aError) {
- TransformWillUpdate();
+ EnsureTarget();
if (!IsTargetValid()) {
aError.Throw(NS_ERROR_FAILURE);
return;
}
- SetTransformInternal(Matrix(aM11, aM12, aM21, aM22, aDx, aDy));
+ Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy);
+ Matrix adjustMatrix = mTarget->GetTransform();
+
+ Matrix inverse = newMatrix;
+ if (inverse.Invert()) {
+ adjustMatrix = adjustMatrix * inverse;
+ }
+ TransformWillUpdate(adjustMatrix);
+ SetTransformInternal(newMatrix);
}
void CanvasRenderingContext2D::SetTransform(const DOMMatrix2DInit& aInit,
ErrorResult& aError) {
- TransformWillUpdate();
+ EnsureTarget();
if (!IsTargetValid()) {
aError.Throw(NS_ERROR_FAILURE);
return;
}
RefPtr<DOMMatrixReadOnly> matrix =
DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError);
if (!aError.Failed()) {
- SetTransformInternal(Matrix(*(matrix->GetInternal2D())));
+ Matrix newMatrix = Matrix(*(matrix->GetInternal2D()));
+ Matrix adjustMatrix = mTarget->GetTransform();
+
+ Matrix inverse = newMatrix;
+ if (inverse.Invert()) {
+ adjustMatrix = adjustMatrix * inverse;
+ }
+ TransformWillUpdate(adjustMatrix);
+ SetTransformInternal(newMatrix);
}
}
void CanvasRenderingContext2D::SetTransformInternal(const Matrix& aTransform) {
if (!aTransform.IsFinite()) {
return;
}
// Save the transform in the clip stack to be able to replay clips properly.
auto& clipsAndTransforms = CurrentState().clipsAndTransforms;
@@ -2941,22 +2970,20 @@ void CanvasRenderingContext2D::StrokeRect(double aX, double aY, double aW,
Redraw();
}
//
// path bits
//
void CanvasRenderingContext2D::BeginPath() {
mPath = nullptr;
mPathBuilder = nullptr;
- mDSPathBuilder = nullptr;
- mPathTransformWillUpdate = false;
}
void CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding) {
EnsureUserSpacePath(aWinding);
if (!mPath || mPath->IsEmpty()) {
return;
}
const bool needBounds = NeedToCalculateBounds();
@@ -3197,31 +3224,21 @@ void CanvasRenderingContext2D::Clip(const CanvasPath& aPath,
void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
double aY2, double aRadius,
ErrorResult& aError) {
if (aRadius < 0) {
return aError.ThrowIndexSizeError("Negative radius");
}
EnsureWritablePath();
// Current point in user space!
- Point p0;
- if (mPathBuilder) {
- p0 = mPathBuilder->CurrentPoint();
- } else {
- Matrix invTransform = mTarget->GetTransform();
- if (!invTransform.Invert()) {
- return;
- }
-
- p0 = invTransform.TransformPoint(mDSPathBuilder->CurrentPoint());
- }
+ Point p0 = mPathBuilder->CurrentPoint();
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.
@@ -3274,56 +3291,41 @@ void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
void CanvasRenderingContext2D::Arc(double aX, double aY, double aR,
double aStartAngle, double aEndAngle,
bool aAnticlockwise, ErrorResult& aError) {
if (aR < 0.0) {
return aError.ThrowIndexSizeError("Negative radius");
}
EnsureWritablePath();
- ArcToBezier(this, Point(aX, aY), Size(aR, aR), aStartAngle, aEndAngle,
+ mPathBuilder->Arc(Point(aX, aY), aR, aStartAngle, aEndAngle,
aAnticlockwise);
}
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));
- mPathBuilder->Close();
- } else {
- mDSPathBuilder->MoveTo(
- mTarget->GetTransform().TransformPoint(Point(aX, aY)));
- if (aW == 0 && aH == 0) {
+ mPathBuilder->MoveTo(Point(aX, aY));
+ if (aW == 0 && aH == 0) {
return;
- }
- mDSPathBuilder->LineTo(
- mTarget->GetTransform().TransformPoint(Point(aX + aW, aY)));
- mDSPathBuilder->LineTo(
- mTarget->GetTransform().TransformPoint(Point(aX + aW, aY + aH)));
- mDSPathBuilder->LineTo(
- mTarget->GetTransform().TransformPoint(Point(aX, aY + aH)));
- mDSPathBuilder->Close();
}
+ mPathBuilder->LineTo(Point(aX + aW, aY));
+ mPathBuilder->LineTo(Point(aX + aW, aY + aH));
+ mPathBuilder->LineTo(Point(aX, aY + aH));
+ mPathBuilder->Close();
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
static void RoundRectImpl(
PathBuilder* aPathBuilder, const Maybe<Matrix>& aTransform, double aX,
double aY, double aW, double aH,
const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence&
aRadii,
ErrorResult& aError) {
// Step 1. If any of x, y, w, or h are infinite or NaN, then return.
@@ -3494,24 +3496,20 @@ static void RoundRectImpl(
void CanvasRenderingContext2D::RoundRect(
double aX, double aY, double aW, double aH,
const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence&
aRadii,
ErrorResult& aError) {
EnsureWritablePath();
PathBuilder* builder = mPathBuilder;
Maybe<Matrix> transform = Nothing();
- if (!builder) {
- builder = mDSPathBuilder;
- transform = Some(mTarget->GetTransform());
- }
RoundRectImpl(builder, transform, aX, aY, aW, aH, aRadii, aError);
}
void CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX,
double aRadiusY, double aRotation,
double aStartAngle, double aEndAngle,
bool aAnticlockwise,
ErrorResult& aError) {
if (aRadiusX < 0.0 || aRadiusY < 0.0) {
@@ -3522,119 +3520,74 @@ void CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX,
ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle,
aEndAngle, aAnticlockwise, aRotation);
}
void CanvasRenderingContext2D::EnsureWritablePath() {
EnsureTarget();
// NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we
// go ahead and create a path anyway since callers depend on that.
- if (mDSPathBuilder) {
- return;
- }
-
FillRule fillRule = CurrentState().fillRule;
if (mPathBuilder) {
- if (mPathTransformWillUpdate) {
- mPath = mPathBuilder->Finish();
- mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
- mPath = nullptr;
- mPathBuilder = nullptr;
- mPathTransformWillUpdate = false;
- }
return;
}
if (!mPath) {
- NS_ASSERTION(
- !mPathTransformWillUpdate,
- "mPathTransformWillUpdate should be false, if all paths are null");
mPathBuilder = mTarget->CreatePathBuilder(fillRule);
- } else if (!mPathTransformWillUpdate) {
- mPathBuilder = mPath->CopyToBuilder(fillRule);
} else {
- mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
- mPathTransformWillUpdate = false;
- mPath = nullptr;
+ mPathBuilder = mPath->CopyToBuilder(fillRule);
}
}
void CanvasRenderingContext2D::EnsureUserSpacePath(
const CanvasWindingRule& aWinding) {
FillRule fillRule = CurrentState().fillRule;
if (aWinding == CanvasWindingRule::Evenodd)
fillRule = FillRule::FILL_EVEN_ODD;
EnsureTarget();
if (!IsTargetValid()) {
return;
}
- if (!mPath && !mPathBuilder && !mDSPathBuilder) {
+ if (!mPath && !mPathBuilder) {
mPathBuilder = mTarget->CreatePathBuilder(fillRule);
}
if (mPathBuilder) {
mPath = mPathBuilder->Finish();
mPathBuilder = nullptr;
}
- if (mPath && mPathTransformWillUpdate) {
- mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
- mPath = nullptr;
- mPathTransformWillUpdate = false;
- }
-
- if (mDSPathBuilder) {
- RefPtr<Path> dsPath;
- dsPath = mDSPathBuilder->Finish();
- mDSPathBuilder = nullptr;
-
- Matrix inverse = mTarget->GetTransform();
- if (!inverse.Invert()) {
- NS_WARNING("Could not invert transform");
- return;
- }
-
- mPathBuilder = dsPath->TransformedCopyToBuilder(inverse, fillRule);
- mPath = mPathBuilder->Finish();
- mPathBuilder = nullptr;
- }
-
if (mPath && mPath->GetFillRule() != fillRule) {
mPathBuilder = mPath->CopyToBuilder(fillRule);
mPath = mPathBuilder->Finish();
mPathBuilder = nullptr;
}
NS_ASSERTION(mPath, "mPath should exist");
}
-void CanvasRenderingContext2D::TransformWillUpdate() {
+void CanvasRenderingContext2D::TransformWillUpdate(const Matrix &aTransformToNew) {
EnsureTarget();
if (!IsTargetValid()) {
return;
}
- // Store the matrix that would transform the current path to device
- // space.
- if (mPath || mPathBuilder) {
- if (!mPathTransformWillUpdate) {
- // If the transform has already been updated, but a device space builder
- // has not been created yet mPathToDS contains the right transform to
- // transform the current mPath into device space.
- // We should leave it alone.
- mPathToDS = mTarget->GetTransform();
- }
- mPathTransformWillUpdate = true;
+ if (mPathBuilder) {
+ RefPtr<Path> path = mPathBuilder->Finish();
+ mPathBuilder = path->TransformedCopyToBuilder(aTransformToNew);
+ } else if (mPath) {
+ mPathBuilder = mPath->TransformedCopyToBuilder(aTransformToNew);
+ mPath = nullptr;
}
}
//
// text
//
void CanvasRenderingContext2D::SetFont(const nsACString& aFont,
ErrorResult& aError) {
SetFontInternal(aFont, aError);
@@ -4876,24 +4829,20 @@ bool CanvasRenderingContext2D::IsPointInPath(
} else if (mOffscreenCanvas && mOffscreenCanvas->ShouldResistFingerprinting(
RFPTarget::CanvasImageExtractionPrompt)) {
return false;
}
EnsureUserSpacePath(aWinding);
if (!mPath) {
return false;
}
- if (mPathTransformWillUpdate) {
- return mPath->ContainsPoint(Point(aX, aY), mPathToDS);
- }
-
return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
}
bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx,
const CanvasPath& aPath, double aX,
double aY,
const CanvasWindingRule& aWinding,
nsIPrincipal& aSubjectPrincipal) {
return IsPointInPath(aCx, aPath, aX, aY, aWinding, Some(&aSubjectPrincipal));
}
@@ -4945,24 +4894,20 @@ bool CanvasRenderingContext2D::IsPointInStroke(
if (!mPath) {
return false;
}
const ContextState& state = CurrentState();
StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, state.lineCap,
state.miterLimit, state.dash.Length(),
state.dash.Elements(), state.dashOffset);
- if (mPathTransformWillUpdate) {
- return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mPathToDS);
- }
-
return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY),
mTarget->GetTransform());
}
bool CanvasRenderingContext2D::IsPointInStroke(
JSContext* aCx, const CanvasPath& aPath, double aX, double aY,
nsIPrincipal& aSubjectPrincipal) {
return IsPointInStroke(aCx, aPath, aX, aY, Some(&aSubjectPrincipal));
}
@@ -6454,21 +6399,21 @@ void CanvasPath::RoundRect(
void CanvasPath::Arc(double aX, double aY, double aRadius, double aStartAngle,
double aEndAngle, bool aAnticlockwise,
ErrorResult& aError) {
if (aRadius < 0.0) {
return aError.ThrowIndexSizeError("Negative radius");
}
EnsurePathBuilder();
- ArcToBezier(this, Point(aX, aY), Size(aRadius, aRadius), aStartAngle,
+ mPathBuilder->Arc(Point(aX, aY), aRadius, aStartAngle,
aEndAngle, aAnticlockwise);
}
void CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY,
double rotation, double startAngle, double endAngle,
bool anticlockwise, ErrorResult& aError) {
if (radiusX < 0.0 || radiusY < 0.0) {
return aError.ThrowIndexSizeError("Negative radius");
}
diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h
index 424c10af1ad12..0a4d4fe97dc0c 100644
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -320,74 +320,54 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
void SetFontKerning(const nsAString& aFontKerning);
void GetLetterSpacing(nsACString& aLetterSpacing);
void SetLetterSpacing(const nsACString& aLetterSpacing);
void GetWordSpacing(nsACString& aWordSpacing);
void SetWordSpacing(const nsACString& aWordSpacing);
void ClosePath() override {
EnsureWritablePath();
- if (mPathBuilder) {
- mPathBuilder->Close();
- } else {
- mDSPathBuilder->Close();
- }
+ mPathBuilder->Close();
}
void MoveTo(double aX, double aY) override {
EnsureWritablePath();
mozilla::gfx::Point pos(ToFloat(aX), ToFloat(aY));
if (!pos.IsFinite()) {
return;
}
- if (mPathBuilder) {
- mPathBuilder->MoveTo(pos);
- } else {
- mozilla::gfx::Point transformedPos =
- mTarget->GetTransform().TransformPoint(pos);
- mDSPathBuilder->MoveTo(transformedPos);
- }
+ mPathBuilder->MoveTo(pos);
}
void LineTo(double aX, double aY) override {
EnsureWritablePath();
LineTo(mozilla::gfx::Point(ToFloat(aX), ToFloat(aY)));
}
void QuadraticCurveTo(double aCpx, double aCpy, double aX,
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) {
- if (cp1 == mPathBuilder->CurrentPoint() && cp1 == cp2) {
- return;
- }
- mPathBuilder->QuadraticBezierTo(cp1, cp2);
- } else {
- mozilla::gfx::Matrix transform = mTarget->GetTransform();
- mozilla::gfx::Point transformedPos = transform.TransformPoint(cp1);
- if (transformedPos == mDSPathBuilder->CurrentPoint() && cp1 == cp2) {
- return;
- }
- mDSPathBuilder->QuadraticBezierTo(transformedPos,
- transform.TransformPoint(cp2));
+ if (cp1 == mPathBuilder->CurrentPoint() && cp1 == cp2) {
+ return;
}
+ mPathBuilder->QuadraticBezierTo(cp1, cp2);
}
void BezierCurveTo(double aCp1x, double aCp1y, double aCp2x, double aCp2y,
double aX, double aY) override {
EnsureWritablePath();
BezierTo(mozilla::gfx::Point(ToFloat(aCp1x), ToFloat(aCp1y)),
mozilla::gfx::Point(ToFloat(aCp2x), ToFloat(aCp2y)),
mozilla::gfx::Point(ToFloat(aX), ToFloat(aY)));
}
@@ -511,58 +491,38 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
PATTERN = 1,
GRADIENT = 2
};
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 {
- mozilla::gfx::Point transformedPt =
- mTarget->GetTransform().TransformPoint(aPoint);
- if (mDSPathBuilder->CurrentPoint() == transformedPt) {
- return;
- }
- mDSPathBuilder->LineTo(transformedPt);
+ if (mPathBuilder->CurrentPoint() == aPoint) {
+ return;
}
+ mPathBuilder->LineTo(aPoint);
}
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();
- 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));
+ if (aCP1 == mPathBuilder->CurrentPoint() && aCP1 == aCP2 &&
+ aCP1 == aCP3) {
+ return;
}
+ mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
}
virtual UniquePtr<uint8_t[]> GetImageBuffer(
int32_t* out_format, gfx::IntSize* out_imageSize) override;
virtual void OnShutdown();
/**
* Update CurrentState().filter with the filter description for
* CurrentState().filterChain.
@@ -666,21 +626,21 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
void EnsureWritablePath();
// Ensures a path in UserSpace is available.
void EnsureUserSpacePath(
const CanvasWindingRule& aWinding = CanvasWindingRule::Nonzero);
/**
* Needs to be called before updating the transform. This makes a call to
* EnsureTarget() so you don't have to.
*/
- void TransformWillUpdate();
+ void TransformWillUpdate(const mozilla::gfx::Matrix& aTransformToNew);
// Report the fillRule has changed.
void FillRuleChanged();
/**
* Create the backing surfacing, if it doesn't exist. If there is an error
* in creating the target then it will put sErrorTarget in place. If there
* is in turn an error in creating the sErrorTarget then they would both
* be null so IsTargetValid() would still return null.
*
@@ -862,24 +822,21 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
* occur correctly.
*
* There's never both a device space path builder and a user space path
* builder present at the same time. There is also never a path and a
* path builder present at the same time. When writing proceeds on an
* existing path the Path is cleared and a new builder is created.
*
* mPath is always in user-space.
*/
RefPtr<mozilla::gfx::Path> mPath;
- RefPtr<mozilla::gfx::PathBuilder> mDSPathBuilder;
RefPtr<mozilla::gfx::PathBuilder> mPathBuilder;
- bool mPathTransformWillUpdate;
- mozilla::gfx::Matrix mPathToDS;
/**
* Number of times we've invalidated before calling redraw
*/
uint32_t mInvalidateCount;
static const uint32_t kCanvasMaxInvalidateCount = 100;
mozilla::intl::Bidi mBidiEngine;
/**
diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h
index 3f3192b8419b6..0390b86f65762 100644
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1537,20 +1537,33 @@ class DrawTarget : public external::AtomicRefCounted<DrawTarget> {
* @param aStart Starting point of the line
* @param aEnd End point of the line
* @param aPattern Pattern that forms the source of this stroking operation
* @param aOptions Options that are applied to this operation
*/
virtual void StrokeLine(const Point& aStart, const Point& aEnd,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions = StrokeOptions(),
const DrawOptions& aOptions = DrawOptions()) = 0;
+ /**
+ * Stroke a circle on the DrawTarget with a certain source pattern.
+ *
+ * @param aCircle the parameters of the circle
+ * @param aPattern Pattern that forms the source of this stroking operation
+ * @param aOptions Options that are applied to this operation
+ */
+ virtual void StrokeCircle(const Point& aOrigin,
+ float radius,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions = StrokeOptions(),
+ const DrawOptions& aOptions = DrawOptions());
+
/**
* Stroke a path on the draw target with a certain source pattern.
*
* @param aPath Path that is to be stroked
* @param aPattern Pattern that should be used for the stroke
* @param aStrokeOptions Stroke options used for this operation
* @param aOptions Draw options used for this operation
*/
virtual void Stroke(const Path* aPath, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions = StrokeOptions(),
diff --git a/gfx/2d/DrawTarget.cpp b/gfx/2d/DrawTarget.cpp
index 11a7ca5afb883..bd206a53ddf9e 100644
--- a/gfx/2d/DrawTarget.cpp
+++ b/gfx/2d/DrawTarget.cpp
@@ -172,20 +172,29 @@ void DrawTarget::PushDeviceSpaceClipRects(const IntRect* aRects,
SetTransform(oldTransform);
}
void DrawTarget::FillRoundedRect(const RoundedRect& aRect,
const Pattern& aPattern,
const DrawOptions& aOptions) {
RefPtr<Path> path = MakePathForRoundedRect(*this, aRect.rect, aRect.corners);
Fill(path, aPattern, aOptions);
}
+void DrawTarget::StrokeCircle(const Point& aOrigin,
+ float radius,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ RefPtr<Path> path = MakePathForCircle(*this, aOrigin, radius);
+ Stroke(path, aPattern, aStrokeOptions, aOptions);
+}
+
void DrawTarget::StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) {
RefPtr<Path> path = aFont->GetPathForGlyphs(aBuffer, this);
Stroke(path, aPattern, aStrokeOptions, aOptions);
}
already_AddRefed<SourceSurface> DrawTarget::IntoLuminanceSource(
LuminanceType aMaskType, float aOpacity) {
diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp
index d6279004a9072..210df31e68317 100644
--- a/gfx/2d/DrawTargetRecording.cpp
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -348,20 +348,31 @@ void DrawTargetRecording::MaskSurface(const Pattern& aSource,
EnsurePatternDependenciesStored(aSource);
EnsureSurfaceStoredRecording(mRecorder, aMask, "MaskSurface");
mRecorder->RecordEvent(
RecordedMaskSurface(this, aSource, aMask, aOffset, aOptions));
}
void DrawTargetRecording::Stroke(const Path* aPath, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) {
+
+ if (aPath->GetBackendType() == BackendType::RECORDING) {
+ const PathRecording* path =
+ static_cast<const PathRecording*>(aPath);
+ auto circle = path->AsCircle();
+ if (circle) {
+ EnsurePatternDependenciesStored(aPattern);
+ mRecorder->RecordEvent(RecordedStrokeCircle(this, circle.value(), aPattern, aStrokeOptions, aOptions));
+ }
+ }
+
RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath);
EnsurePatternDependenciesStored(aPattern);
mRecorder->RecordEvent(
RecordedStroke(this, pathRecording, aPattern, aStrokeOptions, aOptions));
}
already_AddRefed<SourceSurface> DrawTargetRecording::Snapshot() {
RefPtr<SourceSurface> retSurf =
new SourceSurfaceRecording(mRect.Size(), mFormat, mRecorder);
diff --git a/gfx/2d/PathHelpers.h b/gfx/2d/PathHelpers.h
index 22befa91db9af..5fd6d45934cb6 100644
--- a/gfx/2d/PathHelpers.h
+++ b/gfx/2d/PathHelpers.h
@@ -251,20 +251,29 @@ GFX2D_API void AppendEllipseToPath(PathBuilder* aPathBuilder,
const Size& aDimensions);
inline already_AddRefed<Path> MakePathForEllipse(const DrawTarget& aDrawTarget,
const Point& aCenter,
const Size& aDimensions) {
RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
AppendEllipseToPath(builder, aCenter, aDimensions);
return builder->Finish();
}
+inline already_AddRefed<Path> MakePathForCircle(const DrawTarget& aDrawTarget,
+ const Point& aCenter,
+ float aRadius) {
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
+ builder->Arc(aCenter, aRadius, 0.0f, Float(2.0 * M_PI));
+ builder->Close();
+ return builder->Finish();
+}
+
/**
* If aDrawTarget's transform only contains a translation, and if this line is
* a horizontal or vertical line, this function will snap the line's vertices
* to align with the device pixel grid so that stroking the line with a one
* pixel wide stroke will result in a crisp line that is not antialiased over
* two pixels across its width.
*
* @return Returns true if this function snaps aRect's vertices, else returns
* false.
*/
diff --git a/gfx/2d/PathRecording.cpp b/gfx/2d/PathRecording.cpp
index 6b499c6d0c084..a5d2a9495d95b 100644
--- a/gfx/2d/PathRecording.cpp
+++ b/gfx/2d/PathRecording.cpp
@@ -105,20 +105,46 @@ PathOps PathOps::TransformedCopy(const Matrix& aTransform) const {
newPathOps.Close();
break;
default:
MOZ_CRASH("We control mOpTypes, so this should never happen.");
}
}
return newPathOps;
}
+Maybe<Circle> PathOps::AsCircle() const {
+ if (mPathData.empty()) {
+ return Nothing();
+ }
+
+ const uint8_t* nextByte = mPathData.data();
+ const uint8_t* end = nextByte + mPathData.size();
+ const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
+ nextByte += sizeof(OpType);
+ if (opType == OpType::OP_ARC) {
+ NEXT_PARAMS(ArcParams)
+ if (fabs(params.startAngle - params.endAngle) == 2 * M_PI) {
+ 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 });
+ }
+ }
+ }
+ }
+
+ return Nothing();
+}
+
+
#undef NEXT_PARAMS
size_t PathOps::NumberOfOps() const {
size_t size = 0;
const uint8_t* nextByte = mPathData.data();
const uint8_t* end = nextByte + mPathData.size();
while (nextByte < end) {
size++;
const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
nextByte += sizeof(OpType);
diff --git a/gfx/2d/PathRecording.h b/gfx/2d/PathRecording.h
index 9be4b1713ed9a..e45eb99545322 100644
--- a/gfx/2d/PathRecording.h
+++ b/gfx/2d/PathRecording.h
@@ -10,20 +10,25 @@
#include "2D.h"
#include <vector>
#include <ostream>
#include "PathHelpers.h"
#include "RecordingTypes.h"
namespace mozilla {
namespace gfx {
+ struct Circle {
+ Point origin;
+ float radius;
+ };
+
class PathOps {
public:
PathOps() = default;
template <class S>
explicit PathOps(S& aStream);
PathOps(const PathOps& aOther) = default;
PathOps& operator=(const PathOps&) = delete; // assign using std::move()!
@@ -56,20 +61,22 @@ class PathOps {
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;
+
private:
enum class OpType : uint32_t {
OP_MOVETO = 0,
OP_LINETO,
OP_BEZIERTO,
OP_QUADRATICBEZIERTO,
OP_ARC,
OP_CLOSE,
OP_INVALID
};
@@ -199,20 +206,24 @@ class PathRecording final : public Path {
const Matrix& aTransform = Matrix()) const final {
EnsurePath();
return mPath->GetStrokedBounds(aStrokeOptions, aTransform);
}
Maybe<Rect> AsRect() const final {
EnsurePath();
return mPath->AsRect();
}
+ Maybe<Circle> AsCircle() const {
+ return mPathOps.AsCircle();
+ }
+
void StreamToSink(PathSink* aSink) const final {
mPathOps.StreamToSink(*aSink);
}
FillRule GetFillRule() const final { return mFillRule; }
private:
friend class DrawTargetWrapAndRecord;
friend class DrawTargetRecording;
friend class RecordedPathCreation;
diff --git a/gfx/2d/RecordedEvent.h b/gfx/2d/RecordedEvent.h
index 17516436e2c2f..5c697342d0bfc 100644
--- a/gfx/2d/RecordedEvent.h
+++ b/gfx/2d/RecordedEvent.h
@@ -354,20 +354,21 @@ class EventStream {
};
class RecordedEvent {
public:
enum EventType {
DRAWTARGETCREATION = 0,
DRAWTARGETDESTRUCTION,
FILLRECT,
STROKERECT,
STROKELINE,
+ STROKECIRCLE,
CLEARRECT,
COPYSURFACE,
SETTRANSFORM,
PUSHCLIP,
PUSHCLIPRECT,
POPCLIP,
FILL,
FILLGLYPHS,
MASK,
STROKE,
diff --git a/gfx/2d/RecordedEventImpl.h b/gfx/2d/RecordedEventImpl.h
index 995d63101c57c..166f2c603f9c5 100644
--- a/gfx/2d/RecordedEventImpl.h
+++ b/gfx/2d/RecordedEventImpl.h
@@ -299,20 +299,55 @@ class RecordedStrokeLine : public RecordedDrawingEvent<RecordedStrokeLine> {
template <class S>
MOZ_IMPLICIT RecordedStrokeLine(S& aStream);
Point mBegin;
Point mEnd;
PatternStorage mPattern;
StrokeOptions mStrokeOptions;
DrawOptions mOptions;
};
+class RecordedStrokeCircle : public RecordedDrawingEvent<RecordedStrokeCircle> {
+ public:
+ RecordedStrokeCircle(DrawTarget* aDT, Circle aCircle,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(STROKECIRCLE, aDT),
+ mCircle(aCircle),
+ mPattern(),
+ mStrokeOptions(aStrokeOptions),
+ mOptions(aOptions) {
+ StorePattern(mPattern, aPattern);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "StrokeCircle"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedStrokeCircle(S& aStream);
+
+ Circle mCircle;
+ PatternStorage mPattern;
+ StrokeOptions mStrokeOptions;
+ DrawOptions mOptions;
+};
+
+
class RecordedFill : public RecordedDrawingEvent<RecordedFill> {
public:
RecordedFill(DrawTarget* aDT, ReferencePtr aPath, const Pattern& aPattern,
const DrawOptions& aOptions)
: RecordedDrawingEvent(FILL, aDT),
mPath(aPath),
mPattern(),
mOptions(aOptions) {
StorePattern(mPattern, aPattern);
}
@@ -2337,20 +2372,57 @@ RecordedStrokeLine::RecordedStrokeLine(S& aStream)
}
inline void RecordedStrokeLine::OutputSimpleEventInfo(
std::stringstream& aStringStream) const {
aStringStream << "[" << mDT << "] StrokeLine (" << mBegin.x << ", "
<< mBegin.y << " - " << mEnd.x << ", " << mEnd.y
<< ") LineWidth: " << mStrokeOptions.mLineWidth << "px ";
OutputSimplePatternInfo(mPattern, aStringStream);
}
+inline bool RecordedStrokeCircle::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->StrokeCircle(mCircle.origin, mCircle.radius, *GenericPattern(mPattern, aTranslator),
+ mStrokeOptions, mOptions);
+ return true;
+}
+
+template <class S>
+void RecordedStrokeCircle::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mCircle);
+ WriteElement(aStream, mOptions);
+ RecordPatternData(aStream, mPattern);
+ RecordStrokeOptions(aStream, mStrokeOptions);
+}
+
+template <class S>
+RecordedStrokeCircle::RecordedStrokeCircle(S& aStream)
+ : RecordedDrawingEvent(STROKELINE, aStream) {
+ ReadElement(aStream, mCircle);
+ ReadDrawOptions(aStream, mOptions);
+ ReadPatternData(aStream, mPattern);
+ ReadStrokeOptions(aStream, mStrokeOptions);
+}
+
+inline void RecordedStrokeCircle::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] StrokeCircle (" << mCircle.origin.x << ", "
+ << mCircle.origin.y << " - " << mCircle.radius
+ << ") LineWidth: " << mStrokeOptions.mLineWidth << "px ";
+ OutputSimplePatternInfo(mPattern, aStringStream);
+}
+
inline bool RecordedFill::PlayEvent(Translator* aTranslator) const {
DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
if (!dt) {
return false;
}
dt->Fill(aTranslator->LookupPath(mPath),
*GenericPattern(mPattern, aTranslator), mOptions);
return true;
}
@@ -3986,20 +4058,21 @@ inline void RecordedDestination::OutputSimpleEventInfo(
std::stringstream& aStringStream) const {
aStringStream << "Destination [" << mDestination << " @ " << mPoint << "]";
}
#define FOR_EACH_EVENT(f) \
f(DRAWTARGETCREATION, RecordedDrawTargetCreation); \
f(DRAWTARGETDESTRUCTION, RecordedDrawTargetDestruction); \
f(FILLRECT, RecordedFillRect); \
f(STROKERECT, RecordedStrokeRect); \
f(STROKELINE, RecordedStrokeLine); \
+ f(STROKECIRCLE, RecordedStrokeCircle); \
f(CLEARRECT, RecordedClearRect); \
f(COPYSURFACE, RecordedCopySurface); \
f(SETTRANSFORM, RecordedSetTransform); \
f(PUSHCLIPRECT, RecordedPushClipRect); \
f(PUSHCLIP, RecordedPushClip); \
f(POPCLIP, RecordedPopClip); \
f(FILL, RecordedFill); \
f(FILLGLYPHS, RecordedFillGlyphs); \
f(MASK, RecordedMask); \
f(STROKE, RecordedStroke); \
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment