diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 661231da5384..8ff893a97925 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -289,6 +289,141 @@ public: Pattern *mPattern; }; +/* This is an RAII based class that can be used as a drawtarget for + * operations that need to have a filter applied to their results. + * All coordinates passed to the constructor are in device space. + */ +class AdjustedTargetForFilter +{ +public: + typedef CanvasRenderingContext2D::ContextState ContextState; + + AdjustedTargetForFilter(CanvasRenderingContext2D *ctx, + DrawTarget *aFinalTarget, + const mgfx::IntPoint& aFilterSpaceToTargetOffset, + const mgfx::IntRect& aPreFilterBounds, + const mgfx::IntRect& aPostFilterBounds, + mgfx::CompositionOp aCompositionOp) + : mCtx(nullptr) + , mCompositionOp(aCompositionOp) + { + mCtx = ctx; + mFinalTarget = aFinalTarget; + mPostFilterBounds = aPostFilterBounds; + mOffset = aFilterSpaceToTargetOffset; + + nsIntRegion sourceGraphicNeededRegion; + nsIntRegion fillPaintNeededRegion; + nsIntRegion strokePaintNeededRegion; + + FilterSupport::ComputeSourceNeededRegions( + ctx->CurrentState().filter, mgfx::ThebesIntRect(mPostFilterBounds), + sourceGraphicNeededRegion, fillPaintNeededRegion, strokePaintNeededRegion); + + mSourceGraphicRect = mgfx::ToIntRect(sourceGraphicNeededRegion.GetBounds()); + mFillPaintRect = mgfx::ToIntRect(fillPaintNeededRegion.GetBounds()); + mStrokePaintRect = mgfx::ToIntRect(strokePaintNeededRegion.GetBounds()); + + mSourceGraphicRect = mSourceGraphicRect.Intersect(aPreFilterBounds); + + if (mSourceGraphicRect.IsEmpty()) { + // The filter might not make any use of the source graphic. We need to + // create a DrawTarget that we can return from DT() anyway, so we'll + // just use a 1x1-sized one. + mSourceGraphicRect.SizeTo(1, 1); + } + + mTarget = + mFinalTarget->CreateSimilarDrawTarget(mSourceGraphicRect.Size(), SurfaceFormat::B8G8R8A8); + + if (!mTarget) { + // XXX - Deal with the situation where our temp size is too big to + // fit in a texture (bug 1066622). + mTarget = mFinalTarget; + mCtx = nullptr; + mFinalTarget = nullptr; + return; + } + + mTarget->SetTransform( + mFinalTarget->GetTransform().PostTranslate(-mSourceGraphicRect.TopLeft() + mOffset)); + } + + // Return a SourceSurface that contains the FillPaint or StrokePaint source. + TemporaryRef + DoSourcePaint(mgfx::IntRect& aRect, CanvasRenderingContext2D::Style aStyle) + { + if (aRect.IsEmpty()) { + return nullptr; + } + + RefPtr dt = + mFinalTarget->CreateSimilarDrawTarget(aRect.Size(), SurfaceFormat::B8G8R8A8); + if (!dt) { + aRect.SetEmpty(); + return nullptr; + } + + Matrix transform = + mFinalTarget->GetTransform().PostTranslate(-aRect.TopLeft() + mOffset); + + dt->SetTransform(transform); + + if (transform.Invert()) { + mgfx::Rect dtBounds(0, 0, aRect.width, aRect.height); + mgfx::Rect fillRect = transform.TransformBounds(dtBounds); + dt->FillRect(fillRect, CanvasGeneralPattern().ForStyle(mCtx, aStyle, dt)); + } + return dt->Snapshot(); + } + + ~AdjustedTargetForFilter() + { + if (!mCtx) { + return; + } + + RefPtr snapshot = mTarget->Snapshot(); + + RefPtr fillPaint = + DoSourcePaint(mFillPaintRect, CanvasRenderingContext2D::Style::FILL); + RefPtr strokePaint = + DoSourcePaint(mStrokePaintRect, CanvasRenderingContext2D::Style::STROKE); + + Matrix transform = mFinalTarget->GetTransform(); + + mgfx::Point offset(mPostFilterBounds.TopLeft() - mOffset); + mFinalTarget->SetTransform(Matrix::Translation(offset)); + + mgfx::FilterSupport::RenderFilterDescription( + mFinalTarget, mCtx->CurrentState().filter, + mgfx::Rect(mPostFilterBounds), + snapshot, mSourceGraphicRect, + fillPaint, mFillPaintRect, + strokePaint, mStrokePaintRect, + mCtx->CurrentState().filterAdditionalImages, + DrawOptions(1.0f, mCompositionOp)); + + mFinalTarget->SetTransform(transform); + } + + DrawTarget* DT() + { + return mTarget; + } + +private: + RefPtr mTarget; + RefPtr mFinalTarget; + CanvasRenderingContext2D *mCtx; + mgfx::IntRect mSourceGraphicRect; + mgfx::IntRect mFillPaintRect; + mgfx::IntRect mStrokePaintRect; + mgfx::IntRect mPostFilterBounds; + mgfx::IntPoint mOffset; + mgfx::CompositionOp mCompositionOp; +}; + /* This is an RAII based class that can be used as a drawtarget for * operations that need to have a shadow applied to their results. * All coordinates passed to the constructor are in device space. @@ -403,19 +538,54 @@ public: mgfx::Rect r(0, 0, ctx->mWidth, ctx->mHeight); mgfx::Rect maxSourceNeededBoundsForShadow = MaxSourceNeededBoundsForShadow(r, ctx); + mgfx::Rect maxSourceNeededBoundsForFilter = + MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow, ctx); - mgfx::Rect bounds = maxSourceNeededBoundsForShadow; + mgfx::Rect bounds = maxSourceNeededBoundsForFilter; if (aBounds) { bounds = bounds.Intersect(*aBounds); } + mgfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, ctx); mozilla::gfx::CompositionOp op = ctx->CurrentState().op; + mgfx::IntPoint offsetToFinalDT; + + // First set up the shadow draw target, because the shadow goes outside. + // It applies to the post-filter results, if both a filter and a shadow + // are used. if (ctx->NeedToDrawShadow()) { mShadowTarget = MakeUnique( - ctx, mTarget, bounds, op); + ctx, mTarget, boundsAfterFilter, op); mTarget = mShadowTarget->DT(); + offsetToFinalDT = mShadowTarget->OffsetToFinalDT(); + + // If we also have a filter, the filter needs to be drawn with OP_OVER + // because shadow drawing already applies op on the result. + op = mgfx::CompositionOp::OP_OVER; } + + // Now set up the filter draw target. + if (ctx->NeedToApplyFilter()) { + bounds.RoundOut(); + + mgfx::IntRect intBounds; + if (!bounds.ToIntRect(&intBounds)) { + return; + } + mFilterTarget = MakeUnique( + ctx, mTarget, offsetToFinalDT, intBounds, + mgfx::RoundedToInt(boundsAfterFilter), op); + mTarget = mFilterTarget->DT(); + } + } + + ~AdjustedTarget() + { + // The order in which the targets are finalized is important. + // Filters are inside, any shadow applies to the post-filter results. + mFilterTarget.reset(); + mShadowTarget.reset(); } operator DrawTarget*() @@ -430,6 +600,24 @@ public: private: + mgfx::Rect + MaxSourceNeededBoundsForFilter(const mgfx::Rect& aDestBounds, CanvasRenderingContext2D *ctx) + { + if (!ctx->NeedToApplyFilter()) { + return aDestBounds; + } + + nsIntRegion sourceGraphicNeededRegion; + nsIntRegion fillPaintNeededRegion; + nsIntRegion strokePaintNeededRegion; + + FilterSupport::ComputeSourceNeededRegions( + ctx->CurrentState().filter, mgfx::ThebesIntRect(mgfx::RoundedToInt(aDestBounds)), + sourceGraphicNeededRegion, fillPaintNeededRegion, strokePaintNeededRegion); + + return mgfx::Rect(mgfx::ToIntRect(sourceGraphicNeededRegion.GetBounds())); + } + mgfx::Rect MaxSourceNeededBoundsForShadow(const mgfx::Rect& aDestBounds, CanvasRenderingContext2D *ctx) { @@ -446,8 +634,30 @@ private: return sourceBounds.Union(aDestBounds); } + mgfx::Rect + BoundsAfterFilter(const mgfx::Rect& aBounds, CanvasRenderingContext2D *ctx) + { + if (!ctx->NeedToApplyFilter()) { + return aBounds; + } + + mgfx::Rect bounds(aBounds); + bounds.RoundOut(); + + mgfx::IntRect intBounds; + if (!bounds.ToIntRect(&intBounds)) { + return mgfx::Rect(); + } + + nsIntRegion extents = + mgfx::FilterSupport::ComputePostFilterExtents(ctx->CurrentState().filter, + mgfx::ThebesIntRect(intBounds)); + return mgfx::Rect(mgfx::ToIntRect(extents.GetBounds())); + } + RefPtr mTarget; UniquePtr mShadowTarget; + UniquePtr mFilterTarget; }; void diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index f323b67faa1d..99ddab8e9c0d 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -864,8 +864,8 @@ protected: mozilla::gfx::CompositionOp UsedOperation() { - if (NeedToDrawShadow()) { - // In this case the shadow rendering will use the operator. + if (NeedToDrawShadow() || NeedToApplyFilter()) { + // In this case the shadow or filter rendering will use the operator. return mozilla::gfx::CompositionOp::OP_OVER; } @@ -1055,6 +1055,7 @@ protected: friend class CanvasFilterChainObserver; friend class AdjustedTarget; friend class AdjustedTargetForShadow; + friend class AdjustedTargetForFilter; // other helpers void GetAppUnitsValues(int32_t *perDevPixel, int32_t *perCSSPixel)