michael@0: michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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 "Decoder.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsIScriptError.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: namespace mozilla { michael@0: namespace image { michael@0: michael@0: Decoder::Decoder(RasterImage &aImage) michael@0: : mImage(aImage) michael@0: , mCurrentFrame(nullptr) michael@0: , mImageData(nullptr) michael@0: , mColormap(nullptr) michael@0: , mDecodeFlags(0) michael@0: , mDecodeDone(false) michael@0: , mDataError(false) michael@0: , mFrameCount(0) michael@0: , mFailCode(NS_OK) michael@0: , mNeedsNewFrame(false) michael@0: , mInitialized(false) michael@0: , mSizeDecode(false) michael@0: , mInFrame(false) michael@0: , mIsAnimated(false) michael@0: { michael@0: } michael@0: michael@0: Decoder::~Decoder() michael@0: { michael@0: mInitialized = false; michael@0: } michael@0: michael@0: /* michael@0: * Common implementation of the decoder interface. michael@0: */ michael@0: michael@0: void michael@0: Decoder::Init() michael@0: { michael@0: // No re-initializing michael@0: NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!"); michael@0: NS_ABORT_IF_FALSE(mObserver, "Need an observer!"); michael@0: michael@0: // Fire OnStartDecode at init time to support bug 512435. michael@0: if (!IsSizeDecode()) michael@0: mObserver->OnStartDecode(); michael@0: michael@0: // Implementation-specific initialization michael@0: InitInternal(); michael@0: michael@0: mInitialized = true; michael@0: } michael@0: michael@0: // Initializes a decoder whose image and observer is already being used by a michael@0: // parent decoder michael@0: void michael@0: Decoder::InitSharedDecoder(uint8_t* imageData, uint32_t imageDataLength, michael@0: uint32_t* colormap, uint32_t colormapSize, michael@0: imgFrame* currentFrame) michael@0: { michael@0: // No re-initializing michael@0: NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!"); michael@0: NS_ABORT_IF_FALSE(mObserver, "Need an observer!"); michael@0: michael@0: mImageData = imageData; michael@0: mImageDataLength = imageDataLength; michael@0: mColormap = colormap; michael@0: mColormapSize = colormapSize; michael@0: mCurrentFrame = currentFrame; michael@0: // We have all the frame data, so we've started the frame. michael@0: if (!IsSizeDecode()) { michael@0: PostFrameStart(); michael@0: } michael@0: michael@0: // Implementation-specific initialization michael@0: InitInternal(); michael@0: mInitialized = true; michael@0: } michael@0: michael@0: void michael@0: Decoder::Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy) michael@0: { michael@0: PROFILER_LABEL("ImageDecoder", "Write"); michael@0: MOZ_ASSERT(NS_IsMainThread() || aStrategy == DECODE_ASYNC); michael@0: michael@0: // We're strict about decoder errors michael@0: NS_ABORT_IF_FALSE(!HasDecoderError(), michael@0: "Not allowed to make more decoder calls after error!"); michael@0: michael@0: // If a data error occured, just ignore future data michael@0: if (HasDataError()) michael@0: return; michael@0: michael@0: if (IsSizeDecode() && HasSize()) { michael@0: // More data came in since we found the size. We have nothing to do here. michael@0: return; michael@0: } michael@0: michael@0: // Pass the data along to the implementation michael@0: WriteInternal(aBuffer, aCount, aStrategy); michael@0: michael@0: // If we're a synchronous decoder and we need a new frame to proceed, let's michael@0: // create one and call it again. michael@0: while (aStrategy == DECODE_SYNC && NeedsNewFrame() && !HasDataError()) { michael@0: nsresult rv = AllocateFrame(); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Tell the decoder to use the data it saved when it asked for a new frame. michael@0: WriteInternal(nullptr, 0, aStrategy); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: Decoder::Finish(RasterImage::eShutdownIntent aShutdownIntent) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Implementation-specific finalization michael@0: if (!HasError()) michael@0: FinishInternal(); michael@0: michael@0: // If the implementation left us mid-frame, finish that up. michael@0: if (mInFrame && !HasError()) michael@0: PostFrameStop(); michael@0: michael@0: // If PostDecodeDone() has not been called, we need to sent teardown michael@0: // notifications. michael@0: if (!IsSizeDecode() && !mDecodeDone) { michael@0: michael@0: // Log data errors to the error console michael@0: nsCOMPtr consoleService = michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: nsCOMPtr errorObject = michael@0: do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); michael@0: michael@0: if (consoleService && errorObject && !HasDecoderError()) { michael@0: nsAutoString msg(NS_LITERAL_STRING("Image corrupt or truncated: ") + michael@0: NS_ConvertUTF8toUTF16(mImage.GetURIString())); michael@0: michael@0: if (NS_SUCCEEDED(errorObject->InitWithWindowID( michael@0: msg, michael@0: NS_ConvertUTF8toUTF16(mImage.GetURIString()), michael@0: EmptyString(), 0, 0, nsIScriptError::errorFlag, michael@0: "Image", mImage.InnerWindowID() michael@0: ))) { michael@0: consoleService->LogMessage(errorObject); michael@0: } michael@0: } michael@0: michael@0: bool usable = !HasDecoderError(); michael@0: if (aShutdownIntent != RasterImage::eShutdownIntent_NotNeeded && !HasDecoderError()) { michael@0: // If we only have a data error, we're usable if we have at least one complete frame. michael@0: if (GetCompleteFrameCount() == 0) { michael@0: usable = false; michael@0: } michael@0: } michael@0: michael@0: // If we're usable, do exactly what we should have when the decoder michael@0: // completed. michael@0: if (usable) { michael@0: if (mInFrame) { michael@0: PostFrameStop(); michael@0: } michael@0: PostDecodeDone(); michael@0: } else { michael@0: if (mObserver) { michael@0: mObserver->OnStopDecode(NS_ERROR_FAILURE); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Set image metadata before calling DecodingComplete, because DecodingComplete calls Optimize(). michael@0: mImageMetadata.SetOnImage(&mImage); michael@0: michael@0: if (mDecodeDone) { michael@0: mImage.DecodingComplete(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Decoder::FinishSharedDecoder() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (!HasError()) { michael@0: FinishInternal(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: Decoder::AllocateFrame() michael@0: { michael@0: MOZ_ASSERT(mNeedsNewFrame); michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: MarkFrameDirty(); michael@0: michael@0: nsresult rv; michael@0: imgFrame* frame = nullptr; michael@0: if (mNewFrameData.mPaletteDepth) { michael@0: rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX, michael@0: mNewFrameData.mOffsetY, mNewFrameData.mWidth, michael@0: mNewFrameData.mHeight, mNewFrameData.mFormat, michael@0: mNewFrameData.mPaletteDepth, michael@0: &mImageData, &mImageDataLength, michael@0: &mColormap, &mColormapSize, &frame); michael@0: } else { michael@0: rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX, michael@0: mNewFrameData.mOffsetY, mNewFrameData.mWidth, michael@0: mNewFrameData.mHeight, mNewFrameData.mFormat, michael@0: &mImageData, &mImageDataLength, &frame); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mCurrentFrame = frame; michael@0: } else { michael@0: mCurrentFrame = nullptr; michael@0: } michael@0: michael@0: // Notify if appropriate michael@0: if (NS_SUCCEEDED(rv) && mNewFrameData.mFrameNum == mFrameCount) { michael@0: PostFrameStart(); michael@0: } else if (NS_FAILED(rv)) { michael@0: PostDataError(); michael@0: } michael@0: michael@0: // Mark ourselves as not needing another frame before talking to anyone else michael@0: // so they can tell us if they need yet another. michael@0: mNeedsNewFrame = false; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: Decoder::FlushInvalidations() michael@0: { michael@0: NS_ABORT_IF_FALSE(!HasDecoderError(), michael@0: "Not allowed to make more decoder calls after error!"); michael@0: michael@0: // If we've got an empty invalidation rect, we have nothing to do michael@0: if (mInvalidRect.IsEmpty()) michael@0: return; michael@0: michael@0: if (mObserver) { michael@0: #ifdef XP_MACOSX michael@0: // Bug 703231 michael@0: // Because of high quality down sampling on mac we show scan lines while decoding. michael@0: // Bypass this problem by redrawing the border. michael@0: if (mImageMetadata.HasSize()) { michael@0: nsIntRect mImageBound(0, 0, mImageMetadata.GetWidth(), mImageMetadata.GetHeight()); michael@0: michael@0: mInvalidRect.Inflate(1); michael@0: mInvalidRect = mInvalidRect.Intersect(mImageBound); michael@0: } michael@0: #endif michael@0: mObserver->FrameChanged(&mInvalidRect); michael@0: } michael@0: michael@0: // Clear the invalidation rectangle michael@0: mInvalidRect.SetEmpty(); michael@0: } michael@0: michael@0: void michael@0: Decoder::SetSizeOnImage() michael@0: { michael@0: MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size"); michael@0: MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation"); michael@0: michael@0: mImage.SetSize(mImageMetadata.GetWidth(), michael@0: mImageMetadata.GetHeight(), michael@0: mImageMetadata.GetOrientation()); michael@0: } michael@0: michael@0: /* michael@0: * Hook stubs. Override these as necessary in decoder implementations. michael@0: */ michael@0: michael@0: void Decoder::InitInternal() { } michael@0: void Decoder::WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy) { } michael@0: void Decoder::FinishInternal() { } michael@0: michael@0: /* michael@0: * Progress Notifications michael@0: */ michael@0: michael@0: void michael@0: Decoder::PostSize(int32_t aWidth, michael@0: int32_t aHeight, michael@0: Orientation aOrientation /* = Orientation()*/) michael@0: { michael@0: // Validate michael@0: NS_ABORT_IF_FALSE(aWidth >= 0, "Width can't be negative!"); michael@0: NS_ABORT_IF_FALSE(aHeight >= 0, "Height can't be negative!"); michael@0: michael@0: // Tell the image michael@0: mImageMetadata.SetSize(aWidth, aHeight, aOrientation); michael@0: michael@0: // Notify the observer michael@0: if (mObserver) michael@0: mObserver->OnStartContainer(); michael@0: } michael@0: michael@0: void michael@0: Decoder::PostFrameStart() michael@0: { michael@0: // We shouldn't already be mid-frame michael@0: NS_ABORT_IF_FALSE(!mInFrame, "Starting new frame but not done with old one!"); michael@0: michael@0: // We should take care of any invalidation region when wrapping up the michael@0: // previous frame michael@0: NS_ABORT_IF_FALSE(mInvalidRect.IsEmpty(), michael@0: "Start image frame with non-empty invalidation region!"); michael@0: michael@0: // Update our state to reflect the new frame michael@0: mFrameCount++; michael@0: mInFrame = true; michael@0: michael@0: // Decoder implementations should only call this method if they successfully michael@0: // appended the frame to the image. So mFrameCount should always match that michael@0: // reported by the Image. michael@0: NS_ABORT_IF_FALSE(mFrameCount == mImage.GetNumFrames(), michael@0: "Decoder frame count doesn't match image's!"); michael@0: michael@0: // Fire notifications michael@0: if (mObserver) { michael@0: mObserver->OnStartFrame(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Decoder::PostFrameStop(FrameBlender::FrameAlpha aFrameAlpha /* = FrameBlender::kFrameHasAlpha */, michael@0: FrameBlender::FrameDisposalMethod aDisposalMethod /* = FrameBlender::kDisposeKeep */, michael@0: int32_t aTimeout /* = 0 */, michael@0: FrameBlender::FrameBlendMethod aBlendMethod /* = FrameBlender::kBlendOver */) michael@0: { michael@0: // We should be mid-frame michael@0: NS_ABORT_IF_FALSE(mInFrame, "Stopping frame when we didn't start one!"); michael@0: NS_ABORT_IF_FALSE(mCurrentFrame, "Stopping frame when we don't have one!"); michael@0: michael@0: // Update our state michael@0: mInFrame = false; michael@0: michael@0: if (aFrameAlpha == FrameBlender::kFrameOpaque) { michael@0: mCurrentFrame->SetHasNoAlpha(); michael@0: } michael@0: michael@0: mCurrentFrame->SetFrameDisposalMethod(aDisposalMethod); michael@0: mCurrentFrame->SetRawTimeout(aTimeout); michael@0: mCurrentFrame->SetBlendMethod(aBlendMethod); michael@0: mCurrentFrame->ImageUpdated(mCurrentFrame->GetRect()); michael@0: michael@0: // Flush any invalidations before we finish the frame michael@0: FlushInvalidations(); michael@0: michael@0: // Fire notifications michael@0: if (mObserver) { michael@0: mObserver->OnStopFrame(); michael@0: if (mFrameCount > 1 && !mIsAnimated) { michael@0: mIsAnimated = true; michael@0: mObserver->OnImageIsAnimated(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: Decoder::PostInvalidation(nsIntRect& aRect) michael@0: { michael@0: // We should be mid-frame michael@0: NS_ABORT_IF_FALSE(mInFrame, "Can't invalidate when not mid-frame!"); michael@0: NS_ABORT_IF_FALSE(mCurrentFrame, "Can't invalidate when not mid-frame!"); michael@0: michael@0: // Account for the new region michael@0: mInvalidRect.UnionRect(mInvalidRect, aRect); michael@0: mCurrentFrame->ImageUpdated(aRect); michael@0: } michael@0: michael@0: void michael@0: Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */) michael@0: { michael@0: NS_ABORT_IF_FALSE(!IsSizeDecode(), "Can't be done with decoding with size decode!"); michael@0: NS_ABORT_IF_FALSE(!mInFrame, "Can't be done decoding if we're mid-frame!"); michael@0: NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!"); michael@0: mDecodeDone = true; michael@0: michael@0: mImageMetadata.SetLoopCount(aLoopCount); michael@0: mImageMetadata.SetIsNonPremultiplied(GetDecodeFlags() & DECODER_NO_PREMULTIPLY_ALPHA); michael@0: michael@0: if (mObserver) { michael@0: mObserver->OnStopDecode(NS_OK); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Decoder::PostDataError() michael@0: { michael@0: mDataError = true; michael@0: } michael@0: michael@0: void michael@0: Decoder::PostDecoderError(nsresult aFailureCode) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_FAILED(aFailureCode), "Not a failure code!"); michael@0: michael@0: mFailCode = aFailureCode; michael@0: michael@0: // XXXbholley - we should report the image URI here, but imgContainer michael@0: // needs to know its URI first michael@0: NS_WARNING("Image decoding error - This is probably a bug!"); michael@0: } michael@0: michael@0: void michael@0: Decoder::NeedNewFrame(uint32_t framenum, uint32_t x_offset, uint32_t y_offset, michael@0: uint32_t width, uint32_t height, michael@0: gfxImageFormat format, michael@0: uint8_t palette_depth /* = 0 */) michael@0: { michael@0: // Decoders should never call NeedNewFrame without yielding back to Write(). michael@0: MOZ_ASSERT(!mNeedsNewFrame); michael@0: michael@0: // We don't want images going back in time or skipping frames. michael@0: MOZ_ASSERT(framenum == mFrameCount || framenum == (mFrameCount - 1)); michael@0: michael@0: mNewFrameData = NewFrameData(framenum, x_offset, y_offset, width, height, format, palette_depth); michael@0: mNeedsNewFrame = true; michael@0: } michael@0: michael@0: void michael@0: Decoder::MarkFrameDirty() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mCurrentFrame) { michael@0: mCurrentFrame->ApplyDirtToSurfaces(); michael@0: } michael@0: } michael@0: michael@0: } // namespace image michael@0: } // namespace mozilla