mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1341156 - Move nsImageRenderer to a separate file. r=mattwoodrow
MozReview-Commit-ID: EIEl3Jbtcsi
This commit is contained in:
parent
11f13fedeb
commit
3756e950d3
@ -20,6 +20,7 @@ EXPORTS += [
|
||||
'nsDisplayItemTypesList.h',
|
||||
'nsDisplayList.h',
|
||||
'nsDisplayListInvalidation.h',
|
||||
'nsImageRenderer.h',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
@ -40,6 +41,7 @@ UNIFIED_SOURCES += [
|
||||
'nsCSSRenderingBorders.cpp',
|
||||
'nsDisplayList.cpp',
|
||||
'nsDisplayListInvalidation.cpp',
|
||||
'nsImageRenderer.cpp',
|
||||
'PaintTracker.cpp',
|
||||
]
|
||||
|
||||
|
@ -5345,867 +5345,6 @@ nsCSSRendering::GetTextDecorationRectInternal(const Point& aPt,
|
||||
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
|
||||
|
||||
|
@ -17,9 +17,9 @@
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsStyleStruct.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsImageRenderer.h"
|
||||
#include "nsCSSRenderingBorders.h"
|
||||
|
||||
class gfxDrawable;
|
||||
class nsStyleContext;
|
||||
class nsPresContext;
|
||||
class nsRenderingContext;
|
||||
@ -35,72 +35,6 @@ 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
|
||||
@ -109,205 +43,6 @@ 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.
|
||||
@ -315,6 +50,7 @@ private:
|
||||
*/
|
||||
struct nsBackgroundLayerState {
|
||||
typedef mozilla::gfx::CompositionOp CompositionOp;
|
||||
typedef mozilla::nsImageRenderer nsImageRenderer;
|
||||
|
||||
/**
|
||||
* @param aFlags some combination of nsCSSRendering::PAINTBG_* flags
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsColor.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsImageRenderer.h"
|
||||
#include "nsStyleConsts.h"
|
||||
#include "nsStyleStruct.h"
|
||||
#include "nsPresContext.h"
|
||||
|
868
layout/painting/nsImageRenderer.cpp
Normal file
868
layout/painting/nsImageRenderer.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
283
layout/painting/nsImageRenderer.h
Normal file
283
layout/painting/nsImageRenderer.h
Normal file
@ -0,0 +1,283 @@
|
||||
/* -*- 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__
|
||||
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsStyleStruct.h"
|
||||
#include "Units.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__ */
|
Loading…
Reference in New Issue
Block a user