From 874ce61a83d28352594b09ff29bfa4e83b2a307f Mon Sep 17 00:00:00 2001 From: Andrew Osmond Date: Wed, 24 Jul 2024 03:16:16 +0000 Subject: [PATCH] Bug 1901078 - Implement promise based anonymous image decoder. r=tnikkel This bypasses any caching used on the display pipeline and is intended to be used by layers that want asynchronous decoding that fits well into the MozPromise style. This also fixes an issue with the frame count metadata decoder in the GIF decoder. The code only began being used with this patch, and the WPT exposed an overflow bug caused by not clearing nsGIFDecoder2::mColormap and nsGIFDecoder2::mColormapSize. Differential Revision: https://phabricator.services.mozilla.com/D212833 --- dom/canvas/ImageUtils.cpp | 2 +- image/DecoderFactory.cpp | 26 ++ image/DecoderFactory.h | 28 +- image/ImageUtils.cpp | 567 +++++++++++++++++++++++++++++++ image/ImageUtils.h | 141 ++++++++ image/decoders/nsGIFDecoder2.cpp | 3 + image/moz.build | 4 + 7 files changed, 752 insertions(+), 19 deletions(-) create mode 100644 image/ImageUtils.cpp create mode 100644 image/ImageUtils.h diff --git a/dom/canvas/ImageUtils.cpp b/dom/canvas/ImageUtils.cpp index 432fa3355e8c..ada3be5f159f 100644 --- a/dom/canvas/ImageUtils.cpp +++ b/dom/canvas/ImageUtils.cpp @@ -4,7 +4,7 @@ * 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 "ImageUtils.h" +#include "mozilla/dom/ImageUtils.h" #include "ImageContainer.h" #include "Intervals.h" diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp index 55e0c5584762..f36f03c7f262 100644 --- a/image/DecoderFactory.cpp +++ b/image/DecoderFactory.cpp @@ -319,6 +319,32 @@ already_AddRefed DecoderFactory::CloneAnimationDecoder( return decoder.forget(); } +/* static */ +already_AddRefed DecoderFactory::CloneAnonymousMetadataDecoder( + Decoder* aDecoder, const Maybe& aDecoderFlags) { + MOZ_ASSERT(aDecoder); + + DecoderType type = aDecoder->GetType(); + RefPtr decoder = + GetDecoder(type, nullptr, /* aIsRedecode = */ false); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(true); + decoder->SetIterator(aDecoder->GetSourceBuffer()->Iterator()); + if (aDecoderFlags) { + decoder->SetDecoderFlags(*aDecoderFlags); + } else { + decoder->SetDecoderFlags(aDecoder->GetDecoderFlags()); + } + + if (NS_FAILED(decoder->Init())) { + return nullptr; + } + + return decoder.forget(); +} + /* static */ already_AddRefed DecoderFactory::CreateMetadataDecoder( DecoderType aType, NotNull aImage, DecoderFlags aFlags, diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h index cb3b65766ea9..bc5f6ed90e7e 100644 --- a/image/DecoderFactory.h +++ b/image/DecoderFactory.h @@ -12,6 +12,7 @@ #include "mozilla/Maybe.h" #include "mozilla/NotNull.h" #include "mozilla/gfx/2D.h" +#include "mozilla/image/ImageUtils.h" #include "nsCOMPtr.h" #include "Orientation.h" #include "SurfaceFlags.h" @@ -25,24 +26,6 @@ class RasterImage; class SourceBuffer; class SourceBufferIterator; -/** - * The type of decoder; this is usually determined from a MIME type using - * DecoderFactory::GetDecoderType(). - */ -enum class DecoderType { - PNG, - GIF, - JPEG, - BMP, - BMP_CLIPBOARD, - ICO, - ICON, - WEBP, - AVIF, - JXL, - UNKNOWN -}; - class DecoderFactory { public: /// @return the type of decoder which is appropriate for @aMimeType. @@ -118,6 +101,15 @@ class DecoderFactory { */ static already_AddRefed CloneAnimationDecoder(Decoder* aDecoder); + /** + * Creates and initializes a metadata decoder for an anonymous image, cloned + * from the given decoder. + * + * @param aDecoder Decoder to clone. + */ + static already_AddRefed CloneAnonymousMetadataDecoder( + Decoder* aDecoder, const Maybe& aDecoderFlags = Nothing()); + /** * Creates and initializes a metadata decoder of type @aType. This decoder * will only decode the image's header, extracting metadata like the size of diff --git a/image/ImageUtils.cpp b/image/ImageUtils.cpp new file mode 100644 index 000000000000..0485f8c5fa0f --- /dev/null +++ b/image/ImageUtils.cpp @@ -0,0 +1,567 @@ +/* -*- 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/. */ + +#include "mozilla/image/ImageUtils.h" +#include "DecodePool.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "IDecodingTask.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Logging.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" + +namespace mozilla::image { + +static LazyLogModule sLog("ImageUtils"); + +AnonymousDecoder::AnonymousDecoder() = default; + +AnonymousDecoder::~AnonymousDecoder() = default; + +class AnonymousDecoderTask : public IDecodingTask { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecoderTask, final) + + AnonymousDecoderTask(RefPtr&& aDecoder, + ThreadSafeWeakPtr&& aOwner) + : mDecoder(std::move(aDecoder)), mOwner(std::move(aOwner)) {} + + bool ShouldPreferSyncRun() const final { return false; } + + TaskPriority Priority() const final { return TaskPriority::eLow; } + + bool IsValid() const { + return !AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal) && + !mOwner.IsDead(); + } + + bool MaybeStart() { + if (!IsValid()) { + return false; + } + + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderTask::Start -- queue", this)); + DecodePool::Singleton()->AsyncRun(this); + return true; + } + + void Resume() final { + if (!IsValid()) { + return; + } + + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderTask::Resume -- queue", this)); + DecodePool::Singleton()->AsyncRun(this); + } + + void Run() final { + bool resume = true; + while (!mOwner.IsDead() && resume) { + LexerResult result = mDecoder->Decode(WrapNotNull(this)); + if (result == LexerResult(Yield::NEED_MORE_DATA)) { + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderTask::Run -- need more data", this)); + MOZ_ASSERT(result == LexerResult(Yield::NEED_MORE_DATA)); + OnNeedMoreData(); + return; + } + + // Check if we have a new frame to process. + RefPtr frame = mDecoder->GetCurrentFrame(); + if (frame) { + RefPtr surface = frame->GetSourceSurface(); + if (surface) { + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderTask::Run -- new frame %p", this, + frame.get())); + resume = OnFrameAvailable(std::move(frame), std::move(surface)); + } else { + MOZ_ASSERT_UNREACHABLE("No surface from frame?"); + } + } + + if (result.is()) { + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderTask::Run -- complete", this)); + OnComplete(result == LexerResult(TerminalState::SUCCESS)); + break; + } + + MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE)); + } + } + + protected: + virtual ~AnonymousDecoderTask() = default; + + virtual void OnNeedMoreData() {} + + // Returns true if the caller should continue decoding more frames if + // possible. + virtual bool OnFrameAvailable(RefPtr&& aFrame, + RefPtr&& aSurface) { + MOZ_ASSERT_UNREACHABLE("Unhandled frame!"); + return true; + } + + virtual void OnComplete(bool aSuccess) = 0; + + RefPtr mDecoder; + ThreadSafeWeakPtr mOwner; +}; + +class AnonymousMetadataDecoderTask final : public AnonymousDecoderTask { + public: + AnonymousMetadataDecoderTask(RefPtr&& aDecoder, + ThreadSafeWeakPtr&& aOwner) + : AnonymousDecoderTask(std::move(aDecoder), std::move(aOwner)) {} + + protected: + void OnComplete(bool aSuccess) override { + RefPtr owner(mOwner); + if (!owner) { + return; + } + + if (!aSuccess) { + owner->OnMetadata(nullptr); + return; + } + + const auto& mdIn = mDecoder->GetImageMetadata(); + owner->OnMetadata(&mdIn); + } +}; + +class AnonymousFrameCountDecoderTask final : public AnonymousDecoderTask { + public: + AnonymousFrameCountDecoderTask(RefPtr&& aDecoder, + ThreadSafeWeakPtr&& aOwner) + : AnonymousDecoderTask(std::move(aDecoder), std::move(aOwner)) {} + + protected: + void UpdateFrameCount(bool aComplete) { + RefPtr owner(mOwner); + if (!owner) { + return; + } + + const auto& mdIn = mDecoder->GetImageMetadata(); + uint32_t frameCount = mdIn.HasFrameCount() ? mdIn.GetFrameCount() : 0; + owner->OnFrameCount(frameCount, aComplete); + } + + void OnNeedMoreData() override { UpdateFrameCount(/* aComplete */ false); } + + void OnComplete(bool aSuccess) override { + UpdateFrameCount(/* aComplete */ true); + } +}; + +class AnonymousFramesDecoderTask final : public AnonymousDecoderTask { + public: + AnonymousFramesDecoderTask(RefPtr&& aDecoder, + ThreadSafeWeakPtr&& aOwner) + : AnonymousDecoderTask(std::move(aDecoder), std::move(aOwner)) {} + + protected: + bool OnFrameAvailable(RefPtr&& aFrame, + RefPtr&& aSurface) override { + RefPtr owner(mOwner); + if (!owner) { + return false; + } + + return owner->OnFrameAvailable(std::move(aFrame), std::move(aSurface)); + } + + void OnComplete(bool aSuccess) override { + RefPtr owner(mOwner); + if (!owner) { + return; + } + + owner->OnFramesComplete(); + } +}; + +class AnonymousDecoderImpl final : public AnonymousDecoder { + public: + AnonymousDecoderImpl() + : mMutex("mozilla::image::AnonymousDecoderImpl::mMutex") {} + + ~AnonymousDecoderImpl() override { Destroy(); } + +#ifdef MOZ_REFCOUNTED_LEAK_CHECKING + const char* typeName() const override { + return "mozilla::image::AnonymousDecoderImpl"; + } + + size_t typeSize() const override { return sizeof(*this); } +#endif + + bool Initialize(RefPtr&& aDecoder) override { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(!aDecoder)) { + MOZ_LOG(sLog, LogLevel::Error, + ("[%p] AnonymousDecoderImpl::Initialize -- bad decoder", this)); + return false; + } + + RefPtr metadataDecoder = + DecoderFactory::CloneAnonymousMetadataDecoder(aDecoder); + if (NS_WARN_IF(!metadataDecoder)) { + MOZ_LOG(sLog, LogLevel::Error, + ("[%p] AnonymousDecoderImpl::Initialize -- failed clone metadata " + "decoder", + this)); + return false; + } + + DecoderFlags flags = + aDecoder->GetDecoderFlags() | DecoderFlags::COUNT_FRAMES; + RefPtr frameCountDecoder = + DecoderFactory::CloneAnonymousMetadataDecoder(aDecoder, Some(flags)); + if (NS_WARN_IF(!frameCountDecoder)) { + MOZ_LOG(sLog, LogLevel::Error, + ("[%p] AnonymousDecoderImpl::Initialize -- failed clone frame " + "count decoder", + this)); + return false; + } + + mMetadataTask = new AnonymousMetadataDecoderTask( + std::move(metadataDecoder), ThreadSafeWeakPtr(this)); + mFrameCountTask = new AnonymousFrameCountDecoderTask( + std::move(frameCountDecoder), + ThreadSafeWeakPtr(this)); + mFramesTask = new AnonymousFramesDecoderTask( + std::move(aDecoder), ThreadSafeWeakPtr(this)); + + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::Initialize -- success", this)); + return true; + } + + void Destroy() override { + MutexAutoLock lock(mMutex); + DestroyLocked(NS_ERROR_ABORT); + } + + void DestroyLocked(nsresult aResult) MOZ_REQUIRES(mMutex) { + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::Destroy", this)); + + mFramesToDecode = 0; + mMetadataTask = nullptr; + mFrameCountTask = nullptr; + mFramesTask = nullptr; + mPendingFramesResult.mFrames.Clear(); + mPendingFramesResult.mFinished = true; + mMetadataPromise.RejectIfExists(aResult, __func__); + mFrameCountPromise.RejectIfExists(aResult, __func__); + mFramesPromise.RejectIfExists(aResult, __func__); + } + + void OnMetadata(const ImageMetadata* aMetadata) override { + MutexAutoLock lock(mMutex); + + // We must have already gotten destroyed before metadata decoding finished. + if (!mMetadataTask) { + return; + } + + if (!aMetadata) { + MOZ_LOG(sLog, LogLevel::Error, + ("[%p] AnonymousDecoderImpl::OnMetadata -- failed", this)); + DestroyLocked(NS_ERROR_FAILURE); + return; + } + + const auto size = aMetadata->GetSize(); + mMetadataResult.mWidth = size.width; + mMetadataResult.mHeight = size.height; + mMetadataResult.mRepetitions = aMetadata->GetLoopCount(); + mMetadataResult.mAnimated = aMetadata->HasAnimation(); + + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::OnMetadata -- %dx%d, repetitions %d, " + "animated %d", + this, size.width, size.height, mMetadataResult.mRepetitions, + mMetadataResult.mAnimated)); + + if (!mMetadataResult.mAnimated) { + mMetadataResult.mFrameCount = 1; + mMetadataResult.mFrameCountComplete = true; + mMetadataTask = nullptr; + mFrameCountTask = nullptr; + } else if (mFrameCountTask && !mFrameCountTaskRunning) { + MOZ_LOG( + sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::OnMetadata -- start frame count task", + this)); + mFrameCountTaskRunning = mFrameCountTask->MaybeStart(); + return; + } + + mMetadataPromise.Resolve(mMetadataResult, __func__); + + if (mFramesTask && mFramesToDecode > 0 && !mFramesTaskRunning) { + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::OnMetadata -- start frames task, " + "want %zu", + this, mFramesToDecode)); + mFramesTaskRunning = mFramesTask->MaybeStart(); + } + } + + void OnFrameCount(uint32_t aFrameCount, bool aComplete) override { + MutexAutoLock lock(mMutex); + + // We must have already gotten destroyed before frame count decoding + // finished. + if (!mFrameCountTask) { + return; + } + + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::OnFrameCount -- frameCount %u, " + "complete %d", + this, aFrameCount, aComplete)); + + bool resolve = aComplete; + if (mFrameCount < aFrameCount) { + mFrameCount = aFrameCount; + resolve = true; + } + + // If metadata completing is waiting on an updated frame count, resolve it. + mMetadataResult.mFrameCount = mFrameCount; + mMetadataResult.mFrameCountComplete = aComplete; + mMetadataPromise.ResolveIfExists(mMetadataResult, __func__); + + if (mMetadataTask) { + mMetadataTask = nullptr; + if (mFramesTask && mFramesToDecode > 0 && !mFramesTaskRunning) { + MOZ_LOG( + sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::OnFrameCount -- start frames task, " + "want %zu", + this, mFramesToDecode)); + mFramesTaskRunning = mFramesTask->MaybeStart(); + } + } + + if (resolve) { + mFrameCountPromise.ResolveIfExists( + DecodeFrameCountResult{aFrameCount, aComplete}, __func__); + } + + if (aComplete) { + mFrameCountTask = nullptr; + } + } + + bool OnFrameAvailable(RefPtr&& aFrame, + RefPtr&& aSurface) override { + MutexAutoLock lock(mMutex); + + MOZ_DIAGNOSTIC_ASSERT(mFramesTaskRunning); + + // We must have already gotten destroyed before frame decoding finished. + if (!mFramesTask) { + mFramesTaskRunning = false; + return false; + } + + // Filter duplicate frames. + if (mLastFrame == aFrame) { + return true; + } + + mPendingFramesResult.mFrames.AppendElement( + DecodedFrame{std::move(aSurface), mMetadataResult.mAnimated + ? aFrame->GetTimeout() + : FrameTimeout::Forever()}); + mLastFrame = std::move(aFrame); + + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::OnFrameAvailable -- want %zu, got %zu", + this, mFramesToDecode, mPendingFramesResult.mFrames.Length())); + + // Check if we have satisfied the number of requested frames. + if (mFramesToDecode > mPendingFramesResult.mFrames.Length()) { + return true; + } + + mFramesToDecode = 0; + if (!mFramesPromise.IsEmpty()) { + mFramesPromise.Resolve(std::move(mPendingFramesResult), __func__); + } + mFramesTaskRunning = false; + return false; + } + + void OnFramesComplete() override { + MutexAutoLock lock(mMutex); + + // We must have already gotten destroyed before frame decoding finished. + if (!mFramesTask) { + return; + } + + MOZ_LOG( + sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::OnFramesComplete -- wanted %zu, got %zu", + this, mFramesToDecode, mPendingFramesResult.mFrames.Length())); + + mFramesToDecode = 0; + mPendingFramesResult.mFinished = true; + if (!mFramesPromise.IsEmpty()) { + mFramesPromise.Resolve(std::move(mPendingFramesResult), __func__); + } + mLastFrame = nullptr; + mFramesTask = nullptr; + } + + RefPtr DecodeMetadata() override { + MutexAutoLock lock(mMutex); + + if (!mMetadataTask) { + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::DecodeMetadata -- already complete", + this)); + if (mMetadataResult.mWidth > 0 && mMetadataResult.mHeight > 0) { + return DecodeMetadataPromise::CreateAndResolve(mMetadataResult, + __func__); + } + return DecodeMetadataPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + if (!mMetadataTaskRunning) { + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::DecodeMetadata -- queue", this)); + mMetadataTaskRunning = mMetadataTask->MaybeStart(); + } + + return mMetadataPromise.Ensure(__func__); + } + + RefPtr DecodeFrameCount( + uint32_t aKnownFrameCount) override { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mFrameCountPromise.IsEmpty()); + + // If we have finished, or we have an updated frame count, return right + // away. This may drive the frame decoder for the application as the data + // comes in from the network. + if (!mFrameCountTask || aKnownFrameCount < mFrameCount) { + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::DecodeFrameCount -- known %u, " + "detected %u, complete %d", + this, aKnownFrameCount, mFrameCount, !mFrameCountTask)); + return DecodeFrameCountPromise::CreateAndResolve( + DecodeFrameCountResult{mFrameCount, + /* mFinished */ !mFrameCountTask}, + __func__); + } + + // mFrameCountTask is launching when metadata decoding is finished. + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::DecodeFrameCount -- waiting, known " + "%u, detected %u", + this, aKnownFrameCount, mFrameCount)); + return mFrameCountPromise.Ensure(__func__); + } + + RefPtr DecodeFrames(size_t aCount) override { + MutexAutoLock lock(mMutex); + + // If we cleared our task reference, then we know we finished decoding. + if (!mFramesTask) { + mPendingFramesResult.mFinished = true; + return DecodeFramesPromise::CreateAndResolve( + std::move(mPendingFramesResult), __func__); + } + + // If we are not waiting on any frames, then we know we paused decoding. + // If we still are metadata decoding, we need to wait. + if (mFramesToDecode == 0 && !mMetadataTask && !mFramesTaskRunning) { + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::DecodeFrames -- queue", this)); + mFramesTaskRunning = mFramesTask->MaybeStart(); + } + + mFramesToDecode = std::max(mFramesToDecode, aCount); + return mFramesPromise.Ensure(__func__); + } + + void CancelDecodeFrames() override { + MutexAutoLock lock(mMutex); + MOZ_LOG(sLog, LogLevel::Debug, + ("[%p] AnonymousDecoderImpl::CancelDecodeFrames", this)); + mFramesToDecode = 0; + mFramesPromise.RejectIfExists(NS_ERROR_ABORT, __func__); + } + + private: + Mutex mMutex; + MozPromiseHolder mMetadataPromise + MOZ_GUARDED_BY(mMutex); + MozPromiseHolder mFrameCountPromise + MOZ_GUARDED_BY(mMutex); + MozPromiseHolder mFramesPromise MOZ_GUARDED_BY(mMutex); + RefPtr mFramesTask MOZ_GUARDED_BY(mMutex); + RefPtr mMetadataTask MOZ_GUARDED_BY(mMutex); + RefPtr mFrameCountTask MOZ_GUARDED_BY(mMutex); + RefPtr mLastFrame MOZ_GUARDED_BY(mMutex); + DecodeMetadataResult mMetadataResult MOZ_GUARDED_BY(mMutex); + DecodeFramesResult mPendingFramesResult MOZ_GUARDED_BY(mMutex); + size_t mFramesToDecode MOZ_GUARDED_BY(mMutex) = 1; + uint32_t mFrameCount MOZ_GUARDED_BY(mMutex) = 0; + bool mMetadataTaskRunning MOZ_GUARDED_BY(mMutex) = false; + bool mFrameCountTaskRunning MOZ_GUARDED_BY(mMutex) = false; + bool mFramesTaskRunning MOZ_GUARDED_BY(mMutex) = false; +}; + +/* static */ already_AddRefed ImageUtils::CreateDecoder( + SourceBuffer* aSourceBuffer, DecoderType aType, + SurfaceFlags aSurfaceFlags) { + if (NS_WARN_IF(!aSourceBuffer)) { + return nullptr; + } + + if (NS_WARN_IF(aType == DecoderType::UNKNOWN)) { + return nullptr; + } + + RefPtr decoder = DecoderFactory::CreateAnonymousDecoder( + aType, WrapNotNull(aSourceBuffer), Nothing(), + DecoderFlags::IMAGE_IS_TRANSIENT, aSurfaceFlags); + if (NS_WARN_IF(!decoder)) { + return nullptr; + } + + auto anonymousDecoder = MakeRefPtr(); + if (NS_WARN_IF(!anonymousDecoder->Initialize(std::move(decoder)))) { + return nullptr; + } + + return anonymousDecoder.forget(); +} + +/* static */ DecoderType ImageUtils::GetDecoderType( + const nsACString& aMimeType) { + return DecoderFactory::GetDecoderType(aMimeType.Data()); +} + +} // namespace mozilla::image diff --git a/image/ImageUtils.h b/image/ImageUtils.h new file mode 100644 index 000000000000..ccf34a27b38e --- /dev/null +++ b/image/ImageUtils.h @@ -0,0 +1,141 @@ +/* -*- 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_ImageUtils_h +#define mozilla_image_ImageUtils_h + +#include "FrameTimeout.h" +#include "mozilla/image/SurfaceFlags.h" +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +class ErrorResult; + +namespace gfx { +class SourceSurface; +} + +namespace image { +class Decoder; +class imgFrame; +class ImageMetadata; +class SourceBuffer; + +/** + * The type of decoder; this is usually determined from a MIME type using + * DecoderFactory::GetDecoderType() or ImageUtils::GetDecoderType(). + */ +enum class DecoderType { + PNG, + GIF, + JPEG, + BMP, + BMP_CLIPBOARD, + ICO, + ICON, + WEBP, + AVIF, + JXL, + UNKNOWN +}; + +struct DecodeMetadataResult { + int32_t mWidth = 0; + int32_t mHeight = 0; + int32_t mRepetitions = -1; + uint32_t mFrameCount = 0; + bool mAnimated = false; + bool mFrameCountComplete = true; +}; + +struct DecodeFrameCountResult { + uint32_t mFrameCount = 0; + bool mFinished = false; +}; + +struct DecodedFrame { + RefPtr mSurface; + FrameTimeout mTimeout; +}; + +struct DecodeFramesResult { + nsTArray mFrames; + bool mFinished = false; +}; + +using DecodeMetadataPromise = MozPromise; +using DecodeFrameCountPromise = + MozPromise; +using DecodeFramesPromise = MozPromise; + +class AnonymousMetadataDecoderTask; +class AnonymousFrameCountDecoderTask; +class AnonymousFramesDecoderTask; + +class AnonymousDecoder : public SupportsThreadSafeWeakPtr { + public: + virtual RefPtr DecodeMetadata() = 0; + + virtual void Destroy() = 0; + + virtual RefPtr DecodeFrameCount( + uint32_t aKnownFrameCount) = 0; + + virtual RefPtr DecodeFrames(size_t aCount) = 0; + + virtual void CancelDecodeFrames() = 0; + +#ifdef MOZ_REFCOUNTED_LEAK_CHECKING + virtual const char* typeName() const = 0; + virtual size_t typeSize() const = 0; +#endif + + virtual ~AnonymousDecoder(); + + protected: + AnonymousDecoder(); + + // Returns true if successfully initialized else false. + virtual bool Initialize(RefPtr&& aDecoder) = 0; + + virtual void OnMetadata(const ImageMetadata* aMetadata) = 0; + + virtual void OnFrameCount(uint32_t aFrameCount, bool aComplete) = 0; + + // Returns true if the caller should continue decoding more frames if + // possible. + virtual bool OnFrameAvailable(RefPtr&& aFrame, + RefPtr&& aSurface) = 0; + + virtual void OnFramesComplete() = 0; + + friend class AnonymousMetadataDecoderTask; + friend class AnonymousFrameCountDecoderTask; + friend class AnonymousFramesDecoderTask; +}; + +class ImageUtils { + public: + static already_AddRefed CreateDecoder( + SourceBuffer* aSourceBuffer, DecoderType aType, + SurfaceFlags aSurfaceFlags); + + static DecoderType GetDecoderType(const nsACString& aMimeType); + + private: + ImageUtils() = delete; + ~ImageUtils() = delete; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ImageUtils_h diff --git a/image/decoders/nsGIFDecoder2.cpp b/image/decoders/nsGIFDecoder2.cpp index 28ecc85a4f62..49bdf72d6e35 100644 --- a/image/decoders/nsGIFDecoder2.cpp +++ b/image/decoders/nsGIFDecoder2.cpp @@ -269,6 +269,9 @@ void nsGIFDecoder2::EndImageFrame() { if (WantsFrameCount()) { mGIFStruct.pixels_remaining = 0; mGIFStruct.images_decoded++; + mGIFStruct.delay_time = 0; + mColormap = nullptr; + mColormapSize = 0; mCurrentFrameIndex = -1; // Keep updating the count every time we find a frame. diff --git a/image/moz.build b/image/moz.build index 002507132721..b9a4dadee9a1 100644 --- a/image/moz.build +++ b/image/moz.build @@ -64,7 +64,10 @@ EXPORTS.mozilla.image += [ "encoders/png/nsPNGEncoder.h", "ICOFileHeaders.h", "ImageMemoryReporter.h", + "ImageUtils.h", "Resolution.h", + "SourceBuffer.h", + "SurfaceFlags.h", "WebRenderImageProvider.h", ] @@ -86,6 +89,7 @@ UNIFIED_SOURCES += [ "ImageFactory.cpp", "ImageMemoryReporter.cpp", "ImageOps.cpp", + "ImageUtils.cpp", "ImageWrapper.cpp", "imgFrame.cpp", "imgLoader.cpp",