Bug 784573 - Fix problems converting cairo paths between user space and device space. r=joe

This commit is contained in:
Anthony Jones 2012-09-03 17:48:04 -04:00
parent a362c2be73
commit 223dd80132
4 changed files with 122 additions and 185 deletions

View File

@ -281,6 +281,7 @@ NeedIntermediateSurface(const Pattern& aPattern, const DrawOptions& aOptions)
DrawTargetCairo::DrawTargetCairo()
: mContext(nullptr)
, mPathObserver(nullptr)
{
}
@ -687,11 +688,6 @@ DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FILL_WINDING */) cons
const_cast<DrawTargetCairo*>(this),
aFillRule);
// Creating a PathBuilder implicitly resets our mPathObserver, as it calls
// SetPathObserver() on us. Since this guarantees our old path is saved off,
// it's safe to reset the path here.
cairo_new_path(mContext);
return builder;
}
@ -921,12 +917,6 @@ DrawTargetCairo::SetPathObserver(CairoPathContext* aPathObserver)
void
DrawTargetCairo::SetTransform(const Matrix& aTransform)
{
// We're about to logically change our transformation. Our current path will
// need to change, because Cairo stores paths in device space.
if (mPathObserver) {
mPathObserver->MatrixWillChange(aTransform);
}
mTransform = aTransform;
cairo_matrix_t mat;

View File

@ -169,7 +169,12 @@ private: // data
cairo_surface_t* mSurface;
IntSize mSize;
std::vector<SourceSurfaceCairo*> mSnapshots;
mutable RefPtr<CairoPathContext> mPathObserver;
// It is safe to use a regular pointer here because the CairoPathContext will
// deregister itself on destruction. Using a RefPtr would extend the life-
// span of the CairoPathContext. This causes a problem when
// PathBuilderCairo.Finish()
mutable CairoPathContext* mPathObserver;
};
}

View File

@ -13,61 +13,40 @@
namespace mozilla {
namespace gfx {
CairoPathContext::CairoPathContext(cairo_t* aCtx, DrawTargetCairo* aDrawTarget,
FillRule aFillRule,
const Matrix& aTransform /* = Matrix() */)
: mTransform(aTransform)
, mContext(aCtx)
CairoPathContext::CairoPathContext(cairo_t* aCtx, DrawTargetCairo* aDrawTarget)
: mContext(aCtx)
, mDrawTarget(aDrawTarget)
, mFillRule(aFillRule)
{
cairo_reference(mContext);
cairo_set_fill_rule(mContext, GfxFillRuleToCairoFillRule(mFillRule));
// If we don't have an identity transformation, we need to have a separate
// context from the draw target, because we can't set a transformation on its
// context.
if (mDrawTarget && !mTransform.IsIdentity()) {
DuplicateContextAndPath(mTransform);
// A new path in the DrawTarget's context.
aDrawTarget->SetPathObserver(this);
cairo_new_path(mContext);
}
ForgetDrawTarget();
} else if (mDrawTarget) {
mDrawTarget->SetPathObserver(this);
}
CairoPathContext::CairoPathContext(CairoPathContext& aPathContext)
: mContext(aPathContext.mContext)
, mDrawTarget(nullptr)
{
cairo_reference(mContext);
DuplicateContextAndPath();
}
CairoPathContext::~CairoPathContext()
{
if (mDrawTarget) {
mDrawTarget->SetPathObserver(nullptr);
DrawTargetCairo* drawTarget = mDrawTarget;
ForgetDrawTarget();
// We need to set mDrawTarget to nullptr before we tell DrawTarget otherwise
// we will think we need to make a defensive copy of the path.
drawTarget->SetPathObserver(nullptr);
}
cairo_destroy(mContext);
}
void
CairoPathContext::ObserveTarget(DrawTargetCairo* aDrawTarget)
{
if (!aDrawTarget) {
return;
}
if (mDrawTarget) {
mDrawTarget->SetPathObserver(nullptr);
}
mDrawTarget = aDrawTarget;
// If there is a transform on the path, then we must have a separate context
// from the draw target, so we cannot be its observer
if (!mTransform.IsIdentity()) {
ForgetDrawTarget();
return;
}
mDrawTarget->SetPathObserver(this);
}
void
CairoPathContext::DuplicateContextAndPath(const Matrix& aMatrix /* = Matrix() */)
CairoPathContext::DuplicateContextAndPath()
{
// Duplicate the path.
cairo_path_t* path = cairo_copy_path(mContext);
@ -75,20 +54,31 @@ CairoPathContext::DuplicateContextAndPath(const Matrix& aMatrix /* = Matrix() */
// Duplicate the context.
cairo_surface_t* surf = cairo_get_target(mContext);
cairo_matrix_t matrix;
cairo_get_matrix(mContext, &matrix);
cairo_destroy(mContext);
mContext = cairo_create(surf);
// Transform the context.
cairo_matrix_t matrix;
GfxMatrixToCairoMatrix(aMatrix, matrix);
cairo_transform(mContext, &matrix);
// Set the matrix to match the source context so that the path is copied in
// device space. After this point it doesn't matter what the transform is
// set to because it's always swapped out before use.
cairo_set_matrix(mContext, &matrix);
// Add the path, and throw away our duplicate.
cairo_append_path(mContext, path);
cairo_set_fill_rule(mContext, rule);
cairo_path_destroy(path);
}
void
CairoPathContext::ForgetDrawTarget()
{
// We don't need to set the path observer back to nullptr in this case
// because ForgetDrawTarget() is trigged when the target has been
// grabbed by another path observer.
mDrawTarget = nullptr;
}
void
CairoPathContext::PathWillChange()
{
@ -100,68 +90,22 @@ CairoPathContext::PathWillChange()
// The context we point to is going to change from under us. To continue
// using this path, we need to copy it to a new context.
DuplicateContextAndPath();
ForgetDrawTarget();
}
}
void
CairoPathContext::MatrixWillChange(const Matrix& aNewMatrix)
{
// Cairo paths are stored in device space. Since we logically operate in user
// space, we want to make it so our path will be in the same location if and
// when our path is copied out.
// To effect this, we copy out our path (which, in Cairo, implicitly converts
// to user space), then temporarily set the context to have the new
// transform. We then set the path, which ensures that the points are all
// transformed correctly. Finally, we set the matrix back to its original
// value.
cairo_path_t* path = cairo_copy_path(mContext);
cairo_matrix_t origMatrix;
cairo_get_matrix(mContext, &origMatrix);
cairo_matrix_t newMatrix;
GfxMatrixToCairoMatrix(aNewMatrix, newMatrix);
cairo_set_matrix(mContext, &newMatrix);
cairo_new_path(mContext);
cairo_append_path(mContext, path);
cairo_path_destroy(path);
cairo_set_matrix(mContext, &origMatrix);
}
void
CairoPathContext::CopyPathTo(cairo_t* aToContext)
CairoPathContext::CopyPathTo(cairo_t* aToContext, Matrix& aTransform)
{
if (aToContext != mContext) {
cairo_set_fill_rule(aToContext, GfxFillRuleToCairoFillRule(mFillRule));
cairo_matrix_t origMat;
cairo_get_matrix(aToContext, &origMat);
cairo_matrix_t mat;
GfxMatrixToCairoMatrix(mTransform, mat);
cairo_transform(aToContext, &mat);
// cairo_copy_path gives us a user-space copy of the path, so we don't have
// to worry about transformations here.
CairoTempMatrix tempMatrix(mContext, aTransform);
cairo_path_t* path = cairo_copy_path(mContext);
cairo_new_path(aToContext);
cairo_append_path(aToContext, path);
cairo_path_destroy(path);
cairo_set_matrix(aToContext, &origMat);
}
}
void
CairoPathContext::ForgetDrawTarget()
{
mDrawTarget = nullptr;
}
bool
CairoPathContext::ContainsPath(const Path* aPath)
{
@ -175,38 +119,32 @@ CairoPathContext::ContainsPath(const Path* aPath)
}
PathBuilderCairo::PathBuilderCairo(CairoPathContext* aPathContext,
FillRule aFillRule,
const Matrix& aTransform /* = Matrix() */)
: mFillRule(aPathContext->GetFillRule())
{
RefPtr<DrawTargetCairo> drawTarget = aPathContext->GetDrawTarget();
mPathContext = new CairoPathContext(*aPathContext, drawTarget, mFillRule,
aPathContext->GetTransform() * aTransform);
// We need to ensure that we are allowed to modify the path currently set on
// aPathContext. If we don't have a draw target, CairoPathContext's
// constructor has no way to make aPathContext duplicate its path (normally,
// calling drawTarget->SetPathObserver() would do so). In this case, we
// explicitly make aPathContext copy out its context and path, leaving our
// path alone.
if (!drawTarget) {
aPathContext->DuplicateContextAndPath();
}
}
: mPathContext(aPathContext)
, mTransform(aTransform)
, mFillRule(aFillRule)
{}
PathBuilderCairo::PathBuilderCairo(cairo_t* aCtx, DrawTargetCairo* aDrawTarget, FillRule aFillRule)
: mPathContext(new CairoPathContext(aCtx, aDrawTarget, aFillRule))
: mPathContext(new CairoPathContext(aCtx, aDrawTarget))
, mTransform(aDrawTarget->GetTransform())
, mFillRule(aFillRule)
{}
void
PathBuilderCairo::MoveTo(const Point &aPoint)
{
PrepareForWrite();
CairoTempMatrix tempMatrix(*mPathContext, mTransform);
cairo_move_to(*mPathContext, aPoint.x, aPoint.y);
}
void
PathBuilderCairo::LineTo(const Point &aPoint)
{
PrepareForWrite();
CairoTempMatrix tempMatrix(*mPathContext, mTransform);
cairo_line_to(*mPathContext, aPoint.x, aPoint.y);
}
@ -215,6 +153,8 @@ PathBuilderCairo::BezierTo(const Point &aCP1,
const Point &aCP2,
const Point &aCP3)
{
PrepareForWrite();
CairoTempMatrix tempMatrix(*mPathContext, mTransform);
cairo_curve_to(*mPathContext, aCP1.x, aCP1.y, aCP2.x, aCP2.y, aCP3.x, aCP3.y);
}
@ -222,6 +162,9 @@ void
PathBuilderCairo::QuadraticBezierTo(const Point &aCP1,
const Point &aCP2)
{
PrepareForWrite();
CairoTempMatrix tempMatrix(*mPathContext, mTransform);
// We need to elevate the degree of this quadratic Bézier to cubic, so we're
// going to add an intermediate control point, and recompute control point 1.
// The first and last control points remain the same.
@ -237,6 +180,7 @@ PathBuilderCairo::QuadraticBezierTo(const Point &aCP1,
void
PathBuilderCairo::Close()
{
PrepareForWrite();
cairo_close_path(*mPathContext);
}
@ -250,6 +194,7 @@ PathBuilderCairo::Arc(const Point &aOrigin, float aRadius, float aStartAngle,
Point
PathBuilderCairo::CurrentPoint() const
{
CairoTempMatrix tempMatrix(*mPathContext, mTransform);
double x, y;
cairo_get_current_point(*mPathContext, &x, &y);
return Point(x, y);
@ -258,11 +203,7 @@ PathBuilderCairo::CurrentPoint() const
TemporaryRef<Path>
PathBuilderCairo::Finish()
{
RefPtr<PathCairo> path = new PathCairo(*mPathContext,
mPathContext->GetDrawTarget(),
mFillRule,
mPathContext->GetTransform());
return path;
return new PathCairo(mPathContext, mTransform, mFillRule);
}
TemporaryRef<CairoPathContext>
@ -271,43 +212,63 @@ PathBuilderCairo::GetPathContext()
return mPathContext;
}
PathCairo::PathCairo(cairo_t* aCtx, DrawTargetCairo* aDrawTarget, FillRule aFillRule, const Matrix& aTransform)
: mPathContext(new CairoPathContext(aCtx, aDrawTarget, aFillRule, aTransform))
void
PathBuilderCairo::PrepareForWrite()
{
// Only PathBuilder and PathCairo maintain references to CairoPathContext.
// DrawTarget does not. If we're sharing a reference to the context then we
// need to create a copy that we can modify. This provides copy on write
// behaviour.
if (mPathContext->refCount() != 1) {
mPathContext = new CairoPathContext(*mPathContext);
}
}
PathCairo::PathCairo(CairoPathContext* aPathContext, Matrix& aTransform,
FillRule aFillRule)
: mPathContext(aPathContext)
, mTransform(aTransform)
, mFillRule(aFillRule)
{}
TemporaryRef<PathBuilder>
PathCairo::CopyToBuilder(FillRule aFillRule) const
{
// Note: This PathBuilderCairo constructor causes our mPathContext to copy
// out the path, since the path builder is going to change the path on us.
RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(mPathContext);
return builder;
return new PathBuilderCairo(mPathContext, aFillRule, mTransform);
}
TemporaryRef<PathBuilder>
PathCairo::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
{
// Note: This PathBuilderCairo constructor causes our mPathContext to copy
// out the path, since the path builder is going to change the path on us.
RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(mPathContext,
aTransform);
return builder;
// We are given the transform we would apply from device space to user space.
// However in cairo our path is in device space so we view the transform as
// being the other way round. We therefore need to apply the inverse transform
// to our current cairo transform.
Matrix inverse = aTransform;
inverse.Invert();
return new PathBuilderCairo(mPathContext, aFillRule, mTransform * inverse);
}
bool
PathCairo::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
{
CairoTempMatrix(*mPathContext, mTransform);
Matrix inverse = aTransform;
inverse.Invert();
Point transformed = inverse * aPoint;
// Needs the correct fill rule set.
cairo_set_fill_rule(*mPathContext, GfxFillRuleToCairoFillRule(mFillRule));
return cairo_in_fill(*mPathContext, transformed.x, transformed.y);
}
Rect
PathCairo::GetBounds(const Matrix &aTransform) const
{
CairoTempMatrix(*mPathContext, mTransform);
double x1, y1, x2, y2;
cairo_path_extents(*mPathContext, &x1, &y1, &x2, &y2);
@ -319,6 +280,8 @@ Rect
PathCairo::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
const Matrix &aTransform) const
{
CairoTempMatrix(*mPathContext, mTransform);
double x1, y1, x2, y2;
SetCairoStrokeOptions(*mPathContext, aStrokeOptions);
@ -337,13 +300,8 @@ PathCairo::GetPathContext()
void
PathCairo::CopyPathTo(cairo_t* aContext, DrawTargetCairo* aDrawTarget)
{
if (mPathContext->GetContext() != aContext) {
mPathContext->CopyPathTo(aContext);
// Since aDrawTarget wants us to be the current path on its context, we
// should also listen to it for updates to that path (as an optimization).
mPathContext->ObserveTarget(aDrawTarget);
}
mPathContext->CopyPathTo(aContext, mTransform);
cairo_set_fill_rule(aContext, GfxFillRuleToCairoFillRule(mFillRule));
}
}

View File

@ -37,74 +37,57 @@ class DrawTargetCairo;
class CairoPathContext : public RefCounted<CairoPathContext>
{
public:
// Construct a CairoPathContext and set it to be the path observer of
// aDrawTarget. Optionally, this path can be transformed by aMatrix.
CairoPathContext(cairo_t* aCtx, DrawTargetCairo* aDrawTarget,
FillRule aFillRule,
const Matrix& aMatrix = Matrix());
// Construct a new empty CairoPathContext that uses the given draw target and
// its cairo context. Using the existing context may save having to copy the
// path later.
CairoPathContext(cairo_t* aCtx, DrawTargetCairo* aDrawTarget);
// Copy the path.
CairoPathContext(CairoPathContext& aPathContext);
~CairoPathContext();
// Copy the path on mContext to be the path on aToContext, if they aren't the
// same.
void CopyPathTo(cairo_t* aToContext);
// same. At this point we set the fill rule for the destination context as
// there is little point in doing this earlier.
void CopyPathTo(cairo_t* aToContext, Matrix& aTransform);
// This method must be called by the draw target before it changes the path
// currently on the cairo context.
void PathWillChange();
// This method must be called by the draw target whenever it is going to
// change the current transformation on mContext.
void MatrixWillChange(const Matrix& aMatrix);
// This method must be called as the draw target is dying. In this case, we
// forget our reference to the draw target, and become the only reference to
// our context.
void ForgetDrawTarget();
// Create a duplicate context, and copy this path to that context. Optionally,
// the new context can be transformed.
void DuplicateContextAndPath(const Matrix& aMatrix = Matrix());
// Create a duplicate context, and copy this path to that context.
void DuplicateContextAndPath();
// Returns true if this CairoPathContext represents path.
bool ContainsPath(const Path* path);
// add ourselves as an observer of aDrawTarget, if possible
// if we succeed, then mDrawTarget is set to aDrawTarget
void ObserveTarget(DrawTargetCairo* aDrawTarget);
cairo_t* GetContext() const { return mContext; }
DrawTargetCairo* GetDrawTarget() const { return mDrawTarget; }
Matrix GetTransform() const { return mTransform; }
FillRule GetFillRule() const { return mFillRule; }
operator cairo_t* () const { return mContext; }
private: // methods
CairoPathContext(const CairoPathContext&) MOZ_DELETE;
private: // data
Matrix mTransform;
cairo_t* mContext;
// Not a RefPtr to avoid cycles.
DrawTargetCairo* mDrawTarget;
FillRule mFillRule;
};
class PathBuilderCairo : public PathBuilder
{
public:
// This constructor implicitly takes ownership of aCtx by calling
// aDrawTarget->SetPathObserver(). Therefore, if the draw target has a path
// observer, this constructor will cause it to copy out its path.
// The path currently set on aCtx is not changed.
// Creates a new empty path. It also implicitly takes ownership of aCtx by
// calling aDrawTarget->SetPathObserver(). Therefore, if the draw target has a
// path observer, this constructor will cause it to copy out its path.
PathBuilderCairo(cairo_t* aCtx, DrawTargetCairo* aDrawTarget, FillRule aFillRule);
// This constructor, called with a CairoPathContext*, implicitly takes
// ownership of the path, and therefore makes aPathContext copy out its path
// regardless of whether it has a pointer to a DrawTargetCairo.
// The path currently set on aPathContext is not changed.
explicit PathBuilderCairo(CairoPathContext* aPathContext,
const Matrix& aTransform = Matrix());
// Creates a path builder out of an existing CairoPathContext with a new fill
// rule and transform.
PathBuilderCairo(CairoPathContext* aContext, FillRule aFillRule, const Matrix& aTransform = Matrix());
virtual void MoveTo(const Point &aPoint);
virtual void LineTo(const Point &aPoint);
@ -121,18 +104,18 @@ public:
TemporaryRef<CairoPathContext> GetPathContext();
private: // methods
void SetFillRule(FillRule aFillRule);
private: // data
void PrepareForWrite();
RefPtr<CairoPathContext> mPathContext;
Matrix mTransform;
FillRule mFillRule;
};
class PathCairo : public Path
{
public:
PathCairo(cairo_t* aCtx, DrawTargetCairo* aDrawTarget, FillRule aFillRule, const Matrix& aTransform);
PathCairo(CairoPathContext* aPathContex, Matrix& aTransform, FillRule aFillRule);
virtual BackendType GetBackendType() const { return BACKEND_CAIRO; }
@ -158,6 +141,7 @@ public:
private:
RefPtr<CairoPathContext> mPathContext;
Matrix mTransform;
FillRule mFillRule;
};