Bug 600556 - Support Vista-style PNG ICO files. r=joe
authorBrian R. Bondy <netzen@gmail.com>
Thu, 25 Aug 2011 16:09:01 -0400
changeset 75917 1f86f6af9434b4d9afe3eb0998f7f9f8c1149ac3
parent 75913 9f11fc87eaa69990a8230420035e85dd737b44bc
child 75918 5401cb3d7350583b5e467183b68a5b64d92b33bc
push id21070
push usermbrubeck@mozilla.com
push dateFri, 26 Aug 2011 16:20:59 +0000
treeherdermozilla-central@e8af0a8c3632 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe
bugs600556
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 600556 - Support Vista-style PNG ICO files. r=joe
modules/libpr0n/decoders/nsBMPDecoder.cpp
modules/libpr0n/decoders/nsBMPDecoder.h
modules/libpr0n/decoders/nsICODecoder.cpp
modules/libpr0n/decoders/nsICODecoder.h
modules/libpr0n/decoders/nsPNGDecoder.cpp
modules/libpr0n/decoders/nsPNGDecoder.h
modules/libpr0n/src/Decoder.cpp
modules/libpr0n/src/Decoder.h
--- a/modules/libpr0n/decoders/nsBMPDecoder.cpp
+++ b/modules/libpr0n/decoders/nsBMPDecoder.cpp
@@ -17,16 +17,17 @@
  * The Initial Developer of the Original Code is
  * Christian Biesinger <cbiesinger@web.de>.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Neil Rashbrook <neil@parkwaycc.co.uk>
  *   Bobby Holley <bobbyholley@gmail.com>
+ *   Brian R. Bondy <netzen@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -65,36 +66,101 @@ nsBMPDecoder::nsBMPDecoder()
 {
     mColors = nsnull;
     mRow = nsnull;
     mCurPos = mPos = mNumColors = mRowBytes = 0;
     mOldLine = mCurLine = 1; // Otherwise decoder will never start
     mState = eRLEStateInitial;
     mStateData = 0;
     mLOH = WIN_HEADER_LENGTH;
+    mUseAlphaData = PR_FALSE;
 }
 
 nsBMPDecoder::~nsBMPDecoder()
 {
   delete[] mColors;
-  if (mRow)
-      free(mRow);
+  if (mRow) {
+      moz_free(mRow);
+  }
+}
+
+// Sets whether or not the BMP will use alpha data
+void 
+nsBMPDecoder::SetUseAlphaData(PRBool useAlphaData) 
+{
+  mUseAlphaData = useAlphaData;
+}
+
+// Obtains the bits per pixel from the internal BIH header
+PRInt32 
+nsBMPDecoder::GetBitsPerPixel() const
+{
+  return mBIH.bpp;
+}
+
+// Obtains the width from the internal BIH header
+PRInt32 
+nsBMPDecoder::GetWidth() const
+{
+  return mBIH.width; 
+}
+
+// Obtains the height from the internal BIH header
+PRInt32 
+nsBMPDecoder::GetHeight() const
+{
+  return mBIH.height; 
+}
+
+// Obtains the internal output image buffer
+PRUint32* 
+nsBMPDecoder::GetImageData() 
+{
+  return mImageData;
+}
+
+// Obtains the size of the compressed image resource
+PRInt32 
+nsBMPDecoder::GetCompressedImageSize() const
+{
+  // For everything except BI_RGB the header field must be defined
+  if (mBIH.compression != BI_RGB) {
+    return mBIH.image_size;
+  }
+
+  // mBIH.image_size isn't always filled for BI_RGB so calculate it manually
+  // The pixel array size is calculated based on extra 4 byte boundary padding
+  PRUint32 rowSize = (mBIH.bpp * mBIH.width + 7) / 8; // + 7 to round up
+  // Pad to DWORD Boundary
+  if (rowSize % 4) {
+    rowSize += (4 - (rowSize % 4));
+  }
+
+  // The height should be the absolute value of what the height is in the BIH.
+  // If positive the bitmap is stored bottom to top, otherwise top to bottom
+  PRInt32 pixelArraySize = rowSize * abs(mBIH.height); 
+  return pixelArraySize;
 }
 
 void
 nsBMPDecoder::FinishInternal()
 {
     // We shouldn't be called in error cases
     NS_ABORT_IF_FALSE(!HasError(), "Can't call FinishInternal on error!");
 
     // We should never make multiple frames
     NS_ABORT_IF_FALSE(GetFrameCount() <= 1, "Multiple BMP frames?");
 
     // Send notifications if appropriate
     if (!IsSizeDecode() && (GetFrameCount() == 1)) {
+
+        // Invalidate
+        nsIntRect r(0, 0, mBIH.width, mBIH.height);
+        PostInvalidation(r);
+
         PostFrameStop();
         PostDecodeDone();
     }
 }
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
@@ -140,47 +206,51 @@ nsBMPDecoder::WriteInternal(const char* 
 {
     NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
     // aCount=0 means EOF, mCurLine=0 means we're past end of image
     if (!aCount || !mCurLine)
         return;
 
     nsresult rv;
-    if (mPos < BFH_LENGTH) { /* In BITMAPFILEHEADER */
-        PRUint32 toCopy = BFH_LENGTH - mPos;
+    if (mPos < BFH_INTERNAL_LENGTH) { /* In BITMAPFILEHEADER */
+        PRUint32 toCopy = BFH_INTERNAL_LENGTH - mPos;
         if (toCopy > aCount)
             toCopy = aCount;
         memcpy(mRawBuf + mPos, aBuffer, toCopy);
         mPos += toCopy;
         aCount -= toCopy;
         aBuffer += toCopy;
     }
-    if (mPos == BFH_LENGTH) {
+    if (mPos == BFH_INTERNAL_LENGTH) {
         ProcessFileHeader();
         if (mBFH.signature[0] != 'B' || mBFH.signature[1] != 'M') {
             PostDataError();
             return;
         }
         if (mBFH.bihsize == OS2_BIH_LENGTH)
             mLOH = OS2_HEADER_LENGTH;
     }
-    if (mPos >= BFH_LENGTH && mPos < mLOH) { /* In BITMAPINFOHEADER */
+    if (mPos >= BFH_INTERNAL_LENGTH && mPos < mLOH) { /* In BITMAPINFOHEADER */
         PRUint32 toCopy = mLOH - mPos;
         if (toCopy > aCount)
             toCopy = aCount;
-        memcpy(mRawBuf + (mPos - BFH_LENGTH), aBuffer, toCopy);
+        memcpy(mRawBuf + (mPos - BFH_INTERNAL_LENGTH), aBuffer, toCopy);
         mPos += toCopy;
         aCount -= toCopy;
         aBuffer += toCopy;
     }
-    if (mPos == mLOH) {
+
+    // GetNumFrames is called to ensure that if at this point mPos == mLOH but
+    // we have no data left to process, the next time WriteInternal is called
+    // we won't enter this condition again.
+    if (mPos == mLOH && GetFrameCount() == 0) {
         ProcessInfoHeader();
-        PR_LOG(gBMPLog, PR_LOG_DEBUG, ("BMP image is %lix%lix%lu. compression=%lu\n",
-            mBIH.width, mBIH.height, mBIH.bpp, mBIH.compression));
+        PR_LOG(gBMPLog, PR_LOG_DEBUG, ("BMP is %lix%lix%lu. compression=%lu\n",
+               mBIH.width, mBIH.height, mBIH.bpp, mBIH.compression));
         // Verify we support this bit depth
         if (mBIH.bpp != 1 && mBIH.bpp != 4 && mBIH.bpp != 8 &&
             mBIH.bpp != 16 && mBIH.bpp != 24 && mBIH.bpp != 32) {
           PostDataError();
           return;
         }
 
         // BMPs with negative width are invalid
@@ -221,46 +291,80 @@ nsBMPDecoder::WriteInternal(const char* 
         else if (mBIH.compression != BI_BITFIELDS && mBIH.bpp == 16) {
             // Use default 5-5-5 format
             mBitFields.red   = 0x7C00;
             mBitFields.green = 0x03E0;
             mBitFields.blue  = 0x001F;
             CalcBitShift();
         }
 
+        // Make sure we have a valid value for our supported compression modes
+        // before adding the frame
+        if (mBIH.compression != BI_RGB && mBIH.compression != BI_RLE8 && 
+            mBIH.compression != BI_RLE4 && mBIH.compression != BI_BITFIELDS) {
+          PostDataError();
+          return;
+        }
+
+        // If we have RLE4 or RLE8 or BI_ALPHABITFIELDS, then ensure we
+        // have valid BPP values before adding the frame
+        if (mBIH.compression == BI_RLE8 && mBIH.bpp != 8) {
+          PR_LOG(gBMPLog, PR_LOG_DEBUG, 
+                 ("BMP RLE8 compression only supports 8 bits per pixel\n"));
+          PostDataError();
+          return;
+        }
+        if (mBIH.compression == BI_RLE4 && mBIH.bpp != 4) {
+          PR_LOG(gBMPLog, PR_LOG_DEBUG, 
+                 ("BMP RLE4 compression only supports 4 bits per pixel\n"));
+          PostDataError();
+          return;
+        }
+        if (mBIH.compression == BI_ALPHABITFIELDS && 
+            mBIH.bpp != 16 && mBIH.bpp != 32) {
+          PR_LOG(gBMPLog, PR_LOG_DEBUG, 
+                 ("BMP ALPHABITFIELDS only supports 16 or 32 bits per pixel\n"));
+          PostDataError();
+          return;
+        }
+
         PRUint32 imageLength;
-        if ((mBIH.compression == BI_RLE8) || (mBIH.compression == BI_RLE4)) {
-            rv = mImage->EnsureFrame(0, 0, 0, mBIH.width, real_height, gfxASurface::ImageFormatARGB32,
+        if (mBIH.compression == BI_RLE8 || mBIH.compression == BI_RLE4 || 
+            mBIH.compression == BI_ALPHABITFIELDS) {
+            rv = mImage->EnsureFrame(0, 0, 0, mBIH.width, real_height, 
+                                     gfxASurface::ImageFormatARGB32,
                                      (PRUint8**)&mImageData, &imageLength);
         } else {
             // mRow is not used for RLE encoded images
-            mRow = (PRUint8*)moz_malloc((mBIH.width * mBIH.bpp)/8 + 4);
-            // +4 because the line is padded to a 4 bit boundary, but I don't want
+            mRow = (PRUint8*)moz_malloc((mBIH.width * mBIH.bpp) / 8 + 4);
+            // + 4 because the line is padded to a 4 bit boundary, but I don't want
             // to make exact calculations here, that's unnecessary.
             // Also, it compensates rounding error.
             if (!mRow) {
                 PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
                 return;
             }
-            rv = mImage->EnsureFrame(0, 0, 0, mBIH.width, real_height, gfxASurface::ImageFormatRGB24,
-                                     (PRUint8**)&mImageData, &imageLength);
+
+            if (mUseAlphaData) {
+              rv = mImage->EnsureFrame(0, 0, 0, mBIH.width, real_height, 
+                                       gfxASurface::ImageFormatARGB32,
+                                       (PRUint8**)&mImageData, &imageLength);
+            } else {
+              rv = mImage->EnsureFrame(0, 0, 0, mBIH.width, real_height, 
+                                       gfxASurface::ImageFormatRGB24,
+                                       (PRUint8**)&mImageData, &imageLength);
+            }
         }
         if (NS_FAILED(rv) || !mImageData) {
             PostDecoderError(NS_ERROR_FAILURE);
             return;
         }
 
-        // Prepare for transparancy
+        // Prepare for transparency
         if ((mBIH.compression == BI_RLE8) || (mBIH.compression == BI_RLE4)) {
-            if (((mBIH.compression == BI_RLE8) && (mBIH.bpp != 8)) 
-             || ((mBIH.compression == BI_RLE4) && (mBIH.bpp != 4) && (mBIH.bpp != 1))) {
-                PR_LOG(gBMPLog, PR_LOG_DEBUG, ("BMP RLE8/RLE4 compression only supports 8/4 bits per pixel\n"));
-                PostDataError();
-                return;
-            }
             // Clear the image, as the RLE may jump over areas
             memset(mImageData, 0, imageLength);
         }
 
         // Tell the superclass we're starting a frame
         PostFrameStart();
     }
     PRUint8 bpc; // bytes per color
@@ -309,19 +413,20 @@ nsBMPDecoder::WriteInternal(const char* 
     }
     while (aCount && (mPos < mBFH.dataoffset)) { // Skip whatever is between header and data
         mPos++; aBuffer++; aCount--;
     }
     if (aCount && ++mPos >= mBFH.dataoffset) {
         // Need to increment mPos, else we might get to mPos==mLOH again
         // From now on, mPos is irrelevant
         if (!mBIH.compression || mBIH.compression == BI_BITFIELDS) {
-            PRUint32 rowSize = (mBIH.bpp * mBIH.width + 7) / 8; // +7 to round up
-            if (rowSize % 4)
+            PRUint32 rowSize = (mBIH.bpp * mBIH.width + 7) / 8; // + 7 to round up
+            if (rowSize % 4) {
                 rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary
+            }
             PRUint32 toCopy;
             do {
                 toCopy = rowSize - mRowBytes;
                 if (toCopy) {
                     if (toCopy > aCount)
                         toCopy = aCount;
                     memcpy(mRow + mRowBytes, aBuffer, toCopy);
                     aCount -= toCopy;
@@ -365,45 +470,54 @@ nsBMPDecoder::WriteInternal(const char* 
                           SetPixel(d,
                                   (val & mBitFields.red) >> mBitFields.redRightShift << mBitFields.redLeftShift,
                                   (val & mBitFields.green) >> mBitFields.greenRightShift << mBitFields.greenLeftShift,
                                   (val & mBitFields.blue) >> mBitFields.blueRightShift << mBitFields.blueLeftShift);
                           --lpos;
                           p+=2;
                         }
                         break;
-                      case 32:
                       case 24:
                         while (lpos > 0) {
                           SetPixel(d, p[2], p[1], p[0]);
                           p += 2;
                           --lpos;
-                          if (mBIH.bpp == 32)
-                            p++; // Padding byte
                           ++p;
                         }
                         break;
+                      case 32:
+                        while (lpos > 0) {
+                          if (mUseAlphaData) {
+                            SetPixel(d, p[2], p[1], p[0], p[3]);
+                          }
+                          else {
+                            SetPixel(d, p[2], p[1], p[0]);
+                          }
+                          p += 4;
+                          --lpos;
+                        }
+                        break;
                       default:
                         NS_NOTREACHED("Unsupported color depth, but earlier check didn't catch it");
                     }
                     mCurLine --;
                     if (mCurLine == 0) { // Finished last line
-                        break;
+                      break;
                     }
                     mRowBytes = 0;
 
                 }
             } while (aCount > 0);
-        } 
+        }
         else if ((mBIH.compression == BI_RLE8) || (mBIH.compression == BI_RLE4)) {
-            if (((mBIH.compression == BI_RLE8) && (mBIH.bpp != 8)) 
-             || ((mBIH.compression == BI_RLE4) && (mBIH.bpp != 4) && (mBIH.bpp != 1))) {
-                PR_LOG(gBMPLog, PR_LOG_DEBUG, ("BMP RLE8/RLE4 compression only supports 8/4 bits per pixel\n"));
-                PostDataError();
-                return;
+            if (((mBIH.compression == BI_RLE8) && (mBIH.bpp != 8)) || 
+                ((mBIH.compression == BI_RLE4) && (mBIH.bpp != 4) && (mBIH.bpp != 1))) {
+              PR_LOG(gBMPLog, PR_LOG_DEBUG, ("BMP RLE8/RLE4 compression only supports 8/4 bits per pixel\n"));
+              PostDataError();
+              return;
             }
 
             while (aCount > 0) {
                 PRUint8 byte;
 
                 switch(mState) {
                     case eRLEStateInitial:
                         mStateData = (PRUint8)*aBuffer++;
@@ -557,17 +671,17 @@ nsBMPDecoder::WriteInternal(const char* 
                 // Because of the use of the continue statement
                 // we only get here for eol, eof or y delta
                 if (mCurLine == 0) { // Finished last line
                     break;
                 }
             }
         }
     }
-    
+
     const PRUint32 rows = mOldLine - mCurLine;
     if (rows) {
 
         // Invalidate
         nsIntRect r(0, mBIH.height < 0 ? -mBIH.height - mOldLine : mCurLine,
                     mBIH.width, rows);
         PostInvalidation(r);
 
--- a/modules/libpr0n/decoders/nsBMPDecoder.h
+++ b/modules/libpr0n/decoders/nsBMPDecoder.h
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is
  * Christian Biesinger <cbiesinger@web.de>.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Bobby Holley <bobbyholley@gmail.com>
+ *   Brian R. Bondy <netzen@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -51,21 +52,26 @@ namespace imagelib {
 struct BMPFILEHEADER {
     char signature[2]; // String "BM"
     PRUint32 filesize;
     PRInt32 reserved; // Zero
     PRUint32 dataoffset; // Offset to raster data
 
     PRUint32 bihsize;
 };
-#define BFH_LENGTH 18 // Note: For our purposes, we include bihsize in the BFH
+
+// The length of the bitmap file header as defined in the BMP spec.
+#define BFH_LENGTH 14 
+// Internally we store the bitmap file header with an additional 4 bytes which
+// is used to store the bitmap information header size.
+#define BFH_INTERNAL_LENGTH 18
 
 #define OS2_BIH_LENGTH 12 // This is the real BIH size (as contained in the bihsize field of BMPFILEHEADER)
-#define OS2_HEADER_LENGTH (BFH_LENGTH + 8)
-#define WIN_HEADER_LENGTH (BFH_LENGTH + 36)
+#define OS2_HEADER_LENGTH (BFH_INTERNAL_LENGTH + 8)
+#define WIN_HEADER_LENGTH (BFH_INTERNAL_LENGTH + 36)
 
 struct BMPINFOHEADER {
     PRInt32 width; // Uint16 in OS/2 BMPs
     PRInt32 height; // Uint16 in OS/2 BMPs
     PRUint16 planes; // =1
     PRUint16 bpp; // Bits per pixel.
     // The rest of the header is not available in OS/2 BMP Files
     PRUint32 compression; // 0=no compression 1=8bit RLE 2=4bit RLE
@@ -101,27 +107,44 @@ struct bitFields {
 // otherwise, if it is signed/negative, the MSB will be
 // propagated when we shift
 #define LITTLE_TO_NATIVE16(x) (((((PRUint16) x) & 0xFF) << 8) | \
                                (((PRUint16) x) >> 8))
 #define LITTLE_TO_NATIVE32(x) (((((PRUint32) x) & 0xFF) << 24) | \
                                (((((PRUint32) x) >> 8) & 0xFF) << 16) | \
                                (((((PRUint32) x) >> 16) & 0xFF) << 8) | \
                                (((PRUint32) x) >> 24))
+
+#define NATIVE32_TO_LITTLE LITTLE_TO_NATIVE32
+
 #else
 #define LITTLE_TO_NATIVE16(x) x
 #define LITTLE_TO_NATIVE32(x) x
+#define NATIVE32_TO_LITTLE(x) x
+
 #endif
 
 #define USE_RGB
 
 // BMPINFOHEADER.compression defines
+#ifndef BI_RGB
+#define BI_RGB 0
+#endif
+#ifndef BI_RLE8
 #define BI_RLE8 1
+#endif
+#ifndef BI_RLE4
 #define BI_RLE4 2
+#endif
+#ifndef BI_BITFIELDS
 #define BI_BITFIELDS 3
+#endif
+// BI_ALPHABITFIELDS  means no compression and specifies alpha bits
+// valid only for 32bpp and 16bpp
+#define BI_ALPHABITFIELDS 4
 
 // RLE Escape codes
 #define RLE_ESCAPE       0
 #define RLE_ESCAPE_EOL   0
 #define RLE_ESCAPE_EOF   1
 #define RLE_ESCAPE_DELTA 2
 
 /// enums for mState
@@ -141,16 +164,31 @@ class RasterImage;
  */
 class nsBMPDecoder : public Decoder
 {
 public:
 
     nsBMPDecoder();
     ~nsBMPDecoder();
 
+    // Specifies whether or not the BMP file will contain alpha data
+    // If set to true and the BMP is 32BPP, the alpha data will be
+    // retrieved from the 4th byte of image data per pixel 
+    void SetUseAlphaData(PRBool useAlphaData);
+    // Obtains the bits per pixel from the internal BIH header
+    PRInt32 GetBitsPerPixel() const;
+    // Obtains the width from the internal BIH header
+    PRInt32 GetWidth() const;
+    // Obtains the height from the internal BIH header
+    PRInt32 GetHeight() const;
+    // Obtains the internal output image buffer
+    PRUint32* GetImageData();
+    // Obtains the size of the compressed image resource
+    PRInt32 GetCompressedImageSize() const;
+
     virtual void WriteInternal(const char* aBuffer, PRUint32 aCount);
     virtual void FinishInternal();
 
 private:
 
     /** Calculates the red-, green- and blueshift in mBitFields using
      * the bitmasks from mBitFields */
     NS_METHOD CalcBitShift();
@@ -179,16 +217,26 @@ private:
     PRUint32 mStateData;///< Decoding information that is needed depending on mState
 
     /** Set mBFH from the raw data in mRawBuf, converting from little-endian
      * data to native data as necessary */
     void ProcessFileHeader();
     /** Set mBIH from the raw data in mRawBuf, converting from little-endian
      * data to native data as necessary */
     void ProcessInfoHeader();
+
+    // Stores whether the image data stores alpha data, or if
+    // the alpha data is unspecified and filled with a 
+    // padding byte of 0.
+    // When a 32BPP bitmap is stored in an ICO or CUR file, its 4th byte
+    // is used for alpha transparency.  When it is stored in a BMP, its
+    // 4th byte is reserved and is always 0.
+    // Reference: 
+    // http://en.wikipedia.org/wiki/ICO_(file_format)#cite_note-9
+    PRPackedBool mUseAlphaData;
 };
 
 /** Sets the pixel data in aDecoded to the given values.
  * @param aDecoded pointer to pixel to be set, will be incremented to point to the next pixel.
  */
 static inline void SetPixel(PRUint32*& aDecoded, PRUint8 aRed, PRUint8 aGreen, PRUint8 aBlue, PRUint8 aAlpha = 0xFF)
 {
     *aDecoded++ = GFX_PACKED_PIXEL(aAlpha, aRed, aGreen, aBlue);
--- a/modules/libpr0n/decoders/nsICODecoder.cpp
+++ b/modules/libpr0n/decoders/nsICODecoder.cpp
@@ -18,16 +18,17 @@
  * Netscape.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   David Hyatt <hyatt@netscape.com> (Original Author)
  *   Christian Biesinger <cbiesinger@web.de>
  *   Bobby Holley <bobbyholley@gmail.com>
+ *   Brian R. Bondy <netzen@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -60,69 +61,150 @@ namespace imagelib {
 #define DIRENTRYOFFSET 6
 #define BITMAPINFOSIZE 40
 #define PREFICONSIZE 16
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
 
-PRUint32 nsICODecoder::CalcAlphaRowSize()
+PRUint32
+nsICODecoder::CalcAlphaRowSize() 
 {
   // Calculate rowsize in DWORD's and then return in # of bytes
   PRUint32 rowSize = (mDirEntry.mWidth + 31) / 32; // +31 to round up
-  return rowSize * 4;        // Return rowSize in bytes
+  return rowSize * 4; // Return rowSize in bytes
 }
 
+// Obtains the number of colors from the bits per pixel
+PRUint16
+nsICODecoder::GetNumColors() 
+{
+  PRUint16 numColors = 0;
+  if (mBPP <= 8) {
+    switch (mBPP) {
+    case 1:
+      numColors = 2;
+      break;
+    case 4:
+      numColors = 16;
+      break;
+    case 8:
+      numColors = 256;
+      break;
+    default:
+      numColors = (PRUint16)-1;
+    }
+  }
+  return numColors;
+}
+
+
 nsICODecoder::nsICODecoder()
 {
-  mPos = mNumColors = mRowBytes = mImageOffset = mCurrIcon = mNumIcons = 0;
-  mCurLine = 1; // Otherwise decoder will never start
-  mColors = nsnull;
+  mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0;
+  mIsPNG = PR_FALSE;
   mRow = nsnull;
-  mHaveAlphaData = mDecodingAndMask = PR_FALSE;
+  mOldLine = mCurLine = 1; // Otherwise decoder will never start
 }
 
 nsICODecoder::~nsICODecoder()
 {
-  mPos = 0;
-
-  delete[] mColors;
-
-  mCurLine = 0;
-  mRowBytes = 0;
-  mImageOffset = 0;
-  mCurrIcon = 0;
-  mNumIcons = 0;
-
   if (mRow) {
-    free(mRow);
-    mRow = nsnull;
+    moz_free(mRow);
   }
-  mDecodingAndMask = PR_FALSE;
 }
 
 void
 nsICODecoder::FinishInternal()
 {
   // We shouldn't be called in error cases
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call FinishInternal after error!");
 
-  // We should never make multiple frames
-  NS_ABORT_IF_FALSE(GetFrameCount() <= 1, "Multiple ICO frames?");
+  // Finish the internally used decoder as well
+  if (mContainedDecoder) {
+    mContainedDecoder->FinishSharedDecoder();
+    mDecodeDone = mContainedDecoder->GetDecodeDone();
+  }
+}
 
-  // Send notifications if appropriate
-  if (!IsSizeDecode() && (GetFrameCount() == 1)) {
+// Returns a buffer filled with the bitmap file header in little endian:
+// Signature 2 bytes 'BM'
+// FileSize	 4 bytes File size in bytes
+// reserved	 4 bytes unused (=0)
+// DataOffset	 4 bytes File offset to Raster Data
+// Returns PR_TRUE if successful
+PRBool nsICODecoder::FillBitmapFileHeaderBuffer(PRInt8 *bfh) 
+{
+  memset(bfh, 0, 14);
+  bfh[0] = 'B';
+  bfh[1] = 'M';
+  PRInt32 dataOffset = 0;
+  PRInt32 fileSize = 0;
+  dataOffset = BFH_LENGTH + BITMAPINFOSIZE;
+
+  // The color table is present only if BPP is <= 8
+  if (mDirEntry.mBitCount <= 8) {
+    PRUint16 numColors = GetNumColors();
+    if (numColors == (PRUint16)-1) {
+      return PR_FALSE;
+    }
+    dataOffset += 4 * numColors;
+    fileSize = dataOffset + mDirEntry.mWidth * mDirEntry.mHeight;
+  } else {
+    fileSize = dataOffset + (mDirEntry.mBitCount * mDirEntry.mWidth * 
+                             mDirEntry.mHeight) / 8;
+  }
 
-    // Invalidate
-    nsIntRect r(0, 0, mDirEntry.mWidth, mDirEntry.mHeight);
-    PostInvalidation(r);
+  fileSize = NATIVE32_TO_LITTLE(fileSize);
+  memcpy(bfh + 2, &fileSize, sizeof(fileSize));
+  dataOffset = NATIVE32_TO_LITTLE(dataOffset);
+  memcpy(bfh + 10, &dataOffset, sizeof(dataOffset));
+  return PR_TRUE;
+}
+
+// A BMP inside of an ICO has *2 height because of the AND mask
+// that follows the actual bitmap.  The BMP shouldn't know about
+// this difference though.
+void 
+nsICODecoder::FillBitmapInformationBufferHeight(PRInt8 *bih) 
+{
+  PRInt32 height = mDirEntry.mHeight;
+  height = NATIVE32_TO_LITTLE(height);
+  memcpy(bih + 8, &height, sizeof(height));
+}
 
-    PostFrameStop();
-    PostDecodeDone();
+// The BMP information header's bits per pixel should be trusted
+// more than what we have.  Usually the ICO's BPP is set to 0
+PRInt32 
+nsICODecoder::ExtractBPPFromBitmap(PRInt8 *bih)
+{
+  PRInt32 bitsPerPixel;
+  memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel));
+  bitsPerPixel = LITTLE_TO_NATIVE32(bitsPerPixel);
+  return bitsPerPixel;
+}
+
+void
+nsICODecoder::SetHotSpotIfCursor() {
+  if (!mIsCursor) {
+    return;
+  }
+
+  nsCOMPtr<nsISupportsPRUint32> intwrapx = 
+    do_CreateInstance("@mozilla.org/supports-PRUint32;1");
+  nsCOMPtr<nsISupportsPRUint32> intwrapy = 
+    do_CreateInstance("@mozilla.org/supports-PRUint32;1");
+
+  if (intwrapx && intwrapy) {
+    intwrapx->SetData(mDirEntry.mXHotspot);
+    intwrapy->SetData(mDirEntry.mYHotspot);
+
+    mImage->Set("hotspotX", intwrapx);
+    mImage->Set("hotspotY", intwrapy);
   }
 }
 
 void
 nsICODecoder::WriteInternal(const char* aBuffer, PRUint32 aCount)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
@@ -146,40 +228,46 @@ nsICODecoder::WriteInternal(const char* 
     mPos += 2;
     aCount -= 2;
   }
 
   if (mNumIcons == 0)
     return; // Nothing to do.
 
   PRUint16 colorDepth = 0;
-  while (mCurrIcon < mNumIcons) {
-    if (mPos >= DIRENTRYOFFSET + (mCurrIcon*sizeof(mDirEntryArray)) && 
-        mPos < DIRENTRYOFFSET + ((mCurrIcon+1)*sizeof(mDirEntryArray))) {
-      PRUint32 toCopy = sizeof(mDirEntryArray) - (mPos - DIRENTRYOFFSET - mCurrIcon*sizeof(mDirEntryArray));
-      if (toCopy > aCount)
+  // Loop through each entry's dir entry
+  while (mCurrIcon < mNumIcons) { 
+    if (mPos >= DIRENTRYOFFSET + (mCurrIcon * sizeof(mDirEntryArray)) && 
+        mPos < DIRENTRYOFFSET + ((mCurrIcon + 1) * sizeof(mDirEntryArray))) {
+      PRUint32 toCopy = sizeof(mDirEntryArray) - 
+                        (mPos - DIRENTRYOFFSET - mCurrIcon * sizeof(mDirEntryArray));
+      if (toCopy > aCount) {
         toCopy = aCount;
+      }
       memcpy(mDirEntryArray + sizeof(mDirEntryArray) - toCopy, aBuffer, toCopy);
       mPos += toCopy;
       aCount -= toCopy;
       aBuffer += toCopy;
     }
     if (aCount == 0)
       return; // Need more data
 
     IconDirEntry e;
-    if (mPos == 22+mCurrIcon*sizeof(mDirEntryArray)) {
+    if (mPos == (DIRENTRYOFFSET + ICODIRENTRYSIZE) + 
+                (mCurrIcon * sizeof(mDirEntryArray))) {
       mCurrIcon++;
       ProcessDirEntry(e);
-      if ((e.mWidth == PREFICONSIZE && e.mHeight == PREFICONSIZE && e.mBitCount >= colorDepth)
-           || (mCurrIcon == mNumIcons && mImageOffset == 0)) {
+      if ((e.mWidth == PREFICONSIZE && e.mHeight == PREFICONSIZE && 
+           e.mBitCount >= colorDepth) || 
+          (mCurrIcon == mNumIcons && mImageOffset == 0)) {
         mImageOffset = e.mImageOffset;
 
-        // ensure mImageOffset is >= the size of the direntry headers (bug #245631)
-        PRUint32 minImageOffset = DIRENTRYOFFSET + mNumIcons*sizeof(mDirEntryArray);
+        // ensure mImageOffset is >= size of the direntry headers (bug #245631)
+        PRUint32 minImageOffset = DIRENTRYOFFSET + 
+                                  mNumIcons * sizeof(mDirEntryArray);
         if (mImageOffset < minImageOffset) {
           PostDataError();
           return;
         }
 
         colorDepth = e.mBitCount;
         memcpy(&mDirEntry, &e, sizeof(IconDirEntry));
       }
@@ -192,326 +280,251 @@ nsICODecoder::WriteInternal(const char* 
     if (toSkip > aCount)
       toSkip = aCount;
 
     mPos    += toSkip;
     aBuffer += toSkip;
     aCount  -= toSkip;
   }
 
-  if (mCurrIcon == mNumIcons && mPos >= mImageOffset && mPos < mImageOffset + BITMAPINFOSIZE) {
+  // If we are within the first PNGSIGNATURESIZE bytes of the image data,
+  // then we have either a BMP or a PNG.  We use the first PNGSIGNATURESIZE
+  // bytes to determine which one we have.
+  if (mCurrIcon == mNumIcons && mPos >= mImageOffset && 
+      mPos < mImageOffset + PNGSIGNATURESIZE)
+  {
+    PRUint32 toCopy = PNGSIGNATURESIZE - (mPos - mImageOffset);
+    if (toCopy > aCount) {
+      toCopy = aCount;
+    }
+
+    memcpy(mSignature + (mPos - mImageOffset), aBuffer, toCopy);
+    mPos += toCopy;
+    aCount -= toCopy;
+    aBuffer += toCopy;
+
+    mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes, 
+                     PNGSIGNATURESIZE);
+    if (mIsPNG) {
+      mContainedDecoder = new nsPNGDecoder();
+      mContainedDecoder->InitSharedDecoder(mImage, mObserver);
+      mContainedDecoder->Write(mSignature, PNGSIGNATURESIZE);
+      mDataError = mContainedDecoder->HasDataError();
+      if (mContainedDecoder->HasDataError()) {
+        return;
+      }
+    }
+  }
+
+  // If we have a PNG, let the PNG decoder do all of the rest of the work
+  if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
+    mContainedDecoder->Write(aBuffer, aCount);
+    mDataError = mContainedDecoder->HasDataError();
+    if (mContainedDecoder->HasDataError()) {
+      return;
+    }
+    mPos += aCount;
+    aBuffer += aCount;
+    aCount = 0;
+
+    // Raymond Chen says that 32bpp only are valid PNG ICOs
+    // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
+    if (static_cast<nsPNGDecoder*>(mContainedDecoder.get())->HasValidInfo() && 
+        static_cast<nsPNGDecoder*>(mContainedDecoder.get())->GetPixelDepth() != 32) {
+      PostDataError();
+    }
+    return;
+  }
+
+  // We've processed all of the icon dir entries and are within the 
+  // bitmap info size
+  if (!mIsPNG && mCurrIcon == mNumIcons && mPos >= mImageOffset && 
+      mPos >= mImageOffset + PNGSIGNATURESIZE && 
+      mPos < mImageOffset + BITMAPINFOSIZE) {
+
+    // As we were decoding, we did not know if we had a PNG signature or the
+    // start of a bitmap information header.  At this point we know we had
+    // a bitmap information header and not a PNG signature, so fill the bitmap
+    // information header with the data it should already have.
+    memcpy(mBIHraw, mSignature, PNGSIGNATURESIZE);
+
     // We've found the icon.
     PRUint32 toCopy = sizeof(mBIHraw) - (mPos - mImageOffset);
     if (toCopy > aCount)
       toCopy = aCount;
 
     memcpy(mBIHraw + (mPos - mImageOffset), aBuffer, toCopy);
     mPos += toCopy;
     aCount -= toCopy;
     aBuffer += toCopy;
   }
 
-  nsresult rv;
-
-  if (mPos == mImageOffset + BITMAPINFOSIZE) {
-
-    ProcessInfoHeader();
-    PostSize(mDirEntry.mWidth, mDirEntry.mHeight);
-    if (IsSizeDecode())
-      return;
+  // If we have a BMP inside the ICO and we have read the BIH header
+  if (!mIsPNG && mPos == mImageOffset + BITMAPINFOSIZE) {
+    // We are extracting the BPP from the BIH header as it should be trusted 
+    // over the one we have from the icon header
+    mBPP = ExtractBPPFromBitmap((PRInt8*)mBIHraw);
+    
+    // Init the bitmap decoder which will do most of the work for us
+    // It will do everything except the AND mask which isn't present in bitmaps
+    // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
+    nsBMPDecoder *bmpDecoder = new nsBMPDecoder(); 
+    mContainedDecoder = bmpDecoder;
+    bmpDecoder->SetUseAlphaData(PR_TRUE);
+    mContainedDecoder->SetSizeDecode(IsSizeDecode());
+    mContainedDecoder->InitSharedDecoder(mImage, mObserver);
 
-    if (mBIH.bpp <= 8) {
-      switch (mBIH.bpp) {
-        case 1:
-          mNumColors = 2;
-          break;
-        case 4:
-          mNumColors = 16;
-          break;
-        case 8:
-          mNumColors = 256;
-          break;
-        default:
-          PostDataError();
-          return;
-      }
-
-      mColors = new colorTable[mNumColors];
+    // The ICO format when containing a BMP does not include the 14 byte
+    // bitmap file header. To use the code of the BMP decoder we need to 
+    // generate this header ourselves and feed it to the BMP decoder.
+    PRInt8 bfhBuffer[BMPFILEHEADERSIZE];
+    if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
+      PostDataError();
+      return;
+    }
+    mContainedDecoder->Write((const char*)bfhBuffer, sizeof(bfhBuffer));
+    mDataError = mContainedDecoder->HasDataError();
+    if (mContainedDecoder->HasDataError()) {
+      return;
     }
 
-    if (mIsCursor) {
-      nsCOMPtr<nsISupportsPRUint32> intwrapx = do_CreateInstance("@mozilla.org/supports-PRUint32;1");
-      nsCOMPtr<nsISupportsPRUint32> intwrapy = do_CreateInstance("@mozilla.org/supports-PRUint32;1");
-
-      if (intwrapx && intwrapy) {
-        intwrapx->SetData(mDirEntry.mXHotspot);
-        intwrapy->SetData(mDirEntry.mYHotspot);
+    // Setup the cursor hot spot if one is present
+    SetHotSpotIfCursor();
 
-        mImage->Set("hotspotX", intwrapx);
-        mImage->Set("hotspotY", intwrapy);
-      }
-    }
+    // Fix the height on the BMP resource
+    FillBitmapInformationBufferHeight((PRInt8*)mBIHraw);
 
-    mCurLine = mDirEntry.mHeight;
-    mRow = (PRUint8*)moz_malloc((mDirEntry.mWidth * mBIH.bpp)/8 + 4);
-    // +4 because the line is padded to a 4 bit boundary, but I don't want
-    // to make exact calculations here, that's unnecessary.
-    // Also, it compensates rounding error.
-    if (!mRow) {
-      PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
+    // Write out the BMP's bitmap info header
+    mContainedDecoder->Write(mBIHraw, sizeof(mBIHraw));
+    mDataError = mContainedDecoder->HasDataError();
+    if (mContainedDecoder->HasDataError()) {
       return;
     }
 
-    PRUint32 imageLength;
-    rv = mImage->EnsureFrame(0, 0, 0, mDirEntry.mWidth, mDirEntry.mHeight,
-                             gfxASurface::ImageFormatARGB32, (PRUint8**)&mImageData, &imageLength);
-    if (NS_FAILED(rv)) {
-      PostDecoderError(rv);
-      return;
-    }
-
-    // Tell the superclass we're starting a frame
-    PostFrameStart();
-  }
+    // Sometimes the ICO BPP header field is not filled out
+    // so we should trust the contained resource over our own
+    // information.
+    mBPP = bmpDecoder->GetBitsPerPixel();
 
-  if (mColors && (mPos >= mImageOffset + BITMAPINFOSIZE) && 
-                 (mPos < (mImageOffset + BITMAPINFOSIZE + mNumColors * 4))) {
-    // We will receive (mNumColors * 4) bytes of color data
-    PRUint32 colorBytes = mPos - (mImageOffset + 40); // Number of bytes already received
-    PRUint8 colorNum = colorBytes / 4; // Color which is currently received
-    PRUint8 at = colorBytes % 4;
-    while (aCount && (mPos < (mImageOffset + BITMAPINFOSIZE + mNumColors * 4))) {
-      switch (at) {
-        case 0:
-          mColors[colorNum].blue = *aBuffer;
-          break;
-        case 1:
-          mColors[colorNum].green = *aBuffer;
-          break;
-        case 2:
-          mColors[colorNum].red = *aBuffer;
-          break;
-        case 3:
-          colorNum++; // This is a padding byte
-          break;
-      }
-      mPos++; aBuffer++; aCount--;
-      at = (at + 1) % 4;
+    // Check to make sure we have valid color settings
+    PRUint16 numColors = GetNumColors();
+    if (numColors == (PRUint16)-1) {
+      PostDataError();
+      return;
     }
   }
 
-  if (!mDecodingAndMask && (mPos >= (mImageOffset + BITMAPINFOSIZE + mNumColors*4))) {
-    if (mPos == (mImageOffset + BITMAPINFOSIZE + mNumColors*4)) {
-      // Increment mPos to avoid reprocessing the info header.
-      mPos++;
-    }
-
-    // Ensure memory has been allocated before decoding. If we get this far 
-    // without allocated memory, the file is most likely invalid.
-    // XXXbholley - If null values can be triggered by bad input, why are we
-    // asserting here?
-    NS_ASSERTION(mRow, "mRow is null");
-    NS_ASSERTION(mImageData, "mImageData is null");
-    if (!mRow || !mImageData) {
+  // If we have a BMP
+  if (!mIsPNG && mContainedDecoder && mPos >= mImageOffset + BITMAPINFOSIZE) {
+    PRUint16 numColors = GetNumColors();
+    if (numColors == (PRUint16)-1) {
       PostDataError();
       return;
     }
+    // Feed the actual image data (not including headers) into the BMP decoder
+    PRInt32 bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE;
+    PRInt32 bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE + 
+                         static_cast<nsBMPDecoder*>(mContainedDecoder.get())->GetCompressedImageSize() +
+                         4 * numColors;
 
-    PRUint32 rowSize = (mBIH.bpp * mDirEntry.mWidth + 7) / 8; // +7 to round up
-    if (rowSize % 4)
-      rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary
-    PRUint32 toCopy;
-    do {
-        toCopy = rowSize - mRowBytes;
-        if (toCopy) {
-            if (toCopy > aCount)
-                toCopy = aCount;
+    // If we are feeding in the core image data, but we have not yet
+    // reached the ICO's 'AND buffer mask'
+    if (mPos >= bmpDataOffset && mPos < bmpDataEnd) {
+
+      // Figure out how much data the BMP decoder wants
+      PRUint32 toFeed = bmpDataEnd - mPos;
+      if (toFeed > aCount) {
+        toFeed = aCount;
+      }
+
+      mContainedDecoder->Write(aBuffer, toFeed);
+      mDataError = mContainedDecoder->HasDataError();
+      if (mContainedDecoder->HasDataError()) {
+        return;
+      }
+
+      mPos += toFeed;
+      aCount -= toFeed;
+      aBuffer += toFeed;
+    }
+  
+    // If the bitmap is fully processed, treat any left over data as the ICO's
+    // 'AND buffer mask' which appears after the bitmap resource.
+    if (!mIsPNG && mPos >= bmpDataEnd) {
+      // There may be an optional AND bit mask after the data.  This is
+      // only used if the alpha data is not already set. The alpha data 
+      // is used for 32bpp bitmaps as per the comment in ICODecoder.h
+      // The alpha mask should be checked in all other cases.
+      if (static_cast<nsBMPDecoder*>(mContainedDecoder.get())->GetBitsPerPixel() != 32) {
+        PRUint32 rowSize = ((mDirEntry.mWidth + 31) / 32) * 4; // + 31 to round up
+        if (mPos == bmpDataEnd) {
+          mPos++;
+          mRowBytes = 0;
+          mCurLine = mDirEntry.mHeight;
+          mRow = (PRUint8*)moz_realloc(mRow, rowSize);
+          if (!mRow) {
+            PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
+            return;
+          }
+        }
+
+        // Ensure memory has been allocated before decoding.
+        NS_ABORT_IF_FALSE(mRow, "mRow is null");
+        NS_ABORT_IF_FALSE(mImage, "mImage is null");
+        if (!mRow || !mImage) {
+          PostDataError();
+          return;
+        }
+
+        while (mCurLine > 0 && aCount > 0) {
+          PRUint32 toCopy = NS_MIN(rowSize - mRowBytes, aCount);
+          if (toCopy) {
             memcpy(mRow + mRowBytes, aBuffer, toCopy);
             aCount -= toCopy;
             aBuffer += toCopy;
             mRowBytes += toCopy;
-        }
-        if (rowSize == mRowBytes) {
+          }
+          if (rowSize == mRowBytes) {
             mCurLine--;
-            PRUint32* d = mImageData + (mCurLine * mDirEntry.mWidth);
-            PRUint8* p = mRow;
-            PRUint32 lpos = mDirEntry.mWidth;
-            switch (mBIH.bpp) {
-              case 1:
-                while (lpos > 0) {
-                  PRInt8 bit;
-                  PRUint8 idx;
-                  for (bit = 7; bit >= 0 && lpos > 0; bit--) {
-                      idx = (*p >> bit) & 1;
-                      SetPixel(d, idx, mColors);
-                      --lpos;
-                  }
-                  ++p;
-                }
-                break;
-              case 4:
-                while (lpos > 0) {
-                  Set4BitPixel(d, *p, lpos, mColors);
-                  ++p;
-                }
-                break;
-              case 8:
-                while (lpos > 0) {
-                  SetPixel(d, *p, mColors);
-                  --lpos;
-                  ++p;
-                }
-                break;
-              case 16:
-                while (lpos > 0) {
-                  SetPixel(d,
-                          (p[1] & 124) << 1,
-                          ((p[1] & 3) << 6) | ((p[0] & 224) >> 2),
-                          (p[0] & 31) << 3);
+            mRowBytes = 0;
 
-                  --lpos;
-                  p+=2;
-                }
-                break;
-              case 24:
-                while (lpos > 0) {
-                  SetPixel(d, p[2], p[1], p[0]);
-                  p += 3;
-                  --lpos;
+            PRUint32* imageData = static_cast<nsBMPDecoder*>(mContainedDecoder.get())->GetImageData();
+            PRUint32* decoded = imageData + mCurLine * mDirEntry.mWidth;
+            PRUint32* decoded_end = decoded + mDirEntry.mWidth;
+            PRUint8* p = mRow, *p_end = mRow + rowSize; 
+            while (p < p_end) {
+              PRUint8 idx = *p++;
+              for (PRUint8 bit = 0x80; bit && decoded<decoded_end; bit >>= 1) {
+                // Clear pixel completely for transparency.
+                if (idx & bit) {
+                  *decoded = 0;
                 }
-                break;
-              case 32:
-                // We assume that 32bit doesn't have alpha data until we
-                // find a non-zero alpha byte. If we find such a byte, 
-                // it means that all previous pixels are really clear (alphabyte=0).
-                // This working assumption prevents us having to premultiply afterwards.
-                while (lpos > 0) {
-                  if (!mHaveAlphaData && p[3]) {
-                    // Non-zero alpha byte detected! Clear previous pixels from current row to end
-                    memset(mImageData + mCurLine * mDirEntry.mWidth, 0, 
-                           (mDirEntry.mHeight - mCurLine) * mDirEntry.mWidth * sizeof(PRUint32));
-                    mHaveAlphaData = PR_TRUE;
-                  }                        
-                  SetPixel(d, p[2], p[1], p[0], mHaveAlphaData ? p[3] : 0xFF);
-                  p += 4;
-                  --lpos;
-                }
-                break;
-              default:
-                // This is probably the wrong place to check this...
-                PostDataError();
-                return;
+                decoded++;
+              }
             }
-
-            if (mCurLine == 0)
-              mDecodingAndMask = PR_TRUE;
-              
-            mRowBytes = 0;
-        }
-    } while (!mDecodingAndMask && aCount > 0);
-
-  }
-
-  if (mDecodingAndMask && !mHaveAlphaData) {
-    PRUint32 rowSize = CalcAlphaRowSize();
-
-    if (mPos == (1 + mImageOffset + BITMAPINFOSIZE + mNumColors*4)) {
-      mPos++;
-      mRowBytes = 0;
-      mCurLine = mDirEntry.mHeight;
-      mRow = (PRUint8*)realloc(mRow, rowSize);
-      if (!mRow) {
-        PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
-        return;
-      }
-    }
-
-    // Ensure memory has been allocated before decoding.
-    NS_ASSERTION(mRow, "mRow is null");
-    NS_ASSERTION(mImageData, "mImageData is null");
-    if (!mRow || !mImageData) {
-      PostDataError();
-      return;
-    }
-
-    while (mCurLine > 0 && aCount > 0) {
-      PRUint32 toCopy = NS_MIN(rowSize - mRowBytes, aCount);
-      if (toCopy) {
-        memcpy(mRow + mRowBytes, aBuffer, toCopy);
-        aCount -= toCopy;
-        aBuffer += toCopy;
-        mRowBytes += toCopy;
-      }
-      if (rowSize == mRowBytes) {
-        mCurLine--;
-        mRowBytes = 0;
-
-        PRUint32* decoded = mImageData + mCurLine * mDirEntry.mWidth;
-        PRUint32* decoded_end = decoded + mDirEntry.mWidth;
-        PRUint8* p = mRow, *p_end = mRow + rowSize; 
-        while (p < p_end) {
-          PRUint8 idx = *p++;
-          for (PRUint8 bit = 0x80; bit && decoded<decoded_end; bit >>= 1) {
-            // Clear pixel completely for transparency.
-            if (idx & bit) *decoded = 0;
-            decoded ++;
           }
         }
       }
     }
   }
-
-  return;
 }
 
 void
 nsICODecoder::ProcessDirEntry(IconDirEntry& aTarget)
 {
   memset(&aTarget, 0, sizeof(aTarget));
   memcpy(&aTarget.mWidth, mDirEntryArray, sizeof(aTarget.mWidth));
-  memcpy(&aTarget.mHeight, mDirEntryArray+1, sizeof(aTarget.mHeight));
-  memcpy(&aTarget.mColorCount, mDirEntryArray+2, sizeof(aTarget.mColorCount));
-  memcpy(&aTarget.mReserved, mDirEntryArray+3, sizeof(aTarget.mReserved));
-  
-  memcpy(&aTarget.mPlanes, mDirEntryArray+4, sizeof(aTarget.mPlanes));
+  memcpy(&aTarget.mHeight, mDirEntryArray + 1, sizeof(aTarget.mHeight));
+  memcpy(&aTarget.mColorCount, mDirEntryArray + 2, sizeof(aTarget.mColorCount));
+  memcpy(&aTarget.mReserved, mDirEntryArray + 3, sizeof(aTarget.mReserved));
+  memcpy(&aTarget.mPlanes, mDirEntryArray + 4, sizeof(aTarget.mPlanes));
   aTarget.mPlanes = LITTLE_TO_NATIVE16(aTarget.mPlanes);
-
-  memcpy(&aTarget.mBitCount, mDirEntryArray+6, sizeof(aTarget.mBitCount));
+  memcpy(&aTarget.mBitCount, mDirEntryArray + 6, sizeof(aTarget.mBitCount));
   aTarget.mBitCount = LITTLE_TO_NATIVE16(aTarget.mBitCount);
-
-  memcpy(&aTarget.mBytesInRes, mDirEntryArray+8, sizeof(aTarget.mBytesInRes));
+  memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes));
   aTarget.mBytesInRes = LITTLE_TO_NATIVE32(aTarget.mBytesInRes);
-
-  memcpy(&aTarget.mImageOffset, mDirEntryArray+12, sizeof(aTarget.mImageOffset));
+  memcpy(&aTarget.mImageOffset, mDirEntryArray + 12, 
+         sizeof(aTarget.mImageOffset));
   aTarget.mImageOffset = LITTLE_TO_NATIVE32(aTarget.mImageOffset);
 }
 
-void nsICODecoder::ProcessInfoHeader() {
-  memset(&mBIH, 0, sizeof(mBIH));
-  // Ignoring the size; it should always be 40 for icons, anyway
-
-  memcpy(&mBIH.width, mBIHraw + 4, sizeof(mBIH.width));
-  memcpy(&mBIH.height, mBIHraw + 8, sizeof(mBIH.height));
-  memcpy(&mBIH.planes, mBIHraw + 12, sizeof(mBIH.planes));
-  memcpy(&mBIH.bpp, mBIHraw + 14, sizeof(mBIH.bpp));
-  memcpy(&mBIH.compression, mBIHraw + 16, sizeof(mBIH.compression));
-  memcpy(&mBIH.image_size, mBIHraw + 20, sizeof(mBIH.image_size));
-  memcpy(&mBIH.xppm, mBIHraw + 24, sizeof(mBIH.xppm));
-  memcpy(&mBIH.yppm, mBIHraw + 28, sizeof(mBIH.yppm));
-  memcpy(&mBIH.colors, mBIHraw + 32, sizeof(mBIH.colors));
-  memcpy(&mBIH.important_colors, mBIHraw + 36, sizeof(mBIH.important_colors));
-
-  // Convert endianness
-  mBIH.width = LITTLE_TO_NATIVE32(mBIH.width);
-  mBIH.height = LITTLE_TO_NATIVE32(mBIH.height);
-  mBIH.planes = LITTLE_TO_NATIVE16(mBIH.planes);
-  mBIH.bpp = LITTLE_TO_NATIVE16(mBIH.bpp);
-
-  mBIH.compression = LITTLE_TO_NATIVE32(mBIH.compression);
-  mBIH.image_size = LITTLE_TO_NATIVE32(mBIH.image_size);
-  mBIH.xppm = LITTLE_TO_NATIVE32(mBIH.xppm);
-  mBIH.yppm = LITTLE_TO_NATIVE32(mBIH.yppm);
-  mBIH.colors = LITTLE_TO_NATIVE32(mBIH.colors);
-  mBIH.important_colors = LITTLE_TO_NATIVE32(mBIH.important_colors);
-}
-
 } // namespace imagelib
 } // namespace mozilla
--- a/modules/libpr0n/decoders/nsICODecoder.h
+++ b/modules/libpr0n/decoders/nsICODecoder.h
@@ -17,16 +17,17 @@
  * The Initial Developer of the Original Code is
  * Netscape.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   David Hyatt <hyatt@netscape.com> (Original Author)
  *   Bobby Holley <bobbyholley@gmail.com>
+ *   Brian R. Bondy <netzen@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -40,20 +41,25 @@
 
 #ifndef _nsICODecoder_h
 #define _nsICODecoder_h
 
 #include "nsAutoPtr.h"
 #include "Decoder.h"
 #include "imgIDecoderObserver.h"
 #include "nsBMPDecoder.h"
+#include "nsPNGDecoder.h"
 
 namespace mozilla {
 namespace imagelib {
 
+#define ICODIRENTRYSIZE 16
+#define PNGSIGNATURESIZE 8
+#define BMPFILEHEADERSIZE 14
+
 class RasterImage;
 
 struct IconDirEntry
 {
   PRUint8   mWidth;
   PRUint8   mHeight;
   PRUint8   mColorCount;
   PRUint8   mReserved;
@@ -75,45 +81,50 @@ public:
 
   nsICODecoder();
   virtual ~nsICODecoder();
 
   virtual void WriteInternal(const char* aBuffer, PRUint32 aCount);
   virtual void FinishInternal();
 
 private:
-  // Private helper methods
+  // Processes a single dir entry of the icon resource
   void ProcessDirEntry(IconDirEntry& aTarget);
-  void ProcessInfoHeader();
-
-  nsresult SetImageData();
-
+  // Sets the hotspot property of if we have a cursor
+  void SetHotSpotIfCursor();
+  // Creates a bitmap file header buffer, returns PR_TRUE if successful
+  PRBool FillBitmapFileHeaderBuffer(PRInt8 *bfh);
+  // Fixes the height of a BMP information header field
+  void FillBitmapInformationBufferHeight(PRInt8 *bih);
+  // Extract bit count from BMP information header
+  PRInt32 ExtractBPPFromBitmap(PRInt8 *bih);
+  // Calculates the row size in bytes for the AND mask table
   PRUint32 CalcAlphaRowSize();
-
-  PRUint32 mPos;
-  PRUint16 mNumIcons;
-  PRUint16 mCurrIcon;
-  PRUint32 mImageOffset;
-
-  char mDirEntryArray[16];
-  IconDirEntry mDirEntry;
+  // Obtains the number of colors from the BPP, mBPP must be filled in
+  PRUint16 GetNumColors();
 
-  char mBIHraw[40];
-  BMPINFOHEADER mBIH;
-
-  PRUint32 mNumColors;
-  colorTable* mColors;
-
-  PRUint8* mRow; // Holds one raw line of the image
+  PRUint16 mBPP; // Stores the images BPP
+  PRUint32 mPos; // Keeps track of the position we have decoded up until
+  PRUint16 mNumIcons; // Stores the number of icons in the ICO file
+  PRUint16 mCurrIcon; // Stores the current dir entry index we are processing
+  PRUint32 mImageOffset; // Stores the offset of the image data we want
+  PRUint8 *mRow;      // Holds one raw line of the image
+  PRInt32 mCurLine;   // Line index of the image that's currently being decoded
   PRUint32 mRowBytes; // How many bytes of the row were already received
-  PRInt32 mCurLine;
-
-  PRUint32* mImageData;
+  PRInt32 mOldLine;   // Previous index of the line 
+  nsAutoPtr<Decoder> mContainedDecoder; // Contains either a BMP or PNG resource
 
-  PRPackedBool mHaveAlphaData;
+  char mDirEntryArray[ICODIRENTRYSIZE]; // Holds the current dir entry buffer
+  IconDirEntry mDirEntry; // Holds a decoded dir entry
+  // Holds the potential bytes that can be a PNG signature
+  char mSignature[PNGSIGNATURESIZE]; 
+  // Holds the potential bytes for a bitmap information header
+  char mBIHraw[40];
+  // Stores whether or not the icon file we are processing has type 1 (icon)
   PRPackedBool mIsCursor;
-  PRPackedBool mDecodingAndMask;
+  // Stores whether or not the contained resource is a PNG
+  PRPackedBool mIsPNG;
 };
 
 } // namespace imagelib
 } // namespace mozilla
 
 #endif
--- a/modules/libpr0n/decoders/nsPNGDecoder.cpp
+++ b/modules/libpr0n/decoders/nsPNGDecoder.cpp
@@ -72,20 +72,19 @@ static PRLogModuleInfo *gPNGDecoderAccou
 /* limit image dimensions (bug #251381) */
 #define MOZ_PNG_MAX_DIMENSION 1000000L
 
 // For size decodes
 #define WIDTH_OFFSET 16
 #define HEIGHT_OFFSET (WIDTH_OFFSET + 4)
 #define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4)
 
-// This is defined in the PNG spec as an invariant. We use it to
-// do manual validation without libpng.
-static const PRUint8 pngSignatureBytes[] =
-               { 137, 80, 78, 71, 13, 10, 26, 10 };
+// First 8 bytes of a PNG file
+const PRUint8 
+nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
 
 nsPNGDecoder::nsPNGDecoder() :
   mPNG(nsnull), mInfo(nsnull),
   mCMSLine(nsnull), interlacebuf(nsnull),
   mInProfile(nsnull), mTransform(nsnull),
   mHeaderBuf(nsnull), mHeaderBytesRead(0),
   mChannels(0), mFrameIsHidden(PR_FALSE),
   mCMSMode(0), mDisablePremultipliedAlpha(PR_FALSE)
@@ -312,17 +311,18 @@ nsPNGDecoder::WriteInternal(const char *
                                   mHeaderBytesRead);
     memcpy(mHeaderBuf + mHeaderBytesRead, aBuffer, bytesToRead);
     mHeaderBytesRead += bytesToRead;
 
     // If we're done now, verify the data and set up the container
     if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) {
 
       // Check that the signature bytes are right
-      if (memcmp(mHeaderBuf, pngSignatureBytes, sizeof(pngSignatureBytes))) {
+      if (memcmp(mHeaderBuf, nsPNGDecoder::pngSignatureBytes, 
+                 sizeof(pngSignatureBytes))) {
         PostDataError();
         return;
       }
 
       // Grab the width and height, accounting for endianness (thanks libpng!)
       width = png_get_uint_32(mHeaderBuf + WIDTH_OFFSET);
       height = png_get_uint_32(mHeaderBuf + HEIGHT_OFFSET);
 
--- a/modules/libpr0n/decoders/nsPNGDecoder.h
+++ b/modules/libpr0n/decoders/nsPNGDecoder.h
@@ -67,16 +67,31 @@ public:
 
   void CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
                    PRInt32 width, PRInt32 height,
                    gfxASurface::gfxImageFormat format);
   void SetAnimFrameInfo();
 
   void EndImageFrame();
 
+  // Checks if the info header contains valid information
+  bool HasValidInfo() const 
+  {
+    return mInfo && mInfo->valid;
+  }
+
+  // Obtain the pixel depth if available or 0 otherwise
+  PRInt32 GetPixelDepth() const
+  {
+    if (!mInfo) {
+      return 0;
+    }
+    return mInfo->pixel_depth;
+  }
+
 public:
   png_structp mPNG;
   png_infop mInfo;
   nsIntRect mFrameRect;
   PRUint8 *mCMSLine;
   PRUint8 *interlacebuf;
   PRUint8 *mImageData;
   qcms_profile *mInProfile;
@@ -108,14 +123,18 @@ public:
   static void PNGAPI frame_info_callback(png_structp png_ptr,
                                          png_uint_32 frame_num);
 #endif
   static void PNGAPI end_callback(png_structp png_ptr, png_infop info_ptr);
   static void PNGAPI error_callback(png_structp png_ptr,
                                     png_const_charp error_msg);
   static void PNGAPI warning_callback(png_structp png_ptr,
                                       png_const_charp warning_msg);
+
+  // This is defined in the PNG spec as an invariant. We use it to
+  // do manual validation without libpng.
+  static const PRUint8 pngSignatureBytes[];
 };
 
 } // namespace imagelib
 } // namespace mozilla
 
 #endif // nsPNGDecoder_h__
--- a/modules/libpr0n/src/Decoder.cpp
+++ b/modules/libpr0n/src/Decoder.cpp
@@ -83,16 +83,36 @@ Decoder::Init(RasterImage* aImage, imgID
   if (!IsSizeDecode() && mObserver)
       mObserver->OnStartDecode(nsnull);
 
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
 }
 
+// Initializes a decoder whose aImage and aObserver is already being used by a
+// parent decoder
+void 
+Decoder::InitSharedDecoder(RasterImage* aImage, imgIDecoderObserver* aObserver) 
+{
+  // We should always have an image
+  NS_ABORT_IF_FALSE(aImage, "Can't initialize decoder without an image!");
+
+  // No re-initializing
+  NS_ABORT_IF_FALSE(mImage == nsnull, "Can't re-initialize a decoder!");
+
+  // Save our parameters
+  mImage = aImage;
+  mObserver = aObserver;
+
+  // Implementation-specific initialization
+  InitInternal();
+  mInitialized = true;
+}
+
 void
 Decoder::Write(const char* aBuffer, PRUint32 aCount)
 {
   // We're strict about decoder errors
   NS_ABORT_IF_FALSE(!HasDecoderError(),
                     "Not allowed to make more decoder calls after error!");
 
   // If a data error occured, just ignore future data
@@ -151,16 +171,24 @@ Decoder::Finish()
     if (mObserver) {
       mObserver->OnStopContainer(nsnull, mImage);
       mObserver->OnStopDecode(nsnull, salvage ? NS_OK : NS_ERROR_FAILURE, nsnull);
     }
   }
 }
 
 void
+Decoder::FinishSharedDecoder()
+{
+  if (!HasError()) {
+    FinishInternal();
+  }
+}
+
+void
 Decoder::FlushInvalidations()
 {
   NS_ABORT_IF_FALSE(!HasDecoderError(),
                     "Not allowed to make more decoder calls after error!");
 
   // If we've got an empty invalidation rect, we have nothing to do
   if (mInvalidRect.IsEmpty())
     return;
--- a/modules/libpr0n/src/Decoder.h
+++ b/modules/libpr0n/src/Decoder.h
@@ -58,16 +58,28 @@ public:
    *
    * @param aContainer The image container to decode to.
    * @param aObserver The observer for decode notification events.
    *
    * Notifications Sent: TODO
    */
   void Init(RasterImage* aImage, imgIDecoderObserver* aObserver);
 
+
+  /**
+   * Initializes a decoder whose aImage and aObserver is already being used by a
+   * parent decoder. Decoders may not be re-initialized.
+   *
+   * @param aContainer The image container to decode to.
+   * @param aObserver The observer for decode notification events.
+   *
+   * Notifications Sent: TODO
+   */
+  void InitSharedDecoder(RasterImage* aImage, imgIDecoderObserver* aObserver);
+
   /**
    * Writes data to the decoder.
    *
    * @param aBuffer buffer containing the data to be written
    * @param aCount the number of bytes to write
    *
    * Any errors are reported by setting the appropriate state on the decoder.
    *
@@ -78,16 +90,24 @@ public:
   /**
    * Informs the decoder that all the data has been written.
    *
    * Notifications Sent: TODO
    */
   void Finish();
 
   /**
+   * Informs the shared decoder that all the data has been written.
+   * Should only be used if InitSharedDecoder was useed
+   *
+   * Notifications Sent: TODO
+   */
+  void FinishSharedDecoder();
+
+  /**
    * Tells the decoder to flush any pending invalidations. This informs the image
    * frame of its decoded region, and sends the appropriate OnDataAvailable call
    * to consumers.
    *
    * This can be called any time when we're midway through decoding a frame,
    * and must be called after finishing a frame (before starting a new one).
    */
   void FlushInvalidations();
@@ -117,16 +137,19 @@ public:
   PRUint32 GetCompleteFrameCount() { return mInFrame ? mFrameCount - 1 : mFrameCount; }
 
   // Error tracking
   bool HasError() { return HasDataError() || HasDecoderError(); };
   bool HasDataError() { return mDataError; };
   bool HasDecoderError() { return NS_FAILED(mFailCode); };
   nsresult GetDecoderError() { return mFailCode; };
   void PostResizeError() { PostDataError(); }
+  bool GetDecodeDone() const {
+    return mDecodeDone;
+  }
 
   // flags.  Keep these in sync with imgIContainer.idl.
   // SetDecodeFlags must be called before Init(), otherwise
   // default flags are assumed.
   enum {
     DECODER_NO_PREMULTIPLY_ALPHA = 0x2,
     DECODER_NO_COLORSPACE_CONVERSION = 0x4
   };
@@ -171,31 +194,30 @@ protected:
   void PostDataError();
   void PostDecoderError(nsresult aFailCode);
 
   /*
    * Member variables.
    *
    */
   nsRefPtr<RasterImage> mImage;
+  nsCOMPtr<imgIDecoderObserver> mObserver;
 
   PRUint32 mDecodeFlags;
+  bool mDecodeDone;
+  bool mDataError;
 
 private:
-  nsCOMPtr<imgIDecoderObserver> mObserver;
-
   PRUint32 mFrameCount; // Number of frames, including anything in-progress
 
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
 
   nsresult mFailCode;
 
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
-  bool mDecodeDone;
-  bool mDataError;
 };
 
 } // namespace imagelib
 } // namespace mozilla
 
 #endif // MOZILLA_IMAGELIB_DECODER_H_