--- a/layout/painting/moz.build
+++ b/layout/painting/moz.build
@@ -15,16 +15,17 @@ EXPORTS += [
'DisplayListClipState.h',
'FrameLayerBuilder.h',
'LayerState.h',
'nsCSSRenderingBorders.h',
'nsDisplayItemTypes.h',
'nsDisplayItemTypesList.h',
'nsDisplayList.h',
'nsDisplayListInvalidation.h',
+ 'nsImageRenderer.h',
]
EXPORTS.mozilla += [
'PaintTracker.h',
]
UNIFIED_SOURCES += [
'ActiveLayerTracker.cpp',
@@ -35,16 +36,17 @@ UNIFIED_SOURCES += [
'DisplayListClipState.cpp',
'DottedCornerFinder.cpp',
'FrameLayerBuilder.cpp',
'MaskLayerImageCache.cpp',
'nsCSSRendering.cpp',
'nsCSSRenderingBorders.cpp',
'nsDisplayList.cpp',
'nsDisplayListInvalidation.cpp',
+ 'nsImageRenderer.cpp',
'PaintTracker.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
'/docshell/base',
'/dom/base',
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -5340,877 +5340,16 @@ nsCSSRendering::GetTextDecorationRectInt
Swap(r.width, r.height);
} else {
r.y = baseline - floor(offset + 0.5);
}
return r;
}
-// ------------------
-// ImageRenderer
-// ------------------
-nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
- const nsStyleImage* aImage,
- uint32_t aFlags)
- : mForFrame(aForFrame)
- , mImage(aImage)
- , mType(aImage->GetType())
- , mImageContainer(nullptr)
- , mGradientData(nullptr)
- , mPaintServerFrame(nullptr)
- , mPrepareResult(DrawResult::NOT_READY)
- , mSize(0, 0)
- , mFlags(aFlags)
- , mExtendMode(ExtendMode::CLAMP)
- , mMaskOp(NS_STYLE_MASK_MODE_MATCH_SOURCE)
-{
-}
-
-nsImageRenderer::~nsImageRenderer()
-{
-}
-
-static bool
-ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage* aImage,
- uint32_t aFlags)
-{
- if (!(aFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES)) {
- return false;
- }
-
- if (aImage->GetType() != eStyleImageType_Image) {
- return false;
- }
-
- imgRequestProxy* req = aImage->GetImageData();
- if (!req) {
- return false;
- }
-
- uint32_t status = 0;
- if (NS_FAILED(req->GetImageStatus(&status))) {
- return false;
- }
-
- if (status & imgIRequest::STATUS_ERROR) {
- // The image is "complete" since it's a corrupt image. If we created an
- // imgIContainer at all, return true.
- nsCOMPtr<imgIContainer> image;
- req->GetImage(getter_AddRefs(image));
- return bool(image);
- }
-
- if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
- // We must have loaded all of the image's data and the size must be
- // available, or else sync decoding won't be able to decode the image.
- return false;
- }
-
- return true;
-}
-
-bool
-nsImageRenderer::PrepareImage()
-{
- if (mImage->IsEmpty()) {
- mPrepareResult = DrawResult::BAD_IMAGE;
- return false;
- }
-
- if (!mImage->IsComplete()) {
- // Make sure the image is actually decoding.
- bool frameComplete = mImage->StartDecoding();
-
- // Check again to see if we finished.
- // We cannot prepare the image for rendering if it is not fully loaded.
- // Special case: If we requested a sync decode and the image has loaded, push
- // on through because the Draw() will do a sync decode then.
- if (!(frameComplete || mImage->IsComplete()) &&
- !ShouldTreatAsCompleteDueToSyncDecode(mImage, mFlags)) {
- mPrepareResult = DrawResult::NOT_READY;
- return false;
- }
- }
-
- switch (mType) {
- case eStyleImageType_Image: {
- MOZ_ASSERT(mImage->GetImageData(),
- "must have image data, since we checked IsEmpty above");
- nsCOMPtr<imgIContainer> srcImage;
- DebugOnly<nsresult> rv =
- mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
- MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
- "If GetImage() is failing, mImage->IsComplete() "
- "should have returned false");
-
- if (!mImage->GetCropRect()) {
- mImageContainer.swap(srcImage);
- } else {
- nsIntRect actualCropRect;
- bool isEntireImage;
- bool success =
- mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
- NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
- if (!success || actualCropRect.IsEmpty()) {
- // 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,
- Nothing());
- mImageContainer.swap(subImage);
- }
- }
- mPrepareResult = DrawResult::SUCCESS;
- break;
- }
- case eStyleImageType_Gradient:
- mGradientData = mImage->GetGradientData();
- mPrepareResult = DrawResult::SUCCESS;
- break;
- case eStyleImageType_Element:
- {
- nsAutoString elementId =
- NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId());
- nsCOMPtr<nsIURI> targetURI;
- nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
- nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
- mForFrame->GetContent()->GetUncomposedDoc(), base);
- nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
- targetURI, mForFrame->FirstContinuation(),
- nsSVGEffects::BackgroundImageProperty());
- if (!property) {
- mPrepareResult = DrawResult::BAD_IMAGE;
- return false;
- }
-
- // If the referenced element is an <img>, <canvas>, or <video> element,
- // prefer SurfaceFromElement as it's more reliable.
- mImageElementSurface =
- nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
- if (!mImageElementSurface.GetSourceSurface()) {
- mPaintServerFrame = property->GetReferencedFrame();
- if (!mPaintServerFrame) {
- mPrepareResult = DrawResult::BAD_IMAGE;
- return false;
- }
- }
-
- mPrepareResult = DrawResult::SUCCESS;
- break;
- }
- case eStyleImageType_Null:
- default:
- break;
- }
-
- return IsReady();
-}
-
-nsSize
-CSSSizeOrRatio::ComputeConcreteSize() const
-{
- NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
- if (mHasWidth && mHasHeight) {
- return nsSize(mWidth, mHeight);
- }
- if (mHasWidth) {
- nscoord height = NSCoordSaturatingNonnegativeMultiply(
- mWidth,
- double(mRatio.height) / mRatio.width);
- return nsSize(mWidth, height);
- }
-
- MOZ_ASSERT(mHasHeight);
- nscoord width = NSCoordSaturatingNonnegativeMultiply(
- mHeight,
- double(mRatio.width) / mRatio.height);
- return nsSize(width, mHeight);
-}
-
-CSSSizeOrRatio
-nsImageRenderer::ComputeIntrinsicSize()
-{
- NS_ASSERTION(IsReady(), "Ensure PrepareImage() has returned true "
- "before calling me");
-
- CSSSizeOrRatio result;
- switch (mType) {
- case eStyleImageType_Image:
- {
- bool haveWidth, haveHeight;
- CSSIntSize imageIntSize;
- nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
- result.mRatio, haveWidth, haveHeight);
- if (haveWidth) {
- result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
- }
- if (haveHeight) {
- result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
- }
-
- // If we know the aspect ratio and one of the dimensions,
- // we can compute the other missing width or height.
- if (!haveHeight && haveWidth && result.mRatio.width != 0) {
- nscoord intrinsicHeight =
- NSCoordSaturatingNonnegativeMultiply(imageIntSize.width,
- float(result.mRatio.height) /
- float(result.mRatio.width));
- result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight));
- } else if (haveHeight && !haveWidth && result.mRatio.height != 0) {
- nscoord intrinsicWidth =
- NSCoordSaturatingNonnegativeMultiply(imageIntSize.height,
- float(result.mRatio.width) /
- float(result.mRatio.height));
- result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth));
- }
-
- break;
- }
- case eStyleImageType_Element:
- {
- // XXX element() should have the width/height of the referenced element,
- // and that element's ratio, if it matches. If it doesn't match, it
- // should have no width/height or ratio. See element() in CSS images:
- // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
- // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
- // when fixing this!
- if (mPaintServerFrame) {
- // SVG images have no intrinsic size
- if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
- // The intrinsic image size for a generic nsIFrame paint server is
- // the union of the border-box rects of all of its continuations,
- // rounded to device pixels.
- int32_t appUnitsPerDevPixel =
- mForFrame->PresContext()->AppUnitsPerDevPixel();
- result.SetSize(
- IntSizeToAppUnits(
- nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
- ToNearestPixels(appUnitsPerDevPixel),
- appUnitsPerDevPixel));
- }
- } else {
- NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
- "Surface should be ready.");
- IntSize surfaceSize = mImageElementSurface.mSize;
- result.SetSize(
- nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
- nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
- }
- break;
- }
- case eStyleImageType_Gradient:
- // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
- // intrinsic dimensions.
- case eStyleImageType_Null:
- default:
- break;
- }
-
- return result;
-}
-
-/* static */ nsSize
-nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
- const CSSSizeOrRatio& aIntrinsicSize,
- const nsSize& aDefaultSize)
-{
- // The specified size is fully specified, just use that
- if (aSpecifiedSize.IsConcrete()) {
- return aSpecifiedSize.ComputeConcreteSize();
- }
-
- MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
-
- if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
- // no specified size, try using the intrinsic size
- if (aIntrinsicSize.CanComputeConcreteSize()) {
- return aIntrinsicSize.ComputeConcreteSize();
- }
-
- if (aIntrinsicSize.mHasWidth) {
- return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
- }
- if (aIntrinsicSize.mHasHeight) {
- return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
- }
-
- // couldn't use the intrinsic size either, revert to using the default size
- return ComputeConstrainedSize(aDefaultSize,
- aIntrinsicSize.mRatio,
- CONTAIN);
- }
-
- MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
-
- // The specified height is partial, try to compute the missing part.
- if (aSpecifiedSize.mHasWidth) {
- nscoord height;
- if (aIntrinsicSize.HasRatio()) {
- height = NSCoordSaturatingNonnegativeMultiply(
- aSpecifiedSize.mWidth,
- double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
- } else if (aIntrinsicSize.mHasHeight) {
- height = aIntrinsicSize.mHeight;
- } else {
- height = aDefaultSize.height;
- }
- return nsSize(aSpecifiedSize.mWidth, height);
- }
-
- MOZ_ASSERT(aSpecifiedSize.mHasHeight);
- nscoord width;
- if (aIntrinsicSize.HasRatio()) {
- width = NSCoordSaturatingNonnegativeMultiply(
- aSpecifiedSize.mHeight,
- double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
- } else if (aIntrinsicSize.mHasWidth) {
- width = aIntrinsicSize.mWidth;
- } else {
- width = aDefaultSize.width;
- }
- return nsSize(width, aSpecifiedSize.mHeight);
-}
-
-/* static */ nsSize
-nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
- const nsSize& aIntrinsicRatio,
- FitType aFitType)
-{
- if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
- return aConstrainingSize;
- }
-
- float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
- float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
- nsSize size;
- if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
- size.width = aConstrainingSize.width;
- size.height = NSCoordSaturatingNonnegativeMultiply(
- aIntrinsicRatio.height, scaleX);
- // If we're reducing the size by less than one css pixel, then just use the
- // constraining size.
- if (aFitType == CONTAIN && aConstrainingSize.height - size.height < nsPresContext::AppUnitsPerCSSPixel()) {
- size.height = aConstrainingSize.height;
- }
- } else {
- size.width = NSCoordSaturatingNonnegativeMultiply(
- aIntrinsicRatio.width, scaleY);
- if (aFitType == CONTAIN && aConstrainingSize.width - size.width < nsPresContext::AppUnitsPerCSSPixel()) {
- size.width = aConstrainingSize.width;
- }
- size.height = aConstrainingSize.height;
- }
- return size;
-}
-
-/**
- * mSize is the image's "preferred" size for this particular rendering, while
- * the drawn (aka concrete) size is the actual rendered size after accounting
- * for background-size etc.. The preferred size is most often the image's
- * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
- * the preferred size varies, depending on the specified and default sizes, see
- * nsImageRenderer::Compute*Size.
- *
- * This distinction is necessary because the components of a vector image are
- * specified with respect to its preferred size for a rendering situation, not
- * to its actual rendered size. For example, consider a 4px wide background
- * vector image with no height which contains a left-aligned
- * 2px wide black rectangle with height 100%. If the background-size width is
- * auto (or 4px), the vector image will render 4px wide, and the black rectangle
- * will be 2px wide. If the background-size width is 8px, the vector image will
- * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
- * In both cases mSize.width will be 4px; but in the first case the returned
- * width will be 4px, while in the second case the returned width will be 8px.
- */
-void
-nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
- const nsSize& aDefaultSize)
-{
- mSize.width = aIntrinsicSize.mHasWidth
- ? aIntrinsicSize.mWidth
- : aDefaultSize.width;
- mSize.height = aIntrinsicSize.mHasHeight
- ? aIntrinsicSize.mHeight
- : aDefaultSize.height;
-}
-
-// Convert from nsImageRenderer flags to the flags we want to use for drawing in
-// the imgIContainer namespace.
-static uint32_t
-ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
-{
- uint32_t drawFlags = imgIContainer::FLAG_NONE;
- if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
- drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
- }
- if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
- drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
- }
- return drawFlags;
-}
-
-/*
- * SVG11: A luminanceToAlpha operation is equivalent to the following matrix operation: |
- * | R' | | 0 0 0 0 0 | | R |
- * | G' | | 0 0 0 0 0 | | G |
- * | B' | = | 0 0 0 0 0 | * | B |
- * | A' | | 0.2125 0.7154 0.0721 0 0 | | A |
- * | 1 | | 0 0 0 0 1 | | 1 |
- */
-static void
-RGBALuminanceOperation(uint8_t *aData,
- int32_t aStride,
- const IntSize &aSize)
-{
- int32_t redFactor = 55; // 256 * 0.2125
- int32_t greenFactor = 183; // 256 * 0.7154
- int32_t blueFactor = 18; // 256 * 0.0721
-
- for (int32_t y = 0; y < aSize.height; y++) {
- uint32_t *pixel = (uint32_t*)(aData + aStride * y);
- for (int32_t x = 0; x < aSize.width; x++) {
- *pixel = (((((*pixel & 0x00FF0000) >> 16) * redFactor) +
- (((*pixel & 0x0000FF00) >> 8) * greenFactor) +
- ((*pixel & 0x000000FF) * blueFactor)) >> 8) << 24;
- pixel++;
- }
- }
-}
-
-
-DrawResult
-nsImageRenderer::Draw(nsPresContext* aPresContext,
- nsRenderingContext& aRenderingContext,
- const nsRect& aDirtyRect,
- const nsRect& aDest,
- const nsRect& aFill,
- const nsPoint& aAnchor,
- const nsSize& aRepeatSize,
- const CSSIntRect& aSrc,
- float aOpacity)
-{
- if (!IsReady()) {
- NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
- return DrawResult::TEMPORARY_ERROR;
- }
- if (aDest.IsEmpty() || aFill.IsEmpty() ||
- mSize.width <= 0 || mSize.height <= 0) {
- return DrawResult::SUCCESS;
- }
-
- SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
- DrawResult result = DrawResult::SUCCESS;
- RefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
- IntRect tmpDTRect;
-
- if (ctx->CurrentOp() != CompositionOp::OP_OVER || mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
- gfxRect clipRect = ctx->GetClipExtents();
- tmpDTRect = RoundedOut(ToRect(clipRect));
- if (tmpDTRect.IsEmpty()) {
- return DrawResult::SUCCESS;
- }
- RefPtr<DrawTarget> tempDT =
- gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(ctx->GetDrawTarget(),
- tmpDTRect.Size(),
- SurfaceFormat::B8G8R8A8);
- if (!tempDT || !tempDT->IsValid()) {
- gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
- return DrawResult::TEMPORARY_ERROR;
- }
- tempDT->SetTransform(Matrix::Translation(-tmpDTRect.TopLeft()));
- ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
- if (!ctx) {
- gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
- return DrawResult::TEMPORARY_ERROR;
- }
- }
-
- switch (mType) {
- case eStyleImageType_Image:
- {
- CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
- nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
- result =
- nsLayoutUtils::DrawBackgroundImage(*ctx,
- aPresContext,
- mImageContainer, imageSize,
- samplingFilter,
- aDest, aFill, aRepeatSize,
- aAnchor, aDirtyRect,
- ConvertImageRendererToDrawFlags(mFlags),
- mExtendMode, aOpacity);
- break;
- }
- case eStyleImageType_Gradient:
- {
- nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
- mGradientData, aDirtyRect,
- aDest, aFill, aRepeatSize, aSrc, mSize,
- aOpacity);
- break;
- }
- case eStyleImageType_Element:
- {
- RefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
- aRenderingContext);
- if (!drawable) {
- NS_WARNING("Could not create drawable for element");
- return DrawResult::TEMPORARY_ERROR;
- }
-
- nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
- result =
- nsLayoutUtils::DrawImage(*ctx,
- aPresContext, image,
- samplingFilter, aDest, aFill, aAnchor, aDirtyRect,
- ConvertImageRendererToDrawFlags(mFlags),
- aOpacity);
- break;
- }
- case eStyleImageType_Null:
- default:
- break;
- }
-
- if (!tmpDTRect.IsEmpty()) {
- RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
- if (mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
- RefPtr<DataSourceSurface> maskData = surf->GetDataSurface();
- DataSourceSurface::MappedSurface map;
- if (!maskData->Map(DataSourceSurface::MapType::WRITE, &map)) {
- return result;
- }
-
- RGBALuminanceOperation(map.mData, map.mStride, maskData->GetSize());
- maskData->Unmap();
- surf = maskData;
- }
-
- DrawTarget* dt = aRenderingContext.ThebesContext()->GetDrawTarget();
- dt->DrawSurface(surf, Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
- Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
- DrawSurfaceOptions(SamplingFilter::POINT),
- DrawOptions(1.0f, aRenderingContext.ThebesContext()->CurrentOp()));
- }
-
- return result;
-}
-
-already_AddRefed<gfxDrawable>
-nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
- nsRenderingContext& aRenderingContext)
-{
- NS_ASSERTION(mType == eStyleImageType_Element,
- "DrawableForElement only makes sense if backed by an element");
- if (mPaintServerFrame) {
- // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
- // DrawableFromPaintServer would have to return a DrawResult indicating
- // whether any images could not be painted because they weren't fully
- // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
- // problems, as it won't help if there are image which haven't finished
- // loading, but it's better than nothing.
- int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
- nsRect destRect = aImageRect - aImageRect.TopLeft();
- nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
- IntSize imageSize(roundedOut.width, roundedOut.height);
- RefPtr<gfxDrawable> drawable =
- nsSVGIntegrationUtils::DrawableFromPaintServer(
- mPaintServerFrame, mForFrame, mSize, imageSize,
- aRenderingContext.GetDrawTarget(),
- aRenderingContext.ThebesContext()->CurrentMatrix(),
- nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
-
- return drawable.forget();
- }
- NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
- RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
- mImageElementSurface.GetSourceSurface().get(),
- mImageElementSurface.mSize);
- return drawable.forget();
-}
-
-DrawResult
-nsImageRenderer::DrawLayer(nsPresContext* aPresContext,
- nsRenderingContext& aRenderingContext,
- const nsRect& aDest,
- const nsRect& aFill,
- const nsPoint& aAnchor,
- const nsRect& aDirty,
- const nsSize& aRepeatSize,
- float aOpacity)
-{
- if (!IsReady()) {
- NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
- return DrawResult::TEMPORARY_ERROR;
- }
- if (aDest.IsEmpty() || aFill.IsEmpty() ||
- mSize.width <= 0 || mSize.height <= 0) {
- return DrawResult::SUCCESS;
- }
-
- return Draw(aPresContext, aRenderingContext,
- aDirty, aDest, aFill, aAnchor, aRepeatSize,
- CSSIntRect(0, 0,
- nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
- nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
- aOpacity);
-}
-
-/**
- * Compute the size and position of the master copy of the image. I.e., a single
- * tile used to fill the dest rect.
- * aFill The destination rect to be filled
- * aHFill and aVFill are the repeat patterns for the component -
- * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
- * aUnitSize The size of the source rect in dest coords.
- */
-static nsRect
-ComputeTile(nsRect& aFill,
- uint8_t aHFill,
- uint8_t aVFill,
- const nsSize& aUnitSize,
- nsSize& aRepeatSize)
-{
- nsRect tile;
- switch (aHFill) {
- case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
- tile.x = aFill.x;
- tile.width = aFill.width;
- aRepeatSize.width = tile.width;
- break;
- case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
- tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
- tile.width = aUnitSize.width;
- aRepeatSize.width = tile.width;
- break;
- case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
- tile.x = aFill.x;
- tile.width = ComputeRoundedSize(aUnitSize.width, aFill.width);
- aRepeatSize.width = tile.width;
- break;
- case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
- {
- nscoord space;
- aRepeatSize.width =
- ComputeBorderSpacedRepeatSize(aUnitSize.width, aFill.width, space);
- tile.x = aFill.x + space;
- tile.width = aUnitSize.width;
- aFill.x = tile.x;
- aFill.width = aFill.width - space * 2;
- }
- break;
- default:
- NS_NOTREACHED("unrecognized border-image fill style");
- }
-
- switch (aVFill) {
- case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
- tile.y = aFill.y;
- tile.height = aFill.height;
- aRepeatSize.height = tile.height;
- break;
- case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
- tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
- tile.height = aUnitSize.height;
- aRepeatSize.height = tile.height;
- break;
- case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
- tile.y = aFill.y;
- tile.height = ComputeRoundedSize(aUnitSize.height, aFill.height);
- aRepeatSize.height = tile.height;
- break;
- case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
- {
- nscoord space;
- aRepeatSize.height =
- ComputeBorderSpacedRepeatSize(aUnitSize.height, aFill.height, space);
- tile.y = aFill.y + space;
- tile.height = aUnitSize.height;
- aFill.y = tile.y;
- aFill.height = aFill.height - space * 2;
- }
- break;
- default:
- NS_NOTREACHED("unrecognized border-image fill style");
- }
-
- return tile;
-}
-
-/**
- * Returns true if the given set of arguments will require the tiles which fill
- * the dest rect to be scaled from the source tile. See comment on ComputeTile
- * for argument descriptions.
- */
-static bool
-RequiresScaling(const nsRect& aFill,
- uint8_t aHFill,
- uint8_t aVFill,
- const nsSize& aUnitSize)
-{
- // If we have no tiling in either direction, we can skip the intermediate
- // scaling step.
- return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
- aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
- (aUnitSize.width != aFill.width ||
- aUnitSize.height != aFill.height);
-}
-
-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,
- const Maybe<nsSize>& aSVGViewportSize,
- const bool aHasIntrinsicRatio)
-{
- 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;
-
- // To draw one portion of an image into a border component, we stretch that
- // portion to match the size of that border component and then draw onto.
- // However, preserveAspectRatio attribute of a SVG image may break this rule.
- // To get correct rendering result, we add
- // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
- // preserveAspectRatio attribute, and always do non-uniform stretch.
- uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
- imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
- // For those SVG image sources which don't have fixed aspect ratio (i.e.
- // without viewport size and viewBox), we should scale the source uniformly
- // after the viewport size is decided by "Default Sizing Algorithm".
- if (!aHasIntrinsicRatio) {
- drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
- }
- // 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, 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
- // invalidate that cache, and it's not clear that it's worth the trouble
- // since using border-image with -moz-element is rare.
-
- 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, aSVGViewportSize);
- }
-
- MOZ_ASSERT_IF(aSVGViewportSize,
- subImage->GetType() == imgIContainer::TYPE_VECTOR);
-
- SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
-
- if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
- return nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
- aPresContext,
- subImage,
- samplingFilter,
- aFill, aDirtyRect,
- /* no SVGImageContext */ Nothing(),
- drawFlags);
- }
-
- nsSize repeatSize;
- nsRect fillRect(aFill);
- nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
- CSSIntSize imageSize(srcRect.width, srcRect.height);
- return nsLayoutUtils::DrawBackgroundImage(*aRenderingContext.ThebesContext(),
- aPresContext,
- subImage, imageSize, samplingFilter,
- tile, fillRect, repeatSize,
- tile.TopLeft(), aDirtyRect,
- drawFlags,
- ExtendMode::CLAMP, 1.0);
- }
-
- nsSize repeatSize(aFill.Size());
- nsRect fillRect(aFill);
- nsRect destTile = RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
- ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
- : fillRect;
- return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
- fillRect, destTile.TopLeft(), repeatSize, aSrc);
-}
-
-bool
-nsImageRenderer::IsRasterImage()
-{
- if (mType != eStyleImageType_Image || !mImageContainer)
- return false;
- return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
-}
-
-bool
-nsImageRenderer::IsAnimatedImage()
-{
- if (mType != eStyleImageType_Image || !mImageContainer)
- return false;
- bool animated = false;
- if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
- return true;
-
- return false;
-}
-
-already_AddRefed<imgIContainer>
-nsImageRenderer::GetImage()
-{
- if (mType != eStyleImageType_Image || !mImageContainer) {
- return nullptr;
- }
-
- nsCOMPtr<imgIContainer> image = mImageContainer;
- return image.forget();
-}
-
-void
-nsImageRenderer::PurgeCacheForViewportChange(
- const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio)
-{
- // Check if we should flush the cached data - only vector images need to do
- // the check since they might not have fixed ratio.
- if (mImageContainer &&
- mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
- mImage->PurgeCacheForViewportChange(aSVGViewportSize, aHasIntrinsicRatio);
- }
-}
-
#define MAX_BLUR_RADIUS 300
#define MAX_SPREAD_RADIUS 50
static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
int32_t aAppUnitsPerDevPixel,
gfxFloat aScaleX,
gfxFloat aScaleY)
{
--- a/layout/painting/nsCSSRendering.h
+++ b/layout/painting/nsCSSRendering.h
@@ -12,314 +12,50 @@
#include "gfxContext.h"
#include "imgIContainer.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/TypedEnumBits.h"
#include "nsLayoutUtils.h"
#include "nsStyleStruct.h"
#include "nsIFrame.h"
+#include "nsImageRenderer.h"
#include "nsCSSRenderingBorders.h"
-class gfxDrawable;
class nsStyleContext;
class nsPresContext;
class nsRenderingContext;
namespace mozilla {
namespace gfx {
struct Color;
class DrawTarget;
} // namespace gfx
namespace layers {
class ImageContainer;
} // namespace layers
-// A CSSSizeOrRatio represents a (possibly partially specified) size for use
-// in computing image sizes. Either or both of the width and height might be
-// given. A ratio of width to height may also be given. If we at least two
-// of these then we can compute a concrete size, that is a width and height.
-struct CSSSizeOrRatio
-{
- CSSSizeOrRatio()
- : mRatio(0, 0)
- , mHasWidth(false)
- , mHasHeight(false) {}
-
- bool CanComputeConcreteSize() const
- {
- return mHasWidth + mHasHeight + HasRatio() >= 2;
- }
- bool IsConcrete() const { return mHasWidth && mHasHeight; }
- bool HasRatio() const { return mRatio.width > 0 && mRatio.height > 0; }
- bool IsEmpty() const
- {
- return (mHasWidth && mWidth <= 0) ||
- (mHasHeight && mHeight <= 0) ||
- mRatio.width <= 0 || mRatio.height <= 0;
- }
-
- // CanComputeConcreteSize must return true when ComputeConcreteSize is
- // called.
- nsSize ComputeConcreteSize() const;
-
- void SetWidth(nscoord aWidth)
- {
- mWidth = aWidth;
- mHasWidth = true;
- if (mHasHeight) {
- mRatio = nsSize(mWidth, mHeight);
- }
- }
- void SetHeight(nscoord aHeight)
- {
- mHeight = aHeight;
- mHasHeight = true;
- if (mHasWidth) {
- mRatio = nsSize(mWidth, mHeight);
- }
- }
- void SetSize(const nsSize& aSize)
- {
- mWidth = aSize.width;
- mHeight = aSize.height;
- mHasWidth = true;
- mHasHeight = true;
- mRatio = aSize;
- }
- void SetRatio(const nsSize& aRatio)
- {
- MOZ_ASSERT(!mHasWidth || !mHasHeight,
- "Probably shouldn't be setting a ratio if we have a concrete size");
- mRatio = aRatio;
- }
-
- nsSize mRatio;
- nscoord mWidth;
- nscoord mHeight;
- bool mHasWidth;
- bool mHasHeight;
-};
-
enum class PaintBorderFlags : uint8_t
{
SYNC_DECODE_IMAGES = 1 << 0
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PaintBorderFlags)
} // namespace mozilla
/**
- * This is a small wrapper class to encapsulate image drawing that can draw an
- * nsStyleImage image, which may internally be a real image, a sub image, or a
- * CSS gradient.
- *
- * @note Always call the member functions in the order of PrepareImage(),
- * SetSize(), and Draw*().
- */
-class nsImageRenderer {
-public:
- typedef mozilla::image::DrawResult DrawResult;
- typedef mozilla::layers::LayerManager LayerManager;
- typedef mozilla::layers::ImageContainer ImageContainer;
-
- enum {
- FLAG_SYNC_DECODE_IMAGES = 0x01,
- FLAG_PAINTING_TO_WINDOW = 0x02
- };
- enum FitType
- {
- CONTAIN,
- COVER
- };
-
- nsImageRenderer(nsIFrame* aForFrame, const nsStyleImage* aImage, uint32_t aFlags);
- ~nsImageRenderer();
- /**
- * Populates member variables to get ready for rendering.
- * @return true iff the image is ready, and there is at least a pixel to
- * draw.
- */
- bool PrepareImage();
-
- /**
- * The three Compute*Size functions correspond to the sizing algorthms and
- * definitions from the CSS Image Values and Replaced Content spec. See
- * http://dev.w3.org/csswg/css-images-3/#sizing .
- */
-
- /**
- * Compute the intrinsic size of the image as defined in the CSS Image Values
- * spec. The intrinsic size is the unscaled size which the image would ideally
- * like to be in app units.
- */
- mozilla::CSSSizeOrRatio ComputeIntrinsicSize();
-
- /**
- * Computes the placement for a background image, or for the image data
- * inside of a replaced element.
- *
- * @param aPos The CSS <position> value that specifies the image's position.
- * @param aOriginBounds The box to which the tiling position should be
- * relative. For background images, this should correspond to
- * 'background-origin' for the frame, except when painting on the
- * canvas, in which case the origin bounds should be the bounds
- * of the root element's frame. For a replaced element, this should
- * be the element's content-box.
- * @param aTopLeft [out] The top-left corner where an image tile should be
- * drawn.
- * @param aAnchorPoint [out] A point which should be pixel-aligned by
- * nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless
- * CSS specifies a percentage (including 'right' or 'bottom'), in
- * which case it's that percentage within of aOriginBounds. So
- * 'right' would set aAnchorPoint.x to aOriginBounds.XMost().
- *
- * Points are returned relative to aOriginBounds.
- */
- static void ComputeObjectAnchorPoint(const mozilla::Position& aPos,
- const nsSize& aOriginBounds,
- const nsSize& aImageSize,
- nsPoint* aTopLeft,
- nsPoint* aAnchorPoint);
-
- /**
- * Compute the size of the rendered image using either the 'cover' or
- * 'contain' constraints (aFitType).
- * aIntrinsicRatio may be an invalid ratio, that is one or both of its
- * dimensions can be less than or equal to zero.
- */
- static nsSize ComputeConstrainedSize(const nsSize& aConstrainingSize,
- const nsSize& aIntrinsicRatio,
- FitType aFitType);
- /**
- * Compute the size of the rendered image (the concrete size) where no cover/
- * contain constraints are given. The 'default algorithm' from the CSS Image
- * Values spec.
- */
- static nsSize ComputeConcreteSize(const mozilla::CSSSizeOrRatio& aSpecifiedSize,
- const mozilla::CSSSizeOrRatio& aIntrinsicSize,
- const nsSize& aDefaultSize);
-
- /**
- * Set this image's preferred size. This will be its intrinsic size where
- * specified and the default size where it is not. Used as the unscaled size
- * when rendering the image.
- */
- void SetPreferredSize(const mozilla::CSSSizeOrRatio& aIntrinsicSize,
- const nsSize& aDefaultSize);
-
- /**
- * Draws the image to the target rendering context using
- * {background|mask}-specific arguments.
- * @see nsLayoutUtils::DrawImage() for parameters.
- */
- DrawResult DrawLayer(nsPresContext* aPresContext,
- nsRenderingContext& aRenderingContext,
- const nsRect& aDest,
- const nsRect& aFill,
- const nsPoint& aAnchor,
- const nsRect& aDirty,
- const nsSize& aRepeatSize,
- float aOpacity);
-
- /**
- * Draw the image to a single component of a border-image style rendering.
- * aFill The destination rect to be drawn into
- * aSrc is the part of the image to be rendered into a tile (aUnitSize in
- * 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.
- * aHasIntrinsicRatio is used to record if the source image has fixed
- * intrinsic ratio.
- */
- 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,
- const mozilla::Maybe<nsSize>& aSVGViewportSize,
- const bool aHasIntrinsicRatio);
-
- bool IsRasterImage();
- bool IsAnimatedImage();
-
- /// Retrieves the image associated with this nsImageRenderer, if there is one.
- already_AddRefed<imgIContainer> GetImage();
-
- bool IsReady() const { return mPrepareResult == DrawResult::SUCCESS; }
- DrawResult PrepareResult() const { return mPrepareResult; }
- void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; }
- void SetMaskOp(uint8_t aMaskOp) { mMaskOp = aMaskOp; }
- void PurgeCacheForViewportChange(const mozilla::Maybe<nsSize>& aSVGViewportSize,
- const bool aHasRatio);
-
-private:
- /**
- * Draws the image to the target rendering context.
- * aSrc is a rect on the source image which will be mapped to aDest; it's
- * currently only used for gradients.
- *
- * @see nsLayoutUtils::DrawImage() for other parameters.
- */
- DrawResult Draw(nsPresContext* aPresContext,
- nsRenderingContext& aRenderingContext,
- const nsRect& aDirtyRect,
- const nsRect& aDest,
- const nsRect& aFill,
- const nsPoint& aAnchor,
- const nsSize& aRepeatSize,
- const mozilla::CSSIntRect& aSrc,
- float aOpacity = 1.0);
-
- /**
- * Helper method for creating a gfxDrawable from mPaintServerFrame or
- * mImageElementSurface.
- * Requires mType is eStyleImageType_Element.
- * Returns null if we cannot create the drawable.
- */
- already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect,
- nsRenderingContext& aRenderingContext);
-
- nsIFrame* mForFrame;
- const nsStyleImage* mImage;
- nsStyleImageType mType;
- nsCOMPtr<imgIContainer> mImageContainer;
- RefPtr<nsStyleGradient> mGradientData;
- nsIFrame* mPaintServerFrame;
- nsLayoutUtils::SurfaceFromElementResult mImageElementSurface;
- DrawResult mPrepareResult;
- nsSize mSize; // unscaled size of the image, in app units
- uint32_t mFlags;
- mozilla::gfx::ExtendMode mExtendMode;
- uint8_t mMaskOp;
-};
-
-/**
* A struct representing all the information needed to paint a background
* image to some target, taking into account all CSS background-* properties.
* See PrepareImageLayer.
*/
struct nsBackgroundLayerState {
typedef mozilla::gfx::CompositionOp CompositionOp;
+ typedef mozilla::nsImageRenderer nsImageRenderer;
/**
* @param aFlags some combination of nsCSSRendering::PAINTBG_* flags
*/
nsBackgroundLayerState(nsIFrame* aForFrame, const nsStyleImage* aImage,
uint32_t aFlags)
: mImageRenderer(aForFrame, aImage, aFlags)
{}
--- a/layout/painting/nsCSSRenderingBorders.h
+++ b/layout/painting/nsCSSRenderingBorders.h
@@ -10,16 +10,17 @@
#include "gfxRect.h"
#include "mozilla/Attributes.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/BezierUtils.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/RefPtr.h"
#include "nsColor.h"
#include "nsCOMPtr.h"
+#include "nsImageRenderer.h"
#include "nsStyleConsts.h"
#include "nsStyleStruct.h"
#include "nsPresContext.h"
struct nsBorderColors;
class nsDisplayBorder;
namespace mozilla {
new file mode 100644
--- /dev/null
+++ b/layout/painting/nsImageRenderer.cpp
@@ -0,0 +1,868 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* utility functions for drawing borders and backgrounds */
+
+#include "nsImageRenderer.h"
+
+nsSize
+CSSSizeOrRatio::ComputeConcreteSize() const
+{
+ NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
+ if (mHasWidth && mHasHeight) {
+ return nsSize(mWidth, mHeight);
+ }
+ if (mHasWidth) {
+ nscoord height = NSCoordSaturatingNonnegativeMultiply(
+ mWidth,
+ double(mRatio.height) / mRatio.width);
+ return nsSize(mWidth, height);
+ }
+
+ MOZ_ASSERT(mHasHeight);
+ nscoord width = NSCoordSaturatingNonnegativeMultiply(
+ mHeight,
+ double(mRatio.width) / mRatio.height);
+ return nsSize(width, mHeight);
+}
+
+nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
+ const nsStyleImage* aImage,
+ uint32_t aFlags)
+ : mForFrame(aForFrame)
+ , mImage(aImage)
+ , mType(aImage->GetType())
+ , mImageContainer(nullptr)
+ , mGradientData(nullptr)
+ , mPaintServerFrame(nullptr)
+ , mPrepareResult(DrawResult::NOT_READY)
+ , mSize(0, 0)
+ , mFlags(aFlags)
+ , mExtendMode(ExtendMode::CLAMP)
+ , mMaskOp(NS_STYLE_MASK_MODE_MATCH_SOURCE)
+{
+}
+
+nsImageRenderer::~nsImageRenderer()
+{
+}
+
+static bool
+ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage* aImage,
+ uint32_t aFlags)
+{
+ if (!(aFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES)) {
+ return false;
+ }
+
+ if (aImage->GetType() != eStyleImageType_Image) {
+ return false;
+ }
+
+ imgRequestProxy* req = aImage->GetImageData();
+ if (!req) {
+ return false;
+ }
+
+ uint32_t status = 0;
+ if (NS_FAILED(req->GetImageStatus(&status))) {
+ return false;
+ }
+
+ if (status & imgIRequest::STATUS_ERROR) {
+ // The image is "complete" since it's a corrupt image. If we created an
+ // imgIContainer at all, return true.
+ nsCOMPtr<imgIContainer> image;
+ req->GetImage(getter_AddRefs(image));
+ return bool(image);
+ }
+
+ if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+ // We must have loaded all of the image's data and the size must be
+ // available, or else sync decoding won't be able to decode the image.
+ return false;
+ }
+
+ return true;
+}
+
+bool
+nsImageRenderer::PrepareImage()
+{
+ if (mImage->IsEmpty()) {
+ mPrepareResult = DrawResult::BAD_IMAGE;
+ return false;
+ }
+
+ if (!mImage->IsComplete()) {
+ // Make sure the image is actually decoding.
+ bool frameComplete = mImage->StartDecoding();
+
+ // Check again to see if we finished.
+ // We cannot prepare the image for rendering if it is not fully loaded.
+ // Special case: If we requested a sync decode and the image has loaded, push
+ // on through because the Draw() will do a sync decode then.
+ if (!(frameComplete || mImage->IsComplete()) &&
+ !ShouldTreatAsCompleteDueToSyncDecode(mImage, mFlags)) {
+ mPrepareResult = DrawResult::NOT_READY;
+ return false;
+ }
+ }
+
+ switch (mType) {
+ case eStyleImageType_Image: {
+ MOZ_ASSERT(mImage->GetImageData(),
+ "must have image data, since we checked IsEmpty above");
+ nsCOMPtr<imgIContainer> srcImage;
+ DebugOnly<nsresult> rv =
+ mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
+ "If GetImage() is failing, mImage->IsComplete() "
+ "should have returned false");
+
+ if (!mImage->GetCropRect()) {
+ mImageContainer.swap(srcImage);
+ } else {
+ nsIntRect actualCropRect;
+ bool isEntireImage;
+ bool success =
+ mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
+ NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
+ if (!success || actualCropRect.IsEmpty()) {
+ // 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,
+ Nothing());
+ mImageContainer.swap(subImage);
+ }
+ }
+ mPrepareResult = DrawResult::SUCCESS;
+ break;
+ }
+ case eStyleImageType_Gradient:
+ mGradientData = mImage->GetGradientData();
+ mPrepareResult = DrawResult::SUCCESS;
+ break;
+ case eStyleImageType_Element:
+ {
+ nsAutoString elementId =
+ NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId());
+ nsCOMPtr<nsIURI> targetURI;
+ nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
+ mForFrame->GetContent()->GetUncomposedDoc(), base);
+ nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
+ targetURI, mForFrame->FirstContinuation(),
+ nsSVGEffects::BackgroundImageProperty());
+ if (!property) {
+ mPrepareResult = DrawResult::BAD_IMAGE;
+ return false;
+ }
+
+ // If the referenced element is an <img>, <canvas>, or <video> element,
+ // prefer SurfaceFromElement as it's more reliable.
+ mImageElementSurface =
+ nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
+ if (!mImageElementSurface.GetSourceSurface()) {
+ mPaintServerFrame = property->GetReferencedFrame();
+ if (!mPaintServerFrame) {
+ mPrepareResult = DrawResult::BAD_IMAGE;
+ return false;
+ }
+ }
+
+ mPrepareResult = DrawResult::SUCCESS;
+ break;
+ }
+ case eStyleImageType_Null:
+ default:
+ break;
+ }
+
+ return IsReady();
+}
+
+CSSSizeOrRatio
+nsImageRenderer::ComputeIntrinsicSize()
+{
+ NS_ASSERTION(IsReady(), "Ensure PrepareImage() has returned true "
+ "before calling me");
+
+ CSSSizeOrRatio result;
+ switch (mType) {
+ case eStyleImageType_Image:
+ {
+ bool haveWidth, haveHeight;
+ CSSIntSize imageIntSize;
+ nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
+ result.mRatio, haveWidth, haveHeight);
+ if (haveWidth) {
+ result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
+ }
+ if (haveHeight) {
+ result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
+ }
+
+ // If we know the aspect ratio and one of the dimensions,
+ // we can compute the other missing width or height.
+ if (!haveHeight && haveWidth && result.mRatio.width != 0) {
+ nscoord intrinsicHeight =
+ NSCoordSaturatingNonnegativeMultiply(imageIntSize.width,
+ float(result.mRatio.height) /
+ float(result.mRatio.width));
+ result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight));
+ } else if (haveHeight && !haveWidth && result.mRatio.height != 0) {
+ nscoord intrinsicWidth =
+ NSCoordSaturatingNonnegativeMultiply(imageIntSize.height,
+ float(result.mRatio.width) /
+ float(result.mRatio.height));
+ result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth));
+ }
+
+ break;
+ }
+ case eStyleImageType_Element:
+ {
+ // XXX element() should have the width/height of the referenced element,
+ // and that element's ratio, if it matches. If it doesn't match, it
+ // should have no width/height or ratio. See element() in CSS images:
+ // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
+ // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
+ // when fixing this!
+ if (mPaintServerFrame) {
+ // SVG images have no intrinsic size
+ if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
+ // The intrinsic image size for a generic nsIFrame paint server is
+ // the union of the border-box rects of all of its continuations,
+ // rounded to device pixels.
+ int32_t appUnitsPerDevPixel =
+ mForFrame->PresContext()->AppUnitsPerDevPixel();
+ result.SetSize(
+ IntSizeToAppUnits(
+ nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
+ ToNearestPixels(appUnitsPerDevPixel),
+ appUnitsPerDevPixel));
+ }
+ } else {
+ NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
+ "Surface should be ready.");
+ IntSize surfaceSize = mImageElementSurface.mSize;
+ result.SetSize(
+ nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
+ nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
+ }
+ break;
+ }
+ case eStyleImageType_Gradient:
+ // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
+ // intrinsic dimensions.
+ case eStyleImageType_Null:
+ default:
+ break;
+ }
+
+ return result;
+}
+
+/* static */ nsSize
+nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
+ const CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize)
+{
+ // The specified size is fully specified, just use that
+ if (aSpecifiedSize.IsConcrete()) {
+ return aSpecifiedSize.ComputeConcreteSize();
+ }
+
+ MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
+
+ if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
+ // no specified size, try using the intrinsic size
+ if (aIntrinsicSize.CanComputeConcreteSize()) {
+ return aIntrinsicSize.ComputeConcreteSize();
+ }
+
+ if (aIntrinsicSize.mHasWidth) {
+ return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
+ }
+ if (aIntrinsicSize.mHasHeight) {
+ return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
+ }
+
+ // couldn't use the intrinsic size either, revert to using the default size
+ return ComputeConstrainedSize(aDefaultSize,
+ aIntrinsicSize.mRatio,
+ CONTAIN);
+ }
+
+ MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
+
+ // The specified height is partial, try to compute the missing part.
+ if (aSpecifiedSize.mHasWidth) {
+ nscoord height;
+ if (aIntrinsicSize.HasRatio()) {
+ height = NSCoordSaturatingNonnegativeMultiply(
+ aSpecifiedSize.mWidth,
+ double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
+ } else if (aIntrinsicSize.mHasHeight) {
+ height = aIntrinsicSize.mHeight;
+ } else {
+ height = aDefaultSize.height;
+ }
+ return nsSize(aSpecifiedSize.mWidth, height);
+ }
+
+ MOZ_ASSERT(aSpecifiedSize.mHasHeight);
+ nscoord width;
+ if (aIntrinsicSize.HasRatio()) {
+ width = NSCoordSaturatingNonnegativeMultiply(
+ aSpecifiedSize.mHeight,
+ double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
+ } else if (aIntrinsicSize.mHasWidth) {
+ width = aIntrinsicSize.mWidth;
+ } else {
+ width = aDefaultSize.width;
+ }
+ return nsSize(width, aSpecifiedSize.mHeight);
+}
+
+/* static */ nsSize
+nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
+ const nsSize& aIntrinsicRatio,
+ FitType aFitType)
+{
+ if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
+ return aConstrainingSize;
+ }
+
+ float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
+ float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
+ nsSize size;
+ if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
+ size.width = aConstrainingSize.width;
+ size.height = NSCoordSaturatingNonnegativeMultiply(
+ aIntrinsicRatio.height, scaleX);
+ // If we're reducing the size by less than one css pixel, then just use the
+ // constraining size.
+ if (aFitType == CONTAIN && aConstrainingSize.height - size.height < nsPresContext::AppUnitsPerCSSPixel()) {
+ size.height = aConstrainingSize.height;
+ }
+ } else {
+ size.width = NSCoordSaturatingNonnegativeMultiply(
+ aIntrinsicRatio.width, scaleY);
+ if (aFitType == CONTAIN && aConstrainingSize.width - size.width < nsPresContext::AppUnitsPerCSSPixel()) {
+ size.width = aConstrainingSize.width;
+ }
+ size.height = aConstrainingSize.height;
+ }
+ return size;
+}
+
+/**
+ * mSize is the image's "preferred" size for this particular rendering, while
+ * the drawn (aka concrete) size is the actual rendered size after accounting
+ * for background-size etc.. The preferred size is most often the image's
+ * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
+ * the preferred size varies, depending on the specified and default sizes, see
+ * nsImageRenderer::Compute*Size.
+ *
+ * This distinction is necessary because the components of a vector image are
+ * specified with respect to its preferred size for a rendering situation, not
+ * to its actual rendered size. For example, consider a 4px wide background
+ * vector image with no height which contains a left-aligned
+ * 2px wide black rectangle with height 100%. If the background-size width is
+ * auto (or 4px), the vector image will render 4px wide, and the black rectangle
+ * will be 2px wide. If the background-size width is 8px, the vector image will
+ * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
+ * In both cases mSize.width will be 4px; but in the first case the returned
+ * width will be 4px, while in the second case the returned width will be 8px.
+ */
+void
+nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize)
+{
+ mSize.width = aIntrinsicSize.mHasWidth
+ ? aIntrinsicSize.mWidth
+ : aDefaultSize.width;
+ mSize.height = aIntrinsicSize.mHasHeight
+ ? aIntrinsicSize.mHeight
+ : aDefaultSize.height;
+}
+
+// Convert from nsImageRenderer flags to the flags we want to use for drawing in
+// the imgIContainer namespace.
+static uint32_t
+ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
+{
+ uint32_t drawFlags = imgIContainer::FLAG_NONE;
+ if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
+ drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+ if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
+ drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
+ }
+ return drawFlags;
+}
+
+/*
+ * SVG11: A luminanceToAlpha operation is equivalent to the following matrix operation: |
+ * | R' | | 0 0 0 0 0 | | R |
+ * | G' | | 0 0 0 0 0 | | G |
+ * | B' | = | 0 0 0 0 0 | * | B |
+ * | A' | | 0.2125 0.7154 0.0721 0 0 | | A |
+ * | 1 | | 0 0 0 0 1 | | 1 |
+ */
+static void
+RGBALuminanceOperation(uint8_t *aData,
+ int32_t aStride,
+ const IntSize &aSize)
+{
+ int32_t redFactor = 55; // 256 * 0.2125
+ int32_t greenFactor = 183; // 256 * 0.7154
+ int32_t blueFactor = 18; // 256 * 0.0721
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ uint32_t *pixel = (uint32_t*)(aData + aStride * y);
+ for (int32_t x = 0; x < aSize.width; x++) {
+ *pixel = (((((*pixel & 0x00FF0000) >> 16) * redFactor) +
+ (((*pixel & 0x0000FF00) >> 8) * greenFactor) +
+ ((*pixel & 0x000000FF) * blueFactor)) >> 8) << 24;
+ pixel++;
+ }
+ }
+}
+
+
+DrawResult
+nsImageRenderer::Draw(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsSize& aRepeatSize,
+ const CSSIntRect& aSrc,
+ float aOpacity)
+{
+ if (!IsReady()) {
+ NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+ if (aDest.IsEmpty() || aFill.IsEmpty() ||
+ mSize.width <= 0 || mSize.height <= 0) {
+ return DrawResult::SUCCESS;
+ }
+
+ SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
+ DrawResult result = DrawResult::SUCCESS;
+ RefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
+ IntRect tmpDTRect;
+
+ if (ctx->CurrentOp() != CompositionOp::OP_OVER || mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
+ gfxRect clipRect = ctx->GetClipExtents();
+ tmpDTRect = RoundedOut(ToRect(clipRect));
+ if (tmpDTRect.IsEmpty()) {
+ return DrawResult::SUCCESS;
+ }
+ RefPtr<DrawTarget> tempDT =
+ gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(ctx->GetDrawTarget(),
+ tmpDTRect.Size(),
+ SurfaceFormat::B8G8R8A8);
+ if (!tempDT || !tempDT->IsValid()) {
+ gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
+ return DrawResult::TEMPORARY_ERROR;
+ }
+ tempDT->SetTransform(Matrix::Translation(-tmpDTRect.TopLeft()));
+ ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
+ if (!ctx) {
+ gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
+ return DrawResult::TEMPORARY_ERROR;
+ }
+ }
+
+ switch (mType) {
+ case eStyleImageType_Image:
+ {
+ CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
+ nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
+ result =
+ nsLayoutUtils::DrawBackgroundImage(*ctx,
+ aPresContext,
+ mImageContainer, imageSize,
+ samplingFilter,
+ aDest, aFill, aRepeatSize,
+ aAnchor, aDirtyRect,
+ ConvertImageRendererToDrawFlags(mFlags),
+ mExtendMode, aOpacity);
+ break;
+ }
+ case eStyleImageType_Gradient:
+ {
+ nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
+ mGradientData, aDirtyRect,
+ aDest, aFill, aRepeatSize, aSrc, mSize,
+ aOpacity);
+ break;
+ }
+ case eStyleImageType_Element:
+ {
+ RefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
+ aRenderingContext);
+ if (!drawable) {
+ NS_WARNING("Could not create drawable for element");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+
+ nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
+ result =
+ nsLayoutUtils::DrawImage(*ctx,
+ aPresContext, image,
+ samplingFilter, aDest, aFill, aAnchor, aDirtyRect,
+ ConvertImageRendererToDrawFlags(mFlags),
+ aOpacity);
+ break;
+ }
+ case eStyleImageType_Null:
+ default:
+ break;
+ }
+
+ if (!tmpDTRect.IsEmpty()) {
+ RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
+ if (mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
+ RefPtr<DataSourceSurface> maskData = surf->GetDataSurface();
+ DataSourceSurface::MappedSurface map;
+ if (!maskData->Map(DataSourceSurface::MapType::WRITE, &map)) {
+ return result;
+ }
+
+ RGBALuminanceOperation(map.mData, map.mStride, maskData->GetSize());
+ maskData->Unmap();
+ surf = maskData;
+ }
+
+ DrawTarget* dt = aRenderingContext.ThebesContext()->GetDrawTarget();
+ dt->DrawSurface(surf, Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
+ Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, aRenderingContext.ThebesContext()->CurrentOp()));
+ }
+
+ return result;
+}
+
+already_AddRefed<gfxDrawable>
+nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
+ nsRenderingContext& aRenderingContext)
+{
+ NS_ASSERTION(mType == eStyleImageType_Element,
+ "DrawableForElement only makes sense if backed by an element");
+ if (mPaintServerFrame) {
+ // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
+ // DrawableFromPaintServer would have to return a DrawResult indicating
+ // whether any images could not be painted because they weren't fully
+ // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
+ // problems, as it won't help if there are image which haven't finished
+ // loading, but it's better than nothing.
+ int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
+ nsRect destRect = aImageRect - aImageRect.TopLeft();
+ nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
+ IntSize imageSize(roundedOut.width, roundedOut.height);
+ RefPtr<gfxDrawable> drawable =
+ nsSVGIntegrationUtils::DrawableFromPaintServer(
+ mPaintServerFrame, mForFrame, mSize, imageSize,
+ aRenderingContext.GetDrawTarget(),
+ aRenderingContext.ThebesContext()->CurrentMatrix(),
+ nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
+
+ return drawable.forget();
+ }
+ NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
+ RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
+ mImageElementSurface.GetSourceSurface().get(),
+ mImageElementSurface.mSize);
+ return drawable.forget();
+}
+
+DrawResult
+nsImageRenderer::DrawLayer(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsRect& aDirty,
+ const nsSize& aRepeatSize,
+ float aOpacity)
+{
+ if (!IsReady()) {
+ NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+ if (aDest.IsEmpty() || aFill.IsEmpty() ||
+ mSize.width <= 0 || mSize.height <= 0) {
+ return DrawResult::SUCCESS;
+ }
+
+ return Draw(aPresContext, aRenderingContext,
+ aDirty, aDest, aFill, aAnchor, aRepeatSize,
+ CSSIntRect(0, 0,
+ nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
+ nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
+ aOpacity);
+}
+
+/**
+ * Compute the size and position of the master copy of the image. I.e., a single
+ * tile used to fill the dest rect.
+ * aFill The destination rect to be filled
+ * aHFill and aVFill are the repeat patterns for the component -
+ * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
+ * aUnitSize The size of the source rect in dest coords.
+ */
+static nsRect
+ComputeTile(nsRect& aFill,
+ uint8_t aHFill,
+ uint8_t aVFill,
+ const nsSize& aUnitSize,
+ nsSize& aRepeatSize)
+{
+ nsRect tile;
+ switch (aHFill) {
+ case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
+ tile.x = aFill.x;
+ tile.width = aFill.width;
+ aRepeatSize.width = tile.width;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
+ tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
+ tile.width = aUnitSize.width;
+ aRepeatSize.width = tile.width;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
+ tile.x = aFill.x;
+ tile.width = ComputeRoundedSize(aUnitSize.width, aFill.width);
+ aRepeatSize.width = tile.width;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
+ {
+ nscoord space;
+ aRepeatSize.width =
+ ComputeBorderSpacedRepeatSize(aUnitSize.width, aFill.width, space);
+ tile.x = aFill.x + space;
+ tile.width = aUnitSize.width;
+ aFill.x = tile.x;
+ aFill.width = aFill.width - space * 2;
+ }
+ break;
+ default:
+ NS_NOTREACHED("unrecognized border-image fill style");
+ }
+
+ switch (aVFill) {
+ case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
+ tile.y = aFill.y;
+ tile.height = aFill.height;
+ aRepeatSize.height = tile.height;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
+ tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
+ tile.height = aUnitSize.height;
+ aRepeatSize.height = tile.height;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
+ tile.y = aFill.y;
+ tile.height = ComputeRoundedSize(aUnitSize.height, aFill.height);
+ aRepeatSize.height = tile.height;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
+ {
+ nscoord space;
+ aRepeatSize.height =
+ ComputeBorderSpacedRepeatSize(aUnitSize.height, aFill.height, space);
+ tile.y = aFill.y + space;
+ tile.height = aUnitSize.height;
+ aFill.y = tile.y;
+ aFill.height = aFill.height - space * 2;
+ }
+ break;
+ default:
+ NS_NOTREACHED("unrecognized border-image fill style");
+ }
+
+ return tile;
+}
+
+/**
+ * Returns true if the given set of arguments will require the tiles which fill
+ * the dest rect to be scaled from the source tile. See comment on ComputeTile
+ * for argument descriptions.
+ */
+static bool
+RequiresScaling(const nsRect& aFill,
+ uint8_t aHFill,
+ uint8_t aVFill,
+ const nsSize& aUnitSize)
+{
+ // If we have no tiling in either direction, we can skip the intermediate
+ // scaling step.
+ return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
+ aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
+ (aUnitSize.width != aFill.width ||
+ aUnitSize.height != aFill.height);
+}
+
+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,
+ const Maybe<nsSize>& aSVGViewportSize,
+ const bool aHasIntrinsicRatio)
+{
+ 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;
+
+ // To draw one portion of an image into a border component, we stretch that
+ // portion to match the size of that border component and then draw onto.
+ // However, preserveAspectRatio attribute of a SVG image may break this rule.
+ // To get correct rendering result, we add
+ // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
+ // preserveAspectRatio attribute, and always do non-uniform stretch.
+ uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
+ imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
+ // For those SVG image sources which don't have fixed aspect ratio (i.e.
+ // without viewport size and viewBox), we should scale the source uniformly
+ // after the viewport size is decided by "Default Sizing Algorithm".
+ if (!aHasIntrinsicRatio) {
+ drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
+ }
+ // 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, 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
+ // invalidate that cache, and it's not clear that it's worth the trouble
+ // since using border-image with -moz-element is rare.
+
+ 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, aSVGViewportSize);
+ }
+
+ MOZ_ASSERT_IF(aSVGViewportSize,
+ subImage->GetType() == imgIContainer::TYPE_VECTOR);
+
+ SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
+
+ if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
+ return nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
+ aPresContext,
+ subImage,
+ samplingFilter,
+ aFill, aDirtyRect,
+ /* no SVGImageContext */ Nothing(),
+ drawFlags);
+ }
+
+ nsSize repeatSize;
+ nsRect fillRect(aFill);
+ nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
+ CSSIntSize imageSize(srcRect.width, srcRect.height);
+ return nsLayoutUtils::DrawBackgroundImage(*aRenderingContext.ThebesContext(),
+ aPresContext,
+ subImage, imageSize, samplingFilter,
+ tile, fillRect, repeatSize,
+ tile.TopLeft(), aDirtyRect,
+ drawFlags,
+ ExtendMode::CLAMP, 1.0);
+ }
+
+ nsSize repeatSize(aFill.Size());
+ nsRect fillRect(aFill);
+ nsRect destTile = RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
+ ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
+ : fillRect;
+ return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
+ fillRect, destTile.TopLeft(), repeatSize, aSrc);
+}
+
+bool
+nsImageRenderer::IsRasterImage()
+{
+ if (mType != eStyleImageType_Image || !mImageContainer)
+ return false;
+ return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
+}
+
+bool
+nsImageRenderer::IsAnimatedImage()
+{
+ if (mType != eStyleImageType_Image || !mImageContainer)
+ return false;
+ bool animated = false;
+ if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
+ return true;
+
+ return false;
+}
+
+already_AddRefed<imgIContainer>
+nsImageRenderer::GetImage()
+{
+ if (mType != eStyleImageType_Image || !mImageContainer) {
+ return nullptr;
+ }
+
+ nsCOMPtr<imgIContainer> image = mImageContainer;
+ return image.forget();
+}
+
+void
+nsImageRenderer::PurgeCacheForViewportChange(
+ const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio)
+{
+ // Check if we should flush the cached data - only vector images need to do
+ // the check since they might not have fixed ratio.
+ if (mImageContainer &&
+ mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ mImage->PurgeCacheForViewportChange(aSVGViewportSize, aHasIntrinsicRatio);
+ }
+}
+
new file mode 100644
--- /dev/null
+++ b/layout/painting/nsImageRenderer.h
@@ -0,0 +1,280 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImageRenderer_h__
+#define nsImageRenderer_h__
+
+class gfxDrawable;
+
+namespace mozilla {
+
+// A CSSSizeOrRatio represents a (possibly partially specified) size for use
+// in computing image sizes. Either or both of the width and height might be
+// given. A ratio of width to height may also be given. If we at least two
+// of these then we can compute a concrete size, that is a width and height.
+struct CSSSizeOrRatio
+{
+ CSSSizeOrRatio()
+ : mRatio(0, 0)
+ , mHasWidth(false)
+ , mHasHeight(false) {}
+
+ bool CanComputeConcreteSize() const
+ {
+ return mHasWidth + mHasHeight + HasRatio() >= 2;
+ }
+ bool IsConcrete() const { return mHasWidth && mHasHeight; }
+ bool HasRatio() const { return mRatio.width > 0 && mRatio.height > 0; }
+ bool IsEmpty() const
+ {
+ return (mHasWidth && mWidth <= 0) ||
+ (mHasHeight && mHeight <= 0) ||
+ mRatio.width <= 0 || mRatio.height <= 0;
+ }
+
+ // CanComputeConcreteSize must return true when ComputeConcreteSize is
+ // called.
+ nsSize ComputeConcreteSize() const;
+
+ void SetWidth(nscoord aWidth)
+ {
+ mWidth = aWidth;
+ mHasWidth = true;
+ if (mHasHeight) {
+ mRatio = nsSize(mWidth, mHeight);
+ }
+ }
+ void SetHeight(nscoord aHeight)
+ {
+ mHeight = aHeight;
+ mHasHeight = true;
+ if (mHasWidth) {
+ mRatio = nsSize(mWidth, mHeight);
+ }
+ }
+ void SetSize(const nsSize& aSize)
+ {
+ mWidth = aSize.width;
+ mHeight = aSize.height;
+ mHasWidth = true;
+ mHasHeight = true;
+ mRatio = aSize;
+ }
+ void SetRatio(const nsSize& aRatio)
+ {
+ MOZ_ASSERT(!mHasWidth || !mHasHeight,
+ "Probably shouldn't be setting a ratio if we have a concrete size");
+ mRatio = aRatio;
+ }
+
+ nsSize mRatio;
+ nscoord mWidth;
+ nscoord mHeight;
+ bool mHasWidth;
+ bool mHasHeight;
+};
+
+/**
+ * This is a small wrapper class to encapsulate image drawing that can draw an
+ * nsStyleImage image, which may internally be a real image, a sub image, or a
+ * CSS gradient.
+ *
+ * @note Always call the member functions in the order of PrepareImage(),
+ * SetSize(), and Draw*().
+ */
+class nsImageRenderer {
+public:
+ typedef mozilla::image::DrawResult DrawResult;
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+
+ enum {
+ FLAG_SYNC_DECODE_IMAGES = 0x01,
+ FLAG_PAINTING_TO_WINDOW = 0x02
+ };
+ enum FitType
+ {
+ CONTAIN,
+ COVER
+ };
+
+ nsImageRenderer(nsIFrame* aForFrame, const nsStyleImage* aImage, uint32_t aFlags);
+ ~nsImageRenderer();
+ /**
+ * Populates member variables to get ready for rendering.
+ * @return true iff the image is ready, and there is at least a pixel to
+ * draw.
+ */
+ bool PrepareImage();
+
+ /**
+ * The three Compute*Size functions correspond to the sizing algorthms and
+ * definitions from the CSS Image Values and Replaced Content spec. See
+ * http://dev.w3.org/csswg/css-images-3/#sizing .
+ */
+
+ /**
+ * Compute the intrinsic size of the image as defined in the CSS Image Values
+ * spec. The intrinsic size is the unscaled size which the image would ideally
+ * like to be in app units.
+ */
+ mozilla::CSSSizeOrRatio ComputeIntrinsicSize();
+
+ /**
+ * Computes the placement for a background image, or for the image data
+ * inside of a replaced element.
+ *
+ * @param aPos The CSS <position> value that specifies the image's position.
+ * @param aOriginBounds The box to which the tiling position should be
+ * relative. For background images, this should correspond to
+ * 'background-origin' for the frame, except when painting on the
+ * canvas, in which case the origin bounds should be the bounds
+ * of the root element's frame. For a replaced element, this should
+ * be the element's content-box.
+ * @param aTopLeft [out] The top-left corner where an image tile should be
+ * drawn.
+ * @param aAnchorPoint [out] A point which should be pixel-aligned by
+ * nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless
+ * CSS specifies a percentage (including 'right' or 'bottom'), in
+ * which case it's that percentage within of aOriginBounds. So
+ * 'right' would set aAnchorPoint.x to aOriginBounds.XMost().
+ *
+ * Points are returned relative to aOriginBounds.
+ */
+ static void ComputeObjectAnchorPoint(const mozilla::Position& aPos,
+ const nsSize& aOriginBounds,
+ const nsSize& aImageSize,
+ nsPoint* aTopLeft,
+ nsPoint* aAnchorPoint);
+
+ /**
+ * Compute the size of the rendered image using either the 'cover' or
+ * 'contain' constraints (aFitType).
+ * aIntrinsicRatio may be an invalid ratio, that is one or both of its
+ * dimensions can be less than or equal to zero.
+ */
+ static nsSize ComputeConstrainedSize(const nsSize& aConstrainingSize,
+ const nsSize& aIntrinsicRatio,
+ FitType aFitType);
+ /**
+ * Compute the size of the rendered image (the concrete size) where no cover/
+ * contain constraints are given. The 'default algorithm' from the CSS Image
+ * Values spec.
+ */
+ static nsSize ComputeConcreteSize(const mozilla::CSSSizeOrRatio& aSpecifiedSize,
+ const mozilla::CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize);
+
+ /**
+ * Set this image's preferred size. This will be its intrinsic size where
+ * specified and the default size where it is not. Used as the unscaled size
+ * when rendering the image.
+ */
+ void SetPreferredSize(const mozilla::CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize);
+
+ /**
+ * Draws the image to the target rendering context using
+ * {background|mask}-specific arguments.
+ * @see nsLayoutUtils::DrawImage() for parameters.
+ */
+ DrawResult DrawLayer(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsRect& aDirty,
+ const nsSize& aRepeatSize,
+ float aOpacity);
+
+ /**
+ * Draw the image to a single component of a border-image style rendering.
+ * aFill The destination rect to be drawn into
+ * aSrc is the part of the image to be rendered into a tile (aUnitSize in
+ * 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.
+ * aHasIntrinsicRatio is used to record if the source image has fixed
+ * intrinsic ratio.
+ */
+ 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,
+ const mozilla::Maybe<nsSize>& aSVGViewportSize,
+ const bool aHasIntrinsicRatio);
+
+ bool IsRasterImage();
+ bool IsAnimatedImage();
+
+ /// Retrieves the image associated with this nsImageRenderer, if there is one.
+ already_AddRefed<imgIContainer> GetImage();
+
+ bool IsReady() const { return mPrepareResult == DrawResult::SUCCESS; }
+ DrawResult PrepareResult() const { return mPrepareResult; }
+ void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; }
+ void SetMaskOp(uint8_t aMaskOp) { mMaskOp = aMaskOp; }
+ void PurgeCacheForViewportChange(const mozilla::Maybe<nsSize>& aSVGViewportSize,
+ const bool aHasRatio);
+
+private:
+ /**
+ * Draws the image to the target rendering context.
+ * aSrc is a rect on the source image which will be mapped to aDest; it's
+ * currently only used for gradients.
+ *
+ * @see nsLayoutUtils::DrawImage() for other parameters.
+ */
+ DrawResult Draw(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsSize& aRepeatSize,
+ const mozilla::CSSIntRect& aSrc,
+ float aOpacity = 1.0);
+
+ /**
+ * Helper method for creating a gfxDrawable from mPaintServerFrame or
+ * mImageElementSurface.
+ * Requires mType is eStyleImageType_Element.
+ * Returns null if we cannot create the drawable.
+ */
+ already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect,
+ nsRenderingContext& aRenderingContext);
+
+ nsIFrame* mForFrame;
+ const nsStyleImage* mImage;
+ nsStyleImageType mType;
+ nsCOMPtr<imgIContainer> mImageContainer;
+ RefPtr<nsStyleGradient> mGradientData;
+ nsIFrame* mPaintServerFrame;
+ nsLayoutUtils::SurfaceFromElementResult mImageElementSurface;
+ DrawResult mPrepareResult;
+ nsSize mSize; // unscaled size of the image, in app units
+ uint32_t mFlags;
+ mozilla::gfx::ExtendMode mExtendMode;
+ uint8_t mMaskOp;
+};
+
+} // namespace mozilla
+
+#endif /* nsImageRenderer_h__ */