nsresult nsSVGMarkerFrame::PaintMark(gfxContext& aContext, const gfxMatrix& aToMarkedFrameUserSpace, nsSVGPathGeometryFrame *aMarkedFrame, nsSVGMark *aMark, float aStrokeWidth) { // If the flag is set when we get here, it means this marker frame // has already been used painting the current mark, and the document // has a marker reference loop. if (mInUse) return NS_OK; AutoMarkerReferencer markerRef(this, aMarkedFrame); SVGMarkerElement *marker = static_cast<SVGMarkerElement*>(mContent); if (!marker->HasValidDimensions()) { return NS_OK; } const nsSVGViewBoxRect viewBox = marker->GetViewBoxRect(); if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) { // We must disable rendering if the viewBox width or height are zero. return NS_OK; } mStrokeWidth = aStrokeWidth; mX = aMark->x; mY = aMark->y; mAutoAngle = aMark->angle; mIsStart = aMark->type == nsSVGMark::eStart; Matrix viewBoxTM = marker->GetViewBoxTransform(); Matrix markerTM = marker->GetMarkerTransform(mStrokeWidth, mX, mY, mAutoAngle, mIsStart); gfxMatrix markTM = ThebesMatrix(viewBoxTM) * ThebesMatrix(markerTM) * aToMarkedFrameUserSpace; if (StyleDisplay()->IsScrollableOverflow()) { aContext.Save(); gfxRect clipRect = nsSVGUtils::GetClipRectForFrame(this, viewBox.x, viewBox.y, viewBox.width, viewBox.height); nsSVGUtils::SetClipRect(&aContext, markTM, clipRect); } nsIFrame* kid = GetAnonymousChildFrame(this); nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); // The CTM of each frame referencing us may be different. SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); nsSVGUtils::PaintFrameWithEffects(kid, aContext, markTM); if (StyleDisplay()->IsScrollableOverflow()) aContext.Restore(); return NS_OK; }
virtual void Paint(gfxContext& aContext, nsIFrame *aTarget, const gfxMatrix& aTransform, const nsIntRect* aDirtyRect, imgDrawingParams& aImgParams) override { BasicLayerManager* basic = mLayerManager->AsBasicLayerManager(); RefPtr<gfxContext> oldCtx = basic->GetTarget(); basic->SetTarget(&aContext); gfxContextMatrixAutoSaveRestore autoSR(&aContext); aContext.SetMatrixDouble(aContext.CurrentMatrixDouble().PreTranslate(-mUserSpaceToFrameSpaceOffset)); mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder); basic->SetTarget(oldCtx); }
mozilla::Pair<DrawResult, RefPtr<SourceSurface>> nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext, nsIFrame* aClippedFrame, const gfxMatrix& aMatrix, Matrix* aMaskTransform, SourceSurface* aExtraMask, const Matrix& aExtraMasksTransform) { IntPoint offset; RefPtr<DrawTarget> maskDT = CreateClipMask(aReferenceContext, offset); if (!maskDT) { return MakePair(DrawResult::SUCCESS, RefPtr<SourceSurface>()); } RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(maskDT); if (!maskContext) { gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT); return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>()); } maskContext->SetMatrix(aReferenceContext.CurrentMatrix() * gfxMatrix::Translation(-offset)); DrawResult result = PaintClipMask(*maskContext, aClippedFrame, aMatrix, aMaskTransform, aExtraMask, aExtraMasksTransform); RefPtr<SourceSurface> surface = maskDT->Snapshot(); return MakePair(result, Move(surface)); }
virtual void Paint(gfxContext& aContext, nsIFrame *aTarget, const gfxMatrix& aTransform, const nsIntRect* aDirtyRect) override { BasicLayerManager* basic = static_cast<BasicLayerManager*>(mLayerManager); basic->SetTarget(&aContext); gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(-mOffset, aTarget->PresContext()->AppUnitsPerDevPixel()); gfxContextMatrixAutoSaveRestore autoSR(&aContext); aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffset)); mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder); }
void nsSVGClipPathFrame::ApplyClipPath(gfxContext& aContext, nsIFrame* aClippedFrame, const gfxMatrix& aMatrix) { MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask"); DrawTarget& aDrawTarget = *aContext.GetDrawTarget(); // No need for AutoReferenceLimiter since simple clip paths can't create // a reference loop (they don't reference other clip paths). // Restore current transform after applying clip path: gfxContextMatrixAutoSaveRestore autoRestore(&aContext); RefPtr<Path> clipPath; nsISVGChildFrame* singleClipPathChild = nullptr; IsTrivial(&singleClipPathChild); if (singleClipPathChild) { nsSVGPathGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild); if (pathFrame) { nsSVGPathGeometryElement* pathElement = static_cast<nsSVGPathGeometryElement*>(pathFrame->GetContent()); gfxMatrix toChildsUserSpace = pathElement-> PrependLocalTransformsTo(GetClipPathTransform(aClippedFrame) * aMatrix, eUserSpaceToParent); gfxMatrix newMatrix = aContext.CurrentMatrix().PreMultiply(toChildsUserSpace).NudgeToIntegers(); if (!newMatrix.IsSingular()) { aContext.SetMatrix(newMatrix); FillRule clipRule = nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule); clipPath = pathElement->GetOrBuildPath(aDrawTarget, clipRule); } } } if (clipPath) { aContext.Clip(clipPath); } else { // The spec says clip away everything if we have no children or the // clipping path otherwise can't be resolved: aContext.Clip(Rect()); } }
/* static*/ void nsCSSClipPathInstance::ApplyBasicShapeClip(gfxContext& aContext, nsIFrame* aFrame) { auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath; #ifdef DEBUG StyleShapeSourceType type = clipPathStyle.GetType(); MOZ_ASSERT(type == StyleShapeSourceType::Shape || type == StyleShapeSourceType::Box, "This function is used with basic-shape and geometry-box only."); #endif nsCSSClipPathInstance instance(aFrame, clipPathStyle); aContext.NewPath(); RefPtr<Path> path = instance.CreateClipPath(aContext.GetDrawTarget()); aContext.SetPath(path); aContext.Clip(); }
already_AddRefed<DrawTarget> nsSVGClipPathFrame::CreateClipMask(gfxContext& aReferenceContext, IntPoint& aOffset) { gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext); aReferenceContext.SetMatrix(gfxMatrix()); gfxRect rect = aReferenceContext.GetClipExtents(); IntRect bounds = RoundedOut(ToRect(rect)); if (bounds.IsEmpty()) { // We don't need to create a mask surface, all drawing is clipped anyway. return nullptr; } DrawTarget* referenceDT = aReferenceContext.GetDrawTarget(); RefPtr<DrawTarget> maskDT = referenceDT->CreateSimilarDrawTarget(bounds.Size(), SurfaceFormat::A8); aOffset = bounds.TopLeft(); return maskDT.forget(); }
already_AddRefed<SourceSurface> nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext, nsIFrame* aClippedFrame, const gfxMatrix& aMatrix, Matrix* aMaskTransform, SourceSurface* aExtraMask, const Matrix& aExtraMasksTransform) { MOZ_ASSERT(!IsTrivial(), "Caller needs to use ApplyClipPath"); DrawTarget& aReferenceDT = *aReferenceContext.GetDrawTarget(); // A clipPath can reference another clipPath. We re-enter this method for // each clipPath in a reference chain, so here we limit chain length: static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing; AutoReferenceLimiter refChainLengthLimiter(&sRefChainLengthCounter, MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH); if (!refChainLengthLimiter.Reference()) { return nullptr; // Reference chain is too long! } // And to prevent reference loops we check that this clipPath only appears // once in the reference chain (if any) that we're currently processing: AutoReferenceLimiter refLoopDetector(&mReferencing, 1); if (!refLoopDetector.Reference()) { return nullptr; // Reference loop! } IntRect devSpaceClipExtents; { gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext); aReferenceContext.SetMatrix(gfxMatrix()); gfxRect rect = aReferenceContext.GetClipExtents(); devSpaceClipExtents = RoundedOut(ToRect(rect)); if (devSpaceClipExtents.IsEmpty()) { // We don't need to create a mask surface, all drawing is clipped anyway. return nullptr; } } RefPtr<DrawTarget> maskDT = aReferenceDT.CreateSimilarDrawTarget(devSpaceClipExtents.Size(), SurfaceFormat::A8); gfxMatrix mat = aReferenceContext.CurrentMatrix() * gfxMatrix::Translation(-devSpaceClipExtents.TopLeft()); // Paint this clipPath's contents into maskDT: { RefPtr<gfxContext> ctx = new gfxContext(maskDT); ctx->SetMatrix(mat); // We need to set mMatrixForChildren here so that under the PaintSVG calls // on our children (below) our GetCanvasTM() method will return the correct // transform. mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix; // Check if this clipPath is itself clipped by another clipPath: nsSVGClipPathFrame* clipPathThatClipsClipPath = nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr); bool clippingOfClipPathRequiredMasking; if (clipPathThatClipsClipPath) { ctx->Save(); clippingOfClipPathRequiredMasking = !clipPathThatClipsClipPath->IsTrivial(); if (!clippingOfClipPathRequiredMasking) { clipPathThatClipsClipPath->ApplyClipPath(*ctx, aClippedFrame, aMatrix); } else { Matrix maskTransform; RefPtr<SourceSurface> mask = clipPathThatClipsClipPath->GetClipMask(*ctx, aClippedFrame, aMatrix, &maskTransform); ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, mask, maskTransform); // The corresponding PopGroupAndBlend call below will mask the // blend using |mask|. } } // Paint our children into the mask: for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); if (SVGFrame) { // The CTM of each frame referencing us can be different. SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); bool isOK = true; // Children of this clipPath may themselves be clipped. nsSVGClipPathFrame *clipPathThatClipsChild = nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(&isOK); if (!isOK) { continue; } bool childsClipPathRequiresMasking; if (clipPathThatClipsChild) { childsClipPathRequiresMasking = !clipPathThatClipsChild->IsTrivial(); ctx->Save(); if (!childsClipPathRequiresMasking) { clipPathThatClipsChild->ApplyClipPath(*ctx, aClippedFrame, aMatrix); } else { Matrix maskTransform; RefPtr<SourceSurface> mask = clipPathThatClipsChild->GetClipMask(*ctx, aClippedFrame, aMatrix, &maskTransform); ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, mask, maskTransform); // The corresponding PopGroupAndBlend call below will mask the // blend using |mask|. } } gfxMatrix toChildsUserSpace = mMatrixForChildren; nsIFrame* child = do_QueryFrame(SVGFrame); nsIContent* childContent = child->GetContent(); if (childContent->IsSVGElement()) { toChildsUserSpace = static_cast<const nsSVGElement*>(childContent)-> PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent); } // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and // nsSVGPathGeometryFrame::Render checks for that state bit and paints // only the geometry (opaque black) if set. SVGFrame->PaintSVG(*ctx, toChildsUserSpace); if (clipPathThatClipsChild) { if (childsClipPathRequiresMasking) { ctx->PopGroupAndBlend(); } ctx->Restore(); } } } if (clipPathThatClipsClipPath) { if (clippingOfClipPathRequiredMasking) { ctx->PopGroupAndBlend(); } ctx->Restore(); } } // Moz2D transforms in the opposite direction to Thebes mat.Invert(); if (aExtraMask) { // We could potentially due this more efficiently with OPERATOR_IN // but that operator does not work well on CG or D2D RefPtr<SourceSurface> currentMask = maskDT->Snapshot(); Matrix transform = maskDT->GetTransform(); maskDT->SetTransform(Matrix()); maskDT->ClearRect(Rect(0, 0, devSpaceClipExtents.width, devSpaceClipExtents.height)); maskDT->SetTransform(aExtraMasksTransform * transform); // draw currentMask with the inverse of the transform that we just so that // it ends up in the same spot with aExtraMask transformed by aExtraMasksTransform maskDT->MaskSurface(SurfacePattern(currentMask, ExtendMode::CLAMP, aExtraMasksTransform.Inverse() * ToMatrix(mat)), aExtraMask, Point(0, 0)); } *aMaskTransform = ToMatrix(mat); return maskDT->Snapshot(); }
nsresult nsSVGForeignObjectFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, const nsIntRect* aDirtyRect) { NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY), "If display lists are enabled, only painting of non-display " "SVG should take this code path"); if (IsDisabled()) return NS_OK; nsIFrame* kid = GetFirstPrincipalChild(); if (!kid) return NS_OK; if (aTransform.IsSingular()) { NS_WARNING("Can't render foreignObject element!"); return NS_ERROR_FAILURE; } nsRect kidDirtyRect = kid->GetVisualOverflowRect(); /* Check if we need to draw anything. */ if (aDirtyRect) { NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY), "Display lists handle dirty rect intersection test"); // Transform the dirty rect into app units in our userspace. gfxMatrix invmatrix = aTransform; DebugOnly<bool> ok = invmatrix.Invert(); NS_ASSERTION(ok, "inverse of non-singular matrix should be non-singular"); gfxRect transDirtyRect = gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height); transDirtyRect = invmatrix.TransformBounds(transDirtyRect); kidDirtyRect.IntersectRect(kidDirtyRect, nsLayoutUtils::RoundGfxRectToAppRect(transDirtyRect, PresContext()->AppUnitsPerCSSPixel())); // XXX after bug 614732 is fixed, we will compare mRect with aDirtyRect, // not with kidDirtyRect. I.e. // int32_t appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); // mRect.ToOutsidePixels(appUnitsPerDevPx).Intersects(*aDirtyRect) if (kidDirtyRect.IsEmpty()) return NS_OK; } aContext.Save(); if (StyleDisplay()->IsScrollableOverflow()) { float x, y, width, height; static_cast<nsSVGElement*>(mContent)-> GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); gfxRect clipRect = nsSVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height); nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect); } // SVG paints in CSS px, but normally frames paint in dev pixels. Here we // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children // paint correctly. float cssPxPerDevPx = PresContext()-> AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel()); gfxMatrix canvasTMForChildren = aTransform; canvasTMForChildren.Scale(cssPxPerDevPx, cssPxPerDevPx); aContext.Multiply(canvasTMForChildren); uint32_t flags = nsLayoutUtils::PAINT_IN_TRANSFORM; if (SVGAutoRenderState::IsPaintingToWindow(aContext.GetDrawTarget())) { flags |= nsLayoutUtils::PAINT_TO_WINDOW; } nsRenderingContext rendCtx(&aContext); nsresult rv = nsLayoutUtils::PaintFrame(&rendCtx, kid, nsRegion(kidDirtyRect), NS_RGBA(0,0,0,0), flags); aContext.Restore(); return rv; }
//---------------------------------------------------------------------- // nsISVGChildFrame methods: nsresult nsSVGImageFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, const nsIntRect *aDirtyRect) { nsresult rv = NS_OK; if (!StyleVisibility()->IsVisible()) return NS_OK; float x, y, width, height; SVGImageElement *imgElem = static_cast<SVGImageElement*>(mContent); imgElem->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); NS_ASSERTION(width > 0 && height > 0, "Should only be painting things with valid width/height"); if (!mImageContainer) { nsCOMPtr<imgIRequest> currentRequest; nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); if (imageLoader) imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(currentRequest)); if (currentRequest) currentRequest->GetImage(getter_AddRefs(mImageContainer)); } if (mImageContainer) { gfxContextAutoSaveRestore autoRestorer(&aContext); if (StyleDisplay()->IsScrollableOverflow()) { gfxRect clipRect = nsSVGUtils::GetClipRectForFrame(this, x, y, width, height); nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect); } if (!TransformContextForPainting(&aContext, aTransform)) { return NS_ERROR_FAILURE; } // fill-opacity doesn't affect <image>, so if we're allowed to // optimize group opacity, the opacity used for compositing the // image into the current canvas is just the group opacity. float opacity = 1.0f; if (nsSVGUtils::CanOptimizeOpacity(this)) { opacity = StyleDisplay()->mOpacity; } if (opacity != 1.0f || StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { aContext.PushGroup(gfxContentType::COLOR_ALPHA); } nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); nsRect dirtyRect; // only used if aDirtyRect is non-null if (aDirtyRect) { NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY), "Display lists handle dirty rect intersection test"); dirtyRect = aDirtyRect->ToAppUnits(appUnitsPerDevPx); // Adjust dirtyRect to match our local coordinate system. nsRect rootRect = nsSVGUtils::TransformFrameRectToOuterSVG(mRect, aTransform, PresContext()); dirtyRect.MoveBy(-rootRect.TopLeft()); } // XXXbholley - I don't think huge images in SVGs are common enough to // warrant worrying about the responsiveness impact of doing synchronous // decodes. The extra code complexity of determinining when we want to // force sync probably just isn't worth it, so always pass FLAG_SYNC_DECODE uint32_t drawFlags = imgIContainer::FLAG_SYNC_DECODE; if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) { // Package up the attributes of this image element which can override the // attributes of mImageContainer's internal SVG document. The 'width' & // 'height' values we're passing in here are in CSS units (though they // come from width/height *attributes* in SVG). They influence the region // of the SVG image's internal document that is visible, in combination // with preserveAspectRatio and viewBox. SVGImageContext context(CSSIntSize(width, height), Some(imgElem->mPreserveAspectRatio.GetAnimValue())); // For the actual draw operation to draw crisply (and at the right size), // our destination rect needs to be |width|x|height|, *in dev pixels*. LayoutDeviceSize devPxSize(width, height); nsRect destRect(nsPoint(), LayoutDevicePixel::ToAppUnits(devPxSize, appUnitsPerDevPx)); // Note: Can't use DrawSingleUnscaledImage for the TYPE_VECTOR case. // That method needs our image to have a fixed native width & height, // and that's not always true for TYPE_VECTOR images. nsLayoutUtils::DrawSingleImage( aContext, PresContext(), mImageContainer, nsLayoutUtils::GetGraphicsFilterForFrame(this), destRect, aDirtyRect ? dirtyRect : destRect, &context, drawFlags); } else { // mImageContainer->GetType() == TYPE_RASTER nsLayoutUtils::DrawSingleUnscaledImage( aContext, PresContext(), mImageContainer, nsLayoutUtils::GetGraphicsFilterForFrame(this), nsPoint(0, 0), aDirtyRect ? &dirtyRect : nullptr, drawFlags); } if (opacity != 1.0f || StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { aContext.PopGroupToSource(); aContext.SetOperator(gfxContext::OPERATOR_OVER); aContext.Paint(opacity); } // gfxContextAutoSaveRestore goes out of scope & cleans up our gfxContext } return rv; }
DrawResult nsSVGClipPathFrame::PaintFrameIntoMask(nsIFrame *aFrame, nsIFrame* aClippedFrame, gfxContext& aTarget, const gfxMatrix& aMatrix) { nsISVGChildFrame* frame = do_QueryFrame(aFrame); if (!frame) { return DrawResult::SUCCESS; } // The CTM of each frame referencing us can be different. frame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); // Children of this clipPath may themselves be clipped. nsSVGEffects::EffectProperties effectProperties = nsSVGEffects::GetEffectProperties(aFrame); if (effectProperties.HasInvalidClipPath()) { return DrawResult::SUCCESS; } nsSVGClipPathFrame *clipPathThatClipsChild = effectProperties.GetClipPathFrame(); nsSVGUtils::MaskUsage maskUsage; nsSVGUtils::DetermineMaskUsage(aFrame, true, maskUsage); DrawResult result = DrawResult::SUCCESS; if (maskUsage.shouldApplyClipPath) { clipPathThatClipsChild->ApplyClipPath(aTarget, aClippedFrame, aMatrix); } else if (maskUsage.shouldGenerateClipMaskLayer) { Matrix maskTransform; RefPtr<SourceSurface> maskSurface; Tie(result, maskSurface) = clipPathThatClipsChild->GetClipMask(aTarget, aClippedFrame, aMatrix, &maskTransform); aTarget.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface, maskTransform); // The corresponding PopGroupAndBlend call below will mask the // blend using |maskSurface|. } gfxMatrix toChildsUserSpace = mMatrixForChildren; nsIFrame* child = do_QueryFrame(frame); nsIContent* childContent = child->GetContent(); if (childContent->IsSVGElement()) { toChildsUserSpace = static_cast<const nsSVGElement*>(childContent)-> PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent); } // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and // SVGGeometryFrame::Render checks for that state bit and paints // only the geometry (opaque black) if set. result &= frame->PaintSVG(aTarget, toChildsUserSpace); if (maskUsage.shouldGenerateClipMaskLayer) { aTarget.PopGroupAndBlend(); } else if (maskUsage.shouldApplyClipPath) { aTarget.PopClip(); } return result; }
DrawResult nsSVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext, nsIFrame* aClippedFrame, const gfxMatrix& aMatrix, Matrix* aMaskTransform, SourceSurface* aExtraMask, const Matrix& aExtraMasksTransform) { // A clipPath can reference another clipPath. We re-enter this method for // each clipPath in a reference chain, so here we limit chain length: static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing; AutoReferenceLimiter refChainLengthLimiter(&sRefChainLengthCounter, MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH); if (!refChainLengthLimiter.Reference()) { return DrawResult::SUCCESS; // Reference chain is too long! } // And to prevent reference loops we check that this clipPath only appears // once in the reference chain (if any) that we're currently processing: AutoReferenceLimiter refLoopDetector(&mReferencing, 1); if (!refLoopDetector.Reference()) { return DrawResult::SUCCESS; // Reference loop! } DrawResult result = DrawResult::SUCCESS; DrawTarget* maskDT = aMaskContext.GetDrawTarget(); MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8); // Paint this clipPath's contents into aMaskDT: // We need to set mMatrixForChildren here so that under the PaintSVG calls // on our children (below) our GetCanvasTM() method will return the correct // transform. mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix; // Check if this clipPath is itself clipped by another clipPath: nsSVGClipPathFrame* clipPathThatClipsClipPath = nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(); nsSVGUtils::MaskUsage maskUsage; nsSVGUtils::DetermineMaskUsage(this, true, maskUsage); if (maskUsage.shouldApplyClipPath) { clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame, aMatrix); } else if (maskUsage.shouldGenerateClipMaskLayer) { Matrix maskTransform; RefPtr<SourceSurface> maskSurface; Tie(result, maskSurface) = clipPathThatClipsClipPath->GetClipMask(aMaskContext, aClippedFrame, aMatrix, &maskTransform); aMaskContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface, maskTransform); // The corresponding PopGroupAndBlend call below will mask the // blend using |maskSurface|. } // Paint our children into the mask: for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { result &= PaintFrameIntoMask(kid, aClippedFrame, aMaskContext, aMatrix); } if (maskUsage.shouldGenerateClipMaskLayer) { aMaskContext.PopGroupAndBlend(); } else if (maskUsage.shouldApplyClipPath) { aMaskContext.PopClip(); } // Moz2D transforms in the opposite direction to Thebes gfxMatrix maskTransfrom = aMaskContext.CurrentMatrix(); maskTransfrom.Invert(); if (aExtraMask) { ComposeExtraMask(maskDT, maskTransfrom, aExtraMask, aExtraMasksTransform); } *aMaskTransform = ToMatrix(maskTransfrom); return result; }
void nsSVGIntegrationUtils::PaintFramesWithEffects(gfxContext& aContext, nsIFrame* aFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, nsDisplayListBuilder* aBuilder, LayerManager *aLayerManager) { #ifdef DEBUG NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) || (NS_SVGDisplayListPaintingEnabled() && !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)), "Should not use nsSVGIntegrationUtils on this SVG frame"); #endif /* SVG defines the following rendering model: * * 1. Render geometry * 2. Apply filter * 3. Apply clipping, masking, group opacity * * We follow this, but perform a couple of optimizations: * * + Use cairo's clipPath when representable natively (single object * clip region). * * + Merge opacity and masking if both used together. */ const nsIContent* content = aFrame->GetContent(); bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT); if (hasSVGLayout) { nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); if (!svgChildFrame || !aFrame->GetContent()->IsSVGElement()) { NS_ASSERTION(false, "why?"); return; } if (!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { return; // The SVG spec says not to draw _anything_ } } float opacity = aFrame->StyleDisplay()->mOpacity; if (opacity == 0.0f) { return; } if (opacity != 1.0f && hasSVGLayout && nsSVGUtils::CanOptimizeOpacity(aFrame)) { opacity = 1.0f; } /* Properties are added lazily and may have been removed by a restyle, so make sure all applicable ones are set again. */ nsIFrame* firstFrame = nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); nsSVGEffects::EffectProperties effectProperties = nsSVGEffects::GetEffectProperties(firstFrame); bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true; DrawTarget* drawTarget = aContext.GetDrawTarget(); gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&aContext); nsPoint firstFrameOffset = GetOffsetToBoundingBox(firstFrame); nsPoint offsetToBoundingBox = aBuilder->ToReferenceFrame(firstFrame) - firstFrameOffset; if (!firstFrame->IsFrameOfType(nsIFrame::eSVG)) { /* Snap the offset if the reference frame is not a SVG frame, * since other frames will be snapped to pixel when rendering. */ offsetToBoundingBox = nsPoint( aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.x), aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.y)); } // After applying only "offsetToBoundingBox", aCtx would have its origin at // the top left corner of aFrame's bounding box (over all continuations). // However, SVG painting needs the origin to be located at the origin of the // SVG frame's "user space", i.e. the space in which, for example, the // frame's BBox lives. // SVG geometry frames and foreignObject frames apply their own offsets, so // their position is relative to their user space. So for these frame types, // if we want aCtx to be in user space, we first need to subtract the // frame's position so that SVG painting can later add it again and the // frame is painted in the right place. gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame); nsPoint toUserSpace(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)), nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y))); nsPoint offsetToUserSpace = offsetToBoundingBox - toUserSpace; NS_ASSERTION(hasSVGLayout || offsetToBoundingBox == offsetToUserSpace, "For non-SVG frames there shouldn't be any additional offset"); gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint(offsetToUserSpace, aFrame->PresContext()->AppUnitsPerDevPixel()); aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffsetToUserSpace)); gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame); const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset(); // Keep moving forward even if svgMaskFrame is nullptr or isOK is false. // This source is not a svg mask, but it still can be a correct mask image. nsSVGMaskFrame *svgMaskFrame = effectProperties.GetMaskFrame(&isOK); bool complexEffects = false; bool hasValidLayers = svgReset->mMask.HasLayerWithImage(); // These are used if we require a temporary surface for a custom blend mode. RefPtr<gfxContext> 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 || (clipPathFrame && !isTrivialClip) || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL || svgMaskFrame || hasValidLayers) { complexEffects = true; aContext.Save(); nsRect clipRect = aFrame->GetVisualOverflowRectRelativeToSelf() + toUserSpace; aContext.Clip(NSRectToSnappedRect(clipRect, aFrame->PresContext()->AppUnitsPerDevPixel(), *drawTarget)); Matrix maskTransform; RefPtr<SourceSurface> maskSurface; if (svgMaskFrame) { maskSurface = svgMaskFrame->GetMaskForMaskedFrame(&aContext, aFrame, cssPxToDevPxMatrix, opacity, &maskTransform); } else if (hasValidLayers) { gfxRect clipRect = aContext.GetClipExtents(); { gfxContextMatrixAutoSaveRestore matRestore(&aContext); aContext.SetMatrix(gfxMatrix()); clipRect = aContext.GetClipExtents(); } IntRect drawRect = RoundedOut(ToRect(clipRect)); RefPtr<DrawTarget> targetDT = aContext.GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), SurfaceFormat::A8); if (!targetDT) { aContext.Restore(); return; } RefPtr<gfxContext> target = new gfxContext(targetDT); target->SetMatrix(matrixAutoSaveRestore.Matrix() * gfxMatrix::Translation(-drawRect.TopLeft())); // Generate mask surface. uint32_t flags = aBuilder->GetBackgroundPaintFlags() | nsCSSRendering::PAINTBG_MASK_IMAGE; nsRenderingContext rc(target); nsCSSRendering::PaintBackgroundWithSC(aFrame->PresContext(), rc, aFrame, aDirtyRect, aBorderArea, firstFrame->StyleContext(), *aFrame->StyleBorder(), flags, nullptr, -1); maskSurface = targetDT->Snapshot(); // Compute mask transform. Matrix mat = ToMatrix(aContext.CurrentMatrix()); mat.Invert(); maskTransform = Matrix::Translation(drawRect.x, drawRect.y) * mat; } if ((svgMaskFrame || hasValidLayers) && !maskSurface) { // Entire surface is clipped out. aContext.Restore(); return; } 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<DrawTarget> targetDT = aContext.GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), SurfaceFormat::B8G8R8A8); if (!targetDT) { aContext.Restore(); return; } target = new gfxContext(targetDT); target->SetMatrix(aContext.CurrentMatrix() * gfxMatrix::Translation(-drawRect.TopLeft())); targetOffset = drawRect.TopLeft(); } if (clipPathFrame && !isTrivialClip) { Matrix clippedMaskTransform; RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(aContext, aFrame, cssPxToDevPxMatrix, &clippedMaskTransform, maskSurface, maskTransform); if (clipMaskSurface) { maskSurface = clipMaskSurface; maskTransform = clippedMaskTransform; } } if (opacity != 1.0f || svgMaskFrame || hasValidLayers || (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 * we can just do normal painting and get it clipped appropriately. */ if (clipPathFrame && isTrivialClip) { aContext.Save(); clipPathFrame->ApplyClipPath(aContext, aFrame, cssPxToDevPxMatrix); } /* Paint the child */ if (effectProperties.HasValidFilter()) { RegularFramePaintCallback callback(aBuilder, aLayerManager, offsetToUserSpace); nsRegion dirtyRegion = aDirtyRect - offsetToBoundingBox; gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); nsFilterInstance::PaintFilteredFrame(aFrame, target->GetDrawTarget(), tm, &callback, &dirtyRegion); } else { target->SetMatrix(matrixAutoSaveRestore.Matrix()); BasicLayerManager* basic = static_cast<BasicLayerManager*>(aLayerManager); RefPtr<gfxContext> oldCtx = basic->GetTarget(); basic->SetTarget(target); aLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder); basic->SetTarget(oldCtx); } if (clipPathFrame && isTrivialClip) { aContext.Restore(); } /* No more effects, we're done. */ if (!complexEffects) { return; } if (opacity != 1.0f || svgMaskFrame || hasValidLayers || (clipPathFrame && !isTrivialClip)) { target->PopGroupAndBlend(); } if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { RefPtr<DrawTarget> targetDT = target->GetDrawTarget(); target = nullptr; RefPtr<SourceSurface> targetSurf = targetDT->Snapshot(); aContext.SetMatrix(gfxMatrix()); // This will be restored right after. RefPtr<gfxPattern> pattern = new gfxPattern(targetSurf, Matrix::Translation(targetOffset.x, targetOffset.y)); aContext.SetPattern(pattern); aContext.Paint(); } aContext.Restore(); }