michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "imgFrame.h" michael@0: #include "DiscardTracker.h" michael@0: michael@0: #include "prenv.h" michael@0: michael@0: #include "gfx2DGlue.h" michael@0: #include "gfxPlatform.h" michael@0: #include "gfxUtils.h" michael@0: #include "gfxAlphaRecovery.h" michael@0: michael@0: static bool gDisableOptimize = false; michael@0: michael@0: #include "cairo.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "nsMargin.h" michael@0: #include "mozilla/CheckedInt.h" michael@0: michael@0: #if defined(XP_WIN) michael@0: michael@0: #include "gfxWindowsPlatform.h" michael@0: michael@0: /* Whether to use the windows surface; only for desktop win32 */ michael@0: #define USE_WIN_SURFACE 1 michael@0: michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: using namespace mozilla::image; michael@0: michael@0: static cairo_user_data_key_t kVolatileBuffer; michael@0: michael@0: static void michael@0: VolatileBufferRelease(void *vbuf) michael@0: { michael@0: delete static_cast*>(vbuf); michael@0: } michael@0: michael@0: gfxImageSurface * michael@0: LockedImageSurface::CreateSurface(VolatileBuffer *vbuf, michael@0: const gfxIntSize& size, michael@0: gfxImageFormat format) michael@0: { michael@0: VolatileBufferPtr *vbufptr = michael@0: new VolatileBufferPtr(vbuf); michael@0: MOZ_ASSERT(!vbufptr->WasBufferPurged(), "Expected image data!"); michael@0: michael@0: long stride = gfxImageSurface::ComputeStride(size, format); michael@0: gfxImageSurface *img = new gfxImageSurface(*vbufptr, size, stride, format); michael@0: if (!img || img->CairoStatus()) { michael@0: delete img; michael@0: delete vbufptr; michael@0: return nullptr; michael@0: } michael@0: michael@0: img->SetData(&kVolatileBuffer, vbufptr, VolatileBufferRelease); michael@0: return img; michael@0: } michael@0: michael@0: TemporaryRef michael@0: LockedImageSurface::AllocateBuffer(const gfxIntSize& size, michael@0: gfxImageFormat format) michael@0: { michael@0: long stride = gfxImageSurface::ComputeStride(size, format); michael@0: RefPtr buf = new VolatileBuffer(); michael@0: if (buf->Init(stride * size.height, michael@0: 1 << gfxAlphaRecovery::GoodAlignmentLog2())) michael@0: return buf; michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: // Returns true if an image of aWidth x aHeight is allowed and legal. michael@0: static bool AllowedImageSize(int32_t aWidth, int32_t aHeight) michael@0: { michael@0: // reject over-wide or over-tall images michael@0: const int32_t k64KLimit = 0x0000FFFF; michael@0: if (MOZ_UNLIKELY(aWidth > k64KLimit || aHeight > k64KLimit )) { michael@0: NS_WARNING("image too big"); michael@0: return false; michael@0: } michael@0: michael@0: // protect against invalid sizes michael@0: if (MOZ_UNLIKELY(aHeight <= 0 || aWidth <= 0)) { michael@0: return false; michael@0: } michael@0: michael@0: // check to make sure we don't overflow a 32-bit michael@0: CheckedInt32 requiredBytes = CheckedInt32(aWidth) * CheckedInt32(aHeight) * 4; michael@0: if (MOZ_UNLIKELY(!requiredBytes.isValid())) { michael@0: NS_WARNING("width or height too large"); michael@0: return false; michael@0: } michael@0: #if defined(XP_MACOSX) michael@0: // CoreGraphics is limited to images < 32K in *height*, so clamp all surfaces on the Mac to that height michael@0: if (MOZ_UNLIKELY(aHeight > SHRT_MAX)) { michael@0: NS_WARNING("image too big"); michael@0: return false; michael@0: } michael@0: #endif michael@0: return true; michael@0: } michael@0: michael@0: // Returns whether we should, at this time, use image surfaces instead of michael@0: // optimized platform-specific surfaces. michael@0: static bool ShouldUseImageSurfaces() michael@0: { michael@0: #if defined(USE_WIN_SURFACE) michael@0: static const DWORD kGDIObjectsHighWaterMark = 7000; michael@0: michael@0: if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() == michael@0: gfxWindowsPlatform::RENDER_DIRECT2D) { michael@0: return true; michael@0: } michael@0: michael@0: // at 7000 GDI objects, stop allocating normal images to make sure michael@0: // we never hit the 10k hard limit. michael@0: // GetCurrentProcess() just returns (HANDLE)-1, it's inlined afaik michael@0: DWORD count = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS); michael@0: if (count == 0 || michael@0: count > kGDIObjectsHighWaterMark) michael@0: { michael@0: // either something's broken (count == 0), michael@0: // or we hit our high water mark; disable michael@0: // image allocations for a bit. michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: return false; michael@0: } michael@0: michael@0: imgFrame::imgFrame() : michael@0: mDecoded(0, 0, 0, 0), michael@0: mDirtyMutex("imgFrame::mDirty"), michael@0: mPalettedImageData(nullptr), michael@0: mSinglePixelColor(0), michael@0: mTimeout(100), michael@0: mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */ michael@0: mLockCount(0), michael@0: mBlendMethod(1), /* imgIContainer::kBlendOver */ michael@0: mSinglePixel(false), michael@0: mFormatChanged(false), michael@0: mCompositingFailed(false), michael@0: mNonPremult(false), michael@0: mDiscardable(false), michael@0: mInformedDiscardTracker(false), michael@0: mDirty(false) michael@0: { michael@0: static bool hasCheckedOptimize = false; michael@0: if (!hasCheckedOptimize) { michael@0: if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) { michael@0: gDisableOptimize = true; michael@0: } michael@0: hasCheckedOptimize = true; michael@0: } michael@0: } michael@0: michael@0: imgFrame::~imgFrame() michael@0: { michael@0: moz_free(mPalettedImageData); michael@0: mPalettedImageData = nullptr; michael@0: michael@0: if (mInformedDiscardTracker) { michael@0: DiscardTracker::InformDeallocation(4 * mSize.height * mSize.width); michael@0: } michael@0: } michael@0: michael@0: nsresult imgFrame::Init(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, michael@0: gfxImageFormat aFormat, uint8_t aPaletteDepth /* = 0 */) michael@0: { michael@0: // assert for properties that should be verified by decoders, warn for properties related to bad content michael@0: if (!AllowedImageSize(aWidth, aHeight)) { michael@0: NS_WARNING("Should have legal image size"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mOffset.MoveTo(aX, aY); michael@0: mSize.SizeTo(aWidth, aHeight); michael@0: michael@0: mFormat = aFormat; michael@0: mPaletteDepth = aPaletteDepth; michael@0: michael@0: if (aPaletteDepth != 0) { michael@0: // We're creating for a paletted image. michael@0: if (aPaletteDepth > 8) { michael@0: NS_WARNING("Should have legal palette depth"); michael@0: NS_ERROR("This Depth is not supported"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Use the fallible allocator here michael@0: mPalettedImageData = (uint8_t*)moz_malloc(PaletteDataLength() + GetImageDataLength()); michael@0: if (!mPalettedImageData) michael@0: NS_WARNING("moz_malloc for paletted image data should succeed"); michael@0: NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY); michael@0: } else { michael@0: // Inform the discard tracker that we are going to allocate some memory. michael@0: if (!DiscardTracker::TryAllocation(4 * mSize.width * mSize.height)) { michael@0: NS_WARNING("Exceed the hard limit of decode image size"); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: // For Windows, we must create the device surface first (if we're michael@0: // going to) so that the image surface can wrap it. Can't be done michael@0: // the other way around. michael@0: #ifdef USE_WIN_SURFACE michael@0: if (!ShouldUseImageSurfaces()) { michael@0: mWinSurface = new gfxWindowsSurface(gfxIntSize(mSize.width, mSize.height), mFormat); michael@0: if (mWinSurface && mWinSurface->CairoStatus() == 0) { michael@0: // no error michael@0: mImageSurface = mWinSurface->GetAsImageSurface(); michael@0: } else { michael@0: mWinSurface = nullptr; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // For other platforms, space for the image surface is first allocated in michael@0: // a volatile buffer and then wrapped by a LockedImageSurface. michael@0: // This branch is also used on Windows if we're not using device surfaces michael@0: // or if we couldn't create one. michael@0: if (!mImageSurface) { michael@0: mVBuf = LockedImageSurface::AllocateBuffer(mSize, mFormat); michael@0: if (!mVBuf) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: if (mVBuf->OnHeap()) { michael@0: long stride = gfxImageSurface::ComputeStride(mSize, mFormat); michael@0: VolatileBufferPtr ptr(mVBuf); michael@0: memset(ptr, 0, stride * mSize.height); michael@0: } michael@0: mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat); michael@0: } michael@0: michael@0: if (!mImageSurface || mImageSurface->CairoStatus()) { michael@0: mImageSurface = nullptr; michael@0: // guess michael@0: if (!mImageSurface) { michael@0: NS_WARNING("Allocation of gfxImageSurface should succeed"); michael@0: } else if (!mImageSurface->CairoStatus()) { michael@0: NS_WARNING("gfxImageSurface should have good CairoStatus"); michael@0: } michael@0: michael@0: // Image surface allocation is failed, need to return michael@0: // the booked buffer size. michael@0: DiscardTracker::InformDeallocation(4 * mSize.width * mSize.height); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: mInformedDiscardTracker = true; michael@0: michael@0: #ifdef XP_MACOSX michael@0: if (!ShouldUseImageSurfaces()) { michael@0: mQuartzSurface = new gfxQuartzImageSurface(mImageSurface); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult imgFrame::Optimize() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (gDisableOptimize) michael@0: return NS_OK; michael@0: michael@0: if (mPalettedImageData || mOptSurface || mSinglePixel) michael@0: return NS_OK; michael@0: michael@0: // Don't do single-color opts on non-premult data. michael@0: // Cairo doesn't support non-premult single-colors. michael@0: if (mNonPremult) michael@0: return NS_OK; michael@0: michael@0: /* Figure out if the entire image is a constant color */ michael@0: michael@0: // this should always be true michael@0: if (mImageSurface->Stride() == mSize.width * 4) { michael@0: uint32_t *imgData = (uint32_t*) mImageSurface->Data(); michael@0: uint32_t firstPixel = * (uint32_t*) imgData; michael@0: uint32_t pixelCount = mSize.width * mSize.height + 1; michael@0: michael@0: while (--pixelCount && *imgData++ == firstPixel) michael@0: ; michael@0: michael@0: if (pixelCount == 0) { michael@0: // all pixels were the same michael@0: if (mFormat == gfxImageFormat::ARGB32 || michael@0: mFormat == gfxImageFormat::RGB24) michael@0: { michael@0: // Should already be premult if desired. michael@0: gfxRGBA::PackedColorType inputType = gfxRGBA::PACKED_XRGB; michael@0: if (mFormat == gfxImageFormat::ARGB32) michael@0: inputType = gfxRGBA::PACKED_ARGB_PREMULTIPLIED; michael@0: michael@0: mSinglePixelColor = gfxRGBA(firstPixel, inputType); michael@0: michael@0: mSinglePixel = true; michael@0: michael@0: // blow away the older surfaces (if they exist), to release their memory michael@0: mVBuf = nullptr; michael@0: mImageSurface = nullptr; michael@0: mOptSurface = nullptr; michael@0: #ifdef USE_WIN_SURFACE michael@0: mWinSurface = nullptr; michael@0: #endif michael@0: #ifdef XP_MACOSX michael@0: mQuartzSurface = nullptr; michael@0: #endif michael@0: mDrawSurface = nullptr; michael@0: michael@0: // We just dumped most of our allocated memory, so tell the discard michael@0: // tracker that we're not using any at all. michael@0: if (mInformedDiscardTracker) { michael@0: DiscardTracker::InformDeallocation(4 * mSize.width * mSize.height); michael@0: mInformedDiscardTracker = false; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment michael@0: } michael@0: michael@0: // if we're being forced to use image surfaces due to michael@0: // resource constraints, don't try to optimize beyond same-pixel. michael@0: if (ShouldUseImageSurfaces()) michael@0: return NS_OK; michael@0: michael@0: mOptSurface = nullptr; michael@0: michael@0: #ifdef USE_WIN_SURFACE michael@0: if (mWinSurface) { michael@0: if (!mFormatChanged) { michael@0: // just use the DIB if the format has not changed michael@0: mOptSurface = mWinSurface; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: #ifdef XP_MACOSX michael@0: if (mQuartzSurface) { michael@0: mQuartzSurface->Flush(); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef ANDROID michael@0: gfxImageFormat optFormat = michael@0: gfxPlatform::GetPlatform()-> michael@0: OptimalFormatForContent(gfxASurface::ContentFromFormat(mFormat)); michael@0: michael@0: if (optFormat == gfxImageFormat::RGB16_565) { michael@0: RefPtr buf = michael@0: LockedImageSurface::AllocateBuffer(mSize, optFormat); michael@0: if (!buf) michael@0: return NS_OK; michael@0: michael@0: nsRefPtr surf = michael@0: LockedImageSurface::CreateSurface(buf, mSize, optFormat); michael@0: michael@0: gfxContext ctx(surf); michael@0: ctx.SetOperator(gfxContext::OPERATOR_SOURCE); michael@0: ctx.SetSource(mImageSurface); michael@0: ctx.Paint(); michael@0: michael@0: mImageSurface = surf; michael@0: mVBuf = buf; michael@0: mFormat = optFormat; michael@0: mDrawSurface = nullptr; michael@0: } michael@0: #else michael@0: if (mOptSurface == nullptr) michael@0: mOptSurface = gfxPlatform::GetPlatform()->OptimizeImage(mImageSurface, mFormat); michael@0: #endif michael@0: michael@0: if (mOptSurface) { michael@0: mVBuf = nullptr; michael@0: mImageSurface = nullptr; michael@0: #ifdef USE_WIN_SURFACE michael@0: mWinSurface = nullptr; michael@0: #endif michael@0: #ifdef XP_MACOSX michael@0: mQuartzSurface = nullptr; michael@0: #endif michael@0: mDrawSurface = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void michael@0: DoSingleColorFastPath(gfxContext* aContext, michael@0: const gfxRGBA& aSinglePixelColor, michael@0: const gfxRect& aFill) michael@0: { michael@0: // if a == 0, it's a noop michael@0: if (aSinglePixelColor.a == 0.0) michael@0: return; michael@0: michael@0: gfxContext::GraphicsOperator op = aContext->CurrentOperator(); michael@0: if (op == gfxContext::OPERATOR_OVER && aSinglePixelColor.a == 1.0) { michael@0: aContext->SetOperator(gfxContext::OPERATOR_SOURCE); michael@0: } michael@0: michael@0: aContext->SetDeviceColor(aSinglePixelColor); michael@0: aContext->NewPath(); michael@0: aContext->Rectangle(aFill); michael@0: aContext->Fill(); michael@0: aContext->SetOperator(op); michael@0: aContext->SetDeviceColor(gfxRGBA(0,0,0,0)); michael@0: } michael@0: michael@0: imgFrame::SurfaceWithFormat michael@0: imgFrame::SurfaceForDrawing(bool aDoPadding, michael@0: bool aDoPartialDecode, michael@0: bool aDoTile, michael@0: const nsIntMargin& aPadding, michael@0: gfxMatrix& aUserSpaceToImageSpace, michael@0: gfxRect& aFill, michael@0: gfxRect& aSubimage, michael@0: gfxRect& aSourceRect, michael@0: gfxRect& aImageRect, michael@0: gfxASurface* aSurface) michael@0: { michael@0: IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height())); michael@0: if (!aDoPadding && !aDoPartialDecode) { michael@0: NS_ASSERTION(!mSinglePixel, "This should already have been handled"); michael@0: return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, ThebesIntSize(size)), mFormat); michael@0: } michael@0: michael@0: gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height); michael@0: michael@0: if (aDoTile || mSinglePixel) { michael@0: // Create a temporary surface. michael@0: // Give this surface an alpha channel because there are michael@0: // transparent pixels in the padding or undecoded area michael@0: gfxImageFormat format = gfxImageFormat::ARGB32; michael@0: nsRefPtr surface = michael@0: gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, gfxImageSurface::ContentFromFormat(format)); michael@0: if (!surface || surface->CairoStatus()) michael@0: return SurfaceWithFormat(); michael@0: michael@0: // Fill 'available' with whatever we've got michael@0: gfxContext tmpCtx(surface); michael@0: tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE); michael@0: if (mSinglePixel) { michael@0: tmpCtx.SetDeviceColor(mSinglePixelColor); michael@0: } else { michael@0: tmpCtx.SetSource(aSurface, gfxPoint(aPadding.left, aPadding.top)); michael@0: } michael@0: tmpCtx.Rectangle(available); michael@0: tmpCtx.Fill(); michael@0: michael@0: return SurfaceWithFormat(new gfxSurfaceDrawable(surface, ThebesIntSize(size)), format); michael@0: } michael@0: michael@0: // Not tiling, and we have a surface, so we can account for michael@0: // padding and/or a partial decode just by twiddling parameters. michael@0: // First, update our user-space fill rect. michael@0: aSourceRect = aSourceRect.Intersect(available); michael@0: gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace; michael@0: imageSpaceToUserSpace.Invert(); michael@0: aFill = imageSpaceToUserSpace.Transform(aSourceRect); michael@0: michael@0: aSubimage = aSubimage.Intersect(available) - gfxPoint(aPadding.left, aPadding.top); michael@0: aUserSpaceToImageSpace.Multiply(gfxMatrix().Translate(-gfxPoint(aPadding.left, aPadding.top))); michael@0: aSourceRect = aSourceRect - gfxPoint(aPadding.left, aPadding.top); michael@0: aImageRect = gfxRect(0, 0, mSize.width, mSize.height); michael@0: michael@0: gfxIntSize availableSize(mDecoded.width, mDecoded.height); michael@0: return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, availableSize), michael@0: mFormat); michael@0: } michael@0: michael@0: bool imgFrame::Draw(gfxContext *aContext, GraphicsFilter aFilter, michael@0: const gfxMatrix &aUserSpaceToImageSpace, const gfxRect& aFill, michael@0: const nsIntMargin &aPadding, const nsIntRect &aSubimage, michael@0: uint32_t aImageFlags) michael@0: { michael@0: PROFILER_LABEL("image", "imgFrame::Draw"); michael@0: NS_ASSERTION(!aFill.IsEmpty(), "zero dest size --- fix caller"); michael@0: NS_ASSERTION(!aSubimage.IsEmpty(), "zero source size --- fix caller"); michael@0: NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!"); michael@0: michael@0: bool doPadding = aPadding != nsIntMargin(0,0,0,0); michael@0: bool doPartialDecode = !ImageComplete(); michael@0: michael@0: if (mSinglePixel && !doPadding && !doPartialDecode) { michael@0: DoSingleColorFastPath(aContext, mSinglePixelColor, aFill); michael@0: return true; michael@0: } michael@0: michael@0: gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace; michael@0: gfxRect sourceRect = userSpaceToImageSpace.TransformBounds(aFill); michael@0: gfxRect imageRect(0, 0, mSize.width + aPadding.LeftRight(), michael@0: mSize.height + aPadding.TopBottom()); michael@0: gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height); michael@0: gfxRect fill = aFill; michael@0: michael@0: NS_ASSERTION(!sourceRect.Intersect(subimage).IsEmpty(), michael@0: "We must be allowed to sample *some* source pixels!"); michael@0: michael@0: nsRefPtr surf = CachedThebesSurface(); michael@0: VolatileBufferPtr ref(mVBuf); michael@0: if (!mSinglePixel && !surf) { michael@0: if (ref.WasBufferPurged()) { michael@0: return false; michael@0: } michael@0: michael@0: surf = mDrawSurface; michael@0: if (!surf) { michael@0: long stride = gfxImageSurface::ComputeStride(mSize, mFormat); michael@0: nsRefPtr imgSurf = michael@0: new gfxImageSurface(ref, mSize, stride, mFormat); michael@0: #if defined(XP_MACOSX) michael@0: surf = mDrawSurface = new gfxQuartzImageSurface(imgSurf); michael@0: #else michael@0: surf = mDrawSurface = imgSurf; michael@0: #endif michael@0: } michael@0: if (!surf || surf->CairoStatus()) { michael@0: mDrawSurface = nullptr; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: bool doTile = !imageRect.Contains(sourceRect) && michael@0: !(aImageFlags & imgIContainer::FLAG_CLAMP); michael@0: SurfaceWithFormat surfaceResult = michael@0: SurfaceForDrawing(doPadding, doPartialDecode, doTile, aPadding, michael@0: userSpaceToImageSpace, fill, subimage, sourceRect, michael@0: imageRect, surf); michael@0: michael@0: if (surfaceResult.IsValid()) { michael@0: gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable, michael@0: userSpaceToImageSpace, michael@0: subimage, sourceRect, imageRect, fill, michael@0: surfaceResult.mFormat, aFilter, aImageFlags); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // This can be called from any thread, but not simultaneously. michael@0: nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect) michael@0: { michael@0: MutexAutoLock lock(mDirtyMutex); michael@0: michael@0: mDecoded.UnionRect(mDecoded, aUpdateRect); michael@0: michael@0: // clamp to bounds, in case someone sends a bogus updateRect (I'm looking at michael@0: // you, gif decoder) michael@0: nsIntRect boundsRect(mOffset, mSize); michael@0: mDecoded.IntersectRect(mDecoded, boundsRect); michael@0: michael@0: mDirty = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool imgFrame::GetIsDirty() const michael@0: { michael@0: MutexAutoLock lock(mDirtyMutex); michael@0: return mDirty; michael@0: } michael@0: michael@0: nsIntRect imgFrame::GetRect() const michael@0: { michael@0: return nsIntRect(mOffset, mSize); michael@0: } michael@0: michael@0: gfxImageFormat imgFrame::GetFormat() const michael@0: { michael@0: return mFormat; michael@0: } michael@0: michael@0: bool imgFrame::GetNeedsBackground() const michael@0: { michael@0: // We need a background painted if we have alpha or we're incomplete. michael@0: return (mFormat == gfxImageFormat::ARGB32 || !ImageComplete()); michael@0: } michael@0: michael@0: uint32_t imgFrame::GetImageBytesPerRow() const michael@0: { michael@0: if (mImageSurface) michael@0: return mImageSurface->Stride(); michael@0: michael@0: if (mVBuf) michael@0: return gfxImageSurface::ComputeStride(mSize, mFormat); michael@0: michael@0: if (mPaletteDepth) michael@0: return mSize.width; michael@0: michael@0: NS_ERROR("GetImageBytesPerRow called with mImageSurface == null, mVBuf == null and mPaletteDepth == 0"); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: uint32_t imgFrame::GetImageDataLength() const michael@0: { michael@0: return GetImageBytesPerRow() * mSize.height; michael@0: } michael@0: michael@0: void imgFrame::GetImageData(uint8_t **aData, uint32_t *length) const michael@0: { michael@0: NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetImageData unless frame is locked"); michael@0: michael@0: if (mImageSurface) michael@0: *aData = mImageSurface->Data(); michael@0: else if (mPalettedImageData) michael@0: *aData = mPalettedImageData + PaletteDataLength(); michael@0: else michael@0: *aData = nullptr; michael@0: michael@0: *length = GetImageDataLength(); michael@0: } michael@0: michael@0: uint8_t* imgFrame::GetImageData() const michael@0: { michael@0: uint8_t *data; michael@0: uint32_t length; michael@0: GetImageData(&data, &length); michael@0: return data; michael@0: } michael@0: michael@0: bool imgFrame::GetIsPaletted() const michael@0: { michael@0: return mPalettedImageData != nullptr; michael@0: } michael@0: michael@0: bool imgFrame::GetHasAlpha() const michael@0: { michael@0: return mFormat == gfxImageFormat::ARGB32; michael@0: } michael@0: michael@0: void imgFrame::GetPaletteData(uint32_t **aPalette, uint32_t *length) const michael@0: { michael@0: NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetPaletteData unless frame is locked"); michael@0: michael@0: if (!mPalettedImageData) { michael@0: *aPalette = nullptr; michael@0: *length = 0; michael@0: } else { michael@0: *aPalette = (uint32_t *) mPalettedImageData; michael@0: *length = PaletteDataLength(); michael@0: } michael@0: } michael@0: michael@0: uint32_t* imgFrame::GetPaletteData() const michael@0: { michael@0: uint32_t* data; michael@0: uint32_t length; michael@0: GetPaletteData(&data, &length); michael@0: return data; michael@0: } michael@0: michael@0: nsresult imgFrame::LockImageData() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks"); michael@0: if (mLockCount < 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mLockCount++; michael@0: michael@0: // If we are not the first lock, there's nothing to do. michael@0: if (mLockCount != 1) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Paletted images don't have surfaces, so there's nothing to do. michael@0: if (mPalettedImageData) michael@0: return NS_OK; michael@0: michael@0: if (!mImageSurface) { michael@0: if (mVBuf) { michael@0: VolatileBufferPtr ref(mVBuf); michael@0: if (ref.WasBufferPurged()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat); michael@0: if (!mImageSurface || mImageSurface->CairoStatus()) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: if (mOptSurface || mSinglePixel || mFormat == gfxImageFormat::RGB16_565) { michael@0: gfxImageFormat format = mFormat; michael@0: if (mFormat == gfxImageFormat::RGB16_565) michael@0: format = gfxImageFormat::ARGB32; michael@0: michael@0: // Recover the pixels michael@0: RefPtr buf = michael@0: LockedImageSurface::AllocateBuffer(mSize, format); michael@0: if (!buf) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: RefPtr surf = michael@0: LockedImageSurface::CreateSurface(buf, mSize, mFormat); michael@0: if (!surf || surf->CairoStatus()) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: gfxContext context(surf); michael@0: context.SetOperator(gfxContext::OPERATOR_SOURCE); michael@0: if (mSinglePixel) michael@0: context.SetDeviceColor(mSinglePixelColor); michael@0: else if (mFormat == gfxImageFormat::RGB16_565) michael@0: context.SetSource(mImageSurface); michael@0: else michael@0: context.SetSource(mOptSurface); michael@0: context.Paint(); michael@0: michael@0: mFormat = format; michael@0: mVBuf = buf; michael@0: mImageSurface = surf; michael@0: mOptSurface = nullptr; michael@0: #ifdef USE_WIN_SURFACE michael@0: mWinSurface = nullptr; michael@0: #endif michael@0: #ifdef XP_MACOSX michael@0: mQuartzSurface = nullptr; michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: // We might write to the bits in this image surface, so we need to make the michael@0: // surface ready for that. michael@0: if (mImageSurface) michael@0: mImageSurface->Flush(); michael@0: michael@0: #ifdef USE_WIN_SURFACE michael@0: if (mWinSurface) michael@0: mWinSurface->Flush(); michael@0: #endif michael@0: michael@0: #ifdef XP_MACOSX michael@0: if (!mQuartzSurface && !ShouldUseImageSurfaces()) { michael@0: mQuartzSurface = new gfxQuartzImageSurface(mImageSurface); michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult imgFrame::UnlockImageData() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: NS_ABORT_IF_FALSE(mLockCount != 0, "Unlocking an unlocked image!"); michael@0: if (mLockCount == 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mLockCount--; michael@0: michael@0: NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks"); michael@0: if (mLockCount < 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // If we are not the last lock, there's nothing to do. michael@0: if (mLockCount != 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Paletted images don't have surfaces, so there's nothing to do. michael@0: if (mPalettedImageData) michael@0: return NS_OK; michael@0: michael@0: // FIXME: Bug 795737 michael@0: // If this image has been drawn since we were locked, it has had snapshots michael@0: // added, and we need to remove them before calling MarkDirty. michael@0: if (mImageSurface) michael@0: mImageSurface->Flush(); michael@0: michael@0: #ifdef USE_WIN_SURFACE michael@0: if (mWinSurface) michael@0: mWinSurface->Flush(); michael@0: #endif michael@0: michael@0: // Assume we've been written to. michael@0: if (mImageSurface) michael@0: mImageSurface->MarkDirty(); michael@0: michael@0: #ifdef USE_WIN_SURFACE michael@0: if (mWinSurface) michael@0: mWinSurface->MarkDirty(); michael@0: #endif michael@0: michael@0: #ifdef XP_MACOSX michael@0: // The quartz image surface (ab)uses the flush method to get the michael@0: // cairo_image_surface data into a CGImage, so we have to call Flush() here. michael@0: if (mQuartzSurface) michael@0: mQuartzSurface->Flush(); michael@0: #endif michael@0: michael@0: if (mVBuf && mDiscardable) { michael@0: mImageSurface = nullptr; michael@0: #ifdef XP_MACOSX michael@0: mQuartzSurface = nullptr; michael@0: #endif michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void imgFrame::ApplyDirtToSurfaces() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: MutexAutoLock lock(mDirtyMutex); michael@0: if (mDirty) { michael@0: // FIXME: Bug 795737 michael@0: // If this image has been drawn since we were locked, it has had snapshots michael@0: // added, and we need to remove them before calling MarkDirty. michael@0: if (mImageSurface) michael@0: mImageSurface->Flush(); michael@0: michael@0: #ifdef USE_WIN_SURFACE michael@0: if (mWinSurface) michael@0: mWinSurface->Flush(); michael@0: #endif michael@0: michael@0: if (mImageSurface) michael@0: mImageSurface->MarkDirty(); michael@0: michael@0: #ifdef USE_WIN_SURFACE michael@0: if (mWinSurface) michael@0: mWinSurface->MarkDirty(); michael@0: #endif michael@0: michael@0: #ifdef XP_MACOSX michael@0: // The quartz image surface (ab)uses the flush method to get the michael@0: // cairo_image_surface data into a CGImage, so we have to call Flush() here. michael@0: if (mQuartzSurface) michael@0: mQuartzSurface->Flush(); michael@0: #endif michael@0: michael@0: mDirty = false; michael@0: } michael@0: } michael@0: michael@0: void imgFrame::SetDiscardable() michael@0: { michael@0: MOZ_ASSERT(mLockCount, "Expected to be locked when SetDiscardable is called"); michael@0: // Disabled elsewhere due to the cost of calling GetSourceSurfaceForSurface. michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: mDiscardable = true; michael@0: #endif michael@0: } michael@0: michael@0: int32_t imgFrame::GetRawTimeout() const michael@0: { michael@0: return mTimeout; michael@0: } michael@0: michael@0: void imgFrame::SetRawTimeout(int32_t aTimeout) michael@0: { michael@0: mTimeout = aTimeout; michael@0: } michael@0: michael@0: int32_t imgFrame::GetFrameDisposalMethod() const michael@0: { michael@0: return mDisposalMethod; michael@0: } michael@0: michael@0: void imgFrame::SetFrameDisposalMethod(int32_t aFrameDisposalMethod) michael@0: { michael@0: mDisposalMethod = aFrameDisposalMethod; michael@0: } michael@0: michael@0: int32_t imgFrame::GetBlendMethod() const michael@0: { michael@0: return mBlendMethod; michael@0: } michael@0: michael@0: void imgFrame::SetBlendMethod(int32_t aBlendMethod) michael@0: { michael@0: mBlendMethod = (int8_t)aBlendMethod; michael@0: } michael@0: michael@0: // This can be called from any thread. michael@0: bool imgFrame::ImageComplete() const michael@0: { michael@0: MutexAutoLock lock(mDirtyMutex); michael@0: michael@0: return mDecoded.IsEqualInterior(nsIntRect(mOffset, mSize)); michael@0: } michael@0: michael@0: // A hint from the image decoders that this image has no alpha, even michael@0: // though we created is ARGB32. This changes our format to RGB24, michael@0: // which in turn will cause us to Optimize() to RGB24. Has no effect michael@0: // after Optimize() is called, though in all cases it will be just a michael@0: // performance win -- the pixels are still correct and have the A byte michael@0: // set to 0xff. michael@0: void imgFrame::SetHasNoAlpha() michael@0: { michael@0: if (mFormat == gfxImageFormat::ARGB32) { michael@0: mFormat = gfxImageFormat::RGB24; michael@0: mFormatChanged = true; michael@0: ThebesSurface()->SetOpaqueRect(gfxRect(0, 0, mSize.width, mSize.height)); michael@0: } michael@0: } michael@0: michael@0: void imgFrame::SetAsNonPremult(bool aIsNonPremult) michael@0: { michael@0: mNonPremult = aIsNonPremult; michael@0: } michael@0: michael@0: bool imgFrame::GetCompositingFailed() const michael@0: { michael@0: return mCompositingFailed; michael@0: } michael@0: michael@0: void imgFrame::SetCompositingFailed(bool val) michael@0: { michael@0: mCompositingFailed = val; michael@0: } michael@0: michael@0: // If |aLocation| indicates this is heap memory, we try to measure things with michael@0: // |aMallocSizeOf|. If that fails (because the platform doesn't support it) or michael@0: // it's non-heap memory, we fall back to computing the size analytically. michael@0: size_t michael@0: imgFrame::SizeOfExcludingThisWithComputedFallbackIfHeap(gfxMemoryLocation aLocation, mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: // aMallocSizeOf is only used if aLocation==gfxMemoryLocation::IN_PROCESS_HEAP. It michael@0: // should be nullptr otherwise. michael@0: NS_ABORT_IF_FALSE( michael@0: (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP && aMallocSizeOf) || michael@0: (aLocation != gfxMemoryLocation::IN_PROCESS_HEAP && !aMallocSizeOf), michael@0: "mismatch between aLocation and aMallocSizeOf"); michael@0: michael@0: size_t n = 0; michael@0: michael@0: if (mPalettedImageData && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { michael@0: size_t n2 = aMallocSizeOf(mPalettedImageData); michael@0: if (n2 == 0) { michael@0: n2 = GetImageDataLength() + PaletteDataLength(); michael@0: } michael@0: n += n2; michael@0: } michael@0: michael@0: #ifdef USE_WIN_SURFACE michael@0: if (mWinSurface && aLocation == mWinSurface->GetMemoryLocation()) { michael@0: n += mWinSurface->KnownMemoryUsed(); michael@0: } else michael@0: #endif michael@0: #ifdef XP_MACOSX michael@0: if (mQuartzSurface && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { michael@0: n += aMallocSizeOf(mQuartzSurface); michael@0: } michael@0: #endif michael@0: if (mImageSurface && aLocation == mImageSurface->GetMemoryLocation()) { michael@0: size_t n2 = 0; michael@0: if (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { // HEAP: measure michael@0: n2 = mImageSurface->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: if (n2 == 0) { // non-HEAP or computed fallback for HEAP michael@0: n2 = mImageSurface->KnownMemoryUsed(); michael@0: } michael@0: n += n2; michael@0: } michael@0: michael@0: if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { michael@0: n += aMallocSizeOf(mVBuf); michael@0: n += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_NONHEAP) { michael@0: n += mVBuf->NonHeapSizeOfExcludingThis(); michael@0: } michael@0: michael@0: if (mOptSurface && aLocation == mOptSurface->GetMemoryLocation()) { michael@0: size_t n2 = 0; michael@0: if (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP && michael@0: mOptSurface->SizeOfIsMeasured()) { michael@0: // HEAP: measure (but only if the sub-class is capable of measuring) michael@0: n2 = mOptSurface->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: if (n2 == 0) { // non-HEAP or computed fallback for HEAP michael@0: n2 = mOptSurface->KnownMemoryUsed(); michael@0: } michael@0: n += n2; michael@0: } michael@0: michael@0: return n; michael@0: }