diff --git a/gfx/thebes/gfxContext.h b/gfx/thebes/gfxContext.h index ed8557888e36..72d885a614e2 100644 --- a/gfx/thebes/gfxContext.h +++ b/gfx/thebes/gfxContext.h @@ -444,8 +444,6 @@ public: * group back to the current surface with some alpha applied will give * the correct results and using an opaque pushed surface gives better * quality and performance. - * This API really only makes sense if you do a PopGroupToSource and - * immediate Paint with OP_OVER. */ void PushGroupAndCopyBackground(gfxContentType content = gfxContentType::COLOR); already_AddRefed PopGroup(); diff --git a/layout/svg/nsSVGClipPathFrame.cpp b/layout/svg/nsSVGClipPathFrame.cpp index 418a87e29116..7c68e9f8311b 100644 --- a/layout/svg/nsSVGClipPathFrame.cpp +++ b/layout/svg/nsSVGClipPathFrame.cpp @@ -95,7 +95,10 @@ nsSVGClipPathFrame::ApplyClipOrPaintClipMask(gfxContext& aContext, if (referencedClipIsTrivial) { clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix); } else { - aContext.PushGroup(gfxContentType::ALPHA); + Matrix maskTransform; + RefPtr mask = clipPathFrame->GetClipMask(aContext, aClippedFrame, aMatrix, &maskTransform); + + aContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, mask, maskTransform); } } @@ -121,7 +124,10 @@ nsSVGClipPathFrame::ApplyClipOrPaintClipMask(gfxContext& aContext, if (isTrivial) { clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix); } else { - aContext.PushGroup(gfxContentType::ALPHA); + Matrix maskTransform; + RefPtr mask = clipPathFrame->GetClipMask(aContext, aClippedFrame, aMatrix, &maskTransform); + + aContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, mask, maskTransform); } } @@ -138,17 +144,7 @@ nsSVGClipPathFrame::ApplyClipOrPaintClipMask(gfxContext& aContext, if (clipPathFrame) { if (!isTrivial) { - aContext.PopGroupToSource(); - - aContext.PushGroup(gfxContentType::ALPHA); - - clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix); - Matrix maskTransform; - RefPtr clipMaskSurface = aContext.PopGroupToSurface(&maskTransform); - - if (clipMaskSurface) { - aContext.Mask(clipMaskSurface, maskTransform); - } + aContext.PopGroupAndBlend(); } aContext.Restore(); } @@ -157,17 +153,7 @@ nsSVGClipPathFrame::ApplyClipOrPaintClipMask(gfxContext& aContext, if (clipPathFrame) { if (!referencedClipIsTrivial) { - aContext.PopGroupToSource(); - - aContext.PushGroup(gfxContentType::ALPHA); - - clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix); - Matrix maskTransform; - RefPtr clipMaskSurface = aContext.PopGroupToSurface(&maskTransform); - - if (clipMaskSurface) { - aContext.Mask(clipMaskSurface, maskTransform); - } + aContext.PopGroupAndBlend(); } aContext.Restore(); } @@ -175,6 +161,50 @@ nsSVGClipPathFrame::ApplyClipOrPaintClipMask(gfxContext& aContext, return NS_OK; } +already_AddRefed +nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext, nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix, Matrix* aMaskTransform, + SourceSurface* aInputMask, const Matrix& aInputMaskTransform) +{ + if (IsTrivial()) { + return nullptr; + } + + IntRect intRect; + { + gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext); + + aReferenceContext.SetMatrix(gfxMatrix()); + gfxRect rect = aReferenceContext.GetClipExtents(); + intRect = RoundedOut(ToRect(rect)); + } + + RefPtr maskDT = aReferenceContext.GetDrawTarget()->CreateSimilarDrawTarget(intRect.Size(), SurfaceFormat::A8); + + gfxMatrix mat = + aReferenceContext.CurrentMatrix() * gfxMatrix::Translation(-intRect.TopLeft()); + { + RefPtr ctx = new gfxContext(maskDT); + ctx->SetMatrix(mat); + ApplyClipOrPaintClipMask(*ctx, aClippedFrame, aMatrix); + } + + mat.Invert(); + + if (aInputMask) { + MOZ_ASSERT(!aInputMaskTransform.HasNonTranslation()); + + RefPtr currentMask = maskDT->Snapshot(); + maskDT->SetTransform(Matrix()); + maskDT->ClearRect(Rect(0, 0, intRect.width, intRect.height)); + maskDT->MaskSurface(SurfacePattern(currentMask, ExtendMode::CLAMP), aInputMask, + Point(aInputMaskTransform._31 - intRect.x, aInputMaskTransform._32 - intRect.y)); + } + + *aMaskTransform = ToMatrix(mat); + return maskDT->Snapshot(); +} + bool nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame, const gfxPoint &aPoint) diff --git a/layout/svg/nsSVGClipPathFrame.h b/layout/svg/nsSVGClipPathFrame.h index 2674a140ff3f..d0a65225f7c9 100644 --- a/layout/svg/nsSVGClipPathFrame.h +++ b/layout/svg/nsSVGClipPathFrame.h @@ -43,9 +43,7 @@ public: * calling this method simply pushes a clip path onto the DrawTarget. If the * SVG clipPath is not simple then calling this method will paint the * clipPath's contents (geometry being filled only, with opaque black) to the - * DrawTarget. In this latter case callers are expected to first push a - * group before calling this method, then pop the group after calling and use - * it as a mask to mask the clipped frame. + * DrawTarget. * * XXXjwatt Maybe split this into two methods. */ @@ -53,6 +51,20 @@ public: nsIFrame* aClippedFrame, const gfxMatrix &aMatrix); + /** + * If the SVG clipPath is simple (as determined by the IsTrivial() method), + * calling this method simply returns null. If the SVG clipPath is not + * simple then calling this method will return a mask surface containing + * the clipped geometry. The reference context will be used to determine the + * backend for the SourceSurface as well as the size, which will be limited + * to the device clip extents on the context. + */ + already_AddRefed + GetClipMask(gfxContext& aReferenceContext, nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix, Matrix* aMaskTransform, + mozilla::gfx::SourceSurface* aInputMask = nullptr, + const mozilla::gfx::Matrix& aInputMaskTransform = mozilla::gfx::Matrix()); + /** * aPoint is expected to be in aClippedFrame's SVG user space. */ diff --git a/layout/svg/nsSVGIntegrationUtils.cpp b/layout/svg/nsSVGIntegrationUtils.cpp index 845da35e3392..b7559963af7b 100644 --- a/layout/svg/nsSVGIntegrationUtils.cpp +++ b/layout/svg/nsSVGIntegrationUtils.cpp @@ -511,18 +511,68 @@ nsSVGIntegrationUtils::PaintFramesWithEffects(gfxContext& aContext, gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame); bool complexEffects = false; + + // These are used if we require a temporary surface for a custom blend mode. + RefPtr target = &aContext; + IntPoint targetOffset; + /* Check if we need to do additional operations on this child's * rendering, which necessitates rendering into another surface. */ if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip) || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { complexEffects = true; + + Matrix maskTransform; + RefPtr maskSurface = + maskFrame ? maskFrame->GetMaskForMaskedFrame(&aContext, + aFrame, cssPxToDevPxMatrix, opacity, &maskTransform) + : nullptr; + + if (maskFrame && !maskSurface) { + // Entire surface is clipped out. + return; + } + aContext.Save(); nsRect clipRect = aFrame->GetVisualOverflowRectRelativeToSelf() + toUserSpace; aContext.Clip(NSRectToSnappedRect(clipRect, aFrame->PresContext()->AppUnitsPerDevPixel(), *drawTarget)); - aContext.PushGroup(gfxContentType::COLOR_ALPHA); + + if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { + // Create a temporary context to draw to so we can blend it back with + // another operator. + gfxRect clipRect; + { + gfxContextMatrixAutoSaveRestore matRestore(&aContext); + + aContext.SetMatrix(gfxMatrix()); + clipRect = aContext.GetClipExtents(); + } + + IntRect drawRect = RoundedOut(ToRect(clipRect)); + + RefPtr targetDT = aContext.GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), SurfaceFormat::B8G8R8A8); + target = new gfxContext(targetDT); + target->SetMatrix(aContext.CurrentMatrix() * gfxMatrix::Translation(-drawRect.TopLeft())); + targetOffset = drawRect.TopLeft(); + } + + if (clipPathFrame && !isTrivialClip) { + Matrix clippedMaskTransform; + RefPtr clipMaskSurface = clipPathFrame->GetClipMask(aContext, aFrame, cssPxToDevPxMatrix, + &clippedMaskTransform, maskSurface, maskTransform); + + if (clipMaskSurface) { + maskSurface = clipMaskSurface; + maskTransform = clippedMaskTransform; + } + } + + if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) { + target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, maskSurface, maskTransform); + } } /* If this frame has only a trivial clipPath, set up cairo's clipping now so @@ -540,11 +590,14 @@ nsSVGIntegrationUtils::PaintFramesWithEffects(gfxContext& aContext, nsRegion dirtyRegion = aDirtyRect - offsetToBoundingBox; gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); - nsFilterInstance::PaintFilteredFrame(aFrame, aContext, tm, &callback, &dirtyRegion); + nsFilterInstance::PaintFilteredFrame(aFrame, *target, tm, &callback, &dirtyRegion); } else { - aContext.SetMatrix(matrixAutoSaveRestore.Matrix()); + target->SetMatrix(matrixAutoSaveRestore.Matrix()); + BasicLayerManager* basic = static_cast(aLayerManager); + RefPtr oldCtx = basic->GetTarget(); + basic->SetTarget(target); aLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder); - aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffsetToUserSpace)); + basic->SetTarget(oldCtx); } if (clipPathFrame && isTrivialClip) { @@ -556,39 +609,19 @@ nsSVGIntegrationUtils::PaintFramesWithEffects(gfxContext& aContext, return; } - aContext.PopGroupToSource(); - - Matrix maskTransform; - RefPtr maskSurface = - maskFrame ? maskFrame->GetMaskForMaskedFrame(&aContext, - aFrame, cssPxToDevPxMatrix, - opacity, &maskTransform) - : nullptr; - - if (clipPathFrame && !isTrivialClip) { - aContext.PushGroup(gfxContentType::COLOR_ALPHA); - - nsresult rv = clipPathFrame->ApplyClipOrPaintClipMask(aContext, aFrame, cssPxToDevPxMatrix); - Matrix clippedMaskTransform; - RefPtr clipMaskSurface = aContext.PopGroupToSurface(&clippedMaskTransform); - - if (NS_SUCCEEDED(rv) && clipMaskSurface) { - // Still more set after clipping, so clip to another surface - if (maskSurface || opacity != 1.0f) { - aContext.PushGroup(gfxContentType::COLOR_ALPHA); - aContext.Mask(clipMaskSurface, clippedMaskTransform); - aContext.PopGroupToSource(); - } else { - aContext.Mask(clipMaskSurface, clippedMaskTransform); - } - } + if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) { + target->PopGroupAndBlend(); } - if (maskSurface) { - aContext.Mask(maskSurface, maskTransform); - } else if (opacity != 1.0f || - aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { - aContext.Paint(opacity); + if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { + RefPtr targetDT = target->GetDrawTarget(); + target = nullptr; + RefPtr targetSurf = targetDT->Snapshot(); + + aContext.SetMatrix(gfxMatrix()); // This will be restored right after. + RefPtr pattern = new gfxPattern(targetSurf, Matrix::Translation(targetOffset.x, targetOffset.y)); + aContext.SetPattern(pattern); + aContext.Paint(); } aContext.Restore(); diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp index 42c91e7e9689..745eb45b9f34 100644 --- a/layout/svg/nsSVGUtils.cpp +++ b/layout/svg/nsSVGUtils.cpp @@ -585,11 +585,27 @@ nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame, return; } + // These are used if we require a temporary surface for a custom blend mode. + RefPtr target = &aContext; + IntPoint targetOffset; + /* Check if we need to do additional operations on this child's * rendering, which necessitates rendering into another surface. */ if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip) || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { complexEffects = true; + + Matrix maskTransform; + RefPtr maskSurface = + maskFrame ? maskFrame->GetMaskForMaskedFrame(&aContext, + aFrame, aTransform, opacity, &maskTransform) + : nullptr; + + if (maskFrame && !maskSurface) { + // Entire surface is clipped out. + return; + } + aContext.Save(); if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { // aFrame has a valid visual overflow rect, so clip to it before calling @@ -607,7 +623,40 @@ nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame, aFrame->PresContext()->AppUnitsPerDevPixel(), *drawTarget)); } - aContext.PushGroup(gfxContentType::COLOR_ALPHA); + + if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { + // Create a temporary context to draw to so we can blend it back with + // another operator. + gfxRect clipRect; + { + gfxContextMatrixAutoSaveRestore matRestore(&aContext); + + aContext.SetMatrix(gfxMatrix()); + clipRect = aContext.GetClipExtents(); + } + + IntRect drawRect = RoundedOut(ToRect(clipRect)); + + RefPtr targetDT = aContext.GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), SurfaceFormat::B8G8R8A8); + target = new gfxContext(targetDT); + target->SetMatrix(aContext.CurrentMatrix() * gfxMatrix::Translation(-drawRect.TopLeft())); + targetOffset = drawRect.TopLeft(); + } + + if (clipPathFrame && !isTrivialClip) { + Matrix clippedMaskTransform; + RefPtr clipMaskSurface = clipPathFrame->GetClipMask(aContext, aFrame, aTransform, + &clippedMaskTransform, maskSurface, maskTransform); + + if (clipMaskSurface) { + maskSurface = clipMaskSurface; + maskTransform = clippedMaskTransform; + } + } + + if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) { + target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, maskSurface, maskTransform); + } } /* If this frame has only a trivial clipPath, set up cairo's clipping now so @@ -641,10 +690,10 @@ nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame, dirtyRegion = &tmpDirtyRegion; } SVGPaintCallback paintCallback; - nsFilterInstance::PaintFilteredFrame(aFrame, aContext, aTransform, + nsFilterInstance::PaintFilteredFrame(aFrame, *target, aTransform, &paintCallback, dirtyRegion); } else { - svgChildFrame->PaintSVG(aContext, aTransform, aDirtyRect); + svgChildFrame->PaintSVG(*target, aTransform, aDirtyRect); } if (clipPathFrame && isTrivialClip) { @@ -654,39 +703,20 @@ nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame, /* No more effects, we're done. */ if (!complexEffects) return; - - aContext.PopGroupToSource(); - - Matrix maskTransform; - RefPtr maskSurface = - maskFrame ? maskFrame->GetMaskForMaskedFrame(&aContext, - aFrame, aTransform, opacity, &maskTransform) - : nullptr; - - if (clipPathFrame && !isTrivialClip) { - aContext.PushGroup(gfxContentType::COLOR_ALPHA); - - nsresult rv = clipPathFrame->ApplyClipOrPaintClipMask(aContext, aFrame, aTransform); - Matrix clippedMaskTransform; - RefPtr clipMaskSurface = aContext.PopGroupToSurface(&clippedMaskTransform); - - if (NS_SUCCEEDED(rv) && clipMaskSurface) { - // Still more set after clipping, so clip to another surface - if (maskSurface || opacity != 1.0f) { - aContext.PushGroup(gfxContentType::COLOR_ALPHA); - aContext.Mask(clipMaskSurface, clippedMaskTransform); - aContext.PopGroupToSource(); - } else { - aContext.Mask(clipMaskSurface, clippedMaskTransform); - } - } + + if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) { + target->PopGroupAndBlend(); } - if (maskSurface) { - aContext.Mask(maskSurface, maskTransform); - } else if (opacity != 1.0f || - aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { - aContext.Paint(opacity); + if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { + RefPtr targetDT = target->GetDrawTarget(); + target = nullptr; + RefPtr targetSurf = targetDT->Snapshot(); + + aContext.SetMatrix(gfxMatrix()); // This will be restored right after. + RefPtr pattern = new gfxPattern(targetSurf, Matrix::Translation(targetOffset.x, targetOffset.y)); + aContext.SetPattern(pattern); + aContext.Paint(); } aContext.Restore();