Bug 1194059 (Part 2) - Always detect IS_ANIMATED during the metadata decode. r=tn

This commit is contained in:
Seth Fowler 2015-08-14 00:37:13 -07:00
parent 6af0d7ec10
commit 028f2c1726
14 changed files with 282 additions and 261 deletions

View File

@ -37,10 +37,8 @@ Decoder::Decoder(RasterImage* aImage)
, mMetadataDecode(false) , mMetadataDecode(false)
, mSendPartialInvalidations(false) , mSendPartialInvalidations(false)
, mImageIsTransient(false) , mImageIsTransient(false)
, mImageIsLocked(false)
, mFirstFrameDecode(false) , mFirstFrameDecode(false)
, mInFrame(false) , mInFrame(false)
, mIsAnimated(false)
, mDataDone(false) , mDataDone(false)
, mDecodeDone(false) , mDecodeDone(false)
, mDataError(false) , mDataError(false)
@ -237,7 +235,7 @@ Decoder::CompleteDecode()
// If this image wasn't animated and isn't a transient image, mark its frame // If this image wasn't animated and isn't a transient image, mark its frame
// as optimizable. We don't support optimizing animated images and // as optimizable. We don't support optimizing animated images and
// optimizing transient images isn't worth it. // optimizing transient images isn't worth it.
if (!mIsAnimated && !mImageIsTransient && mCurrentFrame) { if (!HasAnimation() && !mImageIsTransient && mCurrentFrame) {
mCurrentFrame->SetOptimizable(); mCurrentFrame->SetOptimizable();
} }
} }
@ -260,7 +258,12 @@ Decoder::AllocateFrame(uint32_t aFrameNum,
mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize); mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
if (aFrameNum + 1 == mFrameCount) { if (aFrameNum + 1 == mFrameCount) {
PostFrameStart(); // 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;
} }
} else { } else {
PostDataError(); PostDataError();
@ -406,19 +409,11 @@ Decoder::PostHasTransparency()
} }
void void
Decoder::PostFrameStart() Decoder::PostIsAnimated(int32_t aFirstFrameTimeout)
{ {
// We shouldn't already be mid-frame mProgress |= FLAG_IS_ANIMATED;
MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!"); mImageMetadata.SetHasAnimation();
mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
// Update our state to reflect the new frame
mInFrame = true;
// If we just became animated, record that fact.
if (mFrameCount > 1) {
mIsAnimated = true;
mProgress |= FLAG_IS_ANIMATED;
}
} }
void void
@ -442,7 +437,7 @@ Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */,
// If we're not sending partial invalidations, then we send an invalidation // If we're not sending partial invalidations, then we send an invalidation
// here when the first frame is complete. // here when the first frame is complete.
if (!mSendPartialInvalidations && !mIsAnimated) { if (!mSendPartialInvalidations && !HasAnimation()) {
mInvalidRect.UnionRect(mInvalidRect, mInvalidRect.UnionRect(mInvalidRect,
gfx::IntRect(gfx::IntPoint(0, 0), GetSize())); gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
} }
@ -459,7 +454,7 @@ Decoder::PostInvalidation(const nsIntRect& aRect,
// Record this invalidation, unless we're not sending partial invalidations // Record this invalidation, unless we're not sending partial invalidations
// or we're past the first frame. // or we're past the first frame.
if (mSendPartialInvalidations && !mIsAnimated) { if (mSendPartialInvalidations && !HasAnimation()) {
mInvalidRect.UnionRect(mInvalidRect, aRect); mInvalidRect.UnionRect(mInvalidRect, aRect);
mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect)); mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect));
} }

View File

@ -181,20 +181,6 @@ public:
mImageIsTransient = aIsTransient; mImageIsTransient = aIsTransient;
} }
/**
* Set whether the image is locked for the lifetime of this decoder. We lock
* the image during our initial decode to ensure that we don't evict any
* surfaces before we realize that the image is animated.
*/
void SetImageIsLocked()
{
MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
mImageIsLocked = true;
}
bool ImageIsLocked() const { return mImageIsLocked; }
/** /**
* Set whether we should stop decoding after the first frame. * Set whether we should stop decoding after the first frame.
*/ */
@ -225,7 +211,7 @@ public:
} }
// Did we discover that the image we're decoding is animated? // Did we discover that the image we're decoding is animated?
bool HasAnimation() const { return mIsAnimated; } bool HasAnimation() const { return mImageMetadata.HasAnimation(); }
// Error tracking // Error tracking
bool HasError() const { return HasDataError() || HasDecoderError(); } bool HasError() const { return HasDataError() || HasDecoderError(); }
@ -344,9 +330,11 @@ protected:
// actual contents of the frame and give a more accurate result. // actual contents of the frame and give a more accurate result.
void PostHasTransparency(); void PostHasTransparency();
// Called by decoders when they begin a frame. Informs the image, sends // Called by decoders if they determine that the image is animated.
// notifications, and does internal book-keeping. //
void PostFrameStart(); // @param aTimeout The time for which the first frame should be shown before
// we advance to the next frame.
void PostIsAnimated(int32_t aFirstFrameTimeout);
// Called by decoders when they end a frame. Informs the image, sends // Called by decoders when they end a frame. Informs the image, sends
// notifications, and does internal book-keeping. // notifications, and does internal book-keeping.
@ -451,10 +439,8 @@ private:
bool mMetadataDecode : 1; bool mMetadataDecode : 1;
bool mSendPartialInvalidations : 1; bool mSendPartialInvalidations : 1;
bool mImageIsTransient : 1; bool mImageIsTransient : 1;
bool mImageIsLocked : 1;
bool mFirstFrameDecode : 1; bool mFirstFrameDecode : 1;
bool mInFrame : 1; bool mInFrame : 1;
bool mIsAnimated : 1;
bool mDataDone : 1; bool mDataDone : 1;
bool mDecodeDone : 1; bool mDecodeDone : 1;
bool mDataError : 1; bool mDataError : 1;

View File

@ -113,8 +113,7 @@ DecoderFactory::CreateDecoder(DecoderType aType,
int aSampleSize, int aSampleSize,
const IntSize& aResolution, const IntSize& aResolution,
bool aIsRedecode, bool aIsRedecode,
bool aImageIsTransient, bool aImageIsTransient)
bool aImageIsLocked)
{ {
if (aType == DecoderType::UNKNOWN) { if (aType == DecoderType::UNKNOWN) {
return nullptr; return nullptr;
@ -131,10 +130,7 @@ DecoderFactory::CreateDecoder(DecoderType aType,
decoder->SetResolution(aResolution); decoder->SetResolution(aResolution);
decoder->SetSendPartialInvalidations(!aIsRedecode); decoder->SetSendPartialInvalidations(!aIsRedecode);
decoder->SetImageIsTransient(aImageIsTransient); decoder->SetImageIsTransient(aImageIsTransient);
decoder->SetIsFirstFrameDecode();
if (aImageIsLocked) {
decoder->SetImageIsLocked();
}
// Set a target size for downscale-during-decode if applicable. // Set a target size for downscale-during-decode if applicable.
if (aTargetSize) { if (aTargetSize) {
@ -152,6 +148,39 @@ DecoderFactory::CreateDecoder(DecoderType aType,
return decoder.forget(); return decoder.forget();
} }
/* static */ already_AddRefed<Decoder>
DecoderFactory::CreateAnimationDecoder(DecoderType aType,
RasterImage* aImage,
SourceBuffer* aSourceBuffer,
uint32_t aFlags,
const IntSize& aResolution)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
}
MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
"Calling CreateAnimationDecoder for non-animating DecoderType");
nsRefPtr<Decoder> decoder =
GetDecoder(aType, aImage, /* aIsRedecode = */ true);
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(false);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetFlags(aFlags);
decoder->SetResolution(aResolution);
decoder->SetSendPartialInvalidations(false);
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
return nullptr;
}
return decoder.forget();
}
/* static */ already_AddRefed<Decoder> /* static */ already_AddRefed<Decoder>
DecoderFactory::CreateMetadataDecoder(DecoderType aType, DecoderFactory::CreateMetadataDecoder(DecoderType aType,
RasterImage* aImage, RasterImage* aImage,

View File

@ -38,12 +38,13 @@ public:
static DecoderType GetDecoderType(const char* aMimeType); static DecoderType GetDecoderType(const char* aMimeType);
/** /**
* Creates and initializes a decoder of type @aType. The decoder will send * Creates and initializes a decoder for non-animated images of type @aType.
* notifications to @aImage. * (If the image *is* animated, only the first frame will be decoded.) The
* decoder will send notifications to @aImage.
* *
* XXX(seth): @aIsRedecode, @aImageIsTransient, and @aImageIsLocked should * XXX(seth): @aIsRedecode and @aImageIsTransient should really be part of
* really be part of @aFlags. This requires changes to the way that decoder * @aFlags. This requires changes to the way that decoder flags work, though.
* flags work, though. See bug 1185800. * See bug 1185800.
* *
* @param aType Which type of decoder to create - JPEG, PNG, etc. * @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aImage The image will own the decoder and which should receive * @param aImage The image will own the decoder and which should receive
@ -62,9 +63,6 @@ public:
* empty rect if none). * empty rect if none).
* @param aIsRedecode Specify 'true' if this image has been decoded before. * @param aIsRedecode Specify 'true' if this image has been decoded before.
* @param aImageIsTransient Specify 'true' if this image is transient. * @param aImageIsTransient Specify 'true' if this image is transient.
* @param aImageIsLocked Specify 'true' if this image is locked for the
* lifetime of this decoder, and should be unlocked
* when the decoder finishes.
*/ */
static already_AddRefed<Decoder> static already_AddRefed<Decoder>
CreateDecoder(DecoderType aType, CreateDecoder(DecoderType aType,
@ -75,8 +73,28 @@ public:
int aSampleSize, int aSampleSize,
const gfx::IntSize& aResolution, const gfx::IntSize& aResolution,
bool aIsRedecode, bool aIsRedecode,
bool aImageIsTransient, bool aImageIsTransient);
bool aImageIsLocked);
/**
* Creates and initializes a decoder for animated images of type @aType.
* The decoder will send notifications to @aImage.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aImage The image will own the decoder and which should receive
* notifications as decoding progresses.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aFlags Flags specifying what type of output the decoder should
* produce; see GetDecodeFlags() in RasterImage.h.
* @param aResolution The resolution requested using #-moz-resolution (or an
* empty rect if none).
*/
static already_AddRefed<Decoder>
CreateAnimationDecoder(DecoderType aType,
RasterImage* aImage,
SourceBuffer* aSourceBuffer,
uint32_t aFlags,
const gfx::IntSize& aResolution);
/** /**
* Creates and initializes a metadata decoder of type @aType. This decoder * Creates and initializes a metadata decoder of type @aType. This decoder

View File

@ -291,14 +291,19 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
int32_t int32_t
FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
{ {
int32_t rawTimeout = 0;
RawAccessFrameRef frame = GetRawFrame(aFrameNum); RawAccessFrameRef frame = GetRawFrame(aFrameNum);
if (!frame) { if (frame) {
AnimationData data = frame->GetAnimationData();
rawTimeout = data.mRawTimeout;
} else if (aFrameNum == 0) {
rawTimeout = mFirstFrameTimeout;
} else {
NS_WARNING("No frame; called GetTimeoutForFrame too early?"); NS_WARNING("No frame; called GetTimeoutForFrame too early?");
return 100; return 100;
} }
AnimationData data = frame->GetAnimationData();
// Ensure a minimal time between updates so we don't throttle the UI thread. // Ensure a minimal time between updates so we don't throttle the UI thread.
// consider 0 == unspecified and make it fast but not too fast. Unless we // consider 0 == unspecified and make it fast but not too fast. Unless we
// have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
@ -312,11 +317,11 @@ FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
// It seems that there are broken tools out there that set a 0ms or 10ms // It seems that there are broken tools out there that set a 0ms or 10ms
// timeout when they really want a "default" one. So munge values in that // timeout when they really want a "default" one. So munge values in that
// range. // range.
if (data.mRawTimeout >= 0 && data.mRawTimeout <= 10) { if (rawTimeout >= 0 && rawTimeout <= 10) {
return 100; return 100;
} }
return data.mRawTimeout; return rawTimeout;
} }
static void static void

View File

@ -33,6 +33,7 @@ public:
, mLoopRemainingCount(-1) , mLoopRemainingCount(-1)
, mLastCompositedFrameIndex(-1) , mLastCompositedFrameIndex(-1)
, mLoopCount(-1) , mLoopCount(-1)
, mFirstFrameTimeout(0)
, mAnimationMode(aAnimationMode) , mAnimationMode(aAnimationMode)
, mDoneDecoding(false) , mDoneDecoding(false)
{ } { }
@ -148,6 +149,12 @@ public:
void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; } void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
int32_t LoopCount() const { return mLoopCount; } int32_t LoopCount() const { return mLoopCount; }
/*
* Set the timeout for the first frame. This is used to allow animation
* scheduling even before a full decode runs for this image.
*/
void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; }
/** /**
* Collect an accounting of the memory occupied by the compositing surfaces we * Collect an accounting of the memory occupied by the compositing surfaces we
* use during animation playback. All of the actual animation frames are * use during animation playback. All of the actual animation frames are
@ -277,6 +284,9 @@ private: // data
//! The total number of loops for the image. //! The total number of loops for the image.
int32_t mLoopCount; int32_t mLoopCount;
//! The timeout for the first frame of this image.
int32_t mFirstFrameTimeout;
//! The animation mode of this image. Constants defined in imgIContainer. //! The animation mode of this image. Constants defined in imgIContainer.
uint16_t mAnimationMode; uint16_t mAnimationMode;

View File

@ -1,44 +0,0 @@
/* -*- 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 "ImageMetadata.h"
#include "RasterImage.h"
#include "nsComponentManagerUtils.h"
#include "nsISupportsPrimitives.h"
#include "nsXPCOMCID.h"
namespace mozilla {
namespace image {
nsresult
ImageMetadata::SetOnImage(RasterImage* aImage)
{
nsresult rv = NS_OK;
if (mHotspotX != -1 && mHotspotY != -1) {
nsCOMPtr<nsISupportsPRUint32> intwrapx =
do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
nsCOMPtr<nsISupportsPRUint32> intwrapy =
do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
intwrapx->SetData(mHotspotX);
intwrapy->SetData(mHotspotY);
aImage->Set("hotspotX", intwrapx);
aImage->Set("hotspotY", intwrapy);
}
aImage->SetLoopCount(mLoopCount);
if (HasSize()) {
MOZ_ASSERT(HasOrientation(), "Should have orientation");
rv = aImage->SetSize(GetWidth(), GetHeight(), GetOrientation());
}
return rv;
}
} // namespace image
} // namespace mozilla

View File

@ -22,23 +22,26 @@ class ImageMetadata
{ {
public: public:
ImageMetadata() ImageMetadata()
: mHotspotX(-1) : mLoopCount(-1)
, mHotspotY(-1) , mFirstFrameTimeout(0)
, mLoopCount(-1) , mHasAnimation(false)
{ } { }
// Set the metadata this object represents on an image. void SetHotspot(uint16_t aHotspotX, uint16_t aHotspotY)
nsresult SetOnImage(RasterImage* aImage);
void SetHotspot(uint16_t hotspotx, uint16_t hotspoty)
{ {
mHotspotX = hotspotx; mHotspot = Some(gfx::IntPoint(aHotspotX, aHotspotY));
mHotspotY = hotspoty;
} }
gfx::IntPoint GetHotspot() const { return *mHotspot; }
bool HasHotspot() const { return mHotspot.isSome(); }
void SetLoopCount(int32_t loopcount) void SetLoopCount(int32_t loopcount)
{ {
mLoopCount = loopcount; mLoopCount = loopcount;
} }
int32_t GetLoopCount() const { return mLoopCount; }
void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; }
int32_t GetFirstFrameTimeout() const { return mFirstFrameTimeout; }
void SetSize(int32_t width, int32_t height, Orientation orientation) void SetSize(int32_t width, int32_t height, Orientation orientation)
{ {
@ -47,25 +50,28 @@ public:
mOrientation.emplace(orientation); mOrientation.emplace(orientation);
} }
} }
nsIntSize GetSize() const { return *mSize; }
Orientation GetOrientation() const { return *mOrientation; }
bool HasSize() const { return mSize.isSome(); } bool HasSize() const { return mSize.isSome(); }
bool HasOrientation() const { return mOrientation.isSome(); } bool HasOrientation() const { return mOrientation.isSome(); }
int32_t GetWidth() const { return mSize->width; } void SetHasAnimation() { mHasAnimation = true; }
int32_t GetHeight() const { return mSize->height; } bool HasAnimation() const { return mHasAnimation; }
nsIntSize GetSize() const { return *mSize; }
Orientation GetOrientation() const { return *mOrientation; }
private: private:
// The hotspot found on cursors, or -1 if none was found. /// The hotspot found on cursors, if present.
int32_t mHotspotX; Maybe<gfx::IntPoint> mHotspot;
int32_t mHotspotY;
// The loop count for animated images, or -1 for infinite loop. /// The loop count for animated images, or -1 for infinite loop.
int32_t mLoopCount; int32_t mLoopCount;
/// The timeout of an animated image's first frame.
int32_t mFirstFrameTimeout;
Maybe<nsIntSize> mSize; Maybe<nsIntSize> mSize;
Maybe<Orientation> mOrientation; Maybe<Orientation> mOrientation;
bool mHasAnimation : 1;
}; };
} // namespace image } // namespace image

View File

@ -24,6 +24,7 @@
#include "nsIConsoleService.h" #include "nsIConsoleService.h"
#include "nsIInputStream.h" #include "nsIInputStream.h"
#include "nsIScriptError.h" #include "nsIScriptError.h"
#include "nsISupportsPrimitives.h"
#include "nsPresContext.h" #include "nsPresContext.h"
#include "SourceBuffer.h" #include "SourceBuffer.h"
#include "SurfaceCache.h" #include "SurfaceCache.h"
@ -478,7 +479,7 @@ RasterImage::LookupFrame(uint32_t aFrameNum,
// We don't have a copy of this frame, and there's no decoder working on // We don't have a copy of this frame, and there's no decoder working on
// one. (Or we're sync decoding and the existing decoder hasn't even started // one. (Or we're sync decoding and the existing decoder hasn't even started
// yet.) Trigger decoding so it'll be available next time. // yet.) Trigger decoding so it'll be available next time.
MOZ_ASSERT(!mAnim, "Animated frames should be locked"); MOZ_ASSERT(!mAnim || GetNumFrames() < 1, "Animated frames should be locked");
Decode(requestedSize, aFlags); Decode(requestedSize, aFlags);
@ -529,7 +530,7 @@ RasterImage::GetRequestedFrameIndex(uint32_t aWhichFrame) const
IntRect IntRect
RasterImage::GetFirstFrameRect() RasterImage::GetFirstFrameRect()
{ {
if (mAnim) { if (mAnim && mHasBeenDecoded) {
return mAnim->GetFirstFrameRefreshArea(); return mAnim->GetFirstFrameRefreshArea();
} }
@ -582,7 +583,10 @@ RasterImage::GetAnimated(bool* aAnimated)
} }
// Otherwise, we need to have been decoded to know for sure, since if we were // Otherwise, we need to have been decoded to know for sure, since if we were
// decoded at least once mAnim would have been created for animated images // decoded at least once mAnim would have been created for animated images.
// This is true even though we check for animation during the metadata decode,
// because we may still discover animation only during the full decode for
// corrupt images.
if (!mHasBeenDecoded) { if (!mHasBeenDecoded) {
return NS_ERROR_NOT_AVAILABLE; return NS_ERROR_NOT_AVAILABLE;
} }
@ -921,21 +925,9 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount,
mFrameCount = aNewFrameCount; mFrameCount = aNewFrameCount;
if (aNewFrameCount == 2) { if (aNewFrameCount == 2) {
// We're becoming animated, so initialize animation stuff. MOZ_ASSERT(mAnim, "Should already have animation state");
MOZ_ASSERT(!mAnim, "Already have animation state?");
mAnim = MakeUnique<FrameAnimator>(this, mSize, mAnimationMode);
// We don't support discarding animated images (See bug 414259).
// Lock the image and throw away the key.
//
// Note that this is inefficient, since we could get rid of the source
// data too. However, doing this is actually hard, because we're probably
// mid-decode, and thus we're decoding out of the source buffer. Since
// we're going to fix this anyway later, and since we didn't kill the
// source data in the old world either, locking is acceptable for the
// moment.
LockImage();
// We may be able to start animating.
if (mPendingAnimation && ShouldAnimate()) { if (mPendingAnimation && ShouldAnimate()) {
StartAnimation(); StartAnimation();
} }
@ -947,7 +939,8 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount,
} }
nsresult nsresult
RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) RasterImage::SetMetadata(const ImageMetadata& aMetadata,
bool aFromMetadataDecode)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
@ -955,26 +948,64 @@ RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation)
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
// Ensure that we have positive values if (aMetadata.HasSize()) {
// XXX - Why isn't the size unsigned? Should this be changed? IntSize size = aMetadata.GetSize();
if ((aWidth < 0) || (aHeight < 0)) { if (size.width < 0 || size.height < 0) {
return NS_ERROR_INVALID_ARG; return NS_ERROR_INVALID_ARG;
}
MOZ_ASSERT(aMetadata.HasOrientation());
Orientation orientation = aMetadata.GetOrientation();
// If we already have a size, check the new size against the old one.
if (mHasSize && (size != mSize || orientation != mOrientation)) {
NS_WARNING("Image changed size or orientation on redecode! "
"This should not happen!");
DoError();
return NS_ERROR_UNEXPECTED;
}
// Set the size and flag that we have it.
mSize = size;
mOrientation = orientation;
mHasSize = true;
} }
// if we already have a size, check the new size against the old one if (mHasSize && aMetadata.HasAnimation() && !mAnim) {
if (mHasSize && // We're becoming animated, so initialize animation stuff.
((aWidth != mSize.width) || mAnim = MakeUnique<FrameAnimator>(this, mSize, mAnimationMode);
(aHeight != mSize.height) ||
(aOrientation != mOrientation))) { // We don't support discarding animated images (See bug 414259).
NS_WARNING("Image changed size on redecode! This should not happen!"); // Lock the image and throw away the key.
DoError(); LockImage();
return NS_ERROR_UNEXPECTED;
if (!aFromMetadataDecode) {
// The metadata decode reported that this image isn't animated, but we
// discovered that it actually was during the full decode. This is a
// rare failure that only occurs for corrupt images. To recover, we need
// to discard all existing surfaces and redecode.
RecoverFromLossOfFrames(mSize, DECODE_FLAGS_DEFAULT);
}
} }
// Set the size and flag that we have it if (mAnim) {
mSize.SizeTo(aWidth, aHeight); mAnim->SetLoopCount(aMetadata.GetLoopCount());
mOrientation = aOrientation; mAnim->SetFirstFrameTimeout(aMetadata.GetFirstFrameTimeout());
mHasSize = true; }
if (aMetadata.HasHotspot()) {
IntPoint hotspot = aMetadata.GetHotspot();
nsCOMPtr<nsISupportsPRUint32> intwrapx =
do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
nsCOMPtr<nsISupportsPRUint32> intwrapy =
do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
intwrapx->SetData(hotspot.x);
intwrapy->SetData(hotspot.y);
Set("hotspotX", intwrapx);
Set("hotspotY", intwrapy);
}
return NS_OK; return NS_OK;
} }
@ -999,10 +1030,9 @@ RasterImage::StartAnimation()
MOZ_ASSERT(ShouldAnimate(), "Should not animate!"); MOZ_ASSERT(ShouldAnimate(), "Should not animate!");
// If we don't have mAnim yet, then we're not ready to animate. Setting // If we're not ready to animate, then set mPendingAnimation, which will cause
// mPendingAnimation will cause us to start animating as soon as we have a // us to start animating if and when we do become ready.
// second frame, which causes mAnim to be constructed. mPendingAnimation = !mAnim || GetNumFrames() < 2;
mPendingAnimation = !mAnim;
if (mPendingAnimation) { if (mPendingAnimation) {
return NS_OK; return NS_OK;
} }
@ -1091,19 +1121,6 @@ RasterImage::GetFrameIndex(uint32_t aWhichFrame)
: mAnim->GetCurrentAnimationFrameIndex(); : mAnim->GetCurrentAnimationFrameIndex();
} }
void
RasterImage::SetLoopCount(int32_t aLoopCount)
{
if (mError) {
return;
}
// No need to set this if we're not an animation.
if (mAnim) {
mAnim->SetLoopCount(aLoopCount);
}
}
NS_IMETHODIMP_(IntRect) NS_IMETHODIMP_(IntRect)
RasterImage::GetImageSpaceInvalidationRect(const IntRect& aRect) RasterImage::GetImageSpaceInvalidationRect(const IntRect& aRect)
{ {
@ -1391,20 +1408,19 @@ RasterImage::Decode(const IntSize& aSize, uint32_t aFlags)
Maybe<IntSize> targetSize = mSize != aSize ? Some(aSize) : Nothing(); Maybe<IntSize> targetSize = mSize != aSize ? Some(aSize) : Nothing();
bool imageIsLocked = false;
if (!mHasBeenDecoded) {
// Lock the image while we're decoding, so that it doesn't get evicted from
// the SurfaceCache before we have a chance to realize that it's animated.
// The corresponding unlock happens in FinalizeDecoder.
LockImage();
imageIsLocked = true;
}
// Create a decoder. // Create a decoder.
nsRefPtr<Decoder> decoder = nsRefPtr<Decoder> decoder;
DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer, targetSize, if (mAnim) {
aFlags, mRequestedSampleSize, mRequestedResolution, decoder = DecoderFactory::CreateAnimationDecoder(mDecoderType, this,
mHasBeenDecoded, mTransient, imageIsLocked); mSourceBuffer, aFlags,
mRequestedResolution);
} else {
decoder = DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer,
targetSize, aFlags,
mRequestedSampleSize,
mRequestedResolution,
mHasBeenDecoded, mTransient);
}
// Make sure DecoderFactory was able to create a decoder successfully. // Make sure DecoderFactory was able to create a decoder successfully.
if (!decoder) { if (!decoder) {
@ -1953,7 +1969,8 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder)
} }
// Record all the metadata the decoder gathered about this image. // Record all the metadata the decoder gathered about this image.
nsresult rv = aDecoder->GetImageMetadata().SetOnImage(this); nsresult rv = SetMetadata(aDecoder->GetImageMetadata(),
aDecoder->IsMetadataDecode());
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
aDecoder->PostResizeError(); aDecoder->PostResizeError();
} }
@ -1964,17 +1981,8 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder)
if (aDecoder->GetDecodeTotallyDone() && !mError) { if (aDecoder->GetDecodeTotallyDone() && !mError) {
// Flag that we've been decoded before. // Flag that we've been decoded before.
mHasBeenDecoded = true; mHasBeenDecoded = true;
if (mAnim) {
if (aDecoder->HasAnimation()) { mAnim->SetDoneDecoding(true);
if (mAnim) {
mAnim->SetDoneDecoding(true);
} else {
// The OnAddedFrame event that will create mAnim is still in the event
// queue. Wait for it.
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &RasterImage::MarkAnimationDecoded);
NS_DispatchToMainThread(runnable);
}
} }
} }
@ -2022,11 +2030,6 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder)
} }
} }
if (aDecoder->ImageIsLocked()) {
// Unlock the image, balancing the LockImage call we made in CreateDecoder.
UnlockImage();
}
// If we were a metadata decode and a full decode was requested, do it. // If we were a metadata decode and a full decode was requested, do it.
if (done && wasMetadata && mWantFullDecode) { if (done && wasMetadata && mWantFullDecode) {
mWantFullDecode = false; mWantFullDecode = false;
@ -2034,17 +2037,6 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder)
} }
} }
void
RasterImage::MarkAnimationDecoded()
{
MOZ_ASSERT(mAnim, "Should have an animation now");
if (!mAnim) {
return;
}
mAnim->SetDoneDecoding(true);
}
void void
RasterImage::ReportDecoderError(Decoder* aDecoder) RasterImage::ReportDecoderError(Decoder* aDecoder)
{ {

View File

@ -132,6 +132,7 @@ namespace image {
class Decoder; class Decoder;
class FrameAnimator; class FrameAnimator;
class ImageMetadata;
class SourceBuffer; class SourceBuffer;
/** /**
@ -188,18 +189,6 @@ public:
void OnAddedFrame(uint32_t aNewFrameCount, const nsIntRect& aNewRefreshArea); void OnAddedFrame(uint32_t aNewFrameCount, const nsIntRect& aNewRefreshArea);
/** Sets the size and inherent orientation of the container. This should only
* be called by the decoder. This function may be called multiple times, but
* will throw an error if subsequent calls do not match the first.
*/
nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation);
/**
* Number of times to loop the image.
* @note -1 means forever.
*/
void SetLoopCount(int32_t aLoopCount);
/** /**
* Sends the provided progress notifications to ProgressTracker. * Sends the provided progress notifications to ProgressTracker.
* *
@ -222,8 +211,7 @@ public:
*/ */
void FinalizeDecoder(Decoder* aDecoder); void FinalizeDecoder(Decoder* aDecoder);
// Helper methods for FinalizeDecoder. // Helper method for FinalizeDecoder.
void MarkAnimationDecoded();
void ReportDecoderError(Decoder* aDecoder); void ReportDecoderError(Decoder* aDecoder);
@ -339,6 +327,19 @@ private:
*/ */
NS_IMETHOD DecodeMetadata(uint32_t aFlags); NS_IMETHOD DecodeMetadata(uint32_t aFlags);
/**
* Sets the size, inherent orientation, animation metadata, and other
* information about the image gathered during decoding.
*
* This function may be called multiple times, but will throw an error if
* subsequent calls do not match the first.
*
* @param aMetadata The metadata to set on this image.
* @param aFromMetadataDecode True if this metadata came from a metadata
* decode; false if it came from a full decode.
*/
nsresult SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode);
/** /**
* In catastrophic circumstances like a GPU driver crash, we may lose our * In catastrophic circumstances like a GPU driver crash, we may lose our
* frames even if they're locked. RecoverFromLossOfFrames discards all * frames even if they're locked. RecoverFromLossOfFrames discards all

View File

@ -857,6 +857,11 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
} }
mGIFStruct.delay_time = GETINT16(q + 1) * 10; mGIFStruct.delay_time = GETINT16(q + 1) * 10;
if (mGIFStruct.delay_time > 0) {
PostIsAnimated(mGIFStruct.delay_time);
}
GETN(1, gif_consume_block); GETN(1, gif_consume_block);
break; break;
@ -921,11 +926,20 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
break; break;
case gif_image_header: { case gif_image_header: {
if (mGIFStruct.images_decoded > 0 && IsFirstFrameDecode()) { if (mGIFStruct.images_decoded == 1) {
// We're about to get a second frame, but we only want the first. Stop if (!HasAnimation()) {
// decoding now. // We should've already called PostIsAnimated(); this must be a
mGIFStruct.state = gif_done; // corrupt animated image with a first frame timeout of zero. Signal
break; // that we're animated now, before the first-frame decode early exit
// below, so that RasterImage can detect that this happened.
PostIsAnimated(/* aFirstFrameTimeout = */ 0);
}
if (IsFirstFrameDecode()) {
// We're about to get a second frame, but we only want the first. Stop
// decoding now.
mGIFStruct.state = gif_done;
break;
}
} }
// Get image offsets, with respect to the screen origin // Get image offsets, with respect to the screen origin

View File

@ -378,8 +378,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
} }
if (!HasSize() && mContainedDecoder->HasSize()) { if (!HasSize() && mContainedDecoder->HasSize()) {
PostSize(mContainedDecoder->GetImageMetadata().GetWidth(), nsIntSize size = mContainedDecoder->GetSize();
mContainedDecoder->GetImageMetadata().GetHeight()); PostSize(size.width, size.height);
} }
mPos += aCount; mPos += aCount;
@ -479,8 +479,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
return; return;
} }
PostSize(mContainedDecoder->GetImageMetadata().GetWidth(), nsIntSize size = mContainedDecoder->GetSize();
mContainedDecoder->GetImageMetadata().GetHeight()); PostSize(size.width, size.height);
// We have the size. If we're doing a metadata decode, we're done. // We have the size. If we're doing a metadata decode, we're done.
if (IsMetadataDecode()) { if (IsMetadataDecode()) {

View File

@ -56,32 +56,33 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
{ } { }
#ifdef PNG_APNG_SUPPORTED #ifdef PNG_APNG_SUPPORTED
int32_t GetNextFrameDelay(png_structp aPNG, png_infop aInfo)
{
// Delay, in seconds, is delayNum / delayDen.
png_uint_16 delayNum = png_get_next_frame_delay_num(aPNG, aInfo);
png_uint_16 delayDen = png_get_next_frame_delay_den(aPNG, aInfo);
if (delayNum == 0) {
return 0; // SetFrameTimeout() will set to a minimum.
}
if (delayDen == 0) {
delayDen = 100; // So says the APNG spec.
}
// Need to cast delay_num to float to have a proper division and
// the result to int to avoid a compiler warning.
return static_cast<int32_t>(static_cast<double>(delayNum) * 1000 / delayDen);
}
nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
: mDispose(DisposalMethod::KEEP) : mDispose(DisposalMethod::KEEP)
, mBlend(BlendMethod::OVER) , mBlend(BlendMethod::OVER)
, mTimeout(0) , mTimeout(0)
{ {
png_uint_16 delay_num, delay_den; png_byte dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
// delay, in seconds is delay_num/delay_den png_byte blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
png_byte dispose_op;
png_byte blend_op;
delay_num = png_get_next_frame_delay_num(aPNG, aInfo);
delay_den = png_get_next_frame_delay_den(aPNG, aInfo);
dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
if (delay_num == 0) {
mTimeout = 0; // SetFrameTimeout() will set to a minimum
} else {
if (delay_den == 0) {
delay_den = 100; // so says the APNG spec
}
// Need to cast delay_num to float to have a proper division and
// the result to int to avoid compiler warning
mTimeout = static_cast<int32_t>(static_cast<double>(delay_num) *
1000 / delay_den);
}
if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) { if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) {
mDispose = DisposalMethod::RESTORE_PREVIOUS; mDispose = DisposalMethod::RESTORE_PREVIOUS;
@ -96,6 +97,8 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
} else { } else {
mBlend = BlendMethod::OVER; mBlend = BlendMethod::OVER;
} }
mTimeout = GetNextFrameDelay(aPNG, aInfo);
} }
#endif #endif
@ -595,18 +598,25 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
png_longjmp(decoder->mPNG, 1); // invalid number of channels png_longjmp(decoder->mPNG, 1); // invalid number of channels
} }
#ifdef PNG_APNG_SUPPORTED
bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL);
if (isAnimated) {
decoder->PostIsAnimated(GetNextFrameDelay(png_ptr, info_ptr));
}
#endif
if (decoder->IsMetadataDecode()) { if (decoder->IsMetadataDecode()) {
decoder->CheckForTransparency(decoder->format, decoder->CheckForTransparency(decoder->format,
IntRect(0, 0, width, height)); IntRect(0, 0, width, height));
// We have the size and transparency information we're looking for, so we // We have the metadata we're looking for, so we don't need to decode any
// don't need to decode any further. // further.
decoder->mSuccessfulEarlyFinish = true; decoder->mSuccessfulEarlyFinish = true;
png_longjmp(decoder->mPNG, 1); png_longjmp(decoder->mPNG, 1);
} }
#ifdef PNG_APNG_SUPPORTED #ifdef PNG_APNG_SUPPORTED
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) { if (isAnimated) {
png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
nullptr); nullptr);
} }

View File

@ -59,7 +59,6 @@ UNIFIED_SOURCES += [
'Image.cpp', 'Image.cpp',
'ImageCacheKey.cpp', 'ImageCacheKey.cpp',
'ImageFactory.cpp', 'ImageFactory.cpp',
'ImageMetadata.cpp',
'ImageOps.cpp', 'ImageOps.cpp',
'ImageWrapper.cpp', 'ImageWrapper.cpp',
'imgFrame.cpp', 'imgFrame.cpp',