Bug 619500: Part 1. Default sizing for specified size of SVG images which have no constraints; r=dholbert r=seth draft
authorCJKu <cku@mozilla.com>
Tue, 08 Mar 2016 11:34:59 +0800
changeset 338037 fd40628ecf34533f681736709a9fed2f84958a64
parent 338036 d8886937457cad74d5382bf7cd29a55c1da191ef
child 338038 5066c0dc7571dd0b9c513c8b47ab329fa5cc6a3b
push id12405
push usercku@mozilla.com
push dateTue, 08 Mar 2016 03:35:29 +0000
reviewersdholbert, seth
bugs619500
milestone47.0a1
Bug 619500: Part 1. Default sizing for specified size of SVG images which have no constraints; r=dholbert r=seth MozReview-Commit-ID: 1RA3H3Sp7oZ
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.isNothing(),
+                  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.
    *