/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Corporation code. * * The Initial Developer of the Original Code is Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "DrawTargetCairo.h" #include "SourceSurfaceCairo.h" #include "PathCairo.h" #include "HelpersCairo.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 { namespace { // An RAII class to prepare to draw a context and optional path. Saves and // restores the context on construction/destruction. class AutoPrepareForDrawing { public: AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx) : mCtx(ctx) { dt->PrepareForDrawing(ctx); cairo_save(mCtx); } AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx, const Path* path) : mCtx(ctx) { dt->PrepareForDrawing(ctx, path); cairo_save(mCtx); } ~AutoPrepareForDrawing() { cairo_restore(mCtx); } private: cairo_t* mCtx; }; } // end anonymous namespace 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; } } static 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; } static 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) { } DrawTargetCairo::~DrawTargetCairo() { MarkSnapshotsIndependent(); if (mPathObserver) { mPathObserver->ForgetDrawTarget(); } 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; } void DrawTargetCairo::Flush() { cairo_surface_t* surf = cairo_get_target(mContext); cairo_surface_flush(surf); } void DrawTargetCairo::PrepareForDrawing(cairo_t* aContext, const Path* aPath /* = NULL */) { WillChange(aPath); } void DrawTargetCairo::DrawSurface(SourceSurface *aSurface, const Rect &aDest, const Rect &aSource, const DrawSurfaceOptions &aSurfOptions, const DrawOptions &aOptions) { AutoPrepareForDrawing prep(this, 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_surface_t* surf = NULL; if (aSurface->GetType() == SURFACE_CAIRO) { surf = static_cast(aSurface)->GetSurface(); } cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf); 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_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) { WillChange(); 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 DrawTargetCairo::DrawPattern(const Pattern& aPattern, const StrokeOptions& aStrokeOptions, const DrawOptions& aOptions, DrawPatternType aDrawType) { cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha); cairo_set_source(mContext, pat); if (NeedIntermediateSurface(aPattern, aOptions)) { cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); // Don't want operators to be applied twice cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); if (aDrawType == DRAW_STROKE) { SetCairoStrokeOptions(mContext, aStrokeOptions); cairo_stroke_preserve(mContext); } else { cairo_fill_preserve(mContext); } cairo_pop_group_to_source(mContext); // Now draw the content using the desired operator cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); cairo_paint_with_alpha(mContext, aOptions.mAlpha); } else { cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); if (aDrawType == DRAW_STROKE) { SetCairoStrokeOptions(mContext, aStrokeOptions); cairo_stroke_preserve(mContext); } else { cairo_fill_preserve(mContext); } } cairo_pattern_destroy(pat); } void DrawTargetCairo::FillRect(const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions) { AutoPrepareForDrawing prep(this, mContext); cairo_new_path(mContext); cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height()); DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL); } void DrawTargetCairo::CopySurface(SourceSurface *aSurface, const IntRect &aSourceRect, const IntPoint &aDestination) { AutoPrepareForDrawing prep(this, mContext); } void DrawTargetCairo::ClearRect(const Rect& aRect) { AutoPrepareForDrawing prep(this, mContext); cairo_save(mContext); cairo_new_path(mContext); cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR); cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); cairo_fill(mContext); cairo_restore(mContext); } void DrawTargetCairo::StrokeRect(const Rect &aRect, const Pattern &aPattern, const StrokeOptions &aStrokeOptions /* = StrokeOptions() */, const DrawOptions &aOptions /* = DrawOptions() */) { AutoPrepareForDrawing prep(this, mContext); cairo_new_path(mContext); cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height()); DrawPattern(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() */) { AutoPrepareForDrawing prep(this, mContext); cairo_new_path(mContext); cairo_move_to(mContext, aStart.x, aStart.y); cairo_line_to(mContext, aEnd.x, aEnd.y); DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); } void DrawTargetCairo::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions /* = StrokeOptions() */, const DrawOptions &aOptions /* = DrawOptions() */) { AutoPrepareForDrawing prep(this, mContext, aPath); if (aPath->GetBackendType() != BACKEND_CAIRO) return; PathCairo* path = const_cast(static_cast(aPath)); path->CopyPathTo(mContext, this); DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); } void DrawTargetCairo::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aOptions /* = DrawOptions() */) { AutoPrepareForDrawing prep(this, mContext, aPath); if (aPath->GetBackendType() != BACKEND_CAIRO) return; PathCairo* path = const_cast(static_cast(aPath)); path->CopyPathTo(mContext, this); DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL); } void DrawTargetCairo::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aOptions) { AutoPrepareForDrawing prep(this, mContext); } void DrawTargetCairo::Mask(const Pattern &aSource, const Pattern &aMask, const DrawOptions &aOptions /* = DrawOptions() */) { AutoPrepareForDrawing prep(this, mContext); } void DrawTargetCairo::PushClip(const Path *aPath) { } void DrawTargetCairo::PushClipRect(const Rect& aRect) { } void DrawTargetCairo::PopClip() { } TemporaryRef DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FILL_WINDING */) const { RefPtr builder = new PathBuilderCairo(mContext, const_cast(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; } TemporaryRef DrawTargetCairo::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode) const { RefPtr stops = new GradientStopsCairo(aStops, aNumStops); return stops; } TemporaryRef DrawTargetCairo::CreateSourceSurfaceFromData(unsigned char *aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat) const { cairo_surface_t* surf = cairo_image_surface_create_for_data(aData, GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height, aStride); RefPtr source_surf = new SourceSurfaceCairo(surf, aSize, aFormat); cairo_surface_destroy(surf); return source_surf; } TemporaryRef DrawTargetCairo::OptimizeSourceSurface(SourceSurface *aSurface) const { 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; } bool DrawTargetCairo::Init(cairo_surface_t* aSurface) { mContext = cairo_create(aSurface); return true; } void * DrawTargetCairo::GetNativeSurface(NativeSurfaceType aType) { 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::WillChange(const Path* aPath /* = NULL */) { 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(); } if (aPath && mPathObserver && !mPathObserver->ContainsPath(aPath)) { mPathObserver->PathWillChange(); mPathObserver = NULL; } } void DrawTargetCairo::SetPathObserver(CairoPathContext* aPathObserver) { if (mPathObserver && mPathObserver != aPathObserver) { mPathObserver->PathWillChange(); } mPathObserver = 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; GfxMatrixToCairoMatrix(mTransform, mat); cairo_set_matrix(mContext, &mat); } } }