Bug 619500: Part 1. Default sizing for specified size of SVG images which have no constraints; r=dholbert r=seth
MozReview-Commit-ID: 1RA3H3Sp7oZ
--- 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.
*