summaryrefslogtreecommitdiffstats
path: root/image/Decoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'image/Decoder.cpp')
-rw-r--r--image/Decoder.cpp570
1 files changed, 570 insertions, 0 deletions
diff --git a/image/Decoder.cpp b/image/Decoder.cpp
new file mode 100644
index 0000000000..76301d7596
--- /dev/null
+++ b/image/Decoder.cpp
@@ -0,0 +1,570 @@
+/* -*- 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 "Decoder.h"
+
+#include "DecodePool.h"
+#include "IDecodingTask.h"
+#include "ISurfaceProvider.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Telemetry.h"
+#include "nsComponentManagerUtils.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+
+using mozilla::gfx::IntPoint;
+using mozilla::gfx::IntRect;
+using mozilla::gfx::IntSize;
+using mozilla::gfx::SurfaceFormat;
+
+namespace mozilla {
+namespace image {
+
+class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final {
+ public:
+ explicit AutoRecordDecoderTelemetry(Decoder* aDecoder) : mDecoder(aDecoder) {
+ MOZ_ASSERT(mDecoder);
+
+ // Begin recording telemetry data.
+ mStartTime = TimeStamp::Now();
+ }
+
+ ~AutoRecordDecoderTelemetry() {
+ // Finish telemetry.
+ mDecoder->mDecodeTime += (TimeStamp::Now() - mStartTime);
+ }
+
+ private:
+ Decoder* mDecoder;
+ TimeStamp mStartTime;
+};
+
+Decoder::Decoder(RasterImage* aImage)
+ : mInProfile(nullptr),
+ mTransform(nullptr),
+ mImageData(nullptr),
+ mImageDataLength(0),
+ mCMSMode(gfxPlatform::GetCMSMode()),
+ mImage(aImage),
+ mFrameRecycler(nullptr),
+ mProgress(NoProgress),
+ mFrameCount(0),
+ mLoopLength(FrameTimeout::Zero()),
+ mDecoderFlags(DefaultDecoderFlags()),
+ mSurfaceFlags(DefaultSurfaceFlags()),
+ mInitialized(false),
+ mMetadataDecode(false),
+ mHaveExplicitOutputSize(false),
+ mInFrame(false),
+ mFinishedNewFrame(false),
+ mHasFrameToTake(false),
+ mReachedTerminalState(false),
+ mDecodeDone(false),
+ mError(false),
+ mShouldReportError(false),
+ mFinalizeFrames(true) {}
+
+Decoder::~Decoder() {
+ MOZ_ASSERT(mProgress == NoProgress || !mImage,
+ "Destroying Decoder without taking all its progress changes");
+ MOZ_ASSERT(mInvalidRect.IsEmpty() || !mImage,
+ "Destroying Decoder without taking all its invalidations");
+ mInitialized = false;
+
+ if (mInProfile) {
+ // mTransform belongs to us only if mInProfile is non-null
+ if (mTransform) {
+ qcms_transform_release(mTransform);
+ }
+ qcms_profile_release(mInProfile);
+ }
+
+ if (mImage && !NS_IsMainThread()) {
+ // Dispatch mImage to main thread to prevent it from being destructed by the
+ // decode thread.
+ SurfaceCache::ReleaseImageOnMainThread(mImage.forget());
+ }
+}
+
+void Decoder::SetSurfaceFlags(SurfaceFlags aSurfaceFlags) {
+ MOZ_ASSERT(!mInitialized);
+ mSurfaceFlags = aSurfaceFlags;
+ if (mSurfaceFlags & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
+ mCMSMode = CMSMode::Off;
+ }
+}
+
+qcms_profile* Decoder::GetCMSOutputProfile() const {
+ if (mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE) {
+ return gfxPlatform::GetCMSsRGBProfile();
+ }
+ return gfxPlatform::GetCMSOutputProfile();
+}
+
+qcms_transform* Decoder::GetCMSsRGBTransform(SurfaceFormat aFormat) const {
+ if (mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE) {
+ // We want a transform to convert from sRGB to device space, but we are
+ // already using sRGB as our device space. That means we can skip
+ // color management entirely.
+ return nullptr;
+ }
+ if (qcms_profile_is_sRGB(gfxPlatform::GetCMSOutputProfile())) {
+ // Device space is sRGB so we can skip color management as well.
+ return nullptr;
+ }
+
+ switch (aFormat) {
+ case SurfaceFormat::B8G8R8A8:
+ case SurfaceFormat::B8G8R8X8:
+ return gfxPlatform::GetCMSBGRATransform();
+ case SurfaceFormat::R8G8B8A8:
+ case SurfaceFormat::R8G8B8X8:
+ return gfxPlatform::GetCMSRGBATransform();
+ case SurfaceFormat::R8G8B8:
+ return gfxPlatform::GetCMSRGBTransform();
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported surface format!");
+ return nullptr;
+ }
+}
+
+/*
+ * Common implementation of the decoder interface.
+ */
+
+nsresult Decoder::Init() {
+ // No re-initializing
+ MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");
+
+ // All decoders must have a SourceBufferIterator.
+ MOZ_ASSERT(mIterator);
+
+ // Metadata decoders must not set an output size.
+ MOZ_ASSERT_IF(mMetadataDecode, !mHaveExplicitOutputSize);
+
+ // All decoders must be anonymous except for metadata decoders.
+ // XXX(seth): Soon that exception will be removed.
+ MOZ_ASSERT_IF(mImage, IsMetadataDecode());
+
+ // Implementation-specific initialization.
+ nsresult rv = InitInternal();
+
+ mInitialized = true;
+
+ return rv;
+}
+
+LexerResult Decoder::Decode(IResumable* aOnResume /* = nullptr */) {
+ MOZ_ASSERT(mInitialized, "Should be initialized here");
+ MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
+
+ // If we're already done, don't attempt to keep decoding.
+ if (GetDecodeDone()) {
+ return LexerResult(HasError() ? TerminalState::FAILURE
+ : TerminalState::SUCCESS);
+ }
+
+ LexerResult lexerResult(TerminalState::FAILURE);
+ {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR_RELEVANT_FOR_JS(GRAPHICS_ImageDecoding);
+ AutoRecordDecoderTelemetry telemetry(this);
+
+ lexerResult = DoDecode(*mIterator, aOnResume);
+ };
+
+ if (lexerResult.is<Yield>()) {
+ // We either need more data to continue (in which case either @aOnResume or
+ // the caller will reschedule us to run again later), or the decoder is
+ // yielding to allow the caller access to some intermediate output.
+ return lexerResult;
+ }
+
+ // We reached a terminal state; we're now done decoding.
+ MOZ_ASSERT(lexerResult.is<TerminalState>());
+ mReachedTerminalState = true;
+
+ // If decoding failed, record that fact.
+ if (lexerResult.as<TerminalState>() == TerminalState::FAILURE) {
+ PostError();
+ }
+
+ // Perform final cleanup.
+ CompleteDecode();
+
+ return LexerResult(HasError() ? TerminalState::FAILURE
+ : TerminalState::SUCCESS);
+}
+
+LexerResult Decoder::TerminateFailure() {
+ PostError();
+
+ // Perform final cleanup if need be.
+ if (!mReachedTerminalState) {
+ mReachedTerminalState = true;
+ CompleteDecode();
+ }
+
+ return LexerResult(TerminalState::FAILURE);
+}
+
+bool Decoder::ShouldSyncDecode(size_t aByteLimit) {
+ MOZ_ASSERT(aByteLimit > 0);
+ MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
+
+ return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
+}
+
+void Decoder::CompleteDecode() {
+ // Implementation-specific finalization.
+ nsresult rv = BeforeFinishInternal();
+ if (NS_FAILED(rv)) {
+ PostError();
+ }
+
+ rv = HasError() ? FinishWithErrorInternal() : FinishInternal();
+ if (NS_FAILED(rv)) {
+ PostError();
+ }
+
+ if (IsMetadataDecode()) {
+ // If this was a metadata decode and we never got a size, the decode failed.
+ if (!HasSize()) {
+ PostError();
+ }
+ return;
+ }
+
+ // If the implementation left us mid-frame, finish that up. Note that it may
+ // have left us transparent.
+ if (mInFrame) {
+ PostHasTransparency();
+ PostFrameStop();
+ }
+
+ // If PostDecodeDone() has not been called, we may need to send teardown
+ // notifications if it is unrecoverable.
+ if (mDecodeDone) {
+ MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
+ } else {
+ // We should always report an error to the console in this case.
+ mShouldReportError = true;
+
+ if (GetCompleteFrameCount() > 0) {
+ // We're usable if we have at least one complete frame, so do exactly
+ // what we should have when the decoder completed.
+ PostHasTransparency();
+ PostDecodeDone();
+ } else {
+ // We're not usable. Record some final progress indicating the error.
+ mProgress |= FLAG_DECODE_COMPLETE | FLAG_HAS_ERROR;
+ }
+ }
+}
+
+void Decoder::SetOutputSize(const OrientedIntSize& aSize) {
+ mOutputSize = Some(aSize);
+ mHaveExplicitOutputSize = true;
+}
+
+Maybe<OrientedIntSize> Decoder::ExplicitOutputSize() const {
+ MOZ_ASSERT_IF(mHaveExplicitOutputSize, mOutputSize);
+ return mHaveExplicitOutputSize ? mOutputSize : Nothing();
+}
+
+Maybe<uint32_t> Decoder::TakeCompleteFrameCount() {
+ const bool finishedNewFrame = mFinishedNewFrame;
+ mFinishedNewFrame = false;
+ return finishedNewFrame ? Some(GetCompleteFrameCount()) : Nothing();
+}
+
+DecoderFinalStatus Decoder::FinalStatus() const {
+ return DecoderFinalStatus(IsMetadataDecode(), GetDecodeDone(), HasError(),
+ ShouldReportError());
+}
+
+DecoderTelemetry Decoder::Telemetry() const {
+ MOZ_ASSERT(mIterator);
+ return DecoderTelemetry(SpeedHistogram(),
+ mIterator ? mIterator->ByteCount() : 0,
+ mIterator ? mIterator->ChunkCount() : 0, mDecodeTime);
+}
+
+nsresult Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
+ gfx::SurfaceFormat aFormat,
+ const Maybe<AnimationParams>& aAnimParams) {
+ mCurrentFrame = AllocateFrameInternal(aOutputSize, aFormat, aAnimParams,
+ std::move(mCurrentFrame));
+
+ if (mCurrentFrame) {
+ mHasFrameToTake = true;
+
+ // Gather the raw pointers the decoders will use.
+ mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
+
+ // We should now be on |aFrameNum|. (Note that we're comparing the frame
+ // number, which is zero-based, with the frame count, which is one-based.)
+ MOZ_ASSERT_IF(aAnimParams, aAnimParams->mFrameNum + 1 == mFrameCount);
+
+ // If we're past the first frame, PostIsAnimated() should've been called.
+ MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());
+
+ // Update our state to reflect the new frame.
+ MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
+ mInFrame = true;
+ }
+
+ return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
+}
+
+RawAccessFrameRef Decoder::AllocateFrameInternal(
+ const gfx::IntSize& aOutputSize, SurfaceFormat aFormat,
+ const Maybe<AnimationParams>& aAnimParams,
+ RawAccessFrameRef&& aPreviousFrame) {
+ if (HasError()) {
+ return RawAccessFrameRef();
+ }
+
+ uint32_t frameNum = aAnimParams ? aAnimParams->mFrameNum : 0;
+ if (frameNum != mFrameCount) {
+ MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
+ return RawAccessFrameRef();
+ }
+
+ if (aOutputSize.width <= 0 || aOutputSize.height <= 0) {
+ NS_WARNING("Trying to add frame with zero or negative size");
+ return RawAccessFrameRef();
+ }
+
+ if (frameNum > 0) {
+ if (aPreviousFrame->GetDisposalMethod() !=
+ DisposalMethod::RESTORE_PREVIOUS) {
+ // If the new restore frame is the direct previous frame, then we know
+ // the dirty rect is composed only of the current frame's blend rect and
+ // the restore frame's clear rect (if applicable) which are handled in
+ // filters.
+ mRestoreFrame = std::move(aPreviousFrame);
+ mRestoreDirtyRect.SetBox(0, 0, 0, 0);
+ } else {
+ // We only need the previous frame's dirty rect, because while there may
+ // have been several frames between us and mRestoreFrame, the only areas
+ // that changed are the restore frame's clear rect, the current frame
+ // blending rect, and the previous frame's blending rect. All else is
+ // forgotten due to us restoring the same frame again.
+ mRestoreDirtyRect = aPreviousFrame->GetBoundedBlendRect();
+ }
+ }
+
+ RawAccessFrameRef ref;
+
+ // If we have a frame recycler, it must be for an animated image producing
+ // full frames. If the higher layers are discarding frames because of the
+ // memory footprint, then the recycler will allow us to reuse the buffers.
+ // Each frame should be the same size and have mostly the same properties.
+ if (mFrameRecycler) {
+ MOZ_ASSERT(aAnimParams);
+
+ ref = mFrameRecycler->RecycleFrame(mRecycleRect);
+ if (ref) {
+ // If the recycled frame is actually the current restore frame, we cannot
+ // use it. If the next restore frame is the new frame we are creating, in
+ // theory we could reuse it, but we would need to store the restore frame
+ // animation parameters elsewhere. For now we just drop it.
+ bool blocked = ref.get() == mRestoreFrame.get();
+ if (!blocked) {
+ blocked = NS_FAILED(ref->InitForDecoderRecycle(aAnimParams.ref()));
+ }
+
+ if (blocked) {
+ ref.reset();
+ }
+ }
+ }
+
+ // Either the recycler had nothing to give us, or we don't have a recycler.
+ // Produce a new frame to store the data.
+ if (!ref) {
+ // There is no underlying data to reuse, so reset the recycle rect to be
+ // the full frame, to ensure the restore frame is fully copied.
+ mRecycleRect = IntRect(IntPoint(0, 0), aOutputSize);
+
+ bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
+ auto frame = MakeNotNull<RefPtr<imgFrame>>();
+ if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFormat, nonPremult,
+ aAnimParams, bool(mFrameRecycler)))) {
+ NS_WARNING("imgFrame::Init should succeed");
+ return RawAccessFrameRef();
+ }
+
+ ref = frame->RawAccessRef();
+ if (!ref) {
+ frame->Abort();
+ return RawAccessFrameRef();
+ }
+ }
+
+ mFrameCount++;
+
+ return ref;
+}
+
+/*
+ * Hook stubs. Override these as necessary in decoder implementations.
+ */
+
+nsresult Decoder::InitInternal() { return NS_OK; }
+nsresult Decoder::BeforeFinishInternal() { return NS_OK; }
+nsresult Decoder::FinishInternal() { return NS_OK; }
+
+nsresult Decoder::FinishWithErrorInternal() {
+ MOZ_ASSERT(!mInFrame);
+ return NS_OK;
+}
+
+/*
+ * Progress Notifications
+ */
+
+void Decoder::PostSize(int32_t aWidth, int32_t aHeight,
+ Orientation aOrientation, Resolution aResolution) {
+ // Validate.
+ MOZ_ASSERT(aWidth >= 0, "Width can't be negative!");
+ MOZ_ASSERT(aHeight >= 0, "Height can't be negative!");
+
+ // Set our intrinsic size.
+ mImageMetadata.SetSize(aWidth, aHeight, aOrientation, aResolution);
+
+ // Verify it is the expected size, if given. Note that this is only used by
+ // the ICO decoder for embedded image types, so only its subdecoders are
+ // required to handle failures in PostSize.
+ if (!IsExpectedSize()) {
+ PostError();
+ return;
+ }
+
+ // Set our output size if it's not already set.
+ if (!mOutputSize) {
+ mOutputSize = Some(mImageMetadata.GetSize());
+ }
+
+ MOZ_ASSERT(mOutputSize->width <= mImageMetadata.GetSize().width &&
+ mOutputSize->height <= mImageMetadata.GetSize().height,
+ "Output size will result in upscaling");
+
+ // Record this notification.
+ mProgress |= FLAG_SIZE_AVAILABLE;
+}
+
+void Decoder::PostHasTransparency() { mProgress |= FLAG_HAS_TRANSPARENCY; }
+
+void Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout) {
+ mProgress |= FLAG_IS_ANIMATED;
+ mImageMetadata.SetHasAnimation();
+ mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
+}
+
+void Decoder::PostFrameStop(Opacity aFrameOpacity) {
+ // We should be mid-frame
+ MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
+ MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
+ MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
+
+ // Update our state.
+ mInFrame = false;
+ mFinishedNewFrame = true;
+
+ mCurrentFrame->Finish(
+ aFrameOpacity, mFinalizeFrames,
+ /* aOrientationSwapsWidthAndHeight = */ mImageMetadata.HasOrientation() &&
+ mImageMetadata.GetOrientation().SwapsWidthAndHeight());
+
+ mProgress |= FLAG_FRAME_COMPLETE;
+
+ mLoopLength += mCurrentFrame->GetTimeout();
+
+ if (mFrameCount == 1) {
+ // If we're not sending partial invalidations, then we send an invalidation
+ // here when the first frame is complete.
+ if (!ShouldSendPartialInvalidations()) {
+ mInvalidRect.UnionRect(mInvalidRect,
+ OrientedIntRect(OrientedIntPoint(), Size()));
+ }
+
+ // If we dispose of the first frame by clearing it, then the first frame's
+ // refresh area is all of itself. RESTORE_PREVIOUS is invalid (assumed to
+ // be DISPOSE_CLEAR).
+ switch (mCurrentFrame->GetDisposalMethod()) {
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
+ case DisposalMethod::CLEAR:
+ case DisposalMethod::CLEAR_ALL:
+ case DisposalMethod::RESTORE_PREVIOUS:
+ mFirstFrameRefreshArea = IntRect(IntPoint(), Size().ToUnknownSize());
+ break;
+ case DisposalMethod::KEEP:
+ case DisposalMethod::NOT_SPECIFIED:
+ break;
+ }
+ } else {
+ // Some GIFs are huge but only have a small area that they animate. We only
+ // need to refresh that small area when frame 0 comes around again.
+ mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea,
+ mCurrentFrame->GetBoundedBlendRect());
+ }
+}
+
+void Decoder::PostInvalidation(const OrientedIntRect& aRect,
+ const Maybe<OrientedIntRect>& aRectAtOutputSize
+ /* = Nothing() */) {
+ // We should be mid-frame
+ MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!");
+ MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!");
+
+ // Record this invalidation, unless we're not sending partial invalidations
+ // or we're past the first frame.
+ if (ShouldSendPartialInvalidations() && mFrameCount == 1) {
+ mInvalidRect.UnionRect(mInvalidRect, aRect);
+ mCurrentFrame->ImageUpdated(
+ aRectAtOutputSize.valueOr(aRect).ToUnknownRect());
+ }
+}
+
+void Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */) {
+ MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode");
+ MOZ_ASSERT(!mInFrame, "Can't be done decoding if we're mid-frame!");
+ MOZ_ASSERT(!mDecodeDone, "Decode already done!");
+ mDecodeDone = true;
+
+ mImageMetadata.SetLoopCount(aLoopCount);
+
+ // Some metadata that we track should take into account every frame in the
+ // image. If this is a first-frame-only decode, our accumulated loop length
+ // and first frame refresh area only includes the first frame, so it's not
+ // correct and we don't record it.
+ if (!IsFirstFrameDecode()) {
+ mImageMetadata.SetLoopLength(mLoopLength);
+ mImageMetadata.SetFirstFrameRefreshArea(mFirstFrameRefreshArea);
+ }
+
+ mProgress |= FLAG_DECODE_COMPLETE;
+}
+
+void Decoder::PostError() {
+ mError = true;
+
+ if (mInFrame) {
+ MOZ_ASSERT(mCurrentFrame);
+ MOZ_ASSERT(mFrameCount > 0);
+ mCurrentFrame->Abort();
+ mInFrame = false;
+ --mFrameCount;
+ mHasFrameToTake = false;
+ }
+}
+
+} // namespace image
+} // namespace mozilla