Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
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/. */
7 #include "imgFrame.h"
8 #include "DiscardTracker.h"
10 #include "prenv.h"
12 #include "gfx2DGlue.h"
13 #include "gfxPlatform.h"
14 #include "gfxUtils.h"
15 #include "gfxAlphaRecovery.h"
17 static bool gDisableOptimize = false;
19 #include "cairo.h"
20 #include "GeckoProfiler.h"
21 #include "mozilla/Likely.h"
22 #include "mozilla/MemoryReporting.h"
23 #include "nsMargin.h"
24 #include "mozilla/CheckedInt.h"
26 #if defined(XP_WIN)
28 #include "gfxWindowsPlatform.h"
30 /* Whether to use the windows surface; only for desktop win32 */
31 #define USE_WIN_SURFACE 1
33 #endif
35 using namespace mozilla;
36 using namespace mozilla::gfx;
37 using namespace mozilla::image;
39 static cairo_user_data_key_t kVolatileBuffer;
41 static void
42 VolatileBufferRelease(void *vbuf)
43 {
44 delete static_cast<VolatileBufferPtr<unsigned char>*>(vbuf);
45 }
47 gfxImageSurface *
48 LockedImageSurface::CreateSurface(VolatileBuffer *vbuf,
49 const gfxIntSize& size,
50 gfxImageFormat format)
51 {
52 VolatileBufferPtr<unsigned char> *vbufptr =
53 new VolatileBufferPtr<unsigned char>(vbuf);
54 MOZ_ASSERT(!vbufptr->WasBufferPurged(), "Expected image data!");
56 long stride = gfxImageSurface::ComputeStride(size, format);
57 gfxImageSurface *img = new gfxImageSurface(*vbufptr, size, stride, format);
58 if (!img || img->CairoStatus()) {
59 delete img;
60 delete vbufptr;
61 return nullptr;
62 }
64 img->SetData(&kVolatileBuffer, vbufptr, VolatileBufferRelease);
65 return img;
66 }
68 TemporaryRef<VolatileBuffer>
69 LockedImageSurface::AllocateBuffer(const gfxIntSize& size,
70 gfxImageFormat format)
71 {
72 long stride = gfxImageSurface::ComputeStride(size, format);
73 RefPtr<VolatileBuffer> buf = new VolatileBuffer();
74 if (buf->Init(stride * size.height,
75 1 << gfxAlphaRecovery::GoodAlignmentLog2()))
76 return buf;
78 return nullptr;
79 }
81 // Returns true if an image of aWidth x aHeight is allowed and legal.
82 static bool AllowedImageSize(int32_t aWidth, int32_t aHeight)
83 {
84 // reject over-wide or over-tall images
85 const int32_t k64KLimit = 0x0000FFFF;
86 if (MOZ_UNLIKELY(aWidth > k64KLimit || aHeight > k64KLimit )) {
87 NS_WARNING("image too big");
88 return false;
89 }
91 // protect against invalid sizes
92 if (MOZ_UNLIKELY(aHeight <= 0 || aWidth <= 0)) {
93 return false;
94 }
96 // check to make sure we don't overflow a 32-bit
97 CheckedInt32 requiredBytes = CheckedInt32(aWidth) * CheckedInt32(aHeight) * 4;
98 if (MOZ_UNLIKELY(!requiredBytes.isValid())) {
99 NS_WARNING("width or height too large");
100 return false;
101 }
102 #if defined(XP_MACOSX)
103 // CoreGraphics is limited to images < 32K in *height*, so clamp all surfaces on the Mac to that height
104 if (MOZ_UNLIKELY(aHeight > SHRT_MAX)) {
105 NS_WARNING("image too big");
106 return false;
107 }
108 #endif
109 return true;
110 }
112 // Returns whether we should, at this time, use image surfaces instead of
113 // optimized platform-specific surfaces.
114 static bool ShouldUseImageSurfaces()
115 {
116 #if defined(USE_WIN_SURFACE)
117 static const DWORD kGDIObjectsHighWaterMark = 7000;
119 if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() ==
120 gfxWindowsPlatform::RENDER_DIRECT2D) {
121 return true;
122 }
124 // at 7000 GDI objects, stop allocating normal images to make sure
125 // we never hit the 10k hard limit.
126 // GetCurrentProcess() just returns (HANDLE)-1, it's inlined afaik
127 DWORD count = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS);
128 if (count == 0 ||
129 count > kGDIObjectsHighWaterMark)
130 {
131 // either something's broken (count == 0),
132 // or we hit our high water mark; disable
133 // image allocations for a bit.
134 return true;
135 }
136 #endif
138 return false;
139 }
141 imgFrame::imgFrame() :
142 mDecoded(0, 0, 0, 0),
143 mDirtyMutex("imgFrame::mDirty"),
144 mPalettedImageData(nullptr),
145 mSinglePixelColor(0),
146 mTimeout(100),
147 mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */
148 mLockCount(0),
149 mBlendMethod(1), /* imgIContainer::kBlendOver */
150 mSinglePixel(false),
151 mFormatChanged(false),
152 mCompositingFailed(false),
153 mNonPremult(false),
154 mDiscardable(false),
155 mInformedDiscardTracker(false),
156 mDirty(false)
157 {
158 static bool hasCheckedOptimize = false;
159 if (!hasCheckedOptimize) {
160 if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
161 gDisableOptimize = true;
162 }
163 hasCheckedOptimize = true;
164 }
165 }
167 imgFrame::~imgFrame()
168 {
169 moz_free(mPalettedImageData);
170 mPalettedImageData = nullptr;
172 if (mInformedDiscardTracker) {
173 DiscardTracker::InformDeallocation(4 * mSize.height * mSize.width);
174 }
175 }
177 nsresult imgFrame::Init(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight,
178 gfxImageFormat aFormat, uint8_t aPaletteDepth /* = 0 */)
179 {
180 // assert for properties that should be verified by decoders, warn for properties related to bad content
181 if (!AllowedImageSize(aWidth, aHeight)) {
182 NS_WARNING("Should have legal image size");
183 return NS_ERROR_FAILURE;
184 }
186 mOffset.MoveTo(aX, aY);
187 mSize.SizeTo(aWidth, aHeight);
189 mFormat = aFormat;
190 mPaletteDepth = aPaletteDepth;
192 if (aPaletteDepth != 0) {
193 // We're creating for a paletted image.
194 if (aPaletteDepth > 8) {
195 NS_WARNING("Should have legal palette depth");
196 NS_ERROR("This Depth is not supported");
197 return NS_ERROR_FAILURE;
198 }
200 // Use the fallible allocator here
201 mPalettedImageData = (uint8_t*)moz_malloc(PaletteDataLength() + GetImageDataLength());
202 if (!mPalettedImageData)
203 NS_WARNING("moz_malloc for paletted image data should succeed");
204 NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY);
205 } else {
206 // Inform the discard tracker that we are going to allocate some memory.
207 if (!DiscardTracker::TryAllocation(4 * mSize.width * mSize.height)) {
208 NS_WARNING("Exceed the hard limit of decode image size");
209 return NS_ERROR_OUT_OF_MEMORY;
210 }
211 // For Windows, we must create the device surface first (if we're
212 // going to) so that the image surface can wrap it. Can't be done
213 // the other way around.
214 #ifdef USE_WIN_SURFACE
215 if (!ShouldUseImageSurfaces()) {
216 mWinSurface = new gfxWindowsSurface(gfxIntSize(mSize.width, mSize.height), mFormat);
217 if (mWinSurface && mWinSurface->CairoStatus() == 0) {
218 // no error
219 mImageSurface = mWinSurface->GetAsImageSurface();
220 } else {
221 mWinSurface = nullptr;
222 }
223 }
224 #endif
226 // For other platforms, space for the image surface is first allocated in
227 // a volatile buffer and then wrapped by a LockedImageSurface.
228 // This branch is also used on Windows if we're not using device surfaces
229 // or if we couldn't create one.
230 if (!mImageSurface) {
231 mVBuf = LockedImageSurface::AllocateBuffer(mSize, mFormat);
232 if (!mVBuf) {
233 return NS_ERROR_OUT_OF_MEMORY;
234 }
235 if (mVBuf->OnHeap()) {
236 long stride = gfxImageSurface::ComputeStride(mSize, mFormat);
237 VolatileBufferPtr<uint8_t> ptr(mVBuf);
238 memset(ptr, 0, stride * mSize.height);
239 }
240 mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
241 }
243 if (!mImageSurface || mImageSurface->CairoStatus()) {
244 mImageSurface = nullptr;
245 // guess
246 if (!mImageSurface) {
247 NS_WARNING("Allocation of gfxImageSurface should succeed");
248 } else if (!mImageSurface->CairoStatus()) {
249 NS_WARNING("gfxImageSurface should have good CairoStatus");
250 }
252 // Image surface allocation is failed, need to return
253 // the booked buffer size.
254 DiscardTracker::InformDeallocation(4 * mSize.width * mSize.height);
255 return NS_ERROR_OUT_OF_MEMORY;
256 }
258 mInformedDiscardTracker = true;
260 #ifdef XP_MACOSX
261 if (!ShouldUseImageSurfaces()) {
262 mQuartzSurface = new gfxQuartzImageSurface(mImageSurface);
263 }
264 #endif
265 }
267 return NS_OK;
268 }
270 nsresult imgFrame::Optimize()
271 {
272 MOZ_ASSERT(NS_IsMainThread());
274 if (gDisableOptimize)
275 return NS_OK;
277 if (mPalettedImageData || mOptSurface || mSinglePixel)
278 return NS_OK;
280 // Don't do single-color opts on non-premult data.
281 // Cairo doesn't support non-premult single-colors.
282 if (mNonPremult)
283 return NS_OK;
285 /* Figure out if the entire image is a constant color */
287 // this should always be true
288 if (mImageSurface->Stride() == mSize.width * 4) {
289 uint32_t *imgData = (uint32_t*) mImageSurface->Data();
290 uint32_t firstPixel = * (uint32_t*) imgData;
291 uint32_t pixelCount = mSize.width * mSize.height + 1;
293 while (--pixelCount && *imgData++ == firstPixel)
294 ;
296 if (pixelCount == 0) {
297 // all pixels were the same
298 if (mFormat == gfxImageFormat::ARGB32 ||
299 mFormat == gfxImageFormat::RGB24)
300 {
301 // Should already be premult if desired.
302 gfxRGBA::PackedColorType inputType = gfxRGBA::PACKED_XRGB;
303 if (mFormat == gfxImageFormat::ARGB32)
304 inputType = gfxRGBA::PACKED_ARGB_PREMULTIPLIED;
306 mSinglePixelColor = gfxRGBA(firstPixel, inputType);
308 mSinglePixel = true;
310 // blow away the older surfaces (if they exist), to release their memory
311 mVBuf = nullptr;
312 mImageSurface = nullptr;
313 mOptSurface = nullptr;
314 #ifdef USE_WIN_SURFACE
315 mWinSurface = nullptr;
316 #endif
317 #ifdef XP_MACOSX
318 mQuartzSurface = nullptr;
319 #endif
320 mDrawSurface = nullptr;
322 // We just dumped most of our allocated memory, so tell the discard
323 // tracker that we're not using any at all.
324 if (mInformedDiscardTracker) {
325 DiscardTracker::InformDeallocation(4 * mSize.width * mSize.height);
326 mInformedDiscardTracker = false;
327 }
329 return NS_OK;
330 }
331 }
333 // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment
334 }
336 // if we're being forced to use image surfaces due to
337 // resource constraints, don't try to optimize beyond same-pixel.
338 if (ShouldUseImageSurfaces())
339 return NS_OK;
341 mOptSurface = nullptr;
343 #ifdef USE_WIN_SURFACE
344 if (mWinSurface) {
345 if (!mFormatChanged) {
346 // just use the DIB if the format has not changed
347 mOptSurface = mWinSurface;
348 }
349 }
350 #endif
352 #ifdef XP_MACOSX
353 if (mQuartzSurface) {
354 mQuartzSurface->Flush();
355 }
356 #endif
358 #ifdef ANDROID
359 gfxImageFormat optFormat =
360 gfxPlatform::GetPlatform()->
361 OptimalFormatForContent(gfxASurface::ContentFromFormat(mFormat));
363 if (optFormat == gfxImageFormat::RGB16_565) {
364 RefPtr<VolatileBuffer> buf =
365 LockedImageSurface::AllocateBuffer(mSize, optFormat);
366 if (!buf)
367 return NS_OK;
369 nsRefPtr<gfxImageSurface> surf =
370 LockedImageSurface::CreateSurface(buf, mSize, optFormat);
372 gfxContext ctx(surf);
373 ctx.SetOperator(gfxContext::OPERATOR_SOURCE);
374 ctx.SetSource(mImageSurface);
375 ctx.Paint();
377 mImageSurface = surf;
378 mVBuf = buf;
379 mFormat = optFormat;
380 mDrawSurface = nullptr;
381 }
382 #else
383 if (mOptSurface == nullptr)
384 mOptSurface = gfxPlatform::GetPlatform()->OptimizeImage(mImageSurface, mFormat);
385 #endif
387 if (mOptSurface) {
388 mVBuf = nullptr;
389 mImageSurface = nullptr;
390 #ifdef USE_WIN_SURFACE
391 mWinSurface = nullptr;
392 #endif
393 #ifdef XP_MACOSX
394 mQuartzSurface = nullptr;
395 #endif
396 mDrawSurface = nullptr;
397 }
399 return NS_OK;
400 }
402 static void
403 DoSingleColorFastPath(gfxContext* aContext,
404 const gfxRGBA& aSinglePixelColor,
405 const gfxRect& aFill)
406 {
407 // if a == 0, it's a noop
408 if (aSinglePixelColor.a == 0.0)
409 return;
411 gfxContext::GraphicsOperator op = aContext->CurrentOperator();
412 if (op == gfxContext::OPERATOR_OVER && aSinglePixelColor.a == 1.0) {
413 aContext->SetOperator(gfxContext::OPERATOR_SOURCE);
414 }
416 aContext->SetDeviceColor(aSinglePixelColor);
417 aContext->NewPath();
418 aContext->Rectangle(aFill);
419 aContext->Fill();
420 aContext->SetOperator(op);
421 aContext->SetDeviceColor(gfxRGBA(0,0,0,0));
422 }
424 imgFrame::SurfaceWithFormat
425 imgFrame::SurfaceForDrawing(bool aDoPadding,
426 bool aDoPartialDecode,
427 bool aDoTile,
428 const nsIntMargin& aPadding,
429 gfxMatrix& aUserSpaceToImageSpace,
430 gfxRect& aFill,
431 gfxRect& aSubimage,
432 gfxRect& aSourceRect,
433 gfxRect& aImageRect,
434 gfxASurface* aSurface)
435 {
436 IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height()));
437 if (!aDoPadding && !aDoPartialDecode) {
438 NS_ASSERTION(!mSinglePixel, "This should already have been handled");
439 return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, ThebesIntSize(size)), mFormat);
440 }
442 gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height);
444 if (aDoTile || mSinglePixel) {
445 // Create a temporary surface.
446 // Give this surface an alpha channel because there are
447 // transparent pixels in the padding or undecoded area
448 gfxImageFormat format = gfxImageFormat::ARGB32;
449 nsRefPtr<gfxASurface> surface =
450 gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, gfxImageSurface::ContentFromFormat(format));
451 if (!surface || surface->CairoStatus())
452 return SurfaceWithFormat();
454 // Fill 'available' with whatever we've got
455 gfxContext tmpCtx(surface);
456 tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE);
457 if (mSinglePixel) {
458 tmpCtx.SetDeviceColor(mSinglePixelColor);
459 } else {
460 tmpCtx.SetSource(aSurface, gfxPoint(aPadding.left, aPadding.top));
461 }
462 tmpCtx.Rectangle(available);
463 tmpCtx.Fill();
465 return SurfaceWithFormat(new gfxSurfaceDrawable(surface, ThebesIntSize(size)), format);
466 }
468 // Not tiling, and we have a surface, so we can account for
469 // padding and/or a partial decode just by twiddling parameters.
470 // First, update our user-space fill rect.
471 aSourceRect = aSourceRect.Intersect(available);
472 gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace;
473 imageSpaceToUserSpace.Invert();
474 aFill = imageSpaceToUserSpace.Transform(aSourceRect);
476 aSubimage = aSubimage.Intersect(available) - gfxPoint(aPadding.left, aPadding.top);
477 aUserSpaceToImageSpace.Multiply(gfxMatrix().Translate(-gfxPoint(aPadding.left, aPadding.top)));
478 aSourceRect = aSourceRect - gfxPoint(aPadding.left, aPadding.top);
479 aImageRect = gfxRect(0, 0, mSize.width, mSize.height);
481 gfxIntSize availableSize(mDecoded.width, mDecoded.height);
482 return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, availableSize),
483 mFormat);
484 }
486 bool imgFrame::Draw(gfxContext *aContext, GraphicsFilter aFilter,
487 const gfxMatrix &aUserSpaceToImageSpace, const gfxRect& aFill,
488 const nsIntMargin &aPadding, const nsIntRect &aSubimage,
489 uint32_t aImageFlags)
490 {
491 PROFILER_LABEL("image", "imgFrame::Draw");
492 NS_ASSERTION(!aFill.IsEmpty(), "zero dest size --- fix caller");
493 NS_ASSERTION(!aSubimage.IsEmpty(), "zero source size --- fix caller");
494 NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!");
496 bool doPadding = aPadding != nsIntMargin(0,0,0,0);
497 bool doPartialDecode = !ImageComplete();
499 if (mSinglePixel && !doPadding && !doPartialDecode) {
500 DoSingleColorFastPath(aContext, mSinglePixelColor, aFill);
501 return true;
502 }
504 gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace;
505 gfxRect sourceRect = userSpaceToImageSpace.TransformBounds(aFill);
506 gfxRect imageRect(0, 0, mSize.width + aPadding.LeftRight(),
507 mSize.height + aPadding.TopBottom());
508 gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height);
509 gfxRect fill = aFill;
511 NS_ASSERTION(!sourceRect.Intersect(subimage).IsEmpty(),
512 "We must be allowed to sample *some* source pixels!");
514 nsRefPtr<gfxASurface> surf = CachedThebesSurface();
515 VolatileBufferPtr<unsigned char> ref(mVBuf);
516 if (!mSinglePixel && !surf) {
517 if (ref.WasBufferPurged()) {
518 return false;
519 }
521 surf = mDrawSurface;
522 if (!surf) {
523 long stride = gfxImageSurface::ComputeStride(mSize, mFormat);
524 nsRefPtr<gfxImageSurface> imgSurf =
525 new gfxImageSurface(ref, mSize, stride, mFormat);
526 #if defined(XP_MACOSX)
527 surf = mDrawSurface = new gfxQuartzImageSurface(imgSurf);
528 #else
529 surf = mDrawSurface = imgSurf;
530 #endif
531 }
532 if (!surf || surf->CairoStatus()) {
533 mDrawSurface = nullptr;
534 return true;
535 }
536 }
538 bool doTile = !imageRect.Contains(sourceRect) &&
539 !(aImageFlags & imgIContainer::FLAG_CLAMP);
540 SurfaceWithFormat surfaceResult =
541 SurfaceForDrawing(doPadding, doPartialDecode, doTile, aPadding,
542 userSpaceToImageSpace, fill, subimage, sourceRect,
543 imageRect, surf);
545 if (surfaceResult.IsValid()) {
546 gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable,
547 userSpaceToImageSpace,
548 subimage, sourceRect, imageRect, fill,
549 surfaceResult.mFormat, aFilter, aImageFlags);
550 }
551 return true;
552 }
554 // This can be called from any thread, but not simultaneously.
555 nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect)
556 {
557 MutexAutoLock lock(mDirtyMutex);
559 mDecoded.UnionRect(mDecoded, aUpdateRect);
561 // clamp to bounds, in case someone sends a bogus updateRect (I'm looking at
562 // you, gif decoder)
563 nsIntRect boundsRect(mOffset, mSize);
564 mDecoded.IntersectRect(mDecoded, boundsRect);
566 mDirty = true;
568 return NS_OK;
569 }
571 bool imgFrame::GetIsDirty() const
572 {
573 MutexAutoLock lock(mDirtyMutex);
574 return mDirty;
575 }
577 nsIntRect imgFrame::GetRect() const
578 {
579 return nsIntRect(mOffset, mSize);
580 }
582 gfxImageFormat imgFrame::GetFormat() const
583 {
584 return mFormat;
585 }
587 bool imgFrame::GetNeedsBackground() const
588 {
589 // We need a background painted if we have alpha or we're incomplete.
590 return (mFormat == gfxImageFormat::ARGB32 || !ImageComplete());
591 }
593 uint32_t imgFrame::GetImageBytesPerRow() const
594 {
595 if (mImageSurface)
596 return mImageSurface->Stride();
598 if (mVBuf)
599 return gfxImageSurface::ComputeStride(mSize, mFormat);
601 if (mPaletteDepth)
602 return mSize.width;
604 NS_ERROR("GetImageBytesPerRow called with mImageSurface == null, mVBuf == null and mPaletteDepth == 0");
606 return 0;
607 }
609 uint32_t imgFrame::GetImageDataLength() const
610 {
611 return GetImageBytesPerRow() * mSize.height;
612 }
614 void imgFrame::GetImageData(uint8_t **aData, uint32_t *length) const
615 {
616 NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetImageData unless frame is locked");
618 if (mImageSurface)
619 *aData = mImageSurface->Data();
620 else if (mPalettedImageData)
621 *aData = mPalettedImageData + PaletteDataLength();
622 else
623 *aData = nullptr;
625 *length = GetImageDataLength();
626 }
628 uint8_t* imgFrame::GetImageData() const
629 {
630 uint8_t *data;
631 uint32_t length;
632 GetImageData(&data, &length);
633 return data;
634 }
636 bool imgFrame::GetIsPaletted() const
637 {
638 return mPalettedImageData != nullptr;
639 }
641 bool imgFrame::GetHasAlpha() const
642 {
643 return mFormat == gfxImageFormat::ARGB32;
644 }
646 void imgFrame::GetPaletteData(uint32_t **aPalette, uint32_t *length) const
647 {
648 NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetPaletteData unless frame is locked");
650 if (!mPalettedImageData) {
651 *aPalette = nullptr;
652 *length = 0;
653 } else {
654 *aPalette = (uint32_t *) mPalettedImageData;
655 *length = PaletteDataLength();
656 }
657 }
659 uint32_t* imgFrame::GetPaletteData() const
660 {
661 uint32_t* data;
662 uint32_t length;
663 GetPaletteData(&data, &length);
664 return data;
665 }
667 nsresult imgFrame::LockImageData()
668 {
669 MOZ_ASSERT(NS_IsMainThread());
671 NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
672 if (mLockCount < 0) {
673 return NS_ERROR_FAILURE;
674 }
676 mLockCount++;
678 // If we are not the first lock, there's nothing to do.
679 if (mLockCount != 1) {
680 return NS_OK;
681 }
683 // Paletted images don't have surfaces, so there's nothing to do.
684 if (mPalettedImageData)
685 return NS_OK;
687 if (!mImageSurface) {
688 if (mVBuf) {
689 VolatileBufferPtr<uint8_t> ref(mVBuf);
690 if (ref.WasBufferPurged())
691 return NS_ERROR_FAILURE;
693 mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
694 if (!mImageSurface || mImageSurface->CairoStatus())
695 return NS_ERROR_OUT_OF_MEMORY;
696 }
697 if (mOptSurface || mSinglePixel || mFormat == gfxImageFormat::RGB16_565) {
698 gfxImageFormat format = mFormat;
699 if (mFormat == gfxImageFormat::RGB16_565)
700 format = gfxImageFormat::ARGB32;
702 // Recover the pixels
703 RefPtr<VolatileBuffer> buf =
704 LockedImageSurface::AllocateBuffer(mSize, format);
705 if (!buf) {
706 return NS_ERROR_OUT_OF_MEMORY;
707 }
709 RefPtr<gfxImageSurface> surf =
710 LockedImageSurface::CreateSurface(buf, mSize, mFormat);
711 if (!surf || surf->CairoStatus())
712 return NS_ERROR_OUT_OF_MEMORY;
714 gfxContext context(surf);
715 context.SetOperator(gfxContext::OPERATOR_SOURCE);
716 if (mSinglePixel)
717 context.SetDeviceColor(mSinglePixelColor);
718 else if (mFormat == gfxImageFormat::RGB16_565)
719 context.SetSource(mImageSurface);
720 else
721 context.SetSource(mOptSurface);
722 context.Paint();
724 mFormat = format;
725 mVBuf = buf;
726 mImageSurface = surf;
727 mOptSurface = nullptr;
728 #ifdef USE_WIN_SURFACE
729 mWinSurface = nullptr;
730 #endif
731 #ifdef XP_MACOSX
732 mQuartzSurface = nullptr;
733 #endif
734 }
735 }
737 // We might write to the bits in this image surface, so we need to make the
738 // surface ready for that.
739 if (mImageSurface)
740 mImageSurface->Flush();
742 #ifdef USE_WIN_SURFACE
743 if (mWinSurface)
744 mWinSurface->Flush();
745 #endif
747 #ifdef XP_MACOSX
748 if (!mQuartzSurface && !ShouldUseImageSurfaces()) {
749 mQuartzSurface = new gfxQuartzImageSurface(mImageSurface);
750 }
751 #endif
753 return NS_OK;
754 }
756 nsresult imgFrame::UnlockImageData()
757 {
758 MOZ_ASSERT(NS_IsMainThread());
760 NS_ABORT_IF_FALSE(mLockCount != 0, "Unlocking an unlocked image!");
761 if (mLockCount == 0) {
762 return NS_ERROR_FAILURE;
763 }
765 mLockCount--;
767 NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
768 if (mLockCount < 0) {
769 return NS_ERROR_FAILURE;
770 }
772 // If we are not the last lock, there's nothing to do.
773 if (mLockCount != 0) {
774 return NS_OK;
775 }
777 // Paletted images don't have surfaces, so there's nothing to do.
778 if (mPalettedImageData)
779 return NS_OK;
781 // FIXME: Bug 795737
782 // If this image has been drawn since we were locked, it has had snapshots
783 // added, and we need to remove them before calling MarkDirty.
784 if (mImageSurface)
785 mImageSurface->Flush();
787 #ifdef USE_WIN_SURFACE
788 if (mWinSurface)
789 mWinSurface->Flush();
790 #endif
792 // Assume we've been written to.
793 if (mImageSurface)
794 mImageSurface->MarkDirty();
796 #ifdef USE_WIN_SURFACE
797 if (mWinSurface)
798 mWinSurface->MarkDirty();
799 #endif
801 #ifdef XP_MACOSX
802 // The quartz image surface (ab)uses the flush method to get the
803 // cairo_image_surface data into a CGImage, so we have to call Flush() here.
804 if (mQuartzSurface)
805 mQuartzSurface->Flush();
806 #endif
808 if (mVBuf && mDiscardable) {
809 mImageSurface = nullptr;
810 #ifdef XP_MACOSX
811 mQuartzSurface = nullptr;
812 #endif
813 }
815 return NS_OK;
816 }
818 void imgFrame::ApplyDirtToSurfaces()
819 {
820 MOZ_ASSERT(NS_IsMainThread());
822 MutexAutoLock lock(mDirtyMutex);
823 if (mDirty) {
824 // FIXME: Bug 795737
825 // If this image has been drawn since we were locked, it has had snapshots
826 // added, and we need to remove them before calling MarkDirty.
827 if (mImageSurface)
828 mImageSurface->Flush();
830 #ifdef USE_WIN_SURFACE
831 if (mWinSurface)
832 mWinSurface->Flush();
833 #endif
835 if (mImageSurface)
836 mImageSurface->MarkDirty();
838 #ifdef USE_WIN_SURFACE
839 if (mWinSurface)
840 mWinSurface->MarkDirty();
841 #endif
843 #ifdef XP_MACOSX
844 // The quartz image surface (ab)uses the flush method to get the
845 // cairo_image_surface data into a CGImage, so we have to call Flush() here.
846 if (mQuartzSurface)
847 mQuartzSurface->Flush();
848 #endif
850 mDirty = false;
851 }
852 }
854 void imgFrame::SetDiscardable()
855 {
856 MOZ_ASSERT(mLockCount, "Expected to be locked when SetDiscardable is called");
857 // Disabled elsewhere due to the cost of calling GetSourceSurfaceForSurface.
858 #ifdef MOZ_WIDGET_ANDROID
859 mDiscardable = true;
860 #endif
861 }
863 int32_t imgFrame::GetRawTimeout() const
864 {
865 return mTimeout;
866 }
868 void imgFrame::SetRawTimeout(int32_t aTimeout)
869 {
870 mTimeout = aTimeout;
871 }
873 int32_t imgFrame::GetFrameDisposalMethod() const
874 {
875 return mDisposalMethod;
876 }
878 void imgFrame::SetFrameDisposalMethod(int32_t aFrameDisposalMethod)
879 {
880 mDisposalMethod = aFrameDisposalMethod;
881 }
883 int32_t imgFrame::GetBlendMethod() const
884 {
885 return mBlendMethod;
886 }
888 void imgFrame::SetBlendMethod(int32_t aBlendMethod)
889 {
890 mBlendMethod = (int8_t)aBlendMethod;
891 }
893 // This can be called from any thread.
894 bool imgFrame::ImageComplete() const
895 {
896 MutexAutoLock lock(mDirtyMutex);
898 return mDecoded.IsEqualInterior(nsIntRect(mOffset, mSize));
899 }
901 // A hint from the image decoders that this image has no alpha, even
902 // though we created is ARGB32. This changes our format to RGB24,
903 // which in turn will cause us to Optimize() to RGB24. Has no effect
904 // after Optimize() is called, though in all cases it will be just a
905 // performance win -- the pixels are still correct and have the A byte
906 // set to 0xff.
907 void imgFrame::SetHasNoAlpha()
908 {
909 if (mFormat == gfxImageFormat::ARGB32) {
910 mFormat = gfxImageFormat::RGB24;
911 mFormatChanged = true;
912 ThebesSurface()->SetOpaqueRect(gfxRect(0, 0, mSize.width, mSize.height));
913 }
914 }
916 void imgFrame::SetAsNonPremult(bool aIsNonPremult)
917 {
918 mNonPremult = aIsNonPremult;
919 }
921 bool imgFrame::GetCompositingFailed() const
922 {
923 return mCompositingFailed;
924 }
926 void imgFrame::SetCompositingFailed(bool val)
927 {
928 mCompositingFailed = val;
929 }
931 // If |aLocation| indicates this is heap memory, we try to measure things with
932 // |aMallocSizeOf|. If that fails (because the platform doesn't support it) or
933 // it's non-heap memory, we fall back to computing the size analytically.
934 size_t
935 imgFrame::SizeOfExcludingThisWithComputedFallbackIfHeap(gfxMemoryLocation aLocation, mozilla::MallocSizeOf aMallocSizeOf) const
936 {
937 // aMallocSizeOf is only used if aLocation==gfxMemoryLocation::IN_PROCESS_HEAP. It
938 // should be nullptr otherwise.
939 NS_ABORT_IF_FALSE(
940 (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP && aMallocSizeOf) ||
941 (aLocation != gfxMemoryLocation::IN_PROCESS_HEAP && !aMallocSizeOf),
942 "mismatch between aLocation and aMallocSizeOf");
944 size_t n = 0;
946 if (mPalettedImageData && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
947 size_t n2 = aMallocSizeOf(mPalettedImageData);
948 if (n2 == 0) {
949 n2 = GetImageDataLength() + PaletteDataLength();
950 }
951 n += n2;
952 }
954 #ifdef USE_WIN_SURFACE
955 if (mWinSurface && aLocation == mWinSurface->GetMemoryLocation()) {
956 n += mWinSurface->KnownMemoryUsed();
957 } else
958 #endif
959 #ifdef XP_MACOSX
960 if (mQuartzSurface && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
961 n += aMallocSizeOf(mQuartzSurface);
962 }
963 #endif
964 if (mImageSurface && aLocation == mImageSurface->GetMemoryLocation()) {
965 size_t n2 = 0;
966 if (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { // HEAP: measure
967 n2 = mImageSurface->SizeOfIncludingThis(aMallocSizeOf);
968 }
969 if (n2 == 0) { // non-HEAP or computed fallback for HEAP
970 n2 = mImageSurface->KnownMemoryUsed();
971 }
972 n += n2;
973 }
975 if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
976 n += aMallocSizeOf(mVBuf);
977 n += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf);
978 }
980 if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_NONHEAP) {
981 n += mVBuf->NonHeapSizeOfExcludingThis();
982 }
984 if (mOptSurface && aLocation == mOptSurface->GetMemoryLocation()) {
985 size_t n2 = 0;
986 if (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP &&
987 mOptSurface->SizeOfIsMeasured()) {
988 // HEAP: measure (but only if the sub-class is capable of measuring)
989 n2 = mOptSurface->SizeOfIncludingThis(aMallocSizeOf);
990 }
991 if (n2 == 0) { // non-HEAP or computed fallback for HEAP
992 n2 = mOptSurface->KnownMemoryUsed();
993 }
994 n += n2;
995 }
997 return n;
998 }