From 188a24ed3080136f68d388c22366341732080910 Mon Sep 17 00:00:00 2001 From: Joe Drew Date: Mon, 9 Jan 2012 16:50:01 -0500 Subject: [PATCH] Bug 715652 - Implement significantly more of the 2D API for the Cairo backend. r=jrmuizel --HG-- extra : rebase_source : b23314691fef65db2518d3e738a66039e3c18909 --- gfx/2d/DrawTargetCairo.cpp | 679 ++++++++++++++++++++++++++++++++-- gfx/2d/DrawTargetCairo.h | 103 ++++-- gfx/2d/SourceSurfaceCairo.cpp | 164 +++++++- gfx/2d/SourceSurfaceCairo.h | 43 ++- gfx/2d/Types.h | 3 +- 5 files changed, 916 insertions(+), 76 deletions(-) diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp index ae6d46718959..23fa8cefffc0 100644 --- a/gfx/2d/DrawTargetCairo.cpp +++ b/gfx/2d/DrawTargetCairo.cpp @@ -35,24 +35,54 @@ * ***** END LICENSE BLOCK ***** */ #include "DrawTargetCairo.h" + #include "SourceSurfaceCairo.h" +#include "cairo.h" + +#include "Blur.h" + +#ifdef CAIRO_HAS_QUARTZ_SURFACE +#include "cairo-quartz.h" +#include +#endif + +#ifdef CAIRO_HAS_XLIB_SURFACE +#include "cairo-xlib.h" +#endif + +#include + namespace mozilla { namespace gfx { -cairo_operator_t +static cairo_operator_t GfxOpToCairoOp(CompositionOp op) { switch (op) { case OP_OVER: return CAIRO_OPERATOR_OVER; - case OP_SOURCE: - return CAIRO_OPERATOR_SOURCE; case OP_ADD: return CAIRO_OPERATOR_ADD; case OP_ATOP: return CAIRO_OPERATOR_ATOP; + case OP_OUT: + return CAIRO_OPERATOR_OUT; + case OP_IN: + return CAIRO_OPERATOR_IN; + case OP_SOURCE: + return CAIRO_OPERATOR_SOURCE; + case OP_DEST_IN: + return CAIRO_OPERATOR_DEST_IN; + case OP_DEST_OUT: + return CAIRO_OPERATOR_DEST_OUT; + case OP_DEST_OVER: + return CAIRO_OPERATOR_DEST_OVER; + case OP_DEST_ATOP: + return CAIRO_OPERATOR_DEST_ATOP; + case OP_XOR: + return CAIRO_OPERATOR_XOR; case OP_COUNT: break; } @@ -60,7 +90,7 @@ GfxOpToCairoOp(CompositionOp op) return CAIRO_OPERATOR_OVER; } -cairo_filter_t +static cairo_filter_t GfxFilterToCairoFilter(Filter filter) { switch (filter) @@ -74,7 +104,23 @@ GfxFilterToCairoFilter(Filter filter) return CAIRO_FILTER_BILINEAR; } -cairo_format_t +static cairo_extend_t +GfxExtendToCairoExtend(ExtendMode extend) +{ + switch (extend) + { + case EXTEND_CLAMP: + return CAIRO_EXTEND_PAD; + case EXTEND_REPEAT: + return CAIRO_EXTEND_REPEAT; + case EXTEND_REFLECT: + return CAIRO_EXTEND_REFLECT; + } + + return CAIRO_EXTEND_PAD; +} + +static cairo_format_t GfxFormatToCairoFormat(SurfaceFormat format) { switch (format) @@ -90,12 +136,223 @@ GfxFormatToCairoFormat(SurfaceFormat format) return CAIRO_FORMAT_ARGB32; } +static cairo_content_t +GfxFormatToCairoContent(SurfaceFormat format) +{ + switch (format) + { + case FORMAT_B8G8R8A8: + return CAIRO_CONTENT_COLOR_ALPHA; + case FORMAT_B8G8R8X8: + return CAIRO_CONTENT_COLOR; + case FORMAT_A8: + return CAIRO_CONTENT_ALPHA; + } + + return CAIRO_CONTENT_COLOR_ALPHA; +} + +static cairo_line_join_t +GfxLineJoinToCairoLineJoin(JoinStyle style) +{ + switch (style) + { + case JOIN_BEVEL: + return CAIRO_LINE_JOIN_BEVEL; + case JOIN_ROUND: + return CAIRO_LINE_JOIN_ROUND; + case JOIN_MITER: + return CAIRO_LINE_JOIN_MITER; + case JOIN_MITER_OR_BEVEL: + return CAIRO_LINE_JOIN_MITER; + } + + return CAIRO_LINE_JOIN_MITER; +} + +static cairo_line_cap_t +GfxLineCapToCairoLineCap(CapStyle style) +{ + switch (style) + { + case CAP_BUTT: + return CAIRO_LINE_CAP_BUTT; + case CAP_ROUND: + return CAIRO_LINE_CAP_ROUND; + case CAP_SQUARE: + return CAIRO_LINE_CAP_SQUARE; + } + + return CAIRO_LINE_CAP_BUTT; +} + +static SurfaceFormat +CairoContentToGfxFormat(cairo_content_t content) +{ + switch (content) + { + case CAIRO_CONTENT_COLOR_ALPHA: + return FORMAT_B8G8R8A8; + case CAIRO_CONTENT_COLOR: + return FORMAT_B8G8R8X8; + case CAIRO_CONTENT_ALPHA: + return FORMAT_A8; + } + + return FORMAT_B8G8R8A8; +} + +static bool +GetCairoSurfaceSize(cairo_surface_t* surface, IntSize& size) +{ + switch (cairo_surface_get_type(surface)) + { + case CAIRO_SURFACE_TYPE_IMAGE: + { + size.width = cairo_image_surface_get_width(surface); + size.height = cairo_image_surface_get_height(surface); + return true; + } + +#ifdef CAIRO_HAS_XLIB_SURFACE + case CAIRO_SURFACE_TYPE_XLIB: + { + size.width = cairo_xlib_surface_get_width(surface); + size.height = cairo_xlib_surface_get_height(surface); + return true; + } +#endif + +#ifdef CAIRO_HAS_QUARTZ_SURFACE + case CAIRO_SURFACE_TYPE_QUARTZ: + { + CGContextRef cgc = cairo_quartz_surface_get_cg_context(surface); + + // It's valid to call these CGBitmapContext functions on non-bitmap + // contexts; they'll just return 0 in that case. + size.width = CGBitmapContextGetWidth(cgc); + size.height = CGBitmapContextGetWidth(cgc); + return size.width != 0; + } +#endif + + default: + return false; + } +} + void GfxMatrixToCairoMatrix(const Matrix& mat, cairo_matrix_t& retval) { cairo_matrix_init(&retval, mat._11, mat._12, mat._21, mat._22, mat._31, mat._32); } +cairo_pattern_t* +GfxPatternToCairoPattern(const Pattern& aPattern, Float aAlpha) +{ + cairo_pattern_t* pat = NULL; + + switch (aPattern.GetType()) + { + case PATTERN_COLOR: + { + Color color = static_cast(aPattern).mColor; + pat = cairo_pattern_create_rgba(color.r, color.g, color.b, color.a * aAlpha); + break; + } + + case PATTERN_SURFACE: + { + const SurfacePattern& pattern = static_cast(aPattern); + cairo_surface_t* surf = NULL; + + if (pattern.mSurface->GetType() == SURFACE_CAIRO) { + const SourceSurfaceCairo* sourcesurf = static_cast(pattern.mSurface.get()); + surf = sourcesurf->GetSurface(); + cairo_surface_reference(surf); + } else if (pattern.mSurface->GetType() == SURFACE_CAIRO_IMAGE) { + const DataSourceSurfaceCairo* sourcesurf = + static_cast(pattern.mSurface.get()); + surf = sourcesurf->GetSurface(); + cairo_surface_reference(surf); + } else { + RefPtr sourcesurf = pattern.mSurface->GetDataSurface(); + surf = cairo_image_surface_create_for_data(sourcesurf->GetData(), + GfxFormatToCairoFormat(sourcesurf->GetFormat()), + sourcesurf->GetSize().width, + sourcesurf->GetSize().height, + sourcesurf->Stride()); + } + + pat = cairo_pattern_create_for_surface(surf); + cairo_pattern_set_filter(pat, GfxFilterToCairoFilter(pattern.mFilter)); + cairo_pattern_set_extend(pat, GfxExtendToCairoExtend(pattern.mExtendMode)); + + cairo_surface_destroy(surf); + + break; + } + case PATTERN_LINEAR_GRADIENT: + { + const LinearGradientPattern& pattern = static_cast(aPattern); + RefPtr stops = pattern.mStops; + if (stops->GetBackendType() == BACKEND_CAIRO) { + pat = cairo_pattern_create_linear(pattern.mBegin.x, pattern.mBegin.y, + pattern.mEnd.x, pattern.mEnd.y); + + const std::vector& stops = + static_cast(pattern.mStops.get())->GetStops(); + for (std::vector::const_iterator i = stops.begin(); + i != stops.end(); + ++i) { + cairo_pattern_add_color_stop_rgba(pat, i->offset, i->color.r, + i->color.g, i->color.b, + i->color.a); + } + } + + break; + } + case PATTERN_RADIAL_GRADIENT: + { + const RadialGradientPattern& pattern = static_cast(aPattern); + RefPtr stops = pattern.mStops; + if (stops->GetBackendType() == BACKEND_CAIRO) { + pat = cairo_pattern_create_radial(pattern.mCenter1.x, pattern.mCenter1.y, pattern.mRadius1, + pattern.mCenter2.x, pattern.mCenter2.y, pattern.mRadius2); + + const std::vector& stops = + static_cast(pattern.mStops.get())->GetStops(); + for (std::vector::const_iterator i = stops.begin(); + i != stops.end(); + ++i) { + cairo_pattern_add_color_stop_rgba(pat, i->offset, i->color.r, + i->color.g, i->color.b, + i->color.a); + } + } + + break; + } + } + + return pat; +} + +bool +NeedIntermediateSurface(const Pattern& aPattern, const DrawOptions& aOptions) +{ + // We pre-multiply colours' alpha by the global alpha, so we don't need to + // use an intermediate surface for them. + if (aPattern.GetType() == PATTERN_COLOR) + return false; + + if (aOptions.mAlpha == 1.0) + return false; + + return true; +} + DrawTargetCairo::DrawTargetCairo() : mContext(NULL) { @@ -103,12 +360,30 @@ DrawTargetCairo::DrawTargetCairo() DrawTargetCairo::~DrawTargetCairo() { + MarkSnapshotsIndependent(); cairo_destroy(mContext); } +IntSize +DrawTargetCairo::GetSize() +{ + return IntSize(); +} + TemporaryRef DrawTargetCairo::Snapshot() { + cairo_surface_t* csurf = cairo_get_target(mContext); + IntSize size; + if (GetCairoSurfaceSize(csurf, size)) { + cairo_content_t content = cairo_surface_get_content(csurf); + RefPtr surf = new SourceSurfaceCairo(csurf, size, + CairoContentToGfxFormat(content), + this); + AppendSnapshot(surf); + return surf; + } + return NULL; } @@ -119,6 +394,16 @@ DrawTargetCairo::Flush() cairo_surface_flush(surf); } +void +DrawTargetCairo::PrepareForDrawing(cairo_t* aContext) +{ + MarkChanged(); + + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(mTransform, mat); + cairo_set_matrix(aContext, &mat); +} + void DrawTargetCairo::DrawSurface(SourceSurface *aSurface, const Rect &aDest, @@ -126,12 +411,14 @@ DrawTargetCairo::DrawSurface(SourceSurface *aSurface, const DrawSurfaceOptions &aSurfOptions, const DrawOptions &aOptions) { + PrepareForDrawing(mContext); + float sx = aSource.Width() / aDest.Width(); float sy = aSource.Height() / aDest.Height(); cairo_matrix_t src_mat; cairo_matrix_init_scale(&src_mat, sx, sy); - cairo_matrix_translate(&src_mat, -aSource.X(), -aSource.Y()); + cairo_matrix_translate(&src_mat, aSource.X(), aSource.Y()); cairo_surface_t* surf = NULL; if (aSurface->GetType() == SURFACE_CAIRO) { @@ -142,31 +429,304 @@ DrawTargetCairo::DrawSurface(SourceSurface *aSurface, cairo_pattern_set_matrix(pat, &src_mat); cairo_pattern_set_filter(pat, GfxFilterToCairoFilter(aSurfOptions.mFilter)); + cairo_save(mContext); + cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); - cairo_rectangle(mContext, aDest.X(), aDest.Y(), - aDest.Width(), aDest.Height()); - cairo_fill(mContext); + + cairo_translate(mContext, aDest.X(), aDest.Y()); + + cairo_set_source(mContext, pat); + + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height()); + cairo_clip(mContext); + cairo_paint_with_alpha(mContext, aOptions.mAlpha); + + cairo_restore(mContext); cairo_pattern_destroy(pat); } +void +DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) +{ + MarkChanged(); + + if (aSurface->GetType() != SURFACE_CAIRO) { + return; + } + + SourceSurfaceCairo* sourcesurf = static_cast(aSurface); + + Float width = aSurface->GetSize().width, + height = aSurface->GetSize().height; + Rect extents(0, 0, width, height); + + AlphaBoxBlur blur(extents, IntSize(0, 0), + AlphaBoxBlur::CalculateBlurRadius(Point(aSigma, aSigma)), + NULL, NULL); + if (!blur.GetData()) { + return; + } + + IntSize blursize = blur.GetSize(); + + cairo_surface_t* blursurf = cairo_image_surface_create_for_data(blur.GetData(), + CAIRO_FORMAT_A8, + blursize.width, + blursize.height, + blur.GetStride()); + + // Draw the source surface into the surface we're going to blur. + cairo_surface_t* surf = sourcesurf->GetSurface(); + cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf); + + cairo_t* ctx = cairo_create(blursurf); + + cairo_set_source(ctx, pat); + + IntRect blurrect = blur.GetRect(); + cairo_new_path(ctx); + cairo_rectangle(ctx, blurrect.x, blurrect.y, blurrect.width, blurrect.height); + cairo_clip(ctx); + cairo_paint(ctx); + + cairo_destroy(ctx); + + // Blur the result, then use that blurred result as a mask to draw the shadow + // colour to the surface. + blur.Blur(); + + cairo_save(mContext); + + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a); + + cairo_identity_matrix(mContext); + cairo_translate(mContext, aDest.x, aDest.y); + + cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y); + + // Now that the shadow has been drawn, we can draw the surface on top. + + cairo_set_operator(mContext, GfxOpToCairoOp(aOperator)); + + cairo_set_source(mContext, pat); + + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, width, height); + cairo_clip(mContext); + + cairo_paint(mContext); + + cairo_restore(mContext); + + cairo_pattern_destroy(pat); +} + +void +SetStrokeOptions(cairo_t* aCtx, const StrokeOptions& aStrokeOptions) +{ + cairo_set_line_width(aCtx, aStrokeOptions.mLineWidth); + + cairo_set_miter_limit(aCtx, aStrokeOptions.mMiterLimit); + + if (aStrokeOptions.mDashPattern) { + // Convert array of floats to array of doubles + std::vector dashes(aStrokeOptions.mDashLength); + for (size_t i = 0; i < aStrokeOptions.mDashLength; ++i) { + dashes[i] = aStrokeOptions.mDashPattern[i]; + } + cairo_set_dash(aCtx, &dashes[0], aStrokeOptions.mDashLength, + aStrokeOptions.mDashOffset); + } + + cairo_set_line_join(aCtx, GfxLineJoinToCairoLineJoin(aStrokeOptions.mLineJoin)); + + cairo_set_line_cap(aCtx, GfxLineCapToCairoLineCap(aStrokeOptions.mLineCap)); +} + +void +DrawTargetCairo::DrawPattern(const Rect& aRect, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions, + DrawPatternType aDrawType) +{ + cairo_save(mContext); + + cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + + bool needIntermediate = NeedIntermediateSurface(aPattern, aOptions); + if (needIntermediate) { + cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); + + // Don't want operators to be applied twice + cairo_set_operator(mContext, CAIRO_OPERATOR_SOURCE); + } + + cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha); + + if (pat) { + cairo_set_source(mContext, pat); + + cairo_new_path(mContext); + cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height()); + + if (aDrawType == DRAW_STROKE) { + SetStrokeOptions(mContext, aStrokeOptions); + cairo_stroke(mContext); + } else { + // It's possible that we could simply always clip and paint here, but the + // old canvas implementation didn't, so to maintain similar performance + // characteristics we choose the same. + if (needIntermediate || aOptions.mAlpha == 1.0) { + cairo_fill(mContext); + } else { + cairo_clip(mContext); + cairo_paint(mContext); + } + } + + cairo_pattern_destroy(pat); + } + + if (needIntermediate) { + cairo_pop_group_to_source(mContext); + + cairo_paint_with_alpha(mContext, aOptions.mAlpha); + } + + cairo_restore(mContext); +} + void DrawTargetCairo::FillRect(const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions) { + PrepareForDrawing(mContext); + + DrawPattern(aRect, aPattern, StrokeOptions(), aOptions, DRAW_FILL); +} + +void +DrawTargetCairo::CopySurface(SourceSurface *aSurface, + const IntRect &aSourceRect, + const IntPoint &aDestination) +{ + PrepareForDrawing(mContext); +} + +void +DrawTargetCairo::ClearRect(const Rect& aRect) +{ + PrepareForDrawing(mContext); + + cairo_save(mContext); + cairo_new_path(mContext); - cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height()); + cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR); + cairo_rectangle(mContext, aRect.X(), aRect.Y(), + aRect.Width(), aRect.Height()); + cairo_fill(mContext); - cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + cairo_restore(mContext); +} - if (aPattern.GetType() == PATTERN_COLOR) { - Color color = static_cast(aPattern).mColor; - cairo_set_source_rgba(mContext, color.r, color.g, - color.b, color.a); +void +DrawTargetCairo::StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions /* = StrokeOptions() */, + const DrawOptions &aOptions /* = DrawOptions() */) +{ + PrepareForDrawing(mContext); + + DrawPattern(aRect, aPattern, aStrokeOptions, aOptions, DRAW_STROKE); +} + +void +DrawTargetCairo::StrokeLine(const Point &aStart, + const Point &aEnd, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions /* = StrokeOptions() */, + const DrawOptions &aOptions /* = DrawOptions() */) +{ + PrepareForDrawing(mContext); + + cairo_save(mContext); + + cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha); + if (pat) { + SetStrokeOptions(mContext, aStrokeOptions); + cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + + cairo_set_source(mContext, pat); + + cairo_new_path(mContext); + cairo_move_to(mContext, aStart.x, aStart.y); + cairo_line_to(mContext, aEnd.x, aEnd.y); + + cairo_stroke(mContext); + + cairo_pattern_destroy(pat); } - cairo_fill(mContext); + cairo_restore(mContext); +} + +void +DrawTargetCairo::Stroke(const Path *aPath, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions /* = StrokeOptions() */, + const DrawOptions &aOptions /* = DrawOptions() */) +{ + PrepareForDrawing(mContext); +} + +void +DrawTargetCairo::Fill(const Path *aPath, + const Pattern &aPattern, + const DrawOptions &aOptions /* = DrawOptions() */) +{ + PrepareForDrawing(mContext); +} + +void +DrawTargetCairo::FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions) +{ + PrepareForDrawing(mContext); +} + +void +DrawTargetCairo::PushClip(const Path *aPath) +{ +} + +void +DrawTargetCairo::PopClip() +{ +} + +TemporaryRef +DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FILL_WINDING */) const +{ + return NULL; +} + +TemporaryRef +DrawTargetCairo::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode) const +{ + RefPtr stops = new GradientStopsCairo(aStops, aNumStops); + return stops; } TemporaryRef @@ -180,8 +740,7 @@ DrawTargetCairo::CreateSourceSurfaceFromData(unsigned char *aData, aSize.width, aSize.height, aStride); - RefPtr source_surf = new SourceSurfaceCairo(); - source_surf->InitFromSurface(surf, aSize, aFormat); + RefPtr source_surf = new SourceSurfaceCairo(surf, aSize, aFormat); cairo_surface_destroy(surf); return source_surf; } @@ -189,12 +748,38 @@ DrawTargetCairo::CreateSourceSurfaceFromData(unsigned char *aData, TemporaryRef DrawTargetCairo::OptimizeSourceSurface(SourceSurface *aSurface) const { - return NULL; + return aSurface; } TemporaryRef DrawTargetCairo::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const { + if (aSurface.mType == NATIVE_SURFACE_CAIRO_SURFACE) { + IntSize size; + cairo_surface_t* surf = static_cast(aSurface.mSurface); + if (GetCairoSurfaceSize(surf, size)) { + RefPtr sourcesurf = + new SourceSurfaceCairo(surf, size, aSurface.mFormat); + return sourcesurf; + } + } + + return NULL; +} + +TemporaryRef +DrawTargetCairo::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const +{ + cairo_surface_t* similar = cairo_surface_create_similar(cairo_get_target(mContext), + GfxFormatToCairoContent(aFormat), + aSize.width, aSize.height); + + if (!cairo_surface_status(similar)) { + RefPtr target = new DrawTargetCairo(); + target->Init(similar); + return target; + } + return NULL; } @@ -206,14 +791,58 @@ DrawTargetCairo::Init(cairo_surface_t* aSurface) return true; } -void -DrawTargetCairo::SetTransform(const Matrix& aTransform) +void * +DrawTargetCairo::GetNativeSurface(NativeSurfaceType aType) { - cairo_matrix_t mat; - GfxMatrixToCairoMatrix(aTransform, mat); - cairo_set_matrix(mContext, &mat); - mTransform = aTransform; + if (aType == NATIVE_SURFACE_CAIRO_SURFACE) { + return cairo_get_target(mContext); + } + + return NULL; } +void +DrawTargetCairo::MarkSnapshotsIndependent() +{ + // Make a copy of the vector, since MarkIndependent implicitly modifies mSnapshots. + std::vector snapshots = mSnapshots; + for (std::vector::iterator iter = snapshots.begin(); + iter != snapshots.end(); + ++iter) { + (*iter)->MarkIndependent(); + } +} + +void +DrawTargetCairo::AppendSnapshot(SourceSurfaceCairo* aSnapshot) +{ + mSnapshots.push_back(aSnapshot); +} + +void +DrawTargetCairo::RemoveSnapshot(SourceSurfaceCairo* aSnapshot) +{ + std::vector::iterator iter = std::find(mSnapshots.begin(), + mSnapshots.end(), + aSnapshot); + if (iter != mSnapshots.end()) { + mSnapshots.erase(iter); + } +} + +void +DrawTargetCairo::MarkChanged() +{ + if (!mSnapshots.empty()) { + for (std::vector::iterator iter = mSnapshots.begin(); + iter != mSnapshots.end(); ++iter) { + (*iter)->DrawTargetWillChange(); + } + // All snapshots will now have copied data. + mSnapshots.clear(); + } +} + + } } diff --git a/gfx/2d/DrawTargetCairo.h b/gfx/2d/DrawTargetCairo.h index 45e6ab0a9104..13f1e41e1d60 100644 --- a/gfx/2d/DrawTargetCairo.h +++ b/gfx/2d/DrawTargetCairo.h @@ -40,9 +40,36 @@ #include "2D.h" #include "cairo.h" +#include + namespace mozilla { namespace gfx { +class SourceSurfaceCairo; + +class GradientStopsCairo : public GradientStops +{ + public: + GradientStopsCairo(GradientStop* aStops, uint32_t aNumStops) + { + for (uint32_t i = 0; i < aNumStops; ++i) { + mStops.push_back(aStops[i]); + } + } + + virtual ~GradientStopsCairo() {} + + const std::vector& GetStops() const + { + return mStops; + } + + virtual BackendType GetBackendType() const { return BACKEND_CAIRO; } + + private: + std::vector mStops; +}; + class DrawTargetCairo : public DrawTarget { public: @@ -51,7 +78,7 @@ public: virtual BackendType GetType() const { return BACKEND_CAIRO; } virtual TemporaryRef Snapshot(); - virtual IntSize GetSize() { return IntSize(); } + virtual IntSize GetSize(); virtual void Flush(); virtual void DrawSurface(SourceSurface *aSurface, @@ -64,16 +91,13 @@ public: const Color &aColor, const Point &aOffset, Float aSigma, - CompositionOp aOperator) - { } + CompositionOp aOperator); - virtual void ClearRect(const Rect &aRect) - { } + virtual void ClearRect(const Rect &aRect); virtual void CopySurface(SourceSurface *aSurface, const IntRect &aSourceRect, - const IntPoint &aDestination) - { } + const IntPoint &aDestination); virtual void FillRect(const Rect &aRect, const Pattern &aPattern, @@ -81,41 +105,37 @@ public: virtual void StrokeRect(const Rect &aRect, const Pattern &aPattern, const StrokeOptions &aStrokeOptions = StrokeOptions(), - const DrawOptions &aOptions = DrawOptions()) - { return; } + const DrawOptions &aOptions = DrawOptions()); virtual void StrokeLine(const Point &aStart, const Point &aEnd, const Pattern &aPattern, const StrokeOptions &aStrokeOptions = StrokeOptions(), - const DrawOptions &aOptions = DrawOptions()) - { return; } + const DrawOptions &aOptions = DrawOptions()); virtual void Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions = StrokeOptions(), - const DrawOptions &aOptions = DrawOptions()) - { return; } + const DrawOptions &aOptions = DrawOptions()); virtual void Fill(const Path *aPath, const Pattern &aPattern, - const DrawOptions &aOptions = DrawOptions()) - { return; } + const DrawOptions &aOptions = DrawOptions()); virtual void FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, - const DrawOptions &aOptions) - { return; } + const DrawOptions &aOptions); virtual void Mask(const Pattern &aSource, const Pattern &aMask, const DrawOptions &aOptions = DrawOptions()) - { return; } + { } - virtual void PushClip(const Path *aPath) { } - virtual void PushClipRect(const Rect &aRect) { } - virtual void PopClip() { } + virtual void PushClip(const Path *aPath); + virtual void PushClipRect(const Rect &aRect) + { } + virtual void PopClip(); - virtual TemporaryRef CreatePathBuilder(FillRule aFillRule = FILL_WINDING) const { return NULL; } + virtual TemporaryRef CreatePathBuilder(FillRule aFillRule = FILL_WINDING) const; virtual TemporaryRef CreateSourceSurfaceFromData(unsigned char *aData, const IntSize &aSize, @@ -125,22 +145,43 @@ public: virtual TemporaryRef CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const; virtual TemporaryRef - CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const - { return NULL; } + CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const; - virtual TemporaryRef CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode = EXTEND_CLAMP) const - { return NULL; } + virtual TemporaryRef + CreateGradientStops(GradientStop *aStops, + uint32_t aNumStops, + ExtendMode aExtendMode = EXTEND_CLAMP) const; - virtual void *GetNativeSurface(NativeSurfaceType aType) - { return NULL; } - - virtual void SetTransform(const Matrix& aTransform); + virtual void *GetNativeSurface(NativeSurfaceType aType); bool Init(cairo_surface_t* aSurface); -private: +private: // methods + void PrepareForDrawing(cairo_t* aContext); + enum DrawPatternType { DRAW_FILL, DRAW_STROKE }; + void DrawPattern(const Rect& aRect, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions, + DrawPatternType aDrawType); + + // Copy-on-write support for snapshot surfaces. + friend class SourceSurfaceCairo; + void AppendSnapshot(SourceSurfaceCairo* aSnapshot); + void RemoveSnapshot(SourceSurfaceCairo* aSnapshot); + + // Call before you make any changes to the backing surface with which this + // context is associated. + void MarkChanged(); + + // Call if there is any reason to disassociate all snapshots from this draw + // target; for example, because we're going to be destroyed. + void MarkSnapshotsIndependent(); + +private: // data cairo_t* mContext; + std::vector mSnapshots; }; } diff --git a/gfx/2d/SourceSurfaceCairo.cpp b/gfx/2d/SourceSurfaceCairo.cpp index e63571bbeea2..9a32d85df42c 100644 --- a/gfx/2d/SourceSurfaceCairo.cpp +++ b/gfx/2d/SourceSurfaceCairo.cpp @@ -36,18 +36,76 @@ * ***** END LICENSE BLOCK ***** */ #include "SourceSurfaceCairo.h" +#include "DrawTargetCairo.h" #include "cairo.h" namespace mozilla { namespace gfx { -SourceSurfaceCairo::SourceSurfaceCairo() +static cairo_format_t +GfxFormatToCairoFormat(SurfaceFormat format) { + switch (format) + { + case FORMAT_B8G8R8A8: + return CAIRO_FORMAT_ARGB32; + case FORMAT_B8G8R8X8: + return CAIRO_FORMAT_RGB24; + case FORMAT_A8: + return CAIRO_FORMAT_A8; + } + + return CAIRO_FORMAT_ARGB32; +} + +static SurfaceFormat +CairoFormatToSurfaceFormat(cairo_format_t format) +{ + switch (format) + { + case CAIRO_FORMAT_ARGB32: + return FORMAT_B8G8R8A8; + case CAIRO_FORMAT_RGB24: + return FORMAT_B8G8R8X8; + case CAIRO_FORMAT_A8: + return FORMAT_A8; + } + + return FORMAT_B8G8R8A8; +} + +static cairo_content_t +GfxFormatToCairoContent(SurfaceFormat format) +{ + switch(format) + { + case FORMAT_B8G8R8A8: + return CAIRO_CONTENT_COLOR_ALPHA; + case FORMAT_B8G8R8X8: + return CAIRO_CONTENT_COLOR; + case FORMAT_A8: + return CAIRO_CONTENT_ALPHA; + } + + return CAIRO_CONTENT_COLOR_ALPHA; +} + +SourceSurfaceCairo::SourceSurfaceCairo(cairo_surface_t* aSurface, + const IntSize& aSize, + const SurfaceFormat& aFormat, + DrawTargetCairo* aDrawTarget /* = NULL */) + : mSize(aSize) + , mFormat(aFormat) + , mSurface(aSurface) + , mDrawTarget(aDrawTarget) +{ + cairo_surface_reference(mSurface); } SourceSurfaceCairo::~SourceSurfaceCairo() { + MarkIndependent(); cairo_surface_destroy(mSurface); } @@ -66,26 +124,108 @@ SourceSurfaceCairo::GetFormat() const TemporaryRef SourceSurfaceCairo::GetDataSurface() { - return NULL; + RefPtr dataSurf; + + if (cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_IMAGE) { + dataSurf = new DataSourceSurfaceCairo(mSurface); + } else { + cairo_surface_t* imageSurf = cairo_image_surface_create(GfxFormatToCairoFormat(mFormat), + mSize.width, mSize.height); + + // Fill the new image surface with the contents of our surface. + cairo_t* ctx = cairo_create(imageSurf); + cairo_pattern_t* pat = cairo_pattern_create_for_surface(mSurface); + cairo_set_source(ctx, pat); + cairo_paint(ctx); + cairo_destroy(ctx); + + dataSurf = new DataSourceSurfaceCairo(imageSurf); + cairo_surface_destroy(imageSurf); + } + + return dataSurf; } cairo_surface_t* -SourceSurfaceCairo::GetSurface() +SourceSurfaceCairo::GetSurface() const { return mSurface; } -bool -SourceSurfaceCairo::InitFromSurface(cairo_surface_t* aSurface, - const IntSize& aSize, - const SurfaceFormat& aFormat) +void +SourceSurfaceCairo::DrawTargetWillChange() { - mSurface = aSurface; - cairo_surface_reference(mSurface); - mSize = aSize; - mFormat = aFormat; + if (mDrawTarget) { + mDrawTarget = NULL; - return true; + // We're about to lose our version of the surface, so make a copy of it. + cairo_surface_t* surface = cairo_surface_create_similar(mSurface, + GfxFormatToCairoContent(mFormat), + mSize.width, mSize.height); + cairo_t* ctx = cairo_create(surface); + cairo_pattern_t* pat = cairo_pattern_create_for_surface(mSurface); + cairo_set_source(ctx, pat); + cairo_paint(ctx); + cairo_destroy(ctx); + + // Swap in this new surface. + cairo_surface_destroy(mSurface); + mSurface = surface; + } +} + +void +SourceSurfaceCairo::MarkIndependent() +{ + if (mDrawTarget) { + mDrawTarget->RemoveSnapshot(this); + mDrawTarget = NULL; + } +} + +DataSourceSurfaceCairo::DataSourceSurfaceCairo(cairo_surface_t* imageSurf) + : mImageSurface(imageSurf) +{ + cairo_surface_reference(mImageSurface); +} + +DataSourceSurfaceCairo::~DataSourceSurfaceCairo() +{ + cairo_surface_destroy(mImageSurface); +} + +unsigned char * +DataSourceSurfaceCairo::GetData() +{ + return cairo_image_surface_get_data(mImageSurface); +} + +int32_t +DataSourceSurfaceCairo::Stride() +{ + return cairo_image_surface_get_stride(mImageSurface); +} + +IntSize +DataSourceSurfaceCairo::GetSize() const +{ + IntSize size; + size.width = cairo_image_surface_get_width(mImageSurface); + size.height = cairo_image_surface_get_height(mImageSurface); + + return size; +} + +SurfaceFormat +DataSourceSurfaceCairo::GetFormat() const +{ + return CairoFormatToSurfaceFormat(cairo_image_surface_get_format(mImageSurface)); +} + +cairo_surface_t* +DataSourceSurfaceCairo::GetSurface() const +{ + return mImageSurface; } } diff --git a/gfx/2d/SourceSurfaceCairo.h b/gfx/2d/SourceSurfaceCairo.h index 82bfda3eb361..b610627d992d 100644 --- a/gfx/2d/SourceSurfaceCairo.h +++ b/gfx/2d/SourceSurfaceCairo.h @@ -42,27 +42,56 @@ namespace mozilla { namespace gfx { +class DrawTargetCairo; + class SourceSurfaceCairo : public SourceSurface { public: - SourceSurfaceCairo(); - ~SourceSurfaceCairo(); + // Create a SourceSurfaceCairo. The surface will not be copied, but simply + // referenced. + // If aDrawTarget is non-NULL, it is assumed that this is a snapshot source + // surface, and we'll call DrawTargetCairo::RemoveSnapshot(this) on it when + // we're destroyed. + SourceSurfaceCairo(cairo_surface_t* aSurface, const IntSize& aSize, + const SurfaceFormat& aFormat, + DrawTargetCairo* aDrawTarget = NULL); + virtual ~SourceSurfaceCairo(); virtual SurfaceType GetType() const { return SURFACE_CAIRO; } virtual IntSize GetSize() const; virtual SurfaceFormat GetFormat() const; virtual TemporaryRef GetDataSurface(); - cairo_surface_t* GetSurface(); + cairo_surface_t* GetSurface() const; - bool InitFromSurface(cairo_surface_t* aSurface, - const IntSize& aSize, - const SurfaceFormat& aFormat); +private: // methods + friend class DrawTargetCairo; + void DrawTargetWillChange(); + void MarkIndependent(); -private: +private: // data IntSize mSize; SurfaceFormat mFormat; cairo_surface_t* mSurface; + DrawTargetCairo* mDrawTarget; +}; + +class DataSourceSurfaceCairo : public DataSourceSurface +{ +public: + DataSourceSurfaceCairo(cairo_surface_t* imageSurf); + virtual ~DataSourceSurfaceCairo(); + virtual unsigned char *GetData(); + virtual int32_t Stride(); + + virtual SurfaceType GetType() const { return SURFACE_CAIRO_IMAGE; } + virtual IntSize GetSize() const; + virtual SurfaceFormat GetFormat() const; + + cairo_surface_t* GetSurface() const; + +private: + cairo_surface_t* mImageSurface; }; } diff --git a/gfx/2d/Types.h b/gfx/2d/Types.h index 7cc1e826ffb6..23fbfc119857 100644 --- a/gfx/2d/Types.h +++ b/gfx/2d/Types.h @@ -84,7 +84,8 @@ enum FontType enum NativeSurfaceType { - NATIVE_SURFACE_D3D10_TEXTURE + NATIVE_SURFACE_D3D10_TEXTURE, + NATIVE_SURFACE_CAIRO_SURFACE }; enum NativeFontType