Exemplo n.º 1
0
//----------------------------------------------------------------------
// 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();
}