//---------------------------------------------------------------------- // 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; }
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(); }