Index: imgContainer.cpp =================================================================== RCS file: /cvsroot/mozilla/modules/libpr0n/src/imgContainer.cpp,v retrieving revision 1.42 diff -u -r1.42 imgContainer.cpp --- imgContainer.cpp 12 Jan 2005 20:16:07 -0000 1.42 +++ imgContainer.cpp 1 Oct 2006 21:46:41 -0000 @@ -40,15 +40,24 @@ * ***** END LICENSE BLOCK ***** */ #include "nsComponentManagerUtils.h" +#include "nsWeakReference.h" #include "imgContainer.h" -NS_IMPL_ISUPPORTS2(imgContainer, imgIContainer, nsIProperties) +NS_IMPL_ISUPPORTS3(imgContainer, imgIContainer, nsITimerCallback, nsIProperties) //****************************************************************************** imgContainer::imgContainer() : mSize(0,0), - mFrame(nsnull) + mFirstFrameRefreshArea(), + mCurrentDecodingFrameIndex(0), + mCurrentAnimationFrameIndex(0), + mLastCompositedFrameIndex(-1), + mDoneDecoding(PR_FALSE), + mAnimating(PR_FALSE), + mAnimationMode(kNormalAnimMode), + mLoopCount(-1), + mObserver(nsnull) { mProperties = do_CreateInstance("@mozilla.org/properties;1"); } @@ -56,12 +65,15 @@ //****************************************************************************** imgContainer::~imgContainer() { - mFrame = nsnull; + if (mTimer) + mTimer->Cancel(); } //****************************************************************************** -/* void init (in PRIn32 aWidth, in PRInt32 aHeight, in imgIContainerObserver aObserver); */ -NS_IMETHODIMP imgContainer::Init(PRInt32 aWidth, PRInt32 aHeight, imgIContainerObserver *aObserver) +/* void init (in PRInt32 aWidth, in PRInt32 aHeight, + in imgIContainerObserver aObserver); */ +NS_IMETHODIMP imgContainer::Init(PRInt32 aWidth, PRInt32 aHeight, + imgIContainerObserver *aObserver) { if (aWidth <= 0 || aHeight <= 0) { NS_WARNING("error - negative image size\n"); @@ -70,6 +82,10 @@ mSize.SizeTo(aWidth, aHeight); + // no matching function for call do_GetWeakReference(imgIContainerObserver*&) + // is this referenced already? + mObserver = do_GetWeakReference(NS_STATIC_CAST(nsISupports*, aObserver)); + return NS_OK; } @@ -117,15 +133,13 @@ NS_ASSERTION(aCurrentFrame, "imgContainer::GetCurrentFrame; Invalid Arg"); if (!aCurrentFrame) return NS_ERROR_INVALID_POINTER; - - if (mFrame) { - *aCurrentFrame = mFrame; - NS_ADDREF(*aCurrentFrame); - return NS_OK; - } else { - *aCurrentFrame = nsnull; + + if (!(*aCurrentFrame = inlinedGetCurrentFrame())) return NS_ERROR_FAILURE; - } + + NS_ADDREF(*aCurrentFrame); + + return NS_OK; } //****************************************************************************** @@ -136,7 +150,8 @@ if (!aNumFrames) return NS_ERROR_INVALID_ARG; - *aNumFrames = mFrame ? 1 : 0; + *aNumFrames = mFrames.Count(); + return NS_OK; } @@ -144,14 +159,17 @@ /* gfxIImageFrame getFrameAt (in unsigned long index); */ NS_IMETHODIMP imgContainer::GetFrameAt(PRUint32 index, gfxIImageFrame **_retval) { + NS_ENSURE_ARG(index < NS_STATIC_CAST(PRUint32, mFrames.Count())); + NS_ASSERTION(_retval, "imgContainer::GetFrameAt; Invalid Arg"); if (!_retval) return NS_ERROR_INVALID_POINTER; - if (!mFrame || index != 0) + if (!(*_retval = mFrames[index])) return NS_ERROR_FAILURE; - *_retval = mFrame; + NS_ADDREF(*_retval); + return NS_OK; } @@ -162,12 +180,35 @@ NS_ASSERTION(item, "imgContainer::AppendFrame; Invalid Arg"); if (!item) return NS_ERROR_INVALID_ARG; - NS_ASSERTION(!mFrame, "imgContainer::AppendFrame; Tried to add another frame when only 1 is allowed"); + + PRInt32 numFrames = mFrames.Count(); + if (numFrames == 0) { + // First Frame + // 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) + PRInt32 frameDisposalMethod; + item->GetFrameDisposalMethod(&frameDisposalMethod); + if (frameDisposalMethod == DISPOSE_CLEAR || + frameDisposalMethod == DISPOSE_RESTORE_PREVIOUS) + item->GetRect(mFirstFrameRefreshArea); + } else { + // Calculate mFirstFrameRefreshArea + // 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 + nsIntRect itemRect; + item->GetRect(itemRect); + mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, itemRect); + } - if (mFrame) - return NS_ERROR_UNEXPECTED; + mFrames.AppendObject(item); - mFrame = item; + // If this is our second frame, start the animation. + // Must be called after AppendElement because StartAnimation checks for > 1 + // frame + if (numFrames == 1) + StartAnimation(); + return NS_OK; } @@ -182,13 +223,10 @@ /* void endFrameDecode (in gfxIImageFrame item, in unsigned long timeout); */ NS_IMETHODIMP imgContainer::EndFrameDecode(PRUint32 aFrameNum, PRUint32 aTimeout) { - NS_ASSERTION(aFrameNum == 0, "Received an EndFrameDecode call with an invalid frame number"); - - if (aFrameNum != 0 || !mFrame) - return NS_ERROR_UNEXPECTED; - - mFrame->SetTimeout(aTimeout); - + // Assume there's another frame. + // both aFrameNum and mCurrentDecodingFrameIndex are 0 based. + mCurrentDecodingFrameIndex = aFrameNum + 1; + return NS_OK; } @@ -196,6 +234,11 @@ /* void decodingComplete (); */ NS_IMETHODIMP imgContainer::DecodingComplete(void) { + mDoneDecoding = PR_TRUE; + // If there's only 1 frame, optimize it. + // Optimizing animated PNGs is not supported + if (mFrames.Count() == 1) + mFrames[0]->SetMutable(PR_FALSE); return NS_OK; } @@ -203,7 +246,7 @@ /* void clear (); */ NS_IMETHODIMP imgContainer::Clear() { - mFrame = nsnull; + mFrames.Clear(); return NS_OK; } @@ -214,13 +257,31 @@ if (!aAnimationMode) return NS_ERROR_INVALID_ARG; - *aAnimationMode = kDontAnimMode; + *aAnimationMode = mAnimationMode; return NS_OK; } NS_IMETHODIMP imgContainer::SetAnimationMode(PRUint16 aAnimationMode) { - // Ignore setting of animation mode + NS_ASSERTION(aAnimationMode == imgIContainer::kNormalAnimMode || + aAnimationMode == imgIContainer::kDontAnimMode || + aAnimationMode == imgIContainer::kLoopOnceAnimMode, + "Wrong Animation Mode is being set!"); + + switch (mAnimationMode = aAnimationMode) { + case kDontAnimMode: + StopAnimation(); + break; + case kNormalAnimMode: + if (mLoopCount != 0 || mCurrentAnimationFrameIndex + 1 < mFrames.Count()) + StartAnimation(); + break; + case kLoopOnceAnimMode: + if (mCurrentAnimationFrameIndex + 1 < mFrames.Count()) + StartAnimation(); + break; + } + return NS_OK; } @@ -228,6 +289,30 @@ /* void startAnimation () */ NS_IMETHODIMP imgContainer::StartAnimation() { + if (mAnimationMode == kDontAnimMode || mAnimating || mTimer) + return NS_OK; + + if (mFrames.Count() > 1) { + PRInt32 timeout; + gfxIImageFrame *currentFrame = inlinedGetCurrentFrame(); + if (currentFrame) { + currentFrame->GetTimeout(&timeout); + if (timeout <= 0) // -1 means display this frame forever + return NS_OK; + } else + timeout = 100; // XXX hack.. the timer notify code will do the right + // thing, so just get that started + + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!mTimer) + return NS_ERROR_OUT_OF_MEMORY; + + // The only way mAnimating becomes true is if the mTimer is created + mAnimating = PR_TRUE; + mTimer->InitWithCallback(NS_STATIC_CAST(nsITimerCallback*, this), + timeout, nsITimer::TYPE_REPEATING_SLACK); + } + return NS_OK; } @@ -235,24 +320,61 @@ /* void stopAnimation (); */ NS_IMETHODIMP imgContainer::StopAnimation() { + mAnimating = PR_FALSE; + + if (!mTimer) + return NS_OK; + + mTimer->Cancel(); + mTimer = nsnull; + return NS_OK; } //****************************************************************************** +/* void resetAnimation (); */ +NS_IMETHODIMP imgContainer::ResetAnimation() +{ + if (mCurrentAnimationFrameIndex == 0 || mAnimationMode == kDontAnimMode) + return NS_OK; + + PRBool oldAnimating = mAnimating; + + if (mAnimating) { + nsresult rv = StopAnimation(); + if (NS_FAILED(rv)) + return rv; + } + + mLastCompositedFrameIndex = -1; + mCurrentAnimationFrameIndex = 0; + // Update display + //~ nsCOMPtr observer(do_QueryReferent(mObserver)); + //~ if (observer) + //~ observer->FrameChanged(this, mFrames[0], &mFirstFrameRefreshArea); + + if (oldAnimating) + return StartAnimation(); + else + return NS_OK; +} + +//****************************************************************************** /* attribute long loopCount; */ NS_IMETHODIMP imgContainer::GetLoopCount(PRInt32 *aLoopCount) { - return NS_ERROR_NOT_IMPLEMENTED; + NS_ASSERTION(aLoopCount, "ptr is null"); + *aLoopCount = mLoopCount; + return NS_OK; } NS_IMETHODIMP imgContainer::SetLoopCount(PRInt32 aLoopCount) { - return NS_ERROR_NOT_IMPLEMENTED; -} + // -1 infinite + // 0 no looping, one iteration + // 1 one loop, two iterations + // ... + mLoopCount = aLoopCount; -//****************************************************************************** -/* void resetAnimation (); */ -NS_IMETHODIMP imgContainer::ResetAnimation() -{ - return NS_ERROR_NOT_IMPLEMENTED; + return NS_OK; } Index: imgContainer.h =================================================================== RCS file: /cvsroot/mozilla/modules/libpr0n/src/imgContainer.h,v retrieving revision 1.20 diff -u -r1.20 imgContainer.h --- imgContainer.h 12 Jan 2005 20:16:07 -0000 1.20 +++ imgContainer.h 1 Oct 2006 21:46:41 -0000 @@ -41,11 +41,13 @@ #ifndef __imgContainer_h__ #define __imgContainer_h__ +#include "nsCOMArray.h" #include "nsCOMPtr.h" #include "imgIContainer.h" #include "gfxIImageFrame.h" #include "nsIProperties.h" - +#include "nsITimer.h" +#include "nsWeakReference.h" #define NS_IMGCONTAINER_CID \ { /* 27f0682c-ff64-4dd2-ae7a-668e59f2fd38 */ \ @@ -55,19 +57,79 @@ {0xae, 0x7a, 0x66, 0x8e, 0x59, 0xf2, 0xfd, 0x38} \ } -class imgContainer : public imgIContainer, public nsIProperties +class imgContainer : public imgIContainer, + public nsITimerCallback, + public nsIProperties { public: NS_DECL_ISUPPORTS NS_DECL_IMGICONTAINER + NS_DECL_NSITIMERCALLBACK NS_FORWARD_SAFE_NSIPROPERTIES(mProperties) imgContainer(); virtual ~imgContainer(); private: + + /** "Disposal" method indicates how the image should be handled before the + * subsequent image is displayed. + */ + enum { + DISPOSE_CLEAR_ALL = -1, //!< Clear the whole image, revealing + //! what was there before the gif displayed + DISPOSE_NOT_SPECIFIED = 0, //!< Leave frame, let new frame draw on top + DISPOSE_KEEP = 1, //!< Leave frame, let new frame draw on top + DISPOSE_CLEAR = 2, //!< Clear the frame's area, revealing bg + DISPOSE_RESTORE_PREVIOUS = 3 //!< Restore the previous (composited) frame + }; + + inline gfxIImageFrame* inlinedGetCurrentFrame() { + if (mLastCompositedFrameIndex == mCurrentAnimationFrameIndex) + return mCompositingFrame; + return mFrames.SafeObjectAt(mCurrentAnimationFrameIndex); + } + nsIntSize mSize; - nsCOMPtr mFrame; + + //! All the s of the PNG + nsCOMArray mFrames; + + //! Area of the first frame that needs to be redrawn on subsequent loops + nsIntRect mFirstFrameRefreshArea; + + PRInt32 mCurrentDecodingFrameIndex; // 0 to numFrames-1 + PRInt32 mCurrentAnimationFrameIndex; // 0 to numFrames-1 + //! Track the last composited frame for Optimizations (See DoComposite code) + PRInt32 mLastCompositedFrameIndex; + + //! Whether we can assume there will be no more frames + //! (and thus loop the animation) + PRBool mDoneDecoding; + + //! Are we currently animating the PNG? + PRBool mAnimating; + //! See imgIContainer for mode constants + PRUint16 mAnimationMode; + //! # loops remaining before animation stops (-1 no stop) + PRInt32 mLoopCount; + + /** For managing blending of frames + * + * Some GIF animations will use the mCompositingFrame to composite images + * and just hand this back to the caller when it is time to draw the frame. + * NOTE: When clearing mCompositingFrame, remember to set + * mLastCompositedFrameIndex to -1. Code assume that if + * mLastCompositedFrameIndex >= 0 then mCompositingFrame exists. + */ + nsCOMPtr mCompositingFrame; + + //! imgIContainerObserver + nsWeakPtr mObserver; + + //! Timer to animate multiframed images + nsCOMPtr mTimer; + nsCOMPtr mProperties; };