mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-06 22:53:08 +00:00
Bug 927892 - Canvas filter drawing support. r=Bas
--HG-- extra : rebase_source : ae76ccd952f17e4dffaf1c7725926241bd2ab233
This commit is contained in:
parent
6cc62b90f4
commit
2e5f53808f
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user