Bug 619500: Part 1. Default sizing for specified size of SVG images which have no constraints; r=dholbert r=seth
authorCJKu <cku@mozilla.com>
Tue, 08 Mar 2016 15:54:13 +0800
changeset 287198 5d03f2103664bbc4b024fe73213883f32afe8535
parent 287197 4adaa4b1ddb234549c59b69ebed03d9bb6f72fbd
child 287199 055cc694bc2635b4c2cfcc94945cee1b8a6d1f77
push id30065
push userkwierso@gmail.com
push dateWed, 09 Mar 2016 00:01:05 +0000
treeherdermozilla-central@886b5480b578 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert, seth
bugs619500
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 619500: Part 1. Default sizing for specified size of SVG images which have no constraints; r=dholbert r=seth MozReview-Commit-ID: 8DI86w6Ni8T
image/ClippedImage.cpp
image/ClippedImage.h
image/ImageOps.cpp
image/ImageOps.h
layout/base/nsCSSRendering.cpp
layout/base/nsCSSRendering.h
--- a/image/ClippedImage.cpp
+++ b/image/ClippedImage.cpp
@@ -132,21 +132,28 @@ private:
   const nsIntSize               mSize;
   const Maybe<SVGImageContext>& mSVGContext;
   const uint32_t                mWhichFrame;
   const uint32_t                mFlags;
   DrawResult                    mDrawResult;
 };
 
 ClippedImage::ClippedImage(Image* aImage,
-                           nsIntRect aClip)
+                           nsIntRect aClip,
+                           const Maybe<nsSize>& aSVGViewportSize)
   : ImageWrapper(aImage)
   , mClip(aClip)
 {
   MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image");
+  MOZ_ASSERT_IF(aSVGViewportSize,
+                aImage->GetType() == imgIContainer::TYPE_VECTOR);
+  if (aSVGViewportSize) {
+    mSVGViewportSize = Some(aSVGViewportSize->ToNearestPixels(
+                                        nsPresContext::AppUnitsPerCSSPixel()));
+  }
 }
 
 ClippedImage::~ClippedImage()
 { }
 
 bool
 ClippedImage::ShouldClip()
 {
@@ -157,16 +164,25 @@ ClippedImage::ShouldClip()
   if (mShouldClip.isNothing()) {
     int32_t width, height;
     RefPtr<ProgressTracker> progressTracker =
       InnerImage()->GetProgressTracker();
     if (InnerImage()->HasError()) {
       // If there's a problem with the inner image we'll let it handle
       // everything.
       mShouldClip.emplace(false);
+    } else if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
+      // Clamp the clipping region to the size of the SVG viewport.
+      nsIntRect svgViewportRect(nsIntPoint(0,0), *mSVGViewportSize);
+
+      mClip = mClip.Intersect(svgViewportRect);
+
+      // If the clipping region is the same size as the SVG viewport size
+      // we don't have to do anything.
+      mShouldClip.emplace(!mClip.IsEqualInterior(svgViewportRect));
     } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 &&
                NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) {
       // Clamp the clipping region to the size of the underlying image.
       mClip = mClip.Intersect(nsIntRect(0, 0, width, height));
 
       // If the clipping region is the same size as the underlying image we
       // don't have to do anything.
       mShouldClip.emplace(!mClip.IsEqualInterior(nsIntRect(0, 0, width,
@@ -415,28 +431,36 @@ ClippedImage::DrawSingleTile(gfxContext*
                              const Maybe<SVGImageContext>& aSVGContext,
                              uint32_t aFlags)
 {
   MOZ_ASSERT(!MustCreateSurface(aContext, aSize, aRegion, aFlags),
              "Shouldn't need to create a surface");
 
   gfxRect clip(mClip.x, mClip.y, mClip.width, mClip.height);
   nsIntSize size(aSize), innerSize(aSize);
-  if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) &&
-      NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) {
+  bool needScale = false;
+  if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
+    innerSize = *mSVGViewportSize;
+    needScale = true;
+  } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) &&
+             NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) {
+    needScale = true;
+  } else {
+    MOZ_ASSERT_UNREACHABLE(
+               "If ShouldClip() led us to draw then we should never get here");
+  }
+
+  if (needScale) {
     double scaleX = aSize.width / clip.width;
     double scaleY = aSize.height / clip.height;
 
     // Map the clip and size to the scale requested by the caller.
     clip.Scale(scaleX, scaleY);
     size = innerSize;
     size.Scale(scaleX, scaleY);
-  } else {
-    MOZ_ASSERT(false,
-               "If ShouldClip() led us to draw then we should never get here");
   }
 
   // We restrict our drawing to only the clipping region, and translate so that
   // the clipping region is placed at the position the caller expects.
   ImageRegion region(aRegion);
   region.MoveBy(clip.x, clip.y);
   region = region.Intersect(clip);
 
@@ -473,18 +497,27 @@ ClippedImage::OptimalImageSizeForDest(co
                                       Filter aFilter, uint32_t aFlags)
 {
   if (!ShouldClip()) {
     return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, aFilter,
                                                  aFlags);
   }
 
   int32_t imgWidth, imgHeight;
-  if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) &&
-      NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) {
+  bool needScale = false;
+  if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
+    imgWidth = mSVGViewportSize->width;
+    imgHeight = mSVGViewportSize->height;
+    needScale = true;
+  } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) &&
+             NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) {
+    needScale = true;
+  }
+
+  if (needScale) {
     // To avoid ugly sampling artifacts, ClippedImage needs the image size to
     // be chosen such that the clipping region lies on pixel boundaries.
 
     // First, we select a scale that's good for ClippedImage. An integer
     // multiple of the size of the clipping region is always fine.
     nsIntSize scale(ceil(aDest.width / mClip.width),
                     ceil(aDest.height / mClip.height));
 
@@ -496,22 +529,22 @@ ClippedImage::OptimalImageSizeForDest(co
                                             aFilter, aFlags);
 
     // To get our final result, we take the inner image's desired size and
     // determine how large the clipped region would be at that scale. (Again, we
     // ensure an integer multiple of the size of the clipping region.)
     nsIntSize finalScale(ceil(double(innerDesiredSize.width) / imgWidth),
                          ceil(double(innerDesiredSize.height) / imgHeight));
     return mClip.Size() * finalScale;
-  } else {
-    MOZ_ASSERT(false,
-               "If ShouldClip() led us to draw then we should never get here");
-    return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, aFilter,
-                                                 aFlags);
   }
+
+  MOZ_ASSERT(false,
+             "If ShouldClip() led us to draw then we should never get here");
+  return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, aFilter,
+                                               aFlags);
 }
 
 NS_IMETHODIMP_(nsIntRect)
 ClippedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
 {
   if (!ShouldClip()) {
     return InnerImage()->GetImageSpaceInvalidationRect(aRect);
   }
--- a/image/ClippedImage.h
+++ b/image/ClippedImage.h
@@ -59,17 +59,18 @@ public:
   NS_IMETHOD_(nsIntRect) GetImageSpaceInvalidationRect(const nsIntRect& aRect)
     override;
   nsIntSize OptimalImageSizeForDest(const gfxSize& aDest,
                                     uint32_t aWhichFrame,
                                     gfx::Filter aFilter,
                                     uint32_t aFlags) override;
 
 protected:
-  ClippedImage(Image* aImage, nsIntRect aClip);
+  ClippedImage(Image* aImage, nsIntRect aClip,
+               const Maybe<nsSize>& aSVGViewportSize);
 
   virtual ~ClippedImage();
 
 private:
   Pair<DrawResult, RefPtr<SourceSurface>>
     GetFrameInternal(const nsIntSize& aSize,
                      const Maybe<SVGImageContext>& aSVGContext,
                      uint32_t aWhichFrame,
@@ -81,19 +82,20 @@ private:
                             uint32_t aWhichFrame,
                             gfx::Filter aFilter,
                             const Maybe<SVGImageContext>& aSVGContext,
                             uint32_t aFlags);
 
   // If we are forced to draw a temporary surface, we cache it here.
   UniquePtr<ClippedImageCachedSurface> mCachedSurface;
 
-  nsIntRect   mClip;              // The region to clip to.
-  Maybe<bool> mShouldClip;        // Memoized ShouldClip() if present.
-
+  nsIntRect        mClip;            // The region to clip to.
+  Maybe<bool>      mShouldClip;      // Memoized ShouldClip() if present.
+  Maybe<nsIntSize> mSVGViewportSize; // If we're clipping a VectorImage, this
+                                     // is the size of viewport of that image.
   friend class DrawSingleTileCallback;
   friend class ImageOps;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_ClippedImage_h
--- a/image/ImageOps.cpp
+++ b/image/ImageOps.cpp
@@ -35,27 +35,29 @@ ImageOps::Freeze(Image* aImage)
 ImageOps::Freeze(imgIContainer* aImage)
 {
   nsCOMPtr<imgIContainer> frozenImage =
     new FrozenImage(static_cast<Image*>(aImage));
   return frozenImage.forget();
 }
 
 /* static */ already_AddRefed<Image>
-ImageOps::Clip(Image* aImage, nsIntRect aClip)
+ImageOps::Clip(Image* aImage, nsIntRect aClip,
+               const Maybe<nsSize>& aSVGViewportSize)
 {
-  RefPtr<Image> clippedImage = new ClippedImage(aImage, aClip);
+  RefPtr<Image> clippedImage = new ClippedImage(aImage, aClip, aSVGViewportSize);
   return clippedImage.forget();
 }
 
 /* static */ already_AddRefed<imgIContainer>
-ImageOps::Clip(imgIContainer* aImage, nsIntRect aClip)
+ImageOps::Clip(imgIContainer* aImage, nsIntRect aClip,
+               const Maybe<nsSize>& aSVGViewportSize)
 {
   nsCOMPtr<imgIContainer> clippedImage =
-    new ClippedImage(static_cast<Image*>(aImage), aClip);
+    new ClippedImage(static_cast<Image*>(aImage), aClip, aSVGViewportSize);
   return clippedImage.forget();
 }
 
 /* static */ already_AddRefed<Image>
 ImageOps::Orient(Image* aImage, Orientation aOrientation)
 {
   RefPtr<Image> orientedImage = new OrientedImage(aImage, aOrientation);
   return orientedImage.forget();
--- a/image/ImageOps.h
+++ b/image/ImageOps.h
@@ -35,22 +35,29 @@ public:
    * @param aImage         The existing image.
    */
   static already_AddRefed<Image> Freeze(Image* aImage);
   static already_AddRefed<imgIContainer> Freeze(imgIContainer* aImage);
 
   /**
    * Creates a clipped version of an existing image. Animation is unaffected.
    *
-   * @param aImage         The existing image.
-   * @param aClip          The rectangle to clip the image against.
+   * @param aImage           The existing image.
+   * @param aClip            The rectangle to clip the image against.
+   * @param aSVGViewportSize The specific viewort size of aImage. Unless aImage
+   *                         is a vector image without intrinsic size, this
+   *                         argument should be pass as Nothing().
    */
-  static already_AddRefed<Image> Clip(Image* aImage, nsIntRect aClip);
+  static already_AddRefed<Image> Clip(Image* aImage, nsIntRect aClip,
+                                      const Maybe<nsSize>& aSVGViewportSize =
+                                        Nothing());
   static already_AddRefed<imgIContainer> Clip(imgIContainer* aImage,
-                                              nsIntRect aClip);
+                                              nsIntRect aClip,
+                                              const Maybe<nsSize>& aSVGViewportSize =
+                                                Nothing());
 
   /**
    * Creates a version of an existing image which is rotated and/or flipped to
    * the specified orientation.
    *
    * @param aImage         The existing image.
    * @param aOrientation   The desired orientation.
    */
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -3695,27 +3695,37 @@ DrawBorderImage(nsPresContext*       aPr
         unitSize.width = borderWidth[i];
         unitSize.height = borderHeight[j];
         fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
         fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
       }
 
       nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
       nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
+      if (subArea.IsEmpty())
+        continue;
+
       nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
+      // intrinsicSize.CanComputeConcreteSize() return false means we can not
+      // read intrinsic size from aStyleBorder.mBorderImageSource.
+      // In this condition, we pass imageSize(a resolved size comes from
+      // default sizing algorithm) to renderer as the viewport size.
+      Maybe<nsSize> svgViewportSize = intrinsicSize.CanComputeConcreteSize() ?
+        Nothing() : Some(imageSize);
 
       result &=
         renderer.DrawBorderImageComponent(aPresContext,
                                           aRenderingContext, aDirtyRect,
                                           destArea, CSSIntRect(intSubArea.x,
                                                                intSubArea.y,
                                                                intSubArea.width,
                                                                intSubArea.height),
                                           fillStyleH, fillStyleV,
-                                          unitSize, j * (RIGHT + 1) + i);
+                                          unitSize, j * (RIGHT + 1) + i,
+                                          svgViewportSize);
     }
   }
 
   return result;
 }
 
 // Begin table border-collapsing section
 // These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
@@ -4780,17 +4790,19 @@ nsImageRenderer::PrepareImage()
           // The cropped image has zero size
           mPrepareResult = DrawResult::BAD_IMAGE;
           return false;
         }
         if (isEntireImage) {
           // The cropped image is identical to the source image
           mImageContainer.swap(srcImage);
         } else {
-          nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage, actualCropRect);
+          nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage,
+                                                            actualCropRect,
+                                                            Nothing());
           mImageContainer.swap(subImage);
         }
       }
       mPrepareResult = DrawResult::SUCCESS;
       break;
     }
     case eStyleImageType_Gradient:
       mGradientData = mImage->GetGradientData();
@@ -5249,34 +5261,35 @@ DrawResult
 nsImageRenderer::DrawBorderImageComponent(nsPresContext*       aPresContext,
                                           nsRenderingContext&  aRenderingContext,
                                           const nsRect&        aDirtyRect,
                                           const nsRect&        aFill,
                                           const CSSIntRect&    aSrc,
                                           uint8_t              aHFill,
                                           uint8_t              aVFill,
                                           const nsSize&        aUnitSize,
-                                          uint8_t              aIndex)
+                                          uint8_t              aIndex,
+                                          const Maybe<nsSize>& aSVGViewportSize)
 {
   if (!IsReady()) {
     NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
     return DrawResult::BAD_ARGS;
   }
   if (aFill.IsEmpty() || aSrc.IsEmpty()) {
     return DrawResult::SUCCESS;
   }
 
   if (mType == eStyleImageType_Image || mType == eStyleImageType_Element) {
     nsCOMPtr<imgIContainer> subImage;
 
     // Retrieve or create the subimage we'll draw.
     nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
     if (mType == eStyleImageType_Image) {
       if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
-        subImage = ImageOps::Clip(mImageContainer, srcRect);
+        subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
         mImage->SetSubImage(aIndex, subImage);
       }
     } else {
       // This path, for eStyleImageType_Element, is currently slower than it
       // needs to be because we don't cache anything. (In particular, if we have
       // to draw to a temporary surface inside ClippedImage, we don't cache that
       // temporary surface since we immediately throw the ClippedImage we create
       // here away.) However, if we did cache, we'd need to know when to
@@ -5286,19 +5299,22 @@ nsImageRenderer::DrawBorderImageComponen
       RefPtr<gfxDrawable> drawable = DrawableForElement(nsRect(nsPoint(), mSize),
                                                           aRenderingContext);
       if (!drawable) {
         NS_WARNING("Could not create drawable for element");
         return DrawResult::TEMPORARY_ERROR;
       }
 
       nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
-      subImage = ImageOps::Clip(image, srcRect);
+      subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize);
     }
 
+    MOZ_ASSERT_IF(aSVGViewportSize,
+                  subImage->GetType() == imgIContainer::TYPE_VECTOR);
+
     Filter filter = nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame);
 
     if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
       return nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
                                             aPresContext,
                                             subImage,
                                             filter,
                                             aFill, aDirtyRect,
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -226,27 +226,32 @@ public:
    * aFill), if aSrc and the dest tile are different sizes, the image will be
    * scaled to map aSrc onto the dest tile.
    * aHFill and aVFill are the repeat patterns for the component -
    * NS_STYLE_BORDER_IMAGE_REPEAT_*
    * aUnitSize The scaled size of a single source rect (in destination coords)
    * aIndex identifies the component: 0 1 2
    *                                  3 4 5
    *                                  6 7 8
+   * aSVGViewportSize The image size evaluated by default sizing algorithm.
+   * Pass Nothing() if we can read a valid viewport size or aspect-ratio from
+   * the drawing image directly, otherwise, pass Some() with viewport size
+   * evaluated from default sizing algorithm.
    */
   DrawResult
   DrawBorderImageComponent(nsPresContext*       aPresContext,
                            nsRenderingContext&  aRenderingContext,
                            const nsRect&        aDirtyRect,
                            const nsRect&        aFill,
                            const mozilla::CSSIntRect& aSrc,
                            uint8_t              aHFill,
                            uint8_t              aVFill,
                            const nsSize&        aUnitSize,
-                           uint8_t              aIndex);
+                           uint8_t              aIndex,
+                           const mozilla::Maybe<nsSize>& aSVGViewportSize);
 
   bool IsRasterImage();
   bool IsAnimatedImage();
 
   /**
    * @return true if this nsImageRenderer wraps an image which has an
    * ImageContainer available.
    *