/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "imgFrame.h" #include "ImageRegion.h" #include "ShutdownTracker.h" #include "prenv.h" #include "gfx2DGlue.h" #include "gfxPlatform.h" #include "gfxUtils.h" #include "gfxAlphaRecovery.h" static bool gDisableOptimize = false; #include "GeckoProfiler.h" #include "mozilla/Likely.h" #include "MainThreadUtils.h" #include "mozilla/MemoryReporting.h" #include "nsMargin.h" #include "nsThreadUtils.h" #include "mozilla/CheckedInt.h" #include "mozilla/gfx/Tools.h" namespace mozilla { using namespace gfx; namespace image { static UserDataKey kVolatileBuffer; static void VolatileBufferRelease(void* vbuf) { delete static_cast*>(vbuf); } static int32_t VolatileSurfaceStride(const IntSize& size, SurfaceFormat format) { // Stride must be a multiple of four or cairo will complain. return (size.width * BytesPerPixel(format) + 0x3) & ~0x3; } static already_AddRefed CreateLockedSurface(VolatileBuffer* vbuf, const IntSize& size, SurfaceFormat format) { VolatileBufferPtr* vbufptr = new VolatileBufferPtr(vbuf); MOZ_ASSERT(!vbufptr->WasBufferPurged(), "Expected image data!"); int32_t stride = VolatileSurfaceStride(size, format); RefPtr surf = Factory::CreateWrappingDataSourceSurface(*vbufptr, stride, size, format); if (!surf) { delete vbufptr; return nullptr; } surf->AddUserData(&kVolatileBuffer, vbufptr, VolatileBufferRelease); return surf.forget(); } static already_AddRefed AllocateBufferForImage(const IntSize& size, SurfaceFormat format) { int32_t stride = VolatileSurfaceStride(size, format); RefPtr buf = new VolatileBuffer(); if (buf->Init(stride * size.height, 1 << gfxAlphaRecovery::GoodAlignmentLog2())) { return buf.forget(); } return nullptr; } // Returns true if an image of aWidth x aHeight is allowed and legal. static bool AllowedImageSize(int32_t aWidth, int32_t aHeight) { // reject over-wide or over-tall images const int32_t k64KLimit = 0x0000FFFF; if (MOZ_UNLIKELY(aWidth > k64KLimit || aHeight > k64KLimit )) { NS_WARNING("image too big"); return false; } // protect against invalid sizes if (MOZ_UNLIKELY(aHeight <= 0 || aWidth <= 0)) { return false; } // check to make sure we don't overflow a 32-bit CheckedInt32 requiredBytes = CheckedInt32(aWidth) * CheckedInt32(aHeight) * 4; if (MOZ_UNLIKELY(!requiredBytes.isValid())) { NS_WARNING("width or height too large"); return false; } #if defined(XP_MACOSX) // CoreGraphics is limited to images < 32K in *height*, so clamp all surfaces // on the Mac to that height if (MOZ_UNLIKELY(aHeight > SHRT_MAX)) { NS_WARNING("image too big"); return false; } #endif return true; } static bool AllowedImageAndFrameDimensions(const nsIntSize& aImageSize, const nsIntRect& aFrameRect) { if (!AllowedImageSize(aImageSize.width, aImageSize.height)) { return false; } if (!AllowedImageSize(aFrameRect.width, aFrameRect.height)) { return false; } nsIntRect imageRect(0, 0, aImageSize.width, aImageSize.height); if (!imageRect.Contains(aFrameRect)) { NS_WARNING("Animated image frame does not fit inside bounds of image"); } return true; } imgFrame::imgFrame() : mMonitor("imgFrame") , mDecoded(0, 0, 0, 0) , mLockCount(0) , mTimeout(100) , mDisposalMethod(DisposalMethod::NOT_SPECIFIED) , mBlendMethod(BlendMethod::OVER) , mHasNoAlpha(false) , mAborted(false) , mOptimizable(false) , mPalettedImageData(nullptr) , mPaletteDepth(0) , mNonPremult(false) , mSinglePixel(false) , mCompositingFailed(false) { static bool hasCheckedOptimize = false; if (!hasCheckedOptimize) { if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) { gDisableOptimize = true; } hasCheckedOptimize = true; } } imgFrame::~imgFrame() { #ifdef DEBUG MonitorAutoLock lock(mMonitor); MOZ_ASSERT(mAborted || IsImageCompleteInternal()); #endif free(mPalettedImageData); mPalettedImageData = nullptr; } nsresult imgFrame::InitForDecoder(const nsIntSize& aImageSize, const nsIntRect& aRect, SurfaceFormat aFormat, uint8_t aPaletteDepth /* = 0 */, bool aNonPremult /* = false */) { // Assert for properties that should be verified by decoders, // warn for properties related to bad content. if (!AllowedImageAndFrameDimensions(aImageSize, aRect)) { NS_WARNING("Should have legal image size"); mAborted = true; return NS_ERROR_FAILURE; } mImageSize = aImageSize; mOffset.MoveTo(aRect.x, aRect.y); mSize.SizeTo(aRect.width, aRect.height); mFormat = aFormat; mPaletteDepth = aPaletteDepth; mNonPremult = aNonPremult; if (aPaletteDepth != 0) { // We're creating for a paletted image. if (aPaletteDepth > 8) { NS_WARNING("Should have legal palette depth"); NS_ERROR("This Depth is not supported"); mAborted = true; return NS_ERROR_FAILURE; } // Use the fallible allocator here. Paletted images always use 1 byte per // pixel, so calculating the amount of memory we need is straightforward. mPalettedImageData = static_cast(malloc(PaletteDataLength() + (mSize.width * mSize.height))); if (!mPalettedImageData) { NS_WARNING("malloc for paletted image data should succeed"); } NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY); } else { MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitForDecoder() twice?"); mVBuf = AllocateBufferForImage(mSize, mFormat); if (!mVBuf) { mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } if (mVBuf->OnHeap()) { int32_t stride = VolatileSurfaceStride(mSize, mFormat); VolatileBufferPtr ptr(mVBuf); memset(ptr, 0, stride * mSize.height); } mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat); if (!mImageSurface) { NS_WARNING("Failed to create VolatileDataSourceSurface"); mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } } return NS_OK; } nsresult imgFrame::InitWithDrawable(gfxDrawable* aDrawable, const nsIntSize& aSize, const SurfaceFormat aFormat, Filter aFilter, uint32_t aImageFlags) { // Assert for properties that should be verified by decoders, // warn for properties related to bad content. if (!AllowedImageSize(aSize.width, aSize.height)) { NS_WARNING("Should have legal image size"); mAborted = true; return NS_ERROR_FAILURE; } mImageSize = aSize; mOffset.MoveTo(0, 0); mSize.SizeTo(aSize.width, aSize.height); mFormat = aFormat; mPaletteDepth = 0; RefPtr target; #if(0) // always true bool canUseDataSurface = gfxPlatform::GetPlatform()->CanRenderContentToDataSurface(); if (canUseDataSurface) { #endif // It's safe to use data surfaces for content on this platform, so we can // get away with using volatile buffers. MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitWithDrawable() twice?"); mVBuf = AllocateBufferForImage(mSize, mFormat); if (!mVBuf) { mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } int32_t stride = VolatileSurfaceStride(mSize, mFormat); VolatileBufferPtr ptr(mVBuf); if (!ptr) { mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } if (mVBuf->OnHeap()) { memset(ptr, 0, stride * mSize.height); } mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat); target = gfxPlatform::GetPlatform()-> CreateDrawTargetForData(ptr, mSize, stride, mFormat); #if(0) } else { // We can't use data surfaces for content, so we'll create an offscreen // surface instead. This means if someone later calls RawAccessRef(), we // may have to do an expensive readback, but we warned callers about that in // the documentation for this method. MOZ_ASSERT(!mOptSurface, "Called imgFrame::InitWithDrawable() twice?"); target = gfxPlatform::GetPlatform()-> CreateOffscreenContentDrawTarget(mSize, mFormat); } #endif if (MOZ_UNLIKELY(!target)) { mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } // Draw using the drawable the caller provided. nsIntRect imageRect(0, 0, mSize.width, mSize.height); RefPtr ctx = new gfxContext(target); gfxUtils::DrawPixelSnapped(ctx, aDrawable, mSize, ImageRegion::Create(ThebesRect(imageRect)), mFormat, aFilter, aImageFlags); #if(0) if (canUseDataSurface && !mImageSurface) { NS_WARNING("Failed to create VolatileDataSourceSurface"); mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } if (!canUseDataSurface) { // We used an offscreen surface, which is an "optimized" surface from // imgFrame's perspective. mOptSurface = target->Snapshot(); } #else if (MOZ_UNLIKELY(!mImageSurface)) { NS_WARNING("Failed to create VolatileDataSourceSurface"); mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } #endif // If we reach this point, we should regard ourselves as complete. mDecoded = GetRect(); MOZ_ASSERT(IsImageComplete()); return NS_OK; } nsresult imgFrame::Optimize() { MOZ_ASSERT(NS_IsMainThread()); mMonitor.AssertCurrentThreadOwns(); MOZ_ASSERT(mLockCount == 1, "Should only optimize when holding the lock exclusively"); // Don't optimize during shutdown because gfxPlatform may not be available. if (ShutdownTracker::ShutdownHasStarted()) { return NS_OK; } if (!mOptimizable || gDisableOptimize) { return NS_OK; } if (mPalettedImageData || mOptSurface || mSinglePixel) { return NS_OK; } // Don't do single-color opts on non-premult data. // Cairo doesn't support non-premult single-colors. if (mNonPremult) { return NS_OK; } /* Figure out if the entire image is a constant color */ if (gfxPrefs::ImageSingleColorOptimizationEnabled() && mImageSurface->Stride() == mSize.width * 4) { uint32_t* imgData = (uint32_t*) ((uint8_t*) mVBufPtr); uint32_t firstPixel = * (uint32_t*) imgData; uint32_t pixelCount = mSize.width * mSize.height + 1; // TenFourFox kludge: // If the first pixel is black, then don't optimize (either it is all // black, and will hit issue 132, or it isn't, and we don't optimize // anyway). if (!(firstPixel & 0x00ffffff)) { // assume ARGB or XRGB return NS_OK; } while (--pixelCount && *imgData++ == firstPixel) ; if (pixelCount == 0) { // all pixels were the same if (mFormat == SurfaceFormat::B8G8R8A8 || mFormat == SurfaceFormat::B8G8R8X8) { mSinglePixel = true; mSinglePixelColor.a = ((firstPixel >> 24) & 0xFF) * (1.0f / 255.0f); mSinglePixelColor.r = ((firstPixel >> 16) & 0xFF) * (1.0f / 255.0f); mSinglePixelColor.g = ((firstPixel >> 8) & 0xFF) * (1.0f / 255.0f); mSinglePixelColor.b = ((firstPixel >> 0) & 0xFF) * (1.0f / 255.0f); mSinglePixelColor.r /= mSinglePixelColor.a; mSinglePixelColor.g /= mSinglePixelColor.a; mSinglePixelColor.b /= mSinglePixelColor.a; // blow away the older surfaces (if they exist), to release their memory mVBuf = nullptr; mVBufPtr = nullptr; mImageSurface = nullptr; mOptSurface = nullptr; return NS_OK; } } // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the // moment } #ifdef ANDROID SurfaceFormat optFormat = gfxPlatform::GetPlatform() ->Optimal2DFormatForContent(gfxContentType::COLOR); if (mFormat != SurfaceFormat::B8G8R8A8 && optFormat == SurfaceFormat::R5G6B5_UINT16) { RefPtr buf = AllocateBufferForImage(mSize, optFormat); if (!buf) { return NS_OK; } RefPtr surf = CreateLockedSurface(buf, mSize, optFormat); if (!surf) { return NS_ERROR_OUT_OF_MEMORY; } DataSourceSurface::MappedSurface mapping; if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) { gfxCriticalError() << "imgFrame::Optimize failed to map surface"; return NS_ERROR_FAILURE; } RefPtr target = Factory::CreateDrawTargetForData(BackendType::CAIRO, mapping.mData, mSize, mapping.mStride, optFormat); if (!target) { gfxWarning() << "imgFrame::Optimize failed in CreateDrawTargetForData"; return NS_ERROR_OUT_OF_MEMORY; } Rect rect(0, 0, mSize.width, mSize.height); target->DrawSurface(mImageSurface, rect, rect); target->Flush(); surf->Unmap(); mImageSurface = surf; mVBuf = buf; mFormat = optFormat; } #else mOptSurface = gfxPlatform::GetPlatform() ->ScreenReferenceDrawTarget()->OptimizeSourceSurface(mImageSurface); if (mOptSurface == mImageSurface) { mOptSurface = nullptr; } #endif if (mOptSurface) { mVBuf = nullptr; mVBufPtr = nullptr; mImageSurface = nullptr; } #ifdef MOZ_WIDGET_ANDROID // On Android, free mImageSurface unconditionally if we're discardable. This // allows the operating system to free our volatile buffer. // XXX(seth): We'd eventually like to do this on all platforms, but right now // converting raw memory to a SourceSurface is expensive on some backends. mImageSurface = nullptr; #endif return NS_OK; } DrawableFrameRef imgFrame::DrawableRef() { return DrawableFrameRef(this); } RawAccessFrameRef imgFrame::RawAccessRef() { return RawAccessFrameRef(this); } void imgFrame::SetRawAccessOnly() { AssertImageDataLocked(); // Lock our data and throw away the key. LockImageData(); } imgFrame::SurfaceWithFormat imgFrame::SurfaceForDrawing(bool aDoPadding, bool aDoPartialDecode, bool aDoTile, gfxContext* aContext, const nsIntMargin& aPadding, gfxRect& aImageRect, ImageRegion& aRegion, SourceSurface* aSurface) { MOZ_ASSERT(NS_IsMainThread()); mMonitor.AssertCurrentThreadOwns(); IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height())); if (!aDoPadding && !aDoPartialDecode) { NS_ASSERTION(!mSinglePixel, "This should already have been handled"); return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, size), mFormat); } gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height); if (aDoTile || mSinglePixel) { // Create a temporary surface. // Give this surface an alpha channel because there are // transparent pixels in the padding or undecoded area RefPtr target = gfxPlatform::GetPlatform()-> CreateOffscreenContentDrawTarget(size, SurfaceFormat::B8G8R8A8); if (!target) { return SurfaceWithFormat(); } // Fill 'available' with whatever we've got if (mSinglePixel) { target->FillRect(ToRect(aRegion.Intersect(available).Rect()), ColorPattern(mSinglePixelColor), DrawOptions(1.0f, CompositionOp::OP_SOURCE)); } else { SurfacePattern pattern(aSurface, aRegion.GetExtendMode(), Matrix::Translation(mDecoded.x, mDecoded.y)); target->FillRect(ToRect(aRegion.Intersect(available).Rect()), pattern); } RefPtr newsurf = target->Snapshot(); return SurfaceWithFormat(new gfxSurfaceDrawable(newsurf, size), target->GetFormat()); } // Not tiling, and we have a surface, so we can account for // padding and/or a partial decode just by twiddling parameters. gfxPoint paddingTopLeft(aPadding.left, aPadding.top); aRegion = aRegion.Intersect(available) - paddingTopLeft; aContext->Multiply(gfxMatrix::Translation(paddingTopLeft)); aImageRect = gfxRect(0, 0, mSize.width, mSize.height); IntSize availableSize(mDecoded.width, mDecoded.height); return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, availableSize), mFormat); } bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion, Filter aFilter, uint32_t aImageFlags) { PROFILER_LABEL("imgFrame", "Draw", js::ProfileEntry::Category::GRAPHICS); MOZ_ASSERT(NS_IsMainThread()); NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!"); NS_ASSERTION(!aRegion.IsRestricted() || !aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(), "We must be allowed to sample *some* source pixels!"); NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!"); MonitorAutoLock lock(mMonitor); nsIntMargin padding(mOffset.y, mImageSize.width - (mOffset.x + mSize.width), mImageSize.height - (mOffset.y + mSize.height), mOffset.x); bool doPadding = padding != nsIntMargin(0,0,0,0); bool doPartialDecode = !IsImageCompleteInternal(); if (mSinglePixel && !doPadding && !doPartialDecode) { if (mSinglePixelColor.a == 0.0) { return true; } RefPtr dt = aContext->GetDrawTarget(); dt->FillRect(ToRect(aRegion.Rect()), ColorPattern(mSinglePixelColor), DrawOptions(1.0f, aContext->CurrentOp())); return true; } RefPtr surf = GetSurfaceInternal(); if (!surf && !mSinglePixel) { return false; } gfxRect imageRect(0, 0, mImageSize.width, mImageSize.height); bool doTile = !imageRect.Contains(aRegion.Rect()) && !(aImageFlags & imgIContainer::FLAG_CLAMP); ImageRegion region(aRegion); // SurfaceForDrawing changes the current transform, and we need it to still // be changed when we call gfxUtils::DrawPixelSnapped. We still need to // restore it before returning though. // XXXjwatt In general having functions require someone further up the stack // to undo transform changes that they make is bad practice. We should // change how this code works. gfxContextMatrixAutoSaveRestore autoSR(aContext); SurfaceWithFormat surfaceResult = SurfaceForDrawing(doPadding, doPartialDecode, doTile, aContext, padding, imageRect, region, surf); if (surfaceResult.IsValid()) { gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable, imageRect.Size(), region, surfaceResult.mFormat, aFilter, aImageFlags); } return true; } nsresult imgFrame::ImageUpdated(const nsIntRect& aUpdateRect) { MonitorAutoLock lock(mMonitor); return ImageUpdatedInternal(aUpdateRect); } nsresult imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect) { mMonitor.AssertCurrentThreadOwns(); mDecoded.UnionRect(mDecoded, aUpdateRect); // clamp to bounds, in case someone sends a bogus updateRect (I'm looking at // you, gif decoder) nsIntRect boundsRect(mOffset, mSize); mDecoded.IntersectRect(mDecoded, boundsRect); // If the image is now complete, wake up anyone who's waiting. if (IsImageCompleteInternal()) { mMonitor.NotifyAll(); } return NS_OK; } void imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */, DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */, int32_t aRawTimeout /* = 0 */, BlendMethod aBlendMethod /* = BlendMethod::OVER */) { MonitorAutoLock lock(mMonitor); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); if (aFrameOpacity == Opacity::OPAQUE) { mHasNoAlpha = true; } mDisposalMethod = aDisposalMethod; mTimeout = aRawTimeout; mBlendMethod = aBlendMethod; ImageUpdatedInternal(GetRect()); } nsIntRect imgFrame::GetRect() const { return gfx::IntRect(mOffset, mSize); } int32_t imgFrame::GetStride() const { mMonitor.AssertCurrentThreadOwns(); if (mImageSurface) { return mImageSurface->Stride(); } return VolatileSurfaceStride(mSize, mFormat); } SurfaceFormat imgFrame::GetFormat() const { MonitorAutoLock lock(mMonitor); return mFormat; } uint32_t imgFrame::GetImageBytesPerRow() const { mMonitor.AssertCurrentThreadOwns(); if (mVBuf) { return mSize.width * BytesPerPixel(mFormat); } if (mPaletteDepth) { return mSize.width; } return 0; } uint32_t imgFrame::GetImageDataLength() const { return GetImageBytesPerRow() * mSize.height; } void imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const { MonitorAutoLock lock(mMonitor); GetImageDataInternal(aData, aLength); } void imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const { mMonitor.AssertCurrentThreadOwns(); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); if (mImageSurface) { *aData = mVBufPtr; MOZ_ASSERT(*aData, "mImageSurface is non-null, but mVBufPtr is null in GetImageData"); } else if (mPalettedImageData) { *aData = mPalettedImageData + PaletteDataLength(); MOZ_ASSERT(*aData, "mPalettedImageData is non-null, but result is null in GetImageData"); } else { MOZ_ASSERT(false, "Have neither mImageSurface nor mPalettedImageData in GetImageData"); *aData = nullptr; } *aLength = GetImageDataLength(); } uint8_t* imgFrame::GetImageData() const { uint8_t* data; uint32_t length; GetImageData(&data, &length); return data; } bool imgFrame::GetIsPaletted() const { return mPalettedImageData != nullptr; } void imgFrame::GetPaletteData(uint32_t** aPalette, uint32_t* length) const { AssertImageDataLocked(); if (!mPalettedImageData) { *aPalette = nullptr; *length = 0; } else { *aPalette = (uint32_t*) mPalettedImageData; *length = PaletteDataLength(); } } uint32_t* imgFrame::GetPaletteData() const { uint32_t* data; uint32_t length; GetPaletteData(&data, &length); return data; } nsresult imgFrame::LockImageData() { MonitorAutoLock lock(mMonitor); MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks"); if (mLockCount < 0) { return NS_ERROR_FAILURE; } mLockCount++; // If we are not the first lock, there's nothing to do. if (mLockCount != 1) { return NS_OK; } // If we're the first lock, but have an image surface, we're OK. if (mImageSurface) { mVBufPtr = mVBuf; return NS_OK; } // Paletted images don't have surfaces, so there's nothing to do. if (mPalettedImageData) { return NS_OK; } MOZ_ASSERT_UNREACHABLE("It's illegal to re-lock an optimized imgFrame"); return NS_ERROR_FAILURE; } void imgFrame::AssertImageDataLocked() const { #ifdef DEBUG MonitorAutoLock lock(mMonitor); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); #endif } class UnlockImageDataRunnable : public nsRunnable { public: explicit UnlockImageDataRunnable(imgFrame* aTarget) : mTarget(aTarget) { MOZ_ASSERT(mTarget); } NS_IMETHOD Run() { return mTarget->UnlockImageData(); } private: RefPtr mTarget; }; nsresult imgFrame::UnlockImageData() { MonitorAutoLock lock(mMonitor); MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!"); if (mLockCount <= 0) { return NS_ERROR_FAILURE; } MOZ_ASSERT(mLockCount > 1 || IsImageCompleteInternal() || mAborted, "Should have marked complete or aborted before unlocking"); // If we're about to become unlocked, we don't need to hold on to our data // surface anymore. (But we don't need to do anything for paletted images, // which don't have surfaces.) if (mLockCount == 1 && !mPalettedImageData) { // We can't safely optimize off-main-thread, so create a runnable to do it. if (!NS_IsMainThread()) { nsCOMPtr runnable = new UnlockImageDataRunnable(this); NS_DispatchToMainThread(runnable); return NS_OK; } // If we're using a surface format with alpha but the image has no alpha, // change the format. This doesn't change the underlying data at all, but // allows DrawTargets to avoid blending when drawing known opaque images. if (mHasNoAlpha && mFormat == SurfaceFormat::B8G8R8A8 && mImageSurface) { mFormat = SurfaceFormat::B8G8R8X8; mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat); } // Convert the data surface to a GPU surface or a single color if possible. // This will also release mImageSurface if possible. Optimize(); // Allow the OS to release our data surface. mVBufPtr = nullptr; } mLockCount--; return NS_OK; } void imgFrame::SetOptimizable() { AssertImageDataLocked(); MonitorAutoLock lock(mMonitor); mOptimizable = true; } Color imgFrame::SinglePixelColor() const { MOZ_ASSERT(NS_IsMainThread()); return mSinglePixelColor; } bool imgFrame::IsSinglePixel() const { MOZ_ASSERT(NS_IsMainThread()); return mSinglePixel; } already_AddRefed imgFrame::GetSurface() { MonitorAutoLock lock(mMonitor); return GetSurfaceInternal(); } already_AddRefed imgFrame::GetSurfaceInternal() { mMonitor.AssertCurrentThreadOwns(); if (mOptSurface) { if (mOptSurface->IsValid()) { RefPtr surf(mOptSurface); return surf.forget(); } else { mOptSurface = nullptr; } } if (mImageSurface) { RefPtr surf(mImageSurface); return surf.forget(); } if (!mVBuf) { return nullptr; } VolatileBufferPtr buf(mVBuf); if (buf.WasBufferPurged()) { return nullptr; } return CreateLockedSurface(mVBuf, mSize, mFormat); } already_AddRefed imgFrame::GetDrawTarget() { MonitorAutoLock lock(mMonitor); uint8_t* data; uint32_t length; GetImageDataInternal(&data, &length); if (!data) { return nullptr; } int32_t stride = GetStride(); return gfxPlatform::GetPlatform()-> CreateDrawTargetForData(data, mSize, stride, mFormat); } AnimationData imgFrame::GetAnimationData() const { MonitorAutoLock lock(mMonitor); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); uint8_t* data; if (mPalettedImageData) { data = mPalettedImageData; } else { uint32_t length; GetImageDataInternal(&data, &length); } bool hasAlpha = mFormat == SurfaceFormat::B8G8R8A8; return AnimationData(data, PaletteDataLength(), mTimeout, GetRect(), mBlendMethod, mDisposalMethod, hasAlpha); } ScalingData imgFrame::GetScalingData() const { MonitorAutoLock lock(mMonitor); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); MOZ_ASSERT(!GetIsPaletted(), "GetScalingData can't handle paletted images"); uint8_t* data; uint32_t length; GetImageDataInternal(&data, &length); return ScalingData(data, mSize, GetImageBytesPerRow(), mFormat); } void imgFrame::Abort() { MonitorAutoLock lock(mMonitor); mAborted = true; // Wake up anyone who's waiting. mMonitor.NotifyAll(); } bool imgFrame::IsImageComplete() const { MonitorAutoLock lock(mMonitor); return IsImageCompleteInternal(); } void imgFrame::WaitUntilComplete() const { MonitorAutoLock lock(mMonitor); while (true) { // Return if we're aborted or complete. if (mAborted || IsImageCompleteInternal()) { return; } // Not complete yet, so we'll have to wait. mMonitor.Wait(); } } bool imgFrame::IsImageCompleteInternal() const { mMonitor.AssertCurrentThreadOwns(); return mDecoded.IsEqualInterior(nsIntRect(mOffset.x, mOffset.y, mSize.width, mSize.height)); } bool imgFrame::GetCompositingFailed() const { MOZ_ASSERT(NS_IsMainThread()); return mCompositingFailed; } void imgFrame::SetCompositingFailed(bool val) { MOZ_ASSERT(NS_IsMainThread()); mCompositingFailed = val; } void imgFrame::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, size_t& aHeapSizeOut, size_t& aNonHeapSizeOut) const { MonitorAutoLock lock(mMonitor); if (mPalettedImageData) { aHeapSizeOut += aMallocSizeOf(mPalettedImageData); } if (mImageSurface) { aHeapSizeOut += aMallocSizeOf(mImageSurface); } if (mOptSurface) { aHeapSizeOut += aMallocSizeOf(mOptSurface); } if (mVBuf) { aHeapSizeOut += aMallocSizeOf(mVBuf); aHeapSizeOut += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf); aNonHeapSizeOut += mVBuf->NonHeapSizeOfExcludingThis(); } } } // namespace image } // namespace mozilla