Back to index

lightning-sunbird  0.9+nobinonly
imgContainerGIF.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 2001
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Stuart Parmenter <pavlov@netscape.com>
00025  *   Chris Saari <saari@netscape.com>
00026  *   Asko Tontti <atontti@cc.hut.fi>
00027  *   Arron Mogge <paper@animecity.nu>
00028  *
00029  * Alternatively, the contents of this file may be used under the terms of
00030  * either the GNU General Public License Version 2 or later (the "GPL"), or
00031  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00032  * in which case the provisions of the GPL or the LGPL are applicable instead
00033  * of those above. If you wish to allow use of your version of this file only
00034  * under the terms of either the GPL or the LGPL, and not to allow others to
00035  * use your version of this file under the terms of the MPL, indicate your
00036  * decision by deleting the provisions above and replace them with the notice
00037  * and other provisions required by the GPL or the LGPL. If you do not delete
00038  * the provisions above, a recipient may use your version of this file under
00039  * the terms of any one of the MPL, the GPL or the LGPL.
00040  *
00041  * ***** END LICENSE BLOCK ***** */
00042 
00043 #include "imgContainerGIF.h"
00044 
00045 #include "nsIServiceManager.h"
00046 #include "nsIImage.h"
00047 #include "nsIInterfaceRequestor.h"
00048 #include "nsIInterfaceRequestorUtils.h"
00049 #include "nsMemory.h"
00050 
00051 NS_IMPL_ISUPPORTS2(imgContainerGIF, imgIContainer, nsITimerCallback)
00052 
00053 //******************************************************************************
00054 imgContainerGIF::imgContainerGIF()
00055   : mObserver(nsnull)
00056   , mSize(0,0)
00057   , mFirstFrameRefreshArea()
00058   , mCurrentDecodingFrameIndex(0)
00059   , mCurrentAnimationFrameIndex(0)
00060   , mLastCompositedFrameIndex(-1)
00061   , mDoneDecoding(PR_FALSE)
00062   , mAnimating(PR_FALSE)
00063   , mAnimationMode(kNormalAnimMode)
00064   , mLoopCount(-1)
00065 {
00066   /* member initializers and constructor code */
00067 }
00068 
00069 //******************************************************************************
00070 imgContainerGIF::~imgContainerGIF()
00071 {
00072   if (mTimer)
00073     mTimer->Cancel();
00074 }
00075 
00076 //******************************************************************************
00077 /* void init (in PRInt32 aWidth, in PRInt32 aHeight,
00078               in imgIContainerObserver aObserver); */
00079 NS_IMETHODIMP imgContainerGIF::Init(PRInt32 aWidth, PRInt32 aHeight,
00080                                     imgIContainerObserver *aObserver)
00081 {
00082   if (aWidth <= 0 || aHeight <= 0) {
00083     NS_WARNING("error - negative image size\n");
00084     return NS_ERROR_FAILURE;
00085   }
00086 
00087   mSize.SizeTo(aWidth, aHeight);
00088 
00089   mObserver = do_GetWeakReference(aObserver);
00090 
00091   return NS_OK;
00092 }
00093 
00094 //******************************************************************************
00095 /* readonly attribute gfx_format preferredAlphaChannelFormat; */
00096 NS_IMETHODIMP imgContainerGIF::GetPreferredAlphaChannelFormat(gfx_format *aFormat)
00097 {
00098   *aFormat = gfxIFormats::RGB_A1;
00099   return NS_OK;
00100 }
00101 
00102 //******************************************************************************
00103 /* readonly attribute PRInt32 width; */
00104 NS_IMETHODIMP imgContainerGIF::GetWidth(PRInt32 *aWidth)
00105 {
00106   *aWidth = mSize.width;
00107   return NS_OK;
00108 }
00109 
00110 //******************************************************************************
00111 /* readonly attribute PRInt32 height; */
00112 NS_IMETHODIMP imgContainerGIF::GetHeight(PRInt32 *aHeight)
00113 {
00114   *aHeight = mSize.height;
00115   return NS_OK;
00116 }
00117 
00118 //******************************************************************************
00119 /* readonly attribute gfxIImageFrame currentFrame; */
00120 NS_IMETHODIMP imgContainerGIF::GetCurrentFrame(gfxIImageFrame * *aCurrentFrame)
00121 {
00122   if (!(*aCurrentFrame = inlinedGetCurrentFrame()))
00123     return NS_ERROR_FAILURE;
00124 
00125   NS_ADDREF(*aCurrentFrame);
00126   return NS_OK;
00127 }
00128 
00129 //******************************************************************************
00130 /* readonly attribute unsigned long numFrames; */
00131 NS_IMETHODIMP imgContainerGIF::GetNumFrames(PRUint32 *aNumFrames)
00132 {
00133   *aNumFrames = mFrames.Count();
00134   return NS_OK;
00135 }
00136 
00137 //******************************************************************************
00138 /* gfxIImageFrame getFrameAt (in unsigned long index); */
00139 NS_IMETHODIMP imgContainerGIF::GetFrameAt(PRUint32 index,
00140                                           gfxIImageFrame **_retval)
00141 {
00142   NS_ENSURE_ARG(index < mFrames.Count());
00143 
00144   if (!(*_retval = mFrames[index]))
00145     return NS_ERROR_FAILURE;
00146 
00147   NS_ADDREF(*_retval);
00148   return NS_OK;
00149 }
00150 
00151 //******************************************************************************
00152 /* void appendFrame (in gfxIImageFrame item); */
00153 NS_IMETHODIMP imgContainerGIF::AppendFrame(gfxIImageFrame *item)
00154 {
00155   NS_ASSERTION(item, "imgContainerGIF::AppendFrame: item is null");
00156   if (!item)
00157     return NS_ERROR_NULL_POINTER;
00158 
00159   PRInt32 numFrames = mFrames.Count();
00160   if (numFrames == 0) {
00161     // First Frame
00162     // If we dispose of the first frame by clearing it, then the
00163     // First Frame's refresh area is all of itself.
00164     // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR)
00165     PRInt32 frameDisposalMethod;
00166     item->GetFrameDisposalMethod(&frameDisposalMethod);
00167     if (frameDisposalMethod == DISPOSE_CLEAR ||
00168         frameDisposalMethod == DISPOSE_RESTORE_PREVIOUS)
00169       item->GetRect(mFirstFrameRefreshArea);
00170   } else {
00171     // Calculate mFirstFrameRefreshArea
00172     // Some gifs are huge but only have a small area that they animate
00173     // We only need to refresh that small area when Frame 0 comes around again
00174     nsIntRect itemRect;
00175     item->GetRect(itemRect);
00176     mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, itemRect);
00177   }
00178 
00179   mFrames.AppendObject(item);
00180 
00181   // If this is our second frame, start the animation.
00182   // Must be called after AppendElement because StartAnimation checks for > 1
00183   // frame
00184   if (numFrames == 1)
00185     StartAnimation();
00186 
00187   return NS_OK;
00188 }
00189 
00190 //******************************************************************************
00191 /* void removeFrame (in gfxIImageFrame item); */
00192 NS_IMETHODIMP imgContainerGIF::RemoveFrame(gfxIImageFrame *item)
00193 {
00194   return NS_ERROR_NOT_IMPLEMENTED;
00195 }
00196 
00197 //******************************************************************************
00198 /* void endFrameDecode (in gfxIImageFrame item, in unsigned long timeout); */
00199 NS_IMETHODIMP imgContainerGIF::EndFrameDecode(PRUint32 aFrameNum,
00200                                               PRUint32 aTimeout)
00201 {
00202   // Assume there's another frame.
00203   // aFrameNum is 1 based, mCurrentDecodingFrameIndex is 0 based.
00204   mCurrentDecodingFrameIndex = aFrameNum;
00205   return NS_OK;
00206 }
00207 
00208 //******************************************************************************
00209 /* void decodingComplete (); */
00210 NS_IMETHODIMP imgContainerGIF::DecodingComplete(void)
00211 {
00212   mDoneDecoding = PR_TRUE;
00213   // If there's only 1 frame, optimize it.
00214   // Optimizing animated gifs is not supported
00215   if (mFrames.Count() == 1)
00216     mFrames[0]->SetMutable(PR_FALSE);
00217   return NS_OK;
00218 }
00219 
00220 /* void clear (); */
00221 NS_IMETHODIMP imgContainerGIF::Clear()
00222 {
00223   mFrames.Clear();
00224   return NS_OK;
00225 }
00226 
00227 //******************************************************************************
00228 NS_IMETHODIMP imgContainerGIF::GetAnimationMode(PRUint16 *aAnimationMode)
00229 {
00230   if (!aAnimationMode)
00231     return NS_ERROR_NULL_POINTER;
00232   *aAnimationMode = mAnimationMode;
00233   return NS_OK;
00234 }
00235 
00236 //******************************************************************************
00237 NS_IMETHODIMP imgContainerGIF::SetAnimationMode(PRUint16 aAnimationMode)
00238 {
00239   NS_ASSERTION(aAnimationMode == imgIContainer::kNormalAnimMode ||
00240                aAnimationMode == imgIContainer::kDontAnimMode ||
00241                aAnimationMode == imgIContainer::kLoopOnceAnimMode,
00242                "Wrong Animation Mode is being set!");
00243 
00244   if (mAnimationMode == kNormalAnimMode &&
00245       (aAnimationMode == kDontAnimMode ||
00246        aAnimationMode == kLoopOnceAnimMode)) {
00247     StopAnimation();
00248   } else if (aAnimationMode == kNormalAnimMode &&
00249              (mAnimationMode == kDontAnimMode ||
00250               mAnimationMode == kLoopOnceAnimMode)) {
00251     mAnimationMode = aAnimationMode;
00252     StartAnimation();
00253     return NS_OK;
00254   }
00255   mAnimationMode = aAnimationMode;
00256 
00257   return NS_OK;
00258 }
00259 
00260 //******************************************************************************
00261 /* void startAnimation () */
00262 NS_IMETHODIMP imgContainerGIF::StartAnimation()
00263 {
00264   if (mAnimationMode == kDontAnimMode || mAnimating || mTimer)
00265     return NS_OK;
00266 
00267   if (mFrames.Count() > 1) {
00268     PRInt32 timeout;
00269     gfxIImageFrame *currentFrame = inlinedGetCurrentFrame();
00270     if (currentFrame) {
00271       currentFrame->GetTimeout(&timeout);
00272       if (timeout <= 0) // -1 means display this frame forever
00273         return NS_OK;
00274     } else
00275       timeout = 100; // XXX hack.. the timer notify code will do the right
00276                      //     thing, so just get that started
00277 
00278     mTimer = do_CreateInstance("@mozilla.org/timer;1");
00279     if (!mTimer)
00280       return NS_ERROR_OUT_OF_MEMORY;
00281 
00282     // The only way mAnimating becomes true is if the mTimer is created
00283     mAnimating = PR_TRUE;
00284     mTimer->InitWithCallback(NS_STATIC_CAST(nsITimerCallback*, this),
00285                              timeout, nsITimer::TYPE_REPEATING_SLACK);
00286   }
00287 
00288   return NS_OK;
00289 }
00290 
00291 //******************************************************************************
00292 /* void stopAnimation (); */
00293 NS_IMETHODIMP imgContainerGIF::StopAnimation()
00294 {
00295   mAnimating = PR_FALSE;
00296 
00297   if (!mTimer)
00298     return NS_OK;
00299 
00300   mTimer->Cancel();
00301   mTimer = nsnull;
00302 
00303   return NS_OK;
00304 }
00305 
00306 //******************************************************************************
00307 /* void ResetAnimation (); */
00308 NS_IMETHODIMP imgContainerGIF::ResetAnimation()
00309 {
00310   if (mCurrentAnimationFrameIndex == 0 || mAnimationMode == kDontAnimMode)
00311     return NS_OK;
00312 
00313   PRBool oldAnimating = mAnimating;
00314 
00315   if (oldAnimating) {
00316     nsresult rv = StopAnimation();
00317     if (NS_FAILED(rv))
00318       return rv;
00319    }
00320 
00321   mLastCompositedFrameIndex = -1;
00322   mCurrentAnimationFrameIndex = 0;
00323   // Update display
00324   nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
00325   if (observer)
00326     observer->FrameChanged(this, mFrames[0], &mFirstFrameRefreshArea);
00327 
00328   if (oldAnimating)
00329     return StartAnimation();
00330   else
00331     return NS_OK;
00332 }
00333 
00334 //******************************************************************************
00335 /* attribute long loopCount; */
00336 NS_IMETHODIMP imgContainerGIF::GetLoopCount(PRInt32 *aLoopCount)
00337 {
00338   NS_ASSERTION(aLoopCount, "ptr is null");
00339   *aLoopCount = mLoopCount;
00340   return NS_OK;
00341 }
00342 
00343 NS_IMETHODIMP imgContainerGIF::SetLoopCount(PRInt32 aLoopCount)
00344 {
00345   // -1  infinite
00346   //  0  no looping, one iteration
00347   //  1  one loop, two iterations
00348   //  ...
00349   mLoopCount = aLoopCount;
00350 
00351   return NS_OK;
00352 }
00353 
00354 
00355 
00356 NS_IMETHODIMP imgContainerGIF::Notify(nsITimer *timer)
00357 {
00358   NS_ASSERTION(mTimer == timer,
00359                "imgContainerGIF::Notify called with incorrect timer");
00360 
00361   if (!mAnimating || !mTimer)
00362     return NS_OK;
00363 
00364   nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
00365   if (!observer) {
00366     // the imgRequest that owns us is dead, we should die now too.
00367     StopAnimation();
00368     return NS_OK;
00369   }
00370 
00371   PRInt32 numFrames = mFrames.Count();
00372   if (!numFrames)
00373     return NS_OK;
00374 
00375   gfxIImageFrame *nextFrame = nsnull;
00376   PRInt32 previousFrameIndex = mCurrentAnimationFrameIndex;
00377   PRInt32 nextFrameIndex = mCurrentAnimationFrameIndex + 1;
00378   PRInt32 timeout = 0;
00379 
00380   // If we're done decoding the next frame, go ahead and display it now and
00381   // reinit the timer with the next frame's delay time.
00382   if (mDoneDecoding || (nextFrameIndex < mCurrentDecodingFrameIndex)) {
00383     if (numFrames == nextFrameIndex) {
00384       // End of Animation
00385 
00386       // If animation mode is "loop once", it's time to stop animating
00387       if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
00388         StopAnimation();
00389         return NS_OK;
00390       } else {
00391         // We may have used mCompositingFrame to build a frame, and then copied
00392         // it back into mFrames[..].  If so, delete composite to save memory
00393         if (mCompositingFrame && mLastCompositedFrameIndex == -1)
00394           mCompositingFrame = nsnull;
00395       }
00396 
00397       nextFrameIndex = 0;
00398       if (mLoopCount > 0)
00399         mLoopCount--;
00400     }
00401 
00402     if (!(nextFrame = mFrames[nextFrameIndex])) {
00403       // something wrong with the next frame, skip it
00404       mCurrentAnimationFrameIndex = nextFrameIndex;
00405       mTimer->SetDelay(100);
00406       return NS_OK;
00407     }
00408     nextFrame->GetTimeout(&timeout);
00409 
00410   } else if (nextFrameIndex == mCurrentDecodingFrameIndex) {
00411     // Uh oh, the frame we want to show is currently being decoded (partial)
00412     // Wait a bit and try again
00413     mTimer->SetDelay(100);
00414     return NS_OK;
00415   } else { //  (nextFrameIndex > mCurrentDecodingFrameIndex)
00416     // We shouldn't get here. However, if we are requesting a frame
00417     // that hasn't been decoded yet, go back to the last frame decoded
00418     NS_WARNING("imgContainerGIF::Notify()  Frame is passed decoded frame");
00419     nextFrameIndex = mCurrentDecodingFrameIndex;
00420     if (!(nextFrame = mFrames[nextFrameIndex])) {
00421       // something wrong with the next frame, skip it
00422       mCurrentAnimationFrameIndex = nextFrameIndex;
00423       mTimer->SetDelay(100);
00424       return NS_OK;
00425     }
00426     nextFrame->GetTimeout(&timeout);
00427   }
00428 
00429   if (timeout > 0)
00430     mTimer->SetDelay(timeout);
00431   else
00432     StopAnimation();
00433 
00434   nsIntRect dirtyRect;
00435   gfxIImageFrame *frameToUse = nsnull;
00436 
00437   if (nextFrameIndex == 0) {
00438     frameToUse = nextFrame;
00439     dirtyRect = mFirstFrameRefreshArea;
00440   } else {
00441     gfxIImageFrame *prevFrame = mFrames[previousFrameIndex];
00442     if (!prevFrame)
00443       return NS_OK;
00444 
00445     // Change frame and announce it
00446     if (NS_FAILED(DoComposite(&frameToUse, &dirtyRect, prevFrame,
00447                               nextFrame, nextFrameIndex))) {
00448       // something went wrong, move on to next
00449       NS_WARNING("imgContainerGIF: Composing Frame Failed\n");
00450       mCurrentAnimationFrameIndex = nextFrameIndex;
00451       return NS_OK;
00452     }
00453   }
00454   // Set mCurrentAnimationFrameIndex at the last possible moment
00455   mCurrentAnimationFrameIndex = nextFrameIndex;
00456   // Refreshes the screen
00457   observer->FrameChanged(this, frameToUse, &dirtyRect);
00458   return NS_OK;
00459 }
00460 
00461 //******************************************************************************
00462 // DoComposite gets called when the timer for animation get fired and we have to
00463 // update the composited frame of the animation.
00464 nsresult imgContainerGIF::DoComposite(gfxIImageFrame** aFrameToUse,
00465                                       nsIntRect* aDirtyRect,
00466                                       gfxIImageFrame* aPrevFrame,
00467                                       gfxIImageFrame* aNextFrame,
00468                                       PRInt32 aNextFrameIndex)
00469 {
00470   NS_ASSERTION(aDirtyRect, "imgContainerGIF::DoComposite aDirtyRect is null");
00471   NS_ASSERTION(aPrevFrame, "imgContainerGIF::DoComposite aPrevFrame is null");
00472   NS_ASSERTION(aNextFrame, "imgContainerGIF::DoComposite aNextFrame is null");
00473   NS_ASSERTION(aFrameToUse, "imgContainerGIF::DoComposite aFrameToUse is null");
00474 
00475   PRInt32 prevFrameDisposalMethod;
00476   aPrevFrame->GetFrameDisposalMethod(&prevFrameDisposalMethod);
00477 
00478   if (prevFrameDisposalMethod == DISPOSE_RESTORE_PREVIOUS &&
00479       !mCompositingPrevFrame)
00480     prevFrameDisposalMethod = DISPOSE_CLEAR;
00481 
00482   // Optimization: Skip compositing if the previous frame wants to clear the
00483   //               whole image
00484   if (prevFrameDisposalMethod == DISPOSE_CLEAR_ALL) {
00485     aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
00486     *aFrameToUse = aNextFrame;
00487     return NS_OK;
00488   }
00489 
00490   nsIntRect prevFrameRect;
00491   aPrevFrame->GetRect(prevFrameRect);
00492   PRBool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 &&
00493                             prevFrameRect.width == mSize.width &&
00494                             prevFrameRect.height == mSize.height);
00495 
00496   // Optimization: Skip compositing if the previous frame is the same size as
00497   //               container and it's clearing itself
00498   if (isFullPrevFrame && prevFrameDisposalMethod == DISPOSE_CLEAR) {
00499     aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
00500     *aFrameToUse = aNextFrame;
00501     return NS_OK;
00502   }
00503 
00504   PRInt32 nextFrameDisposalMethod;
00505   nsIntRect nextFrameRect;
00506   aNextFrame->GetFrameDisposalMethod(&nextFrameDisposalMethod);
00507   aNextFrame->GetRect(nextFrameRect);
00508   PRBool isFullNextFrame = (nextFrameRect.x == 0 && nextFrameRect.y == 0 &&
00509                             nextFrameRect.width == mSize.width &&
00510                             nextFrameRect.height == mSize.height);
00511 
00512   PRBool nextFrameHasAlpha;
00513   PRUint32 aBPR;
00514   nextFrameHasAlpha = NS_SUCCEEDED(aNextFrame->GetAlphaBytesPerRow(&aBPR));
00515 
00516   // Optimization: Skip compositing if this frame is the same size as the
00517   //               container and it's fully drawing over prev frame (no alpha)
00518   if (isFullNextFrame &&
00519       (nextFrameDisposalMethod != DISPOSE_RESTORE_PREVIOUS) &&
00520       !nextFrameHasAlpha) {
00521 
00522     aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
00523     *aFrameToUse = aNextFrame;
00524     return NS_OK;
00525   }
00526 
00527   // Calculate area that needs updating
00528   switch (prevFrameDisposalMethod) {
00529     default:
00530     case DISPOSE_NOT_SPECIFIED:
00531     case DISPOSE_KEEP:
00532       *aDirtyRect = nextFrameRect;
00533       break;
00534 
00535     case DISPOSE_CLEAR:
00536       // Calc area that needs to be redrawn (the combination of previous and
00537       // this frame)
00538       // XXX - This could be done with multiple framechanged calls
00539       //       Having prevFrame way at the top of the image, and nextFrame
00540       //       way at the bottom, and both frames being small, we'd be
00541       //       telling framechanged to refresh the whole image when only two
00542       //       small areas are needed.
00543       aDirtyRect->UnionRect(nextFrameRect, prevFrameRect);
00544       break;
00545 
00546     case DISPOSE_RESTORE_PREVIOUS:
00547       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
00548       break;
00549   }
00550 
00551   // Optimization:
00552   //   Skip compositing if the last composited frame is this frame
00553   //   (Only one composited frame was made for this animation.  Example:
00554   //    Only Frame 3 of a 10 frame GIF required us to build a composite frame
00555   //    On the second loop of the GIF, we do not need to rebuild the frame
00556   //    since it's still sitting in mCompositingFrame)
00557   if (mLastCompositedFrameIndex == aNextFrameIndex) {
00558     *aFrameToUse = mCompositingFrame;
00559     return NS_OK;
00560   }
00561 
00562   PRBool needToBlankComposite = PR_FALSE;
00563 
00564   // Create the Compositing Frame
00565   if (!mCompositingFrame) {
00566     nsresult rv;
00567     mCompositingFrame = do_CreateInstance("@mozilla.org/gfx/image/frame;2", &rv);
00568     if (NS_FAILED(rv))
00569       return rv;
00570     rv = mCompositingFrame->Init(0, 0, mSize.width, mSize.height,
00571                                  gfxIFormats::RGB_A1, 24);
00572     if (NS_FAILED(rv)) {
00573       NS_WARNING("Failed to init mCompositingFrame!\n");
00574       mCompositingFrame = nsnull;
00575       return rv;
00576     }
00577     needToBlankComposite = PR_TRUE;
00578   }
00579 
00580   // Copy previous frame into mCompositingFrame before we put the new frame on top
00581   // Assumes that the previous frame represents a full frame (it could be
00582   // smaller in size than the container, as long as the frame before it erased
00583   // itself)
00584   // Note: Frame 1 never gets into DoComposite(), so (aNextFrameIndex - 1) will
00585   // always be a valid frame number.
00586   if (mLastCompositedFrameIndex != aNextFrameIndex - 1 &&
00587       prevFrameDisposalMethod != DISPOSE_RESTORE_PREVIOUS) {
00588 
00589     // XXX If we had a method of drawing a section of a frame into another, we
00590     //     could optimize further:
00591     //     if aPrevFrameIndex == 1 && mLastCompositedFrameIndex <> -1,
00592     //     only mFirstFrameRefreshArea needs to be drawn back to composite
00593     if (isFullPrevFrame) {
00594       CopyFrameImage(aPrevFrame, mCompositingFrame);
00595     } else {
00596       BlackenFrame(mCompositingFrame);
00597       SetMaskVisibility(mCompositingFrame, PR_FALSE);
00598       aPrevFrame->DrawTo(mCompositingFrame, prevFrameRect.x, prevFrameRect.y,
00599                          prevFrameRect.width, prevFrameRect.height);
00600 
00601       BuildCompositeMask(mCompositingFrame, aPrevFrame);
00602       needToBlankComposite = PR_FALSE;
00603     }
00604   }
00605 
00606   // Dispose of previous
00607   switch (prevFrameDisposalMethod) {
00608     case DISPOSE_CLEAR:
00609       if (needToBlankComposite) {
00610         // If we just created the composite, it could have anything in it's
00611         // buffers. Clear them
00612         BlackenFrame(mCompositingFrame);
00613         SetMaskVisibility(mCompositingFrame, PR_FALSE);
00614         needToBlankComposite = PR_FALSE;
00615       } else {
00616         // Blank out previous frame area (both color & Mask/Alpha)
00617         BlackenFrame(mCompositingFrame, prevFrameRect);
00618         SetMaskVisibility(mCompositingFrame, prevFrameRect, PR_FALSE);
00619       }
00620       break;
00621 
00622     case DISPOSE_RESTORE_PREVIOUS:
00623       // It would be better to copy only the area changed back to
00624       // mCompositingFrame.
00625       if (mCompositingPrevFrame) {
00626         CopyFrameImage(mCompositingPrevFrame, mCompositingFrame);
00627 
00628         // destroy only if we don't need it for this frame's disposal
00629         if (nextFrameDisposalMethod != DISPOSE_RESTORE_PREVIOUS)
00630           mCompositingPrevFrame = nsnull;
00631       } else {
00632         BlackenFrame(mCompositingFrame);
00633         SetMaskVisibility(mCompositingFrame, PR_FALSE);
00634       }
00635       break;
00636   }
00637 
00638   // Check if the frame we are composing wants the previous image restored afer
00639   // it is done. Don't store it (again) if last frame wanted it's image restored
00640   // too
00641   if ((nextFrameDisposalMethod == DISPOSE_RESTORE_PREVIOUS) &&
00642       (prevFrameDisposalMethod != DISPOSE_RESTORE_PREVIOUS)) {
00643     // We are storing the whole image.
00644     // It would be better if we just stored the area that nextFrame is going to
00645     // overwrite.
00646     if (!mCompositingPrevFrame) {
00647       nsresult rv;
00648       mCompositingPrevFrame = do_CreateInstance("@mozilla.org/gfx/image/frame;2",
00649                                                 &rv);
00650       if (NS_FAILED(rv))
00651         return rv;
00652       rv = mCompositingPrevFrame->Init(0, 0, mSize.width, mSize.height,
00653                                        gfxIFormats::RGB_A1, 24);
00654       if (NS_FAILED(rv))
00655         return rv;
00656     }
00657     CopyFrameImage(mCompositingFrame, mCompositingPrevFrame);
00658   }
00659 
00660   // blit next frame into it's correct spot
00661   aNextFrame->DrawTo(mCompositingFrame, nextFrameRect.x, nextFrameRect.y,
00662                      nextFrameRect.width, nextFrameRect.height);
00663   // put the mask in
00664   BuildCompositeMask(mCompositingFrame, aNextFrame);
00665   // Set timeout of CompositeFrame to timeout of frame we just composed
00666   // Bug 177948
00667   PRInt32 timeout;
00668   aNextFrame->GetTimeout(&timeout);
00669   mCompositingFrame->SetTimeout(timeout);
00670 
00671   if (isFullNextFrame && mAnimationMode == kNormalAnimMode && mLoopCount != 0) {
00672     // We have a composited full frame
00673     // Store the composited frame into the mFrames[..] so we don't have to
00674     // continuously re-build it
00675     // Then set the previous frame's disposal to CLEAR_ALL so we just draw the
00676     // frame next time around
00677     if (CopyFrameImage(mCompositingFrame, aNextFrame)) {
00678       aPrevFrame->SetFrameDisposalMethod(DISPOSE_CLEAR_ALL);
00679       mLastCompositedFrameIndex = -1;
00680       *aFrameToUse = aNextFrame;
00681       return NS_OK;
00682     }
00683   }
00684 
00685   mLastCompositedFrameIndex = aNextFrameIndex;
00686   *aFrameToUse = mCompositingFrame;
00687 
00688   return NS_OK;
00689 }
00690 
00691 //******************************************************************************
00692 void imgContainerGIF::BuildCompositeMask(gfxIImageFrame *aCompositingFrame,
00693                                          gfxIImageFrame *aOverlayFrame)
00694 {
00695   if (!aCompositingFrame || !aOverlayFrame) return;
00696 
00697   nsresult res;
00698   PRUint8* compositingAlphaData;
00699   PRUint32 compositingAlphaDataLength;
00700   aCompositingFrame->LockAlphaData();
00701   res = aCompositingFrame->GetAlphaData(&compositingAlphaData,
00702                                         &compositingAlphaDataLength);
00703   if (!compositingAlphaData || !compositingAlphaDataLength || NS_FAILED(res)) {
00704     aCompositingFrame->UnlockAlphaData();
00705     return;
00706   }
00707 
00708   PRInt32 widthOverlay, heightOverlay;
00709   PRInt32 overlayXOffset, overlayYOffset;
00710   aOverlayFrame->GetWidth(&widthOverlay);
00711   aOverlayFrame->GetHeight(&heightOverlay);
00712   aOverlayFrame->GetX(&overlayXOffset);
00713   aOverlayFrame->GetY(&overlayYOffset);
00714 
00715   if (NS_FAILED(aOverlayFrame->LockAlphaData())) {
00716     // set the region of the overlay frame to visible in compositingFrame
00717     SetMaskVisibility(aCompositingFrame, overlayXOffset, overlayYOffset,
00718                       widthOverlay, heightOverlay, PR_TRUE);
00719     aCompositingFrame->UnlockAlphaData();
00720     return;
00721   }
00722 
00723   PRUint32 abprComposite;
00724   aCompositingFrame->GetAlphaBytesPerRow(&abprComposite);
00725 
00726   PRUint32 abprOverlay;
00727   aOverlayFrame->GetAlphaBytesPerRow(&abprOverlay);
00728 
00729   // Only the composite's width & height are needed.  x & y should always be 0.
00730   PRInt32 widthComposite, heightComposite;
00731   aCompositingFrame->GetWidth(&widthComposite);
00732   aCompositingFrame->GetHeight(&heightComposite);
00733 
00734   PRUint8* overlayAlphaData;
00735   PRUint32 overlayAlphaDataLength;
00736   res = aOverlayFrame->GetAlphaData(&overlayAlphaData, &overlayAlphaDataLength);
00737 
00738   gfx_format format;
00739   aCompositingFrame->GetFormat(&format);
00740   if (format != gfxIFormats::RGB_A1 && format != gfxIFormats::BGR_A1) {
00741     NS_NOTREACHED("GIFs only support 1 bit alpha");
00742     aCompositingFrame->UnlockAlphaData();
00743     aOverlayFrame->UnlockAlphaData();
00744     return;
00745   }
00746 
00747   // Exit if overlay is beyond the area of the composite
00748   if (widthComposite <= overlayXOffset || heightComposite <= overlayYOffset)
00749     return;
00750 
00751   const PRUint32 width  = PR_MIN(widthOverlay,
00752                                  widthComposite - overlayXOffset);
00753   const PRUint32 height = PR_MIN(heightOverlay,
00754                                  heightComposite - overlayYOffset);
00755 
00756 #ifdef MOZ_PLATFORM_IMAGES_BOTTOM_TO_TOP
00757   // Account for bottom-up storage
00758   PRInt32 offset = ((heightComposite - 1) - overlayYOffset) * abprComposite;
00759 #else
00760   PRInt32 offset = overlayYOffset * abprComposite;
00761 #endif
00762   PRUint8* alphaLine = compositingAlphaData + offset + (overlayXOffset >> 3);
00763 
00764 #ifdef MOZ_PLATFORM_IMAGES_BOTTOM_TO_TOP
00765   offset = (heightOverlay - 1) * abprOverlay;
00766 #else
00767   offset = 0;
00768 #endif
00769   PRUint8* overlayLine = overlayAlphaData + offset;
00770 
00771   /*
00772     This is the number of pixels of offset between alpha and overlay
00773     (the number of bits at the front of alpha to skip when starting a row).
00774     I.e:, for a mask_offset of 3:
00775     (these are representations of bits)
00776     overlay 'pixels':   76543210 hgfedcba
00777     alpha:              xxx76543 210hgfed ...
00778     where 'x' is data already in alpha
00779     the first 5 pixels of overlay are or'd into the low 5 bits of alpha
00780   */
00781   PRUint8 mask_offset = (overlayXOffset & 0x7);
00782 
00783   for(PRUint32 i = 0; i < height; i++) {
00784     PRUint8 pixels;
00785     PRUint32 j;
00786     // use locals to avoid keeping track of how much we need to add
00787     // at the end of a line.  we don't really need this since we may
00788     // be able to calculate the ending offsets, but it's simpler and
00789     // cheap.
00790     PRUint8 *localOverlay = overlayLine;
00791     PRUint8 *localAlpha   = alphaLine;
00792 
00793     for (j = width; j >= 8; j -= 8) {
00794       // don't do in for(...) to avoid reference past end of buffer
00795       pixels = *localOverlay++;
00796 
00797       if (pixels == 0) // no bits to set - iterate and bump output pointer
00798         localAlpha++;
00799       else {
00800         // for the last few bits of a line, we need to special-case it
00801         if (mask_offset == 0) // simple case, no offset
00802           *localAlpha++ |= pixels;
00803         else {
00804           *localAlpha++ |= (pixels >> mask_offset);
00805           *localAlpha   |= (pixels << (8U-mask_offset));
00806         }
00807       }
00808     }
00809     if (j != 0) {
00810       // handle the end of the line, 1 to 7 pixels
00811       pixels = *localOverlay++;
00812       if (pixels != 0) {
00813         // last few bits have to be handled more carefully if
00814         // width is not a multiple of 8.
00815 
00816         // set bits we don't want to change to 0
00817         pixels = (pixels >> (8U-j)) << (8U-j);
00818         *localAlpha++ |= (pixels >> mask_offset);
00819         // don't touch this byte unless we have bits for it
00820         if (j > (8U - mask_offset))
00821           *localAlpha |= (pixels << (8U-mask_offset));
00822       }
00823     }
00824 
00825 #ifdef MOZ_PLATFORM_IMAGES_BOTTOM_TO_TOP
00826     alphaLine   -= abprComposite;
00827     overlayLine -= abprOverlay;
00828 #else
00829     alphaLine   += abprComposite;
00830     overlayLine += abprOverlay;
00831 #endif
00832   }
00833 
00834   aCompositingFrame->UnlockAlphaData();
00835   aOverlayFrame->UnlockAlphaData();
00836   return;
00837 }
00838 
00839 //******************************************************************************
00840 void imgContainerGIF::SetMaskVisibility(gfxIImageFrame *aFrame,
00841                                         PRInt32 aX, PRInt32 aY,
00842                                         PRInt32 aWidth, PRInt32 aHeight,
00843                                         PRBool aVisible)
00844 {
00845   if (!aFrame)
00846     return;
00847 
00848   nsresult res;
00849   PRUint8* alphaData;
00850   PRUint32 alphaDataLength;
00851   aFrame->LockAlphaData();
00852   res = aFrame->GetAlphaData(&alphaData, &alphaDataLength);
00853   if (!alphaData || !alphaDataLength || NS_FAILED(res)) {
00854     aFrame->UnlockAlphaData();
00855     return;
00856   }
00857 
00858   PRInt32 frameWidth;
00859   PRInt32 frameHeight;
00860   aFrame->GetWidth(&frameWidth);
00861   aFrame->GetHeight(&frameHeight);
00862 
00863   const PRInt32 width  = PR_MIN(aWidth, frameWidth - aX);
00864   const PRInt32 height = PR_MIN(aHeight, frameHeight - aY);
00865 
00866   if (width <= 0 || height <= 0) {
00867     aFrame->UnlockAlphaData();
00868     return;
00869   }
00870 
00871   gfx_format format;
00872   aFrame->GetFormat(&format);
00873   if (format != gfxIFormats::RGB_A1 && format != gfxIFormats::BGR_A1) {
00874     NS_NOTREACHED("GIFs only support 1 bit alpha");
00875     aFrame->UnlockAlphaData();
00876     return;
00877   }
00878 
00879   PRUint32 abpr;
00880   aFrame->GetAlphaBytesPerRow(&abpr);
00881 
00882 #ifdef MOZ_PLATFORM_IMAGES_BOTTOM_TO_TOP
00883   // Account for bottom-up storage.
00884   // Start at the bottom (top in memory), go to the top (bottom in memory)
00885   PRUint8* alphaLine = alphaData + ((frameHeight - aY - height) * abpr) +
00886                        (aX >> 3);
00887 #else
00888   PRUint8* alphaLine = alphaData + (aY * abpr) + (aX >> 3);
00889 #endif
00890   PRUint8 maskShiftStartBy = aX & 0x7;
00891   PRUint8 numReplacingStart = 8U - maskShiftStartBy;
00892   PRUint32 rowBytes;
00893   PRUint8 maskStart = 0; // Init to shutup compiler; Only used if
00894                          // maskShiftStartBy != 0
00895   PRUint8 maskEnd;
00896 
00897   if (width <= numReplacingStart) {
00898     maskEnd = (0xFF >> (8U - width)) << (numReplacingStart - width);
00899     // Don't write start bits, only end bits (which contain both start & end)
00900     maskShiftStartBy = 0;
00901     rowBytes = 0;
00902   } else {
00903     if (maskShiftStartBy == 0)
00904       numReplacingStart = 0;
00905     else
00906       maskStart = 0xFF >> maskShiftStartBy;
00907 
00908     PRUint8 maskShiftEndBy = (width - numReplacingStart) & 0x7;
00909     maskEnd = ~(0xFF >> maskShiftEndBy);
00910     rowBytes = (width - numReplacingStart - maskShiftEndBy) >> 3;
00911   }
00912 
00913   if (aVisible) {
00914     for (PRInt32 i = 0; i < height; i++) {
00915       PRUint8 *localAlpha = alphaLine;
00916 
00917       if (maskShiftStartBy != 0)
00918         *localAlpha++ |= maskStart;
00919 
00920       if (rowBytes > 0)
00921         memset(localAlpha, 0xFF, rowBytes);
00922 
00923       if (maskEnd != 0)
00924         localAlpha[rowBytes] |= maskEnd;
00925 
00926       alphaLine += abpr;
00927     }
00928   } else {
00929     for (PRInt32 i = 0; i < height; i++) {
00930       PRUint8 *localAlpha = alphaLine;
00931 
00932       if (maskShiftStartBy != 0)
00933         *localAlpha++ &= ~maskStart;
00934 
00935       if (rowBytes > 0)
00936         memset(localAlpha, 0x00, rowBytes);
00937 
00938       if (maskEnd != 0)
00939         localAlpha[rowBytes] &= ~maskEnd;
00940 
00941       alphaLine += abpr;
00942     } // for
00943   } // if aVisible
00944 
00945   aFrame->UnlockAlphaData();
00946   return;
00947 }
00948 
00949 //******************************************************************************
00950 void imgContainerGIF::SetMaskVisibility(gfxIImageFrame *aFrame, PRBool aVisible)
00951 {
00952   if (!aFrame)
00953     return;
00954 
00955   PRUint8* alphaData;
00956   PRUint32 alphaDataLength;
00957   const PRUint8 setMaskTo = aVisible ? 0xFF : 0x00;
00958 
00959   aFrame->LockAlphaData();
00960   nsresult res = aFrame->GetAlphaData(&alphaData, &alphaDataLength);
00961   if (NS_SUCCEEDED(res) && alphaData && alphaDataLength)
00962     memset(alphaData, setMaskTo, alphaDataLength);
00963   aFrame->UnlockAlphaData();
00964   return;
00965 }
00966 
00967 //******************************************************************************
00968 // Fill aFrame with black. Does not change the mask.
00969 void imgContainerGIF::BlackenFrame(gfxIImageFrame *aFrame)
00970 {
00971   if (!aFrame)
00972     return;
00973 
00974   aFrame->LockImageData();
00975 
00976   PRUint8* aData;
00977   PRUint32 aDataLength;
00978 
00979   aFrame->GetImageData(&aData, &aDataLength);
00980   memset(aData, 0, aDataLength);
00981 
00982   nsCOMPtr<nsIInterfaceRequestor> ireq(do_QueryInterface(aFrame));
00983   if (ireq) {
00984     PRInt32 width;
00985     PRInt32 height;
00986     aFrame->GetWidth(&width);
00987     aFrame->GetHeight(&height);
00988 
00989     nsCOMPtr<nsIImage> img(do_GetInterface(ireq));
00990     nsIntRect r(0, 0, width, height);
00991 
00992     img->ImageUpdated(nsnull, nsImageUpdateFlags_kBitsChanged, &r);
00993   }
00994 
00995   aFrame->UnlockImageData();
00996 }
00997 
00998 //******************************************************************************
00999 void imgContainerGIF::BlackenFrame(gfxIImageFrame *aFrame,
01000                                    PRInt32 aX, PRInt32 aY,
01001                                    PRInt32 aWidth, PRInt32 aHeight)
01002 {
01003   if (!aFrame)
01004     return;
01005 
01006   aFrame->LockImageData();
01007 
01008   PRInt32 widthFrame;
01009   PRInt32 heightFrame;
01010   aFrame->GetWidth(&widthFrame);
01011   aFrame->GetHeight(&heightFrame);
01012 
01013   const PRInt32 width  = PR_MIN(aWidth, (widthFrame - aX));
01014   const PRInt32 height = PR_MIN(aHeight, (heightFrame - aY));
01015 
01016   if (width <= 0 || height <= 0) {
01017     aFrame->UnlockImageData();
01018     return;
01019   }
01020 
01021   PRUint32 bpr; // Bytes Per Row
01022   aFrame->GetImageBytesPerRow(&bpr);
01023 
01024 #if defined(XP_MAC) || defined(XP_MACOSX)
01025   const PRUint8 bpp = 4;
01026 #else
01027   const PRUint8 bpp = 3;
01028 #endif
01029   const PRUint32 bprToWrite = width * bpp;
01030   const PRUint32 xOffset = aX * bpp; // offset into row to start writing
01031 
01032   PRUint8* tmpRow = NS_STATIC_CAST(PRUint8*, nsMemory::Alloc(bprToWrite));
01033 
01034   if (!tmpRow) {
01035     aFrame->UnlockImageData();
01036     return;
01037   }
01038 
01039   memset(tmpRow, 0, bprToWrite);
01040 
01041   for (PRInt32 y = 0; y < height; y++) {
01042     aFrame->SetImageData(tmpRow, bprToWrite, ((y + aY) * bpr) + xOffset);
01043   }
01044   nsMemory::Free(tmpRow);
01045 
01046   aFrame->UnlockImageData();
01047 }
01048 
01049 
01050 //******************************************************************************
01051 // Whether we succeed or fail will not cause a crash, and there's not much
01052 // we can do about a failure, so there we don't return a nsresult
01053 PRBool imgContainerGIF::CopyFrameImage(gfxIImageFrame *aSrcFrame,
01054                                        gfxIImageFrame *aDstFrame)
01055 {
01056   PRUint8* aDataSrc;
01057   PRUint8* aDataDest;
01058   PRUint32 aDataLengthSrc;
01059   PRUint32 aDataLengthDest;
01060 
01061   if (!aSrcFrame || !aDstFrame)
01062     return PR_FALSE;
01063 
01064   if (NS_FAILED(aDstFrame->LockImageData()))
01065     return PR_FALSE;
01066 
01067   // Copy Image Over
01068   aSrcFrame->GetImageData(&aDataSrc, &aDataLengthSrc);
01069   aDstFrame->GetImageData(&aDataDest, &aDataLengthDest);
01070   if (!aDataDest || !aDataSrc || aDataLengthDest != aDataLengthSrc) {
01071     aDstFrame->UnlockImageData();
01072     return PR_FALSE;
01073   }
01074   memcpy(aDataDest, aDataSrc, aDataLengthSrc);
01075   aDstFrame->UnlockImageData();
01076 
01077   // Copy Alpha/Mask Over
01078   // If no mask, lockAlpha will tell us
01079   if (NS_SUCCEEDED(aDstFrame->LockAlphaData())) {
01080     aSrcFrame->GetAlphaData(&aDataSrc, &aDataLengthSrc);
01081     aDstFrame->GetAlphaData(&aDataDest, &aDataLengthDest);
01082     if (aDataDest && aDataSrc && aDataLengthDest == aDataLengthSrc)
01083       memcpy(aDataDest, aDataSrc, aDataLengthSrc);
01084     else
01085       memset(aDataDest, 0xFF, aDataLengthDest);
01086 
01087     aDstFrame->UnlockAlphaData();
01088   }
01089 
01090   // Tell the image that it's data has been updated
01091   nsCOMPtr<nsIInterfaceRequestor> ireq(do_QueryInterface(aDstFrame));
01092   if (!ireq)
01093     return PR_FALSE;
01094   nsCOMPtr<nsIImage> img(do_GetInterface(ireq));
01095   if (!img)
01096     return PR_FALSE;
01097   nsIntRect r;
01098   aDstFrame->GetRect(r);
01099   img->ImageUpdated(nsnull, nsImageUpdateFlags_kBitsChanged, &r);
01100 
01101   return PR_TRUE;
01102 }