Bug 1224207 - Part 7. (Main) Pass frame transform info down to nsFilterInstance. draft
authorcku <cku@mozilla.com>
Tue, 07 Feb 2017 11:51:34 +0800
changeset 480546 9f8a1ef41a4e94fa7ce09a4017f2ba3de23f5225
parent 480545 9c527027a72f76807f3f5a962429f41050bb5f8f
child 480547 343cdbbb92afb587abc8c530783686b2e90650c6
push id44575
push userbmo:cku@mozilla.com
push dateWed, 08 Feb 2017 13:39:48 +0000
bugs1224207
milestone54.0a1
Bug 1224207 - Part 7. (Main) Pass frame transform info down to nsFilterInstance. The biggest change is located in nsFilterInstance::ComputeUserSpaceToFilterSpaceScale. Originally, nsSVGUtils::GetCanvasTM is used. This function returns combination of svg-transform, e.g. <rect transform="translate(30,40)" />, and css-to-dev-scale-transform. That why we do not see this bug in a transformed svg element. For example, the following svg can be rendered correctly on FF: <svg xmlns="http://www.w3.org/2000/svg"> <defs> <filter id="blurMe"> <feGaussianBlur in="SourceGraphic" stdDeviation="1"/> </filter> </defs> <!-- nsSVGUtils::GetCanvasTM return transform="scale(3)" correctly --> <text x="0" y="35" font-size="35" transform="scale(3)" filter="url(#blurMe)"> Hello, out there </text> </svg> Unfortunately, this function does not report css-transform at all. So, I replaced it by mPaintTransfom, which is passed from the caller. So now it's the caller's responsibility to pass a UserSpace-To-DeviceSpace transform into nsFilterInstance. And, we actually change the meaning of mPaintTransform in this patch. Before this patch, mPaintTransform means css-to-dev-px scaling transform; After this patch it means "userspace-to-filterspace-scaling * css-to-dev-scaling" transform. All the other modifictions are to respect the change in nsFilterInstance::ComputeUserSpaceToFilterSpaceScale. MozReview-Commit-ID: LwNUAMo98M
layout/svg/nsFilterInstance.cpp
layout/svg/nsFilterInstance.h
layout/svg/nsSVGIntegrationUtils.cpp
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -34,20 +34,20 @@ using namespace mozilla::image;
 FilterDescription
 nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement,
                                        const nsTArray<nsStyleFilter>& aFilterChain,
                                        bool aFilterInputIsTainted,
                                        const UserSpaceMetrics& aMetrics,
                                        const gfxRect& aBBox,
                                        nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
 {
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix identity;
   nsFilterInstance instance(nullptr, aFilteredElement, aMetrics,
                             aFilterChain, aFilterInputIsTainted, nullptr,
-                            unused, nullptr, nullptr, nullptr, &aBBox);
+                            identity, nullptr, nullptr, nullptr, &aBBox);
   if (!instance.IsInitialized()) {
     return FilterDescription();
   }
   return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
 }
 
 static UniquePtr<UserSpaceMetrics>
 UserSpaceMetricsForFrame(nsIFrame* aFrame)
@@ -65,64 +65,65 @@ nsFilterInstance::PaintFilteredFrame(nsI
                                      const gfxMatrix& aTransform,
                                      nsSVGFilterPaintCallback *aPaintCallback,
                                      const nsRegion *aDirtyArea)
 {
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, aPaintCallback,
-                            aTransform, aDirtyArea, nullptr, nullptr, nullptr);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            aPaintCallback, aTransform, aDirtyArea, nullptr,
+                            nullptr, nullptr);
   if (!instance.IsInitialized()) {
     return DrawResult::BAD_IMAGE;
   }
 
   return instance.Render(aDrawTarget);
 }
 
 nsRegion
 nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
                                          const nsRegion& aPreFilterDirtyRegion)
 {
   if (aPreFilterDirtyRegion.IsEmpty()) {
     return nsRegion();
   }
 
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, nullptr, unused,
-                            nullptr, &aPreFilterDirtyRegion);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            nullptr, tm, nullptr, &aPreFilterDirtyRegion);
   if (!instance.IsInitialized()) {
     return nsRegion();
   }
 
   // We've passed in the source's dirty area so the instance knows about it.
   // Now we can ask the instance to compute the area of the filter output
   // that's dirty.
   return instance.ComputePostFilterDirtyRegion();
 }
 
 nsRegion
 nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
                                          const nsRegion& aPostFilterDirtyRegion)
 {
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, nullptr, unused,
-                            &aPostFilterDirtyRegion);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            nullptr, tm, &aPostFilterDirtyRegion);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
 
   // Now we can ask the instance to compute the area of the source
   // that's needed.
   return instance.ComputeSourceNeededRect();
 }
@@ -138,25 +139,25 @@ nsFilterInstance::GetPostFilterBounds(ns
 
   nsRegion preFilterRegion;
   nsRegion* preFilterRegionPtr = nullptr;
   if (aPreFilterBounds) {
     preFilterRegion = *aPreFilterBounds;
     preFilterRegionPtr = &preFilterRegion;
   }
 
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, nullptr, unused,
-                            nullptr, preFilterRegionPtr, aPreFilterBounds,
-                            aOverrideBBox);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            nullptr, tm, nullptr, preFilterRegionPtr,
+                            aPreFilterBounds, aOverrideBBox);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
 
   return instance.ComputePostFilterExtents();
 }
 
 nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
@@ -198,21 +199,16 @@ nsFilterInstance::nsFilterInstance(nsIFr
   }
 
   // Get various transforms:
 
   gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f,
                               0.0f, mFilterSpaceToUserSpaceScale.height,
                               0.0f, 0.0f);
 
-  // Only used (so only set) when we paint:
-  if (mPaintCallback) {
-    mFilterSpaceToDeviceSpaceTransform = filterToUserSpace * mPaintTransform;
-  }
-
   mFilterSpaceToFrameSpaceInCSSPxTransform =
     filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
   // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
   mFrameSpaceInCSSPxToFilterSpaceTransform =
     mFilterSpaceToFrameSpaceInCSSPxTransform;
   mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
 
   nsIntRect targetBounds;
@@ -238,32 +234,29 @@ nsFilterInstance::nsFilterInstance(nsIFr
   mInitialized = true;
 }
 
 nsresult
 nsFilterInstance::ComputeUserSpaceToFilterSpaceScale()
 {
   gfxMatrix canvasTransform;
   if (mTargetFrame) {
-    canvasTransform = nsSVGUtils::GetCanvasTM(mTargetFrame);
-    if (canvasTransform.IsSingular()) {
+    mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(true);
+    if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
+        mUserSpaceToFilterSpaceScale.height <= 0.0f) {
       // Nothing should be rendered.
       return NS_ERROR_FAILURE;
     }
+  } else {
+    mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
   }
 
-  mUserSpaceToFilterSpaceScale = canvasTransform.ScaleFactors(true);
-  if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
-      mUserSpaceToFilterSpaceScale.height <= 0.0f) {
-    // Nothing should be rendered.
-    return NS_ERROR_FAILURE;
-  }
-
-  mFilterSpaceToUserSpaceScale = gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
-                                         1.0f / mUserSpaceToFilterSpaceScale.height);
+  mFilterSpaceToUserSpaceScale =
+    gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
+            1.0f / mUserSpaceToFilterSpaceScale.height);
   return NS_OK;
 }
 
 gfxRect
 nsFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const
 {
   gfxRect filterSpaceRect = aUserSpaceRect;
   filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
@@ -369,40 +362,32 @@ nsFilterInstance::BuildSourcePaint(Sourc
 
   RefPtr<DrawTarget> offscreenDT =
     gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
       neededRect.Size(), SurfaceFormat::B8G8R8A8);
   if (!offscreenDT || !offscreenDT->IsValid()) {
     return DrawResult::TEMPORARY_ERROR;
   }
 
-  gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
-  DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
-  MOZ_ASSERT(invertible,
-             "The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
-             "be an invertible matrix(not a singular one), since we already"
-             "checked it and early return if it's not from the caller side"
-             "(nsFilterInstance::Render)");
+  RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
+  MOZ_ASSERT(ctx); // already checked the draw target above
+  gfxContextAutoSaveRestore saver(ctx);
 
-  if (!mPaintTransform.IsSingular()) {
-    RefPtr<gfxContext> sourceCtx = gfxContext::CreateOrNull(offscreenDT);
-    MOZ_ASSERT(sourceCtx); // already checked the draw target above
-    sourceCtx->Multiply(mPaintTransform *
-                  deviceToFilterSpace *
-                  gfxMatrix::Translation(-neededRect.TopLeft()));
-    GeneralPattern pattern;
-    if (aSource == &mFillPaint) {
-      nsSVGUtils::MakeFillPatternFor(mTargetFrame, sourceCtx, &pattern);
-    } else if (aSource == &mStrokePaint) {
-      nsSVGUtils::MakeStrokePatternFor(mTargetFrame, sourceCtx, &pattern);
-    }
-    if (pattern.GetPattern()) {
-      offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
-                            pattern);
-    }
+  ctx->SetMatrix(mPaintTransform *
+                 gfxMatrix::Translation(-neededRect.TopLeft()));
+  GeneralPattern pattern;
+  if (aSource == &mFillPaint) {
+    nsSVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern);
+  } else if (aSource == &mStrokePaint) {
+    nsSVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern);
+  }
+
+  if (pattern.GetPattern()) {
+    offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
+                          pattern);
   }
 
   aSource->mSourceSurface = offscreenDT->Snapshot();
   aSource->mSurfaceRect = neededRect;
 
   return DrawResult::SUCCESS;
 }
 
@@ -456,29 +441,23 @@ nsFilterInstance::BuildSourceImage()
   //
   // (In theory it would be better to minimize error by having filtered SVG
   // graphics temporarily paint to user space when painting the sources and
   // only set a user space to filter space transform on the gfxContext
   // (since that would eliminate the transform multiplications from user
   // space to device space and back again). However, that would make the
   // code more complex while being hard to get right without introducing
   // subtle bugs, and in practice it probably makes no real difference.)
-  gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
-  DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
-  MOZ_ASSERT(invertible,
-             "The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
-             "be an invertible matrix(not a singular one), since we already"
-             "checked it and early return if it's not from the caller side"
-             "(nsFilterInstance::Render)");
-
   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
   MOZ_ASSERT(ctx); // already checked the draw target above
-  ctx->SetMatrix(
-    ctx->CurrentMatrix().Translate(-neededRect.TopLeft()).
-                         PreMultiply(deviceToFilterSpace));
+  gfxMatrix devPxToCssPxTM = nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
+  DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
+  MOZ_ASSERT(invertible);
+  ctx->SetMatrix(devPxToCssPxTM * mPaintTransform *
+                 gfxMatrix::Translation(-neededRect.TopLeft()));
 
   DrawResult result =
     mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty);
 
   mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
   mSourceGraphic.mSurfaceRect = neededRect;
 
   return result;
@@ -491,28 +470,32 @@ nsFilterInstance::Render(DrawTarget* aDr
 
   if (mPrimitiveDescriptions.IsEmpty()) {
     // An filter without any primitive. Treat it as success and paint nothing.
     return DrawResult::SUCCESS;
   }
 
   nsIntRect filterRect =
     mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
-  gfxMatrix ctm = GetFilterSpaceToDeviceSpaceTransform();
-
-  if (filterRect.IsEmpty() || ctm.IsSingular()) {
+  if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
     return DrawResult::SUCCESS;
   }
 
   AutoRestoreTransform autoRestoreTransform(aDrawTarget);
-  Matrix newTM = ToMatrix(ctm).PreTranslate(filterRect.x, filterRect.y) *
-                 aDrawTarget->GetTransform();
-  aDrawTarget->SetTransform(newTM);
+  gfxMatrix filterSpaceToUserSpace = mPaintTransform;
+  DebugOnly<bool> invertible = filterSpaceToUserSpace.Invert();
+  MOZ_ASSERT(invertible);
+  filterSpaceToUserSpace *= nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
+
+  aDrawTarget->SetTransform(ToMatrix(filterSpaceToUserSpace) *
+                            aDrawTarget->GetTransform() *
+                            Matrix::Translation(filterRect.TopLeft()));
 
   ComputeNeededBoxes();
+
   DrawResult result = BuildSourceImage();
   if (result != DrawResult::SUCCESS){
     return result;
   }
   result = BuildSourcePaints();
   if (result != DrawResult::SUCCESS){
     return result;
   }
--- a/layout/svg/nsFilterInstance.h
+++ b/layout/svg/nsFilterInstance.h
@@ -197,23 +197,16 @@ private:
    * Sets the aDirty outparam to the pre-filter bounds in frame space of the
    * area of mTargetFrame that is needed in order to paint the filtered output
    * for a given post-filter dirtied area. The post-filter area must have been
    * specified before calling this method by passing it as the aPostFilterDirtyRegion
    * argument to the nsFilterInstance constructor.
    */
   nsRect ComputeSourceNeededRect();
 
-  /**
-   * Returns the transform from filter space to outer-<svg> device space.
-   */
-  gfxMatrix GetFilterSpaceToDeviceSpaceTransform() const {
-    return mFilterSpaceToDeviceSpaceTransform;
-  }
-
   struct SourceInfo {
     // Specifies which parts of the source need to be rendered.
     // Set by ComputeNeededBoxes().
     nsIntRect mNeededBounds;
 
     // The surface that contains the input rendering.
     // Set by BuildSourceImage / BuildSourcePaint.
     RefPtr<SourceSurface> mSourceSurface;
@@ -333,21 +326,16 @@ private:
   gfxRect mTargetBBox;
 
   /**
    * The SVG bbox of the element that is being filtered, in filter space.
    */
   nsIntRect mTargetBBoxInFilterSpace;
 
   /**
-   * The transform from filter space to outer-<svg> device space.
-   */
-  gfxMatrix mFilterSpaceToDeviceSpaceTransform;
-
-  /**
    * Transform rects between filter space and frame space in CSS pixels.
    */
   gfxMatrix mFilterSpaceToFrameSpaceInCSSPxTransform;
   gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform;
 
   /**
    * The scale factors between user space and filter space.
    */
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -1118,17 +1118,22 @@ nsSVGIntegrationUtils::PaintFilter(const
     context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
                                   nullptr, Matrix());
   }
 
   /* Paint the child and apply filters */
   RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
                                      offsets.offsetToUserSpaceInDevPx);
   nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
-  gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
+  gfxSize scaleFactors = context.CurrentMatrix().ScaleFactors(true);
+  gfxMatrix scaleMatrix(scaleFactors.width, 0.0f,
+                        0.0f, scaleFactors.height,
+                        0.0f, 0.0f);
+  gfxMatrix tm =
+    scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
   DrawResult result =
     nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(),
                                          tm, &callback, &dirtyRegion);
 
   if (opacity != 1.0f) {
     context.PopGroupAndBlend();
   }