Bug 927892 - Canvas filter drawing support. r=Bas

--HG--
extra : rebase_source : ae76ccd952f17e4dffaf1c7725926241bd2ab233
This commit is contained in:
Markus Stange 2014-09-23 17:47:20 -04:00
parent fc0589d87b
commit 50753955d6
2 changed files with 215 additions and 4 deletions

View File

@ -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<SourceSurface>
DoSourcePaint(mgfx::IntRect& aRect, CanvasRenderingContext2D::Style aStyle)
{
if (aRect.IsEmpty()) {
return nullptr;
}
RefPtr<DrawTarget> 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<SourceSurface> snapshot = mTarget->Snapshot();
RefPtr<SourceSurface> fillPaint =
DoSourcePaint(mFillPaintRect, CanvasRenderingContext2D::Style::FILL);
RefPtr<SourceSurface> 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<DrawTarget> mTarget;
RefPtr<DrawTarget> 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<AdjustedTargetForShadow>(
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<AdjustedTargetForFilter>(
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<DrawTarget> mTarget;
UniquePtr<AdjustedTargetForShadow> mShadowTarget;
UniquePtr<AdjustedTargetForFilter> mFilterTarget;
};
void

View File

@ -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)