Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : // vim:cindent:ts=2:et:sw=2:
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : /* utility functions for drawing borders and backgrounds */
8 :
9 : #include "nsImageRenderer.h"
10 :
11 : #include "mozilla/webrender/WebRenderAPI.h"
12 :
13 : #include "gfxContext.h"
14 : #include "gfxDrawable.h"
15 : #include "ImageOps.h"
16 : #include "mozilla/layers/StackingContextHelper.h"
17 : #include "nsContentUtils.h"
18 : #include "nsCSSRendering.h"
19 : #include "nsCSSRenderingGradients.h"
20 : #include "nsIFrame.h"
21 : #include "nsStyleStructInlines.h"
22 : #include "nsSVGDisplayableFrame.h"
23 : #include "nsSVGEffects.h"
24 : #include "nsSVGIntegrationUtils.h"
25 :
26 : using namespace mozilla;
27 : using namespace mozilla::gfx;
28 : using namespace mozilla::image;
29 : using namespace mozilla::layers;
30 :
31 : nsSize
32 268 : CSSSizeOrRatio::ComputeConcreteSize() const
33 : {
34 268 : NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
35 268 : if (mHasWidth && mHasHeight) {
36 268 : return nsSize(mWidth, mHeight);
37 : }
38 0 : if (mHasWidth) {
39 0 : nscoord height = NSCoordSaturatingNonnegativeMultiply(
40 0 : mWidth,
41 0 : double(mRatio.height) / mRatio.width);
42 0 : return nsSize(mWidth, height);
43 : }
44 :
45 0 : MOZ_ASSERT(mHasHeight);
46 0 : nscoord width = NSCoordSaturatingNonnegativeMultiply(
47 0 : mHeight,
48 0 : double(mRatio.width) / mRatio.height);
49 0 : return nsSize(width, mHeight);
50 : }
51 :
52 457 : nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
53 : const nsStyleImage* aImage,
54 457 : uint32_t aFlags)
55 : : mForFrame(aForFrame)
56 : , mImage(aImage)
57 457 : , mType(aImage->GetType())
58 : , mImageContainer(nullptr)
59 : , mGradientData(nullptr)
60 : , mPaintServerFrame(nullptr)
61 : , mPrepareResult(DrawResult::NOT_READY)
62 : , mSize(0, 0)
63 : , mFlags(aFlags)
64 : , mExtendMode(ExtendMode::CLAMP)
65 914 : , mMaskOp(NS_STYLE_MASK_MODE_MATCH_SOURCE)
66 : {
67 457 : }
68 :
69 463 : nsImageRenderer::~nsImageRenderer()
70 : {
71 463 : }
72 :
73 : static bool
74 0 : ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage* aImage,
75 : uint32_t aFlags)
76 : {
77 0 : if (!(aFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES)) {
78 0 : return false;
79 : }
80 :
81 0 : if (aImage->GetType() != eStyleImageType_Image) {
82 0 : return false;
83 : }
84 :
85 0 : imgRequestProxy* req = aImage->GetImageData();
86 0 : if (!req) {
87 0 : return false;
88 : }
89 :
90 0 : uint32_t status = 0;
91 0 : if (NS_FAILED(req->GetImageStatus(&status))) {
92 0 : return false;
93 : }
94 :
95 0 : if (status & imgIRequest::STATUS_ERROR) {
96 : // The image is "complete" since it's a corrupt image. If we created an
97 : // imgIContainer at all, return true.
98 0 : nsCOMPtr<imgIContainer> image;
99 0 : req->GetImage(getter_AddRefs(image));
100 0 : return bool(image);
101 : }
102 :
103 0 : if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
104 : // We must have loaded all of the image's data and the size must be
105 : // available, or else sync decoding won't be able to decode the image.
106 0 : return false;
107 : }
108 :
109 0 : return true;
110 : }
111 :
112 : bool
113 457 : nsImageRenderer::PrepareImage()
114 : {
115 457 : if (mImage->IsEmpty()) {
116 2 : mPrepareResult = DrawResult::BAD_IMAGE;
117 2 : return false;
118 : }
119 :
120 455 : if (!mImage->IsComplete()) {
121 : // Make sure the image is actually decoding.
122 9 : bool frameComplete = mImage->StartDecoding();
123 :
124 : // Check again to see if we finished.
125 : // We cannot prepare the image for rendering if it is not fully loaded.
126 : // Special case: If we requested a sync decode and the image has loaded, push
127 : // on through because the Draw() will do a sync decode then.
128 9 : if (!(frameComplete || mImage->IsComplete()) &&
129 0 : !ShouldTreatAsCompleteDueToSyncDecode(mImage, mFlags)) {
130 0 : mPrepareResult = DrawResult::NOT_READY;
131 0 : return false;
132 : }
133 : }
134 :
135 455 : switch (mType) {
136 : case eStyleImageType_Image: {
137 324 : MOZ_ASSERT(mImage->GetImageData(),
138 : "must have image data, since we checked IsEmpty above");
139 324 : nsCOMPtr<imgIContainer> srcImage;
140 : DebugOnly<nsresult> rv =
141 324 : mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
142 324 : MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
143 : "If GetImage() is failing, mImage->IsComplete() "
144 : "should have returned false");
145 :
146 324 : if (!mImage->GetCropRect()) {
147 272 : mImageContainer.swap(srcImage);
148 : } else {
149 52 : nsIntRect actualCropRect;
150 : bool isEntireImage;
151 : bool success =
152 52 : mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
153 52 : if (!success || actualCropRect.IsEmpty()) {
154 : // The cropped image has zero size
155 0 : mPrepareResult = DrawResult::BAD_IMAGE;
156 0 : return false;
157 : }
158 52 : if (isEntireImage) {
159 : // The cropped image is identical to the source image
160 0 : mImageContainer.swap(srcImage);
161 : } else {
162 104 : nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage,
163 : actualCropRect,
164 156 : Nothing());
165 52 : mImageContainer.swap(subImage);
166 : }
167 : }
168 324 : mPrepareResult = DrawResult::SUCCESS;
169 324 : break;
170 : }
171 : case eStyleImageType_Gradient:
172 131 : mGradientData = mImage->GetGradientData();
173 131 : mPrepareResult = DrawResult::SUCCESS;
174 131 : break;
175 : case eStyleImageType_Element:
176 : {
177 : nsAutoString elementId =
178 0 : NS_LITERAL_STRING("#") + nsDependentAtomString(mImage->GetElementId());
179 0 : nsCOMPtr<nsIURI> targetURI;
180 0 : nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
181 0 : nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
182 0 : mForFrame->GetContent()->GetUncomposedDoc(), base);
183 0 : nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
184 0 : targetURI, mForFrame->FirstContinuation(),
185 0 : nsSVGEffects::BackgroundImageProperty());
186 0 : if (!property) {
187 0 : mPrepareResult = DrawResult::BAD_IMAGE;
188 0 : return false;
189 : }
190 :
191 : // If the referenced element is an <img>, <canvas>, or <video> element,
192 : // prefer SurfaceFromElement as it's more reliable.
193 : mImageElementSurface =
194 0 : nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
195 0 : if (!mImageElementSurface.GetSourceSurface()) {
196 0 : nsIFrame* paintServerFrame = property->GetReferencedFrame();
197 : // If there's no referenced frame, or the referenced frame is
198 : // non-displayable SVG, then we have nothing valid to paint.
199 0 : if (!paintServerFrame ||
200 0 : (paintServerFrame->IsFrameOfType(nsIFrame::eSVG) &&
201 0 : !paintServerFrame->IsFrameOfType(nsIFrame::eSVGPaintServer) &&
202 0 : !static_cast<nsSVGDisplayableFrame*>(do_QueryFrame(paintServerFrame)))) {
203 0 : mPrepareResult = DrawResult::BAD_IMAGE;
204 0 : return false;
205 : }
206 0 : mPaintServerFrame = paintServerFrame;
207 : }
208 :
209 0 : mPrepareResult = DrawResult::SUCCESS;
210 0 : break;
211 : }
212 : case eStyleImageType_Null:
213 : default:
214 0 : break;
215 : }
216 :
217 455 : return IsReady();
218 : }
219 :
220 : CSSSizeOrRatio
221 458 : nsImageRenderer::ComputeIntrinsicSize()
222 : {
223 458 : NS_ASSERTION(IsReady(), "Ensure PrepareImage() has returned true "
224 : "before calling me");
225 :
226 458 : CSSSizeOrRatio result;
227 458 : switch (mType) {
228 : case eStyleImageType_Image:
229 : {
230 : bool haveWidth, haveHeight;
231 324 : CSSIntSize imageIntSize;
232 324 : nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
233 324 : result.mRatio, haveWidth, haveHeight);
234 324 : if (haveWidth) {
235 324 : result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
236 : }
237 324 : if (haveHeight) {
238 324 : result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
239 : }
240 :
241 : // If we know the aspect ratio and one of the dimensions,
242 : // we can compute the other missing width or height.
243 324 : if (!haveHeight && haveWidth && result.mRatio.width != 0) {
244 : nscoord intrinsicHeight =
245 0 : NSCoordSaturatingNonnegativeMultiply(imageIntSize.width,
246 0 : float(result.mRatio.height) /
247 0 : float(result.mRatio.width));
248 0 : result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight));
249 324 : } else if (haveHeight && !haveWidth && result.mRatio.height != 0) {
250 : nscoord intrinsicWidth =
251 0 : NSCoordSaturatingNonnegativeMultiply(imageIntSize.height,
252 0 : float(result.mRatio.width) /
253 0 : float(result.mRatio.height));
254 0 : result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth));
255 : }
256 :
257 324 : break;
258 : }
259 : case eStyleImageType_Element:
260 : {
261 : // XXX element() should have the width/height of the referenced element,
262 : // and that element's ratio, if it matches. If it doesn't match, it
263 : // should have no width/height or ratio. See element() in CSS images:
264 : // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
265 : // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
266 : // when fixing this!
267 0 : if (mPaintServerFrame) {
268 : // SVG images have no intrinsic size
269 0 : if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
270 : // The intrinsic image size for a generic nsIFrame paint server is
271 : // the union of the border-box rects of all of its continuations,
272 : // rounded to device pixels.
273 : int32_t appUnitsPerDevPixel =
274 0 : mForFrame->PresContext()->AppUnitsPerDevPixel();
275 : result.SetSize(
276 0 : IntSizeToAppUnits(
277 0 : nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
278 : ToNearestPixels(appUnitsPerDevPixel),
279 0 : appUnitsPerDevPixel));
280 : }
281 : } else {
282 0 : NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
283 : "Surface should be ready.");
284 0 : IntSize surfaceSize = mImageElementSurface.mSize;
285 : result.SetSize(
286 0 : nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
287 0 : nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
288 : }
289 0 : break;
290 : }
291 : case eStyleImageType_Gradient:
292 : // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
293 : // intrinsic dimensions.
294 : case eStyleImageType_Null:
295 : default:
296 134 : break;
297 : }
298 :
299 458 : return result;
300 : }
301 :
302 : /* static */ nsSize
303 455 : nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
304 : const CSSSizeOrRatio& aIntrinsicSize,
305 : const nsSize& aDefaultSize)
306 : {
307 : // The specified size is fully specified, just use that
308 455 : if (aSpecifiedSize.IsConcrete()) {
309 216 : return aSpecifiedSize.ComputeConcreteSize();
310 : }
311 :
312 239 : MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
313 :
314 239 : if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
315 : // no specified size, try using the intrinsic size
316 127 : if (aIntrinsicSize.CanComputeConcreteSize()) {
317 52 : return aIntrinsicSize.ComputeConcreteSize();
318 : }
319 :
320 75 : if (aIntrinsicSize.mHasWidth) {
321 0 : return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
322 : }
323 75 : if (aIntrinsicSize.mHasHeight) {
324 0 : return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
325 : }
326 :
327 : // couldn't use the intrinsic size either, revert to using the default size
328 : return ComputeConstrainedSize(aDefaultSize,
329 : aIntrinsicSize.mRatio,
330 75 : CONTAIN);
331 : }
332 :
333 112 : MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
334 :
335 : // The specified height is partial, try to compute the missing part.
336 112 : if (aSpecifiedSize.mHasWidth) {
337 : nscoord height;
338 0 : if (aIntrinsicSize.HasRatio()) {
339 0 : height = NSCoordSaturatingNonnegativeMultiply(
340 0 : aSpecifiedSize.mWidth,
341 0 : double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
342 0 : } else if (aIntrinsicSize.mHasHeight) {
343 0 : height = aIntrinsicSize.mHeight;
344 : } else {
345 0 : height = aDefaultSize.height;
346 : }
347 0 : return nsSize(aSpecifiedSize.mWidth, height);
348 : }
349 :
350 112 : MOZ_ASSERT(aSpecifiedSize.mHasHeight);
351 : nscoord width;
352 112 : if (aIntrinsicSize.HasRatio()) {
353 56 : width = NSCoordSaturatingNonnegativeMultiply(
354 56 : aSpecifiedSize.mHeight,
355 112 : double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
356 56 : } else if (aIntrinsicSize.mHasWidth) {
357 0 : width = aIntrinsicSize.mWidth;
358 : } else {
359 56 : width = aDefaultSize.width;
360 : }
361 112 : return nsSize(width, aSpecifiedSize.mHeight);
362 : }
363 :
364 : /* static */ nsSize
365 75 : nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
366 : const nsSize& aIntrinsicRatio,
367 : FitType aFitType)
368 : {
369 75 : if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
370 75 : return aConstrainingSize;
371 : }
372 :
373 0 : float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
374 0 : float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
375 0 : nsSize size;
376 0 : if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
377 0 : size.width = aConstrainingSize.width;
378 0 : size.height = NSCoordSaturatingNonnegativeMultiply(
379 0 : aIntrinsicRatio.height, scaleX);
380 : // If we're reducing the size by less than one css pixel, then just use the
381 : // constraining size.
382 0 : if (aFitType == CONTAIN && aConstrainingSize.height - size.height < nsPresContext::AppUnitsPerCSSPixel()) {
383 0 : size.height = aConstrainingSize.height;
384 : }
385 : } else {
386 0 : size.width = NSCoordSaturatingNonnegativeMultiply(
387 0 : aIntrinsicRatio.width, scaleY);
388 0 : if (aFitType == CONTAIN && aConstrainingSize.width - size.width < nsPresContext::AppUnitsPerCSSPixel()) {
389 0 : size.width = aConstrainingSize.width;
390 : }
391 0 : size.height = aConstrainingSize.height;
392 : }
393 0 : return size;
394 : }
395 :
396 : /**
397 : * mSize is the image's "preferred" size for this particular rendering, while
398 : * the drawn (aka concrete) size is the actual rendered size after accounting
399 : * for background-size etc.. The preferred size is most often the image's
400 : * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
401 : * the preferred size varies, depending on the specified and default sizes, see
402 : * nsImageRenderer::Compute*Size.
403 : *
404 : * This distinction is necessary because the components of a vector image are
405 : * specified with respect to its preferred size for a rendering situation, not
406 : * to its actual rendered size. For example, consider a 4px wide background
407 : * vector image with no height which contains a left-aligned
408 : * 2px wide black rectangle with height 100%. If the background-size width is
409 : * auto (or 4px), the vector image will render 4px wide, and the black rectangle
410 : * will be 2px wide. If the background-size width is 8px, the vector image will
411 : * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
412 : * In both cases mSize.width will be 4px; but in the first case the returned
413 : * width will be 4px, while in the second case the returned width will be 8px.
414 : */
415 : void
416 455 : nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
417 : const nsSize& aDefaultSize)
418 : {
419 910 : mSize.width = aIntrinsicSize.mHasWidth
420 455 : ? aIntrinsicSize.mWidth
421 : : aDefaultSize.width;
422 910 : mSize.height = aIntrinsicSize.mHasHeight
423 455 : ? aIntrinsicSize.mHeight
424 : : aDefaultSize.height;
425 455 : }
426 :
427 : // Convert from nsImageRenderer flags to the flags we want to use for drawing in
428 : // the imgIContainer namespace.
429 : static uint32_t
430 36 : ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
431 : {
432 36 : uint32_t drawFlags = imgIContainer::FLAG_NONE;
433 36 : if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
434 0 : drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
435 : }
436 36 : if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
437 36 : drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
438 : }
439 36 : return drawFlags;
440 : }
441 :
442 : /*
443 : * SVG11: A luminanceToAlpha operation is equivalent to the following matrix operation: |
444 : * | R' | | 0 0 0 0 0 | | R |
445 : * | G' | | 0 0 0 0 0 | | G |
446 : * | B' | = | 0 0 0 0 0 | * | B |
447 : * | A' | | 0.2125 0.7154 0.0721 0 0 | | A |
448 : * | 1 | | 0 0 0 0 1 | | 1 |
449 : */
450 : static void
451 0 : RGBALuminanceOperation(uint8_t *aData,
452 : int32_t aStride,
453 : const IntSize &aSize)
454 : {
455 0 : int32_t redFactor = 55; // 256 * 0.2125
456 0 : int32_t greenFactor = 183; // 256 * 0.7154
457 0 : int32_t blueFactor = 18; // 256 * 0.0721
458 :
459 0 : for (int32_t y = 0; y < aSize.height; y++) {
460 0 : uint32_t *pixel = (uint32_t*)(aData + aStride * y);
461 0 : for (int32_t x = 0; x < aSize.width; x++) {
462 0 : *pixel = (((((*pixel & 0x00FF0000) >> 16) * redFactor) +
463 0 : (((*pixel & 0x0000FF00) >> 8) * greenFactor) +
464 0 : ((*pixel & 0x000000FF) * blueFactor)) >> 8) << 24;
465 0 : pixel++;
466 : }
467 : }
468 0 : }
469 :
470 : DrawResult
471 62 : nsImageRenderer::Draw(nsPresContext* aPresContext,
472 : gfxContext& aRenderingContext,
473 : const nsRect& aDirtyRect,
474 : const nsRect& aDest,
475 : const nsRect& aFill,
476 : const nsPoint& aAnchor,
477 : const nsSize& aRepeatSize,
478 : const CSSIntRect& aSrc,
479 : float aOpacity)
480 : {
481 62 : if (!IsReady()) {
482 0 : NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
483 0 : return DrawResult::TEMPORARY_ERROR;
484 : }
485 248 : if (aDest.IsEmpty() || aFill.IsEmpty() ||
486 186 : mSize.width <= 0 || mSize.height <= 0) {
487 0 : return DrawResult::SUCCESS;
488 : }
489 :
490 62 : SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
491 62 : DrawResult result = DrawResult::SUCCESS;
492 124 : RefPtr<gfxContext> ctx = &aRenderingContext;
493 62 : IntRect tmpDTRect;
494 :
495 62 : if (ctx->CurrentOp() != CompositionOp::OP_OVER || mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
496 0 : gfxRect clipRect = ctx->GetClipExtents();
497 0 : tmpDTRect = RoundedOut(ToRect(clipRect));
498 0 : if (tmpDTRect.IsEmpty()) {
499 0 : return DrawResult::SUCCESS;
500 : }
501 : RefPtr<DrawTarget> tempDT =
502 0 : gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(ctx->GetDrawTarget(),
503 0 : tmpDTRect.Size(),
504 0 : SurfaceFormat::B8G8R8A8);
505 0 : if (!tempDT || !tempDT->IsValid()) {
506 0 : gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
507 0 : return DrawResult::TEMPORARY_ERROR;
508 : }
509 0 : tempDT->SetTransform(Matrix::Translation(-tmpDTRect.TopLeft()));
510 0 : ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
511 0 : if (!ctx) {
512 0 : gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
513 0 : return DrawResult::TEMPORARY_ERROR;
514 : }
515 : }
516 :
517 62 : switch (mType) {
518 : case eStyleImageType_Image:
519 : {
520 : CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
521 36 : nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
522 : result =
523 36 : nsLayoutUtils::DrawBackgroundImage(*ctx, mForFrame,
524 : aPresContext,
525 : mImageContainer, imageSize,
526 : samplingFilter,
527 : aDest, aFill, aRepeatSize,
528 : aAnchor, aDirtyRect,
529 : ConvertImageRendererToDrawFlags(mFlags),
530 36 : mExtendMode, aOpacity);
531 36 : break;
532 : }
533 : case eStyleImageType_Gradient:
534 : {
535 : nsCSSGradientRenderer renderer =
536 52 : nsCSSGradientRenderer::Create(aPresContext, mGradientData, mSize);
537 :
538 26 : renderer.Paint(*ctx, aDest, aFill, aRepeatSize, aSrc, aDirtyRect, aOpacity);
539 26 : break;
540 : }
541 : case eStyleImageType_Element:
542 : {
543 0 : RefPtr<gfxDrawable> drawable = DrawableForElement(aDest, *ctx);
544 0 : if (!drawable) {
545 0 : NS_WARNING("Could not create drawable for element");
546 0 : return DrawResult::TEMPORARY_ERROR;
547 : }
548 :
549 0 : nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
550 : result =
551 0 : nsLayoutUtils::DrawImage(*ctx, mForFrame->StyleContext(),
552 : aPresContext, image,
553 : samplingFilter, aDest, aFill, aAnchor, aDirtyRect,
554 : ConvertImageRendererToDrawFlags(mFlags),
555 0 : aOpacity);
556 0 : break;
557 : }
558 : case eStyleImageType_Null:
559 : default:
560 0 : break;
561 : }
562 :
563 62 : if (!tmpDTRect.IsEmpty()) {
564 0 : RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
565 0 : if (mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
566 0 : RefPtr<DataSourceSurface> maskData = surf->GetDataSurface();
567 : DataSourceSurface::MappedSurface map;
568 0 : if (!maskData->Map(DataSourceSurface::MapType::WRITE, &map)) {
569 0 : return result;
570 : }
571 :
572 0 : RGBALuminanceOperation(map.mData, map.mStride, maskData->GetSize());
573 0 : maskData->Unmap();
574 0 : surf = maskData;
575 : }
576 :
577 0 : DrawTarget* dt = aRenderingContext.GetDrawTarget();
578 0 : dt->DrawSurface(surf, Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
579 0 : Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
580 0 : DrawSurfaceOptions(SamplingFilter::POINT),
581 0 : DrawOptions(1.0f, aRenderingContext.CurrentOp()));
582 : }
583 :
584 62 : return result;
585 : }
586 :
587 : DrawResult
588 0 : nsImageRenderer::BuildWebRenderDisplayItems(nsPresContext* aPresContext,
589 : mozilla::wr::DisplayListBuilder& aBuilder,
590 : const mozilla::layers::StackingContextHelper& aSc,
591 : nsTArray<WebRenderParentCommand>& aParentCommands,
592 : mozilla::layers::WebRenderDisplayItemLayer* aLayer,
593 : mozilla::layers::WebRenderLayerManager* aManager,
594 : nsDisplayItem* aItem,
595 : const nsRect& aDirtyRect,
596 : const nsRect& aDest,
597 : const nsRect& aFill,
598 : const nsPoint& aAnchor,
599 : const nsSize& aRepeatSize,
600 : const CSSIntRect& aSrc,
601 : float aOpacity)
602 : {
603 0 : if (!IsReady()) {
604 0 : NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
605 0 : return DrawResult::NOT_READY;
606 : }
607 0 : if (aDest.IsEmpty() || aFill.IsEmpty() ||
608 0 : mSize.width <= 0 || mSize.height <= 0) {
609 0 : return DrawResult::SUCCESS;
610 : }
611 :
612 0 : switch (mType) {
613 : case eStyleImageType_Gradient:
614 : {
615 : nsCSSGradientRenderer renderer =
616 0 : nsCSSGradientRenderer::Create(aPresContext, mGradientData, mSize);
617 :
618 0 : renderer.BuildWebRenderDisplayItems(aBuilder, aSc, aLayer, aDest, aFill, aRepeatSize, aSrc, aOpacity);
619 0 : break;
620 : }
621 : case eStyleImageType_Image:
622 : {
623 : // XXX(aosmond): We will support downscale-on-decode in bug 1368776. Until
624 : // then, don't pass FLAG_HIGH_QUALITY_SCALING.
625 0 : uint32_t containerFlags = imgIContainer::FLAG_NONE;
626 0 : if (mFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
627 0 : containerFlags |= imgIContainer::FLAG_SYNC_DECODE;
628 : }
629 : RefPtr<layers::ImageContainer> container =
630 0 : mImageContainer->GetImageContainer(aManager, containerFlags);
631 0 : if (!container) {
632 0 : NS_WARNING("Failed to get image container");
633 0 : return DrawResult::NOT_READY;
634 : }
635 :
636 0 : gfx::IntSize size;
637 0 : Maybe<wr::ImageKey> key = aManager->CreateImageKey(aItem, container, aBuilder, aSc, size);
638 :
639 0 : if (key.isNothing()) {
640 0 : return DrawResult::BAD_IMAGE;
641 : }
642 :
643 0 : const int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
644 : LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(
645 0 : aDest, appUnitsPerDevPixel);
646 :
647 0 : nsPoint firstTilePos = nsLayoutUtils::GetBackgroundFirstTilePos(aDest.TopLeft(),
648 0 : aFill.TopLeft(),
649 0 : aRepeatSize);
650 : LayoutDeviceRect fillRect = LayoutDeviceRect::FromAppUnits(
651 0 : nsRect(firstTilePos.x, firstTilePos.y,
652 0 : aFill.XMost() - firstTilePos.x, aFill.YMost() - firstTilePos.y),
653 0 : appUnitsPerDevPixel);
654 0 : WrRect fill = aSc.ToRelativeWrRect(fillRect);
655 : WrRect clip = aSc.ToRelativeWrRect(
656 0 : LayoutDeviceRect::FromAppUnits(aFill, appUnitsPerDevPixel));
657 :
658 : LayoutDeviceSize gapSize = LayoutDeviceSize::FromAppUnits(
659 0 : aRepeatSize - aDest.Size(), appUnitsPerDevPixel);
660 0 : aBuilder.PushImage(fill, clip,
661 0 : wr::ToWrSize(destRect.Size()), wr::ToWrSize(gapSize),
662 0 : wr::ImageRendering::Auto, key.value());
663 0 : break;
664 : }
665 : default:
666 0 : break;
667 : }
668 :
669 0 : return DrawResult::SUCCESS;
670 : }
671 :
672 : already_AddRefed<gfxDrawable>
673 0 : nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
674 : gfxContext& aContext)
675 : {
676 0 : NS_ASSERTION(mType == eStyleImageType_Element,
677 : "DrawableForElement only makes sense if backed by an element");
678 0 : if (mPaintServerFrame) {
679 : // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
680 : // DrawableFromPaintServer would have to return a DrawResult indicating
681 : // whether any images could not be painted because they weren't fully
682 : // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
683 : // problems, as it won't help if there are image which haven't finished
684 : // loading, but it's better than nothing.
685 0 : int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
686 0 : nsRect destRect = aImageRect - aImageRect.TopLeft();
687 0 : nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
688 0 : IntSize imageSize(roundedOut.width, roundedOut.height);
689 : RefPtr<gfxDrawable> drawable =
690 0 : nsSVGIntegrationUtils::DrawableFromPaintServer(
691 : mPaintServerFrame, mForFrame, mSize, imageSize,
692 0 : aContext.GetDrawTarget(),
693 0 : aContext.CurrentMatrix(),
694 0 : nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
695 :
696 0 : return drawable.forget();
697 : }
698 0 : NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
699 : RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
700 0 : mImageElementSurface.GetSourceSurface().get(),
701 0 : mImageElementSurface.mSize);
702 0 : return drawable.forget();
703 : }
704 :
705 : DrawResult
706 59 : nsImageRenderer::DrawLayer(nsPresContext* aPresContext,
707 : gfxContext& aRenderingContext,
708 : const nsRect& aDest,
709 : const nsRect& aFill,
710 : const nsPoint& aAnchor,
711 : const nsRect& aDirty,
712 : const nsSize& aRepeatSize,
713 : float aOpacity)
714 : {
715 59 : if (!IsReady()) {
716 0 : NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
717 0 : return DrawResult::TEMPORARY_ERROR;
718 : }
719 236 : if (aDest.IsEmpty() || aFill.IsEmpty() ||
720 177 : mSize.width <= 0 || mSize.height <= 0) {
721 0 : return DrawResult::SUCCESS;
722 : }
723 :
724 : return Draw(aPresContext, aRenderingContext,
725 : aDirty, aDest, aFill, aAnchor, aRepeatSize,
726 177 : CSSIntRect(0, 0,
727 : nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
728 59 : nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
729 59 : aOpacity);
730 : }
731 :
732 : DrawResult
733 0 : nsImageRenderer::BuildWebRenderDisplayItemsForLayer(nsPresContext* aPresContext,
734 : mozilla::wr::DisplayListBuilder& aBuilder,
735 : const mozilla::layers::StackingContextHelper& aSc,
736 : nsTArray<WebRenderParentCommand>& aParentCommands,
737 : WebRenderDisplayItemLayer* aLayer,
738 : mozilla::layers::WebRenderLayerManager* aManager,
739 : nsDisplayItem* aItem,
740 : const nsRect& aDest,
741 : const nsRect& aFill,
742 : const nsPoint& aAnchor,
743 : const nsRect& aDirty,
744 : const nsSize& aRepeatSize,
745 : float aOpacity)
746 : {
747 0 : if (!IsReady()) {
748 0 : NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
749 0 : return mPrepareResult;
750 : }
751 0 : if (aDest.IsEmpty() || aFill.IsEmpty() ||
752 0 : mSize.width <= 0 || mSize.height <= 0) {
753 0 : return DrawResult::SUCCESS;
754 : }
755 : return BuildWebRenderDisplayItems(aPresContext, aBuilder, aSc, aParentCommands,
756 : aLayer, aManager, aItem,
757 : aDirty, aDest, aFill, aAnchor, aRepeatSize,
758 0 : CSSIntRect(0, 0,
759 : nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
760 0 : nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
761 0 : aOpacity);
762 : }
763 :
764 : /**
765 : * Compute the size and position of the master copy of the image. I.e., a single
766 : * tile used to fill the dest rect.
767 : * aFill The destination rect to be filled
768 : * aHFill and aVFill are the repeat patterns for the component -
769 : * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
770 : * aUnitSize The size of the source rect in dest coords.
771 : */
772 : static nsRect
773 0 : ComputeTile(nsRect& aFill,
774 : uint8_t aHFill,
775 : uint8_t aVFill,
776 : const nsSize& aUnitSize,
777 : nsSize& aRepeatSize)
778 : {
779 0 : nsRect tile;
780 0 : switch (aHFill) {
781 : case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
782 0 : tile.x = aFill.x;
783 0 : tile.width = aFill.width;
784 0 : aRepeatSize.width = tile.width;
785 0 : break;
786 : case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
787 0 : tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
788 0 : tile.width = aUnitSize.width;
789 0 : aRepeatSize.width = tile.width;
790 0 : break;
791 : case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
792 0 : tile.x = aFill.x;
793 0 : tile.width = nsCSSRendering::ComputeRoundedSize(aUnitSize.width,
794 : aFill.width);
795 0 : aRepeatSize.width = tile.width;
796 0 : break;
797 : case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
798 : {
799 : nscoord space;
800 0 : aRepeatSize.width =
801 0 : nsCSSRendering::ComputeBorderSpacedRepeatSize(aUnitSize.width,
802 : aFill.width, space);
803 0 : tile.x = aFill.x + space;
804 0 : tile.width = aUnitSize.width;
805 0 : aFill.x = tile.x;
806 0 : aFill.width = aFill.width - space * 2;
807 : }
808 0 : break;
809 : default:
810 0 : NS_NOTREACHED("unrecognized border-image fill style");
811 : }
812 :
813 0 : switch (aVFill) {
814 : case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
815 0 : tile.y = aFill.y;
816 0 : tile.height = aFill.height;
817 0 : aRepeatSize.height = tile.height;
818 0 : break;
819 : case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
820 0 : tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
821 0 : tile.height = aUnitSize.height;
822 0 : aRepeatSize.height = tile.height;
823 0 : break;
824 : case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
825 0 : tile.y = aFill.y;
826 0 : tile.height = nsCSSRendering::ComputeRoundedSize(aUnitSize.height,
827 : aFill.height);
828 0 : aRepeatSize.height = tile.height;
829 0 : break;
830 : case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
831 : {
832 : nscoord space;
833 0 : aRepeatSize.height =
834 0 : nsCSSRendering::ComputeBorderSpacedRepeatSize(aUnitSize.height,
835 : aFill.height, space);
836 0 : tile.y = aFill.y + space;
837 0 : tile.height = aUnitSize.height;
838 0 : aFill.y = tile.y;
839 0 : aFill.height = aFill.height - space * 2;
840 : }
841 0 : break;
842 : default:
843 0 : NS_NOTREACHED("unrecognized border-image fill style");
844 : }
845 :
846 0 : return tile;
847 : }
848 :
849 : /**
850 : * Returns true if the given set of arguments will require the tiles which fill
851 : * the dest rect to be scaled from the source tile. See comment on ComputeTile
852 : * for argument descriptions.
853 : */
854 : static bool
855 3 : RequiresScaling(const nsRect& aFill,
856 : uint8_t aHFill,
857 : uint8_t aVFill,
858 : const nsSize& aUnitSize)
859 : {
860 : // If we have no tiling in either direction, we can skip the intermediate
861 : // scaling step.
862 3 : return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
863 3 : aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
864 0 : (aUnitSize.width != aFill.width ||
865 3 : aUnitSize.height != aFill.height);
866 : }
867 :
868 : DrawResult
869 24 : nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext,
870 : gfxContext& aRenderingContext,
871 : const nsRect& aDirtyRect,
872 : const nsRect& aFill,
873 : const CSSIntRect& aSrc,
874 : uint8_t aHFill,
875 : uint8_t aVFill,
876 : const nsSize& aUnitSize,
877 : uint8_t aIndex,
878 : const Maybe<nsSize>& aSVGViewportSize,
879 : const bool aHasIntrinsicRatio)
880 : {
881 24 : if (!IsReady()) {
882 0 : NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
883 0 : return DrawResult::BAD_ARGS;
884 : }
885 24 : if (aFill.IsEmpty() || aSrc.IsEmpty()) {
886 21 : return DrawResult::SUCCESS;
887 : }
888 :
889 3 : if (mType == eStyleImageType_Image || mType == eStyleImageType_Element) {
890 0 : nsCOMPtr<imgIContainer> subImage;
891 :
892 : // To draw one portion of an image into a border component, we stretch that
893 : // portion to match the size of that border component and then draw onto.
894 : // However, preserveAspectRatio attribute of a SVG image may break this rule.
895 : // To get correct rendering result, we add
896 : // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
897 : // preserveAspectRatio attribute, and always do non-uniform stretch.
898 0 : uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
899 0 : imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
900 : // For those SVG image sources which don't have fixed aspect ratio (i.e.
901 : // without viewport size and viewBox), we should scale the source uniformly
902 : // after the viewport size is decided by "Default Sizing Algorithm".
903 0 : if (!aHasIntrinsicRatio) {
904 0 : drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
905 : }
906 : // Retrieve or create the subimage we'll draw.
907 0 : nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
908 0 : if (mType == eStyleImageType_Image) {
909 0 : if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
910 0 : subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
911 0 : mImage->SetSubImage(aIndex, subImage);
912 : }
913 : } else {
914 : // This path, for eStyleImageType_Element, is currently slower than it
915 : // needs to be because we don't cache anything. (In particular, if we have
916 : // to draw to a temporary surface inside ClippedImage, we don't cache that
917 : // temporary surface since we immediately throw the ClippedImage we create
918 : // here away.) However, if we did cache, we'd need to know when to
919 : // invalidate that cache, and it's not clear that it's worth the trouble
920 : // since using border-image with -moz-element is rare.
921 :
922 : RefPtr<gfxDrawable> drawable =
923 0 : DrawableForElement(nsRect(nsPoint(), mSize),
924 0 : aRenderingContext);
925 0 : if (!drawable) {
926 0 : NS_WARNING("Could not create drawable for element");
927 0 : return DrawResult::TEMPORARY_ERROR;
928 : }
929 :
930 0 : nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
931 0 : subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize);
932 : }
933 :
934 0 : MOZ_ASSERT(!aSVGViewportSize ||
935 : subImage->GetType() == imgIContainer::TYPE_VECTOR);
936 :
937 0 : SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
938 :
939 0 : if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
940 0 : return nsLayoutUtils::DrawSingleImage(aRenderingContext,
941 : aPresContext,
942 : subImage,
943 : samplingFilter,
944 : aFill, aDirtyRect,
945 0 : /* no SVGImageContext */ Nothing(),
946 0 : drawFlags);
947 : }
948 :
949 0 : nsSize repeatSize;
950 0 : nsRect fillRect(aFill);
951 0 : nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
952 0 : CSSIntSize imageSize(srcRect.width, srcRect.height);
953 0 : return nsLayoutUtils::DrawBackgroundImage(aRenderingContext,
954 : mForFrame, aPresContext,
955 : subImage, imageSize, samplingFilter,
956 : tile, fillRect, repeatSize,
957 0 : tile.TopLeft(), aDirtyRect,
958 : drawFlags,
959 0 : ExtendMode::CLAMP, 1.0);
960 : }
961 :
962 3 : nsSize repeatSize(aFill.Size());
963 6 : nsRect fillRect(aFill);
964 3 : nsRect destTile = RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
965 : ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
966 6 : : fillRect;
967 : return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
968 3 : fillRect, destTile.TopLeft(), repeatSize, aSrc);
969 : }
970 :
971 : bool
972 194 : nsImageRenderer::IsRasterImage()
973 : {
974 194 : if (mType != eStyleImageType_Image || !mImageContainer)
975 50 : return false;
976 144 : return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
977 : }
978 :
979 : bool
980 0 : nsImageRenderer::IsAnimatedImage()
981 : {
982 0 : if (mType != eStyleImageType_Image || !mImageContainer)
983 0 : return false;
984 0 : bool animated = false;
985 0 : if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
986 0 : return true;
987 :
988 0 : return false;
989 : }
990 :
991 : already_AddRefed<imgIContainer>
992 72 : nsImageRenderer::GetImage()
993 : {
994 72 : if (mType != eStyleImageType_Image || !mImageContainer) {
995 0 : return nullptr;
996 : }
997 :
998 144 : nsCOMPtr<imgIContainer> image = mImageContainer;
999 72 : return image.forget();
1000 : }
1001 :
1002 : bool
1003 0 : nsImageRenderer::IsImageContainerAvailable(layers::LayerManager* aManager, uint32_t aFlags)
1004 : {
1005 0 : if (!mImageContainer) {
1006 0 : return false;
1007 : }
1008 0 : return mImageContainer->IsImageContainerAvailable(aManager, aFlags);
1009 : }
1010 :
1011 : void
1012 3 : nsImageRenderer::PurgeCacheForViewportChange(
1013 : const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio)
1014 : {
1015 : // Check if we should flush the cached data - only vector images need to do
1016 : // the check since they might not have fixed ratio.
1017 3 : if (mImageContainer &&
1018 3 : mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
1019 0 : mImage->PurgeCacheForViewportChange(aSVGViewportSize, aHasIntrinsicRatio);
1020 : }
1021 3 : }
1022 :
1023 : already_AddRefed<nsStyleGradient>
1024 0 : nsImageRenderer::GetGradientData()
1025 : {
1026 0 : RefPtr<nsStyleGradient> res = mGradientData;
1027 0 : return res.forget();
1028 9 : }
1029 :
|