diff options
Diffstat (limited to 'image/imgFrame.h')
-rw-r--r-- | image/imgFrame.h | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/image/imgFrame.h b/image/imgFrame.h new file mode 100644 index 000000000..e864aca7f --- /dev/null +++ b/image/imgFrame.h @@ -0,0 +1,599 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_imgFrame_h +#define mozilla_image_imgFrame_h + +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Monitor.h" +#include "mozilla/Move.h" +#include "mozilla/VolatileBuffer.h" +#include "gfxDrawable.h" +#include "imgIContainer.h" +#include "MainThreadUtils.h" + +namespace mozilla { +namespace image { + +class ImageRegion; +class DrawableFrameRef; +class RawAccessFrameRef; + +enum class BlendMethod : int8_t { + // All color components of the frame, including alpha, overwrite the current + // contents of the frame's output buffer region. + SOURCE, + + // The frame should be composited onto the output buffer based on its alpha, + // using a simple OVER operation. + OVER +}; + +enum class DisposalMethod : int8_t { + CLEAR_ALL = -1, // Clear the whole image, revealing what's underneath. + NOT_SPECIFIED, // Leave the frame and let the new frame draw on top. + KEEP, // Leave the frame and let the new frame draw on top. + CLEAR, // Clear the frame's area, revealing what's underneath. + RESTORE_PREVIOUS // Restore the previous (composited) frame. +}; + +enum class Opacity : uint8_t { + FULLY_OPAQUE, + SOME_TRANSPARENCY +}; + +/** + * FrameTimeout wraps a frame timeout value (measured in milliseconds) after + * first normalizing it. This normalization is necessary because some tools + * generate incorrect frame timeout values which we nevertheless have to + * support. For this reason, code that deals with frame timeouts should always + * use a FrameTimeout value rather than the raw value from the image header. + */ +struct FrameTimeout +{ + /** + * @return a FrameTimeout of zero. This should be used only for math + * involving FrameTimeout values. You can't obtain a zero FrameTimeout from + * FromRawMilliseconds(). + */ + static FrameTimeout Zero() { return FrameTimeout(0); } + + /// @return an infinite FrameTimeout. + static FrameTimeout Forever() { return FrameTimeout(-1); } + + /// @return a FrameTimeout obtained by normalizing a raw timeout value. + static FrameTimeout FromRawMilliseconds(int32_t aRawMilliseconds) + { + // Normalize all infinite timeouts to the same value. + if (aRawMilliseconds < 0) { + return FrameTimeout::Forever(); + } + + // Very small timeout values are problematic for two reasons: we don't want + // to burn energy redrawing animated images extremely fast, and broken tools + // generate these values when they actually want a "default" value, so such + // images won't play back right without normalization. For some context, + // see bug 890743, bug 125137, bug 139677, and bug 207059. The historical + // behavior of IE and Opera was: + // IE 6/Win: + // 10 - 50ms is normalized to 100ms. + // >50ms is used unnormalized. + // Opera 7 final/Win: + // 10ms is normalized to 100ms. + // >10ms is used unnormalized. + if (aRawMilliseconds >= 0 && aRawMilliseconds <= 10 ) { + return FrameTimeout(100); + } + + // The provided timeout value is OK as-is. + return FrameTimeout(aRawMilliseconds); + } + + bool operator==(const FrameTimeout& aOther) const + { + return mTimeout == aOther.mTimeout; + } + + bool operator!=(const FrameTimeout& aOther) const { return !(*this == aOther); } + + FrameTimeout operator+(const FrameTimeout& aOther) + { + if (*this == Forever() || aOther == Forever()) { + return Forever(); + } + + return FrameTimeout(mTimeout + aOther.mTimeout); + } + + FrameTimeout& operator+=(const FrameTimeout& aOther) + { + *this = *this + aOther; + return *this; + } + + /** + * @return this FrameTimeout's value in milliseconds. Illegal to call on a + * an infinite FrameTimeout value. + */ + uint32_t AsMilliseconds() const + { + if (*this == Forever()) { + MOZ_ASSERT_UNREACHABLE("Calling AsMilliseconds() on an infinite FrameTimeout"); + return 100; // Fail to something sane. + } + + return uint32_t(mTimeout); + } + + /** + * @return this FrameTimeout value encoded so that non-negative values + * represent a timeout in milliseconds, and -1 represents an infinite + * timeout. + * + * XXX(seth): This is a backwards compatibility hack that should be removed. + */ + int32_t AsEncodedValueDeprecated() const { return mTimeout; } + +private: + explicit FrameTimeout(int32_t aTimeout) + : mTimeout(aTimeout) + { } + + int32_t mTimeout; +}; + +/** + * AnimationData contains all of the information necessary for using an imgFrame + * as part of an animation. + * + * It includes pointers to the raw image data of the underlying imgFrame, but + * does not own that data. A RawAccessFrameRef for the underlying imgFrame must + * outlive the AnimationData for it to remain valid. + */ +struct AnimationData +{ + AnimationData(uint8_t* aRawData, uint32_t aPaletteDataLength, + FrameTimeout aTimeout, const nsIntRect& aRect, + BlendMethod aBlendMethod, const Maybe<gfx::IntRect>& aBlendRect, + DisposalMethod aDisposalMethod, bool aHasAlpha) + : mRawData(aRawData) + , mPaletteDataLength(aPaletteDataLength) + , mTimeout(aTimeout) + , mRect(aRect) + , mBlendMethod(aBlendMethod) + , mBlendRect(aBlendRect) + , mDisposalMethod(aDisposalMethod) + , mHasAlpha(aHasAlpha) + { } + + uint8_t* mRawData; + uint32_t mPaletteDataLength; + FrameTimeout mTimeout; + nsIntRect mRect; + BlendMethod mBlendMethod; + Maybe<gfx::IntRect> mBlendRect; + DisposalMethod mDisposalMethod; + bool mHasAlpha; +}; + +class imgFrame +{ + typedef gfx::Color Color; + typedef gfx::DataSourceSurface DataSourceSurface; + typedef gfx::DrawTarget DrawTarget; + typedef gfx::SamplingFilter SamplingFilter; + typedef gfx::IntPoint IntPoint; + typedef gfx::IntRect IntRect; + typedef gfx::IntSize IntSize; + typedef gfx::SourceSurface SourceSurface; + typedef gfx::SurfaceFormat SurfaceFormat; + +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(imgFrame) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(imgFrame) + + imgFrame(); + + /** + * Initialize this imgFrame with an empty surface and prepare it for being + * written to by a decoder. + * + * This is appropriate for use with decoded images, but it should not be used + * when drawing content into an imgFrame, as it may use a different graphics + * backend than normal content drawing. + */ + nsresult InitForDecoder(const nsIntSize& aImageSize, + const nsIntRect& aRect, + SurfaceFormat aFormat, + uint8_t aPaletteDepth = 0, + bool aNonPremult = false); + + nsresult InitForDecoder(const nsIntSize& aSize, + SurfaceFormat aFormat, + uint8_t aPaletteDepth = 0) + { + return InitForDecoder(aSize, nsIntRect(0, 0, aSize.width, aSize.height), + aFormat, aPaletteDepth); + } + + + /** + * Initialize this imgFrame with a new surface and draw the provided + * gfxDrawable into it. + * + * This is appropriate to use when drawing content into an imgFrame, as it + * uses the same graphics backend as normal content drawing. The downside is + * that the underlying surface may not be stored in a volatile buffer on all + * platforms, and raw access to the surface (using RawAccessRef()) may be much + * more expensive than in the InitForDecoder() case. + * + * aBackend specifies the DrawTarget backend type this imgFrame is supposed + * to be drawn to. + */ + nsresult InitWithDrawable(gfxDrawable* aDrawable, + const nsIntSize& aSize, + const SurfaceFormat aFormat, + SamplingFilter aSamplingFilter, + uint32_t aImageFlags, + gfx::BackendType aBackend); + + DrawableFrameRef DrawableRef(); + RawAccessFrameRef RawAccessRef(); + + /** + * Make this imgFrame permanently available for raw access. + * + * This is irrevocable, and should be avoided whenever possible, since it + * prevents this imgFrame from being optimized and makes it impossible for its + * volatile buffer to be freed. + * + * It is an error to call this without already holding a RawAccessFrameRef to + * this imgFrame. + */ + void SetRawAccessOnly(); + + bool Draw(gfxContext* aContext, const ImageRegion& aRegion, + SamplingFilter aSamplingFilter, uint32_t aImageFlags); + + nsresult ImageUpdated(const nsIntRect& aUpdateRect); + + /** + * Mark this imgFrame as completely decoded, and set final options. + * + * You must always call either Finish() or Abort() before releasing the last + * RawAccessFrameRef pointing to an imgFrame. + * + * @param aFrameOpacity Whether this imgFrame is opaque. + * @param aDisposalMethod For animation frames, how this imgFrame is cleared + * from the compositing frame before the next frame is + * displayed. + * @param aTimeout For animation frames, the timeout before the next + * frame is displayed. + * @param aBlendMethod For animation frames, a blending method to be used + * when compositing this frame. + * @param aBlendRect For animation frames, if present, the subrect in + * which @aBlendMethod applies. Outside of this + * subrect, BlendMethod::OVER is always used. + */ + void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY, + DisposalMethod aDisposalMethod = DisposalMethod::KEEP, + FrameTimeout aTimeout = FrameTimeout::FromRawMilliseconds(0), + BlendMethod aBlendMethod = BlendMethod::OVER, + const Maybe<IntRect>& aBlendRect = Nothing()); + + /** + * Mark this imgFrame as aborted. This informs the imgFrame that if it isn't + * completely decoded now, it never will be. + * + * You must always call either Finish() or Abort() before releasing the last + * RawAccessFrameRef pointing to an imgFrame. + */ + void Abort(); + + /** + * Returns true if this imgFrame has been aborted. + */ + bool IsAborted() const; + + /** + * Returns true if this imgFrame is completely decoded. + */ + bool IsFinished() const; + + /** + * Blocks until this imgFrame is either completely decoded, or is marked as + * aborted. + * + * Note that calling this on the main thread _blocks the main thread_. Be very + * careful in your use of this method to avoid excessive main thread jank or + * deadlock. + */ + void WaitUntilFinished() const; + + /** + * Returns the number of bytes per pixel this imgFrame requires. This is a + * worst-case value that does not take into account the effects of format + * changes caused by Optimize(), since an imgFrame is not optimized throughout + * its lifetime. + */ + uint32_t GetBytesPerPixel() const { return GetIsPaletted() ? 1 : 4; } + + IntSize GetImageSize() const { return mImageSize; } + IntRect GetRect() const { return mFrameRect; } + IntSize GetSize() const { return mFrameRect.Size(); } + void GetImageData(uint8_t** aData, uint32_t* length) const; + uint8_t* GetImageData() const; + + bool GetIsPaletted() const; + void GetPaletteData(uint32_t** aPalette, uint32_t* length) const; + uint32_t* GetPaletteData() const; + uint8_t GetPaletteDepth() const { return mPaletteDepth; } + + AnimationData GetAnimationData() const; + + bool GetCompositingFailed() const; + void SetCompositingFailed(bool val); + + void SetOptimizable(); + + already_AddRefed<SourceSurface> GetSourceSurface(); + + void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) const; + +private: // methods + + ~imgFrame(); + + nsresult LockImageData(); + nsresult UnlockImageData(); + bool CanOptimizeOpaqueImage(); + nsresult Optimize(gfx::DrawTarget* aTarget); + + void AssertImageDataLocked() const; + + bool AreAllPixelsWritten() const; + nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect); + void GetImageDataInternal(uint8_t** aData, uint32_t* length) const; + uint32_t GetImageBytesPerRow() const; + uint32_t GetImageDataLength() const; + already_AddRefed<SourceSurface> GetSourceSurfaceInternal(); + + uint32_t PaletteDataLength() const + { + return mPaletteDepth ? (size_t(1) << mPaletteDepth) * sizeof(uint32_t) + : 0; + } + + struct SurfaceWithFormat { + RefPtr<gfxDrawable> mDrawable; + SurfaceFormat mFormat; + SurfaceWithFormat() { } + SurfaceWithFormat(gfxDrawable* aDrawable, SurfaceFormat aFormat) + : mDrawable(aDrawable), mFormat(aFormat) + { } + bool IsValid() { return !!mDrawable; } + }; + + SurfaceWithFormat SurfaceForDrawing(bool aDoPartialDecode, + bool aDoTile, + ImageRegion& aRegion, + SourceSurface* aSurface); + +private: // data + friend class DrawableFrameRef; + friend class RawAccessFrameRef; + friend class UnlockImageDataRunnable; + + ////////////////////////////////////////////////////////////////////////////// + // Thread-safe mutable data, protected by mMonitor. + ////////////////////////////////////////////////////////////////////////////// + + mutable Monitor mMonitor; + + RefPtr<DataSourceSurface> mImageSurface; + RefPtr<SourceSurface> mOptSurface; + + RefPtr<VolatileBuffer> mVBuf; + VolatileBufferPtr<uint8_t> mVBufPtr; + + nsIntRect mDecoded; + + //! Number of RawAccessFrameRefs currently alive for this imgFrame. + int32_t mLockCount; + + //! The timeout for this frame. + FrameTimeout mTimeout; + + DisposalMethod mDisposalMethod; + BlendMethod mBlendMethod; + Maybe<IntRect> mBlendRect; + SurfaceFormat mFormat; + + bool mHasNoAlpha; + bool mAborted; + bool mFinished; + bool mOptimizable; + + + ////////////////////////////////////////////////////////////////////////////// + // Effectively const data, only mutated in the Init methods. + ////////////////////////////////////////////////////////////////////////////// + + IntSize mImageSize; + IntRect mFrameRect; + + // The palette and image data for images that are paletted, since Cairo + // doesn't support these images. + // The paletted data comes first, then the image data itself. + // Total length is PaletteDataLength() + GetImageDataLength(). + uint8_t* mPalettedImageData; + uint8_t mPaletteDepth; + + bool mNonPremult; + + + ////////////////////////////////////////////////////////////////////////////// + // Main-thread-only mutable data. + ////////////////////////////////////////////////////////////////////////////// + + bool mCompositingFailed; +}; + +/** + * A reference to an imgFrame that holds the imgFrame's surface in memory, + * allowing drawing. If you have a DrawableFrameRef |ref| and |if (ref)| returns + * true, then calls to Draw() and GetSourceSurface() are guaranteed to succeed. + */ +class DrawableFrameRef final +{ +public: + DrawableFrameRef() { } + + explicit DrawableFrameRef(imgFrame* aFrame) + : mFrame(aFrame) + , mRef(aFrame->mVBuf) + { + if (mRef.WasBufferPurged()) { + mFrame = nullptr; + mRef = nullptr; + } + } + + DrawableFrameRef(DrawableFrameRef&& aOther) + : mFrame(aOther.mFrame.forget()) + , mRef(Move(aOther.mRef)) + { } + + DrawableFrameRef& operator=(DrawableFrameRef&& aOther) + { + MOZ_ASSERT(this != &aOther, "Self-moves are prohibited"); + mFrame = aOther.mFrame.forget(); + mRef = Move(aOther.mRef); + return *this; + } + + explicit operator bool() const { return bool(mFrame); } + + imgFrame* operator->() + { + MOZ_ASSERT(mFrame); + return mFrame; + } + + const imgFrame* operator->() const + { + MOZ_ASSERT(mFrame); + return mFrame; + } + + imgFrame* get() { return mFrame; } + const imgFrame* get() const { return mFrame; } + + void reset() + { + mFrame = nullptr; + mRef = nullptr; + } + +private: + DrawableFrameRef(const DrawableFrameRef& aOther) = delete; + + RefPtr<imgFrame> mFrame; + VolatileBufferPtr<uint8_t> mRef; +}; + +/** + * A reference to an imgFrame that holds the imgFrame's surface in memory in a + * format appropriate for access as raw data. If you have a RawAccessFrameRef + * |ref| and |if (ref)| is true, then calls to GetImageData() and + * GetPaletteData() are guaranteed to succeed. This guarantee is stronger than + * DrawableFrameRef, so everything that a valid DrawableFrameRef guarantees is + * also guaranteed by a valid RawAccessFrameRef. + * + * This may be considerably more expensive than is necessary just for drawing, + * so only use this when you need to read or write the raw underlying image data + * that the imgFrame holds. + * + * Once all an imgFrame's RawAccessFrameRefs go out of scope, new + * RawAccessFrameRefs cannot be created. + */ +class RawAccessFrameRef final +{ +public: + RawAccessFrameRef() { } + + explicit RawAccessFrameRef(imgFrame* aFrame) + : mFrame(aFrame) + { + MOZ_ASSERT(mFrame, "Need a frame"); + + if (NS_FAILED(mFrame->LockImageData())) { + mFrame->UnlockImageData(); + mFrame = nullptr; + } + } + + RawAccessFrameRef(RawAccessFrameRef&& aOther) + : mFrame(aOther.mFrame.forget()) + { } + + ~RawAccessFrameRef() + { + if (mFrame) { + mFrame->UnlockImageData(); + } + } + + RawAccessFrameRef& operator=(RawAccessFrameRef&& aOther) + { + MOZ_ASSERT(this != &aOther, "Self-moves are prohibited"); + + if (mFrame) { + mFrame->UnlockImageData(); + } + + mFrame = aOther.mFrame.forget(); + + return *this; + } + + explicit operator bool() const { return bool(mFrame); } + + imgFrame* operator->() + { + MOZ_ASSERT(mFrame); + return mFrame.get(); + } + + const imgFrame* operator->() const + { + MOZ_ASSERT(mFrame); + return mFrame; + } + + imgFrame* get() { return mFrame; } + const imgFrame* get() const { return mFrame; } + + void reset() + { + if (mFrame) { + mFrame->UnlockImageData(); + } + mFrame = nullptr; + } + +private: + RawAccessFrameRef(const RawAccessFrameRef& aOther) = delete; + + RefPtr<imgFrame> mFrame; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_imgFrame_h |