Bug 847310 - WBMP decoder implementation. r=joe
authorShih-Chiang Chien <schien@mozilla.com>
Fri, 08 Mar 2013 11:17:29 +0800
changeset 124945 e8101722986a93c9cfff23b40a71b1d328ee5a75
parent 124944 d5dcd62f32dd809af751904e6dd80cd542c5f40c
child 124946 83720eb64f44d19a2a80b8078d6dbcfa9f64f66b
push id24694
push userryanvm@gmail.com
push dateFri, 15 Mar 2013 17:38:01 +0000
treeherdermozilla-inbound@83720eb64f44 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe
bugs847310
milestone22.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 847310 - WBMP decoder implementation. r=joe
image/build/nsImageModule.cpp
image/decoders/Makefile.in
image/decoders/nsWBMPDecoder.cpp
image/decoders/nsWBMPDecoder.h
image/src/Image.cpp
image/src/Image.h
image/src/RasterImage.cpp
image/src/imgLoader.cpp
netwerk/mime/nsMimeTypes.h
uriloader/exthandler/nsExternalHelperAppService.cpp
--- a/image/build/nsImageModule.cpp
+++ b/image/build/nsImageModule.cpp
@@ -76,16 +76,17 @@ static const mozilla::Module::CategoryEn
   { "Gecko-Content-Viewers", IMAGE_JPG, "@mozilla.org/content/document-loader-factory;1" },
   { "Gecko-Content-Viewers", IMAGE_ICO, "@mozilla.org/content/document-loader-factory;1" },
   { "Gecko-Content-Viewers", IMAGE_ICO_MS, "@mozilla.org/content/document-loader-factory;1" },
   { "Gecko-Content-Viewers", IMAGE_BMP, "@mozilla.org/content/document-loader-factory;1" },
   { "Gecko-Content-Viewers", IMAGE_BMP_MS, "@mozilla.org/content/document-loader-factory;1" },
   { "Gecko-Content-Viewers", IMAGE_ICON_MS, "@mozilla.org/content/document-loader-factory;1" },
   { "Gecko-Content-Viewers", IMAGE_PNG, "@mozilla.org/content/document-loader-factory;1" },
   { "Gecko-Content-Viewers", IMAGE_X_PNG, "@mozilla.org/content/document-loader-factory;1" },
+  { "Gecko-Content-Viewers", IMAGE_WBMP, "@mozilla.org/content/document-loader-factory;1" },
   { "content-sniffing-services", "@mozilla.org/image/loader;1", "@mozilla.org/image/loader;1" },
   { NULL }
 };
 
 static nsresult
 imglib_Initialize()
 {
   mozilla::image::DiscardTracker::Initialize();
--- a/image/decoders/Makefile.in
+++ b/image/decoders/Makefile.in
@@ -17,16 +17,17 @@ MODULE_NAME = nsDecodersModule
 LIBXUL_LIBRARY  = 1
 ifndef _MSC_VER
 FAIL_ON_WARNINGS = 1
 endif # !_MSC_VER
 
 
 CPPSRCS = nsPNGDecoder.cpp nsJPEGDecoder.cpp nsGIFDecoder2.cpp \
           nsBMPDecoder.cpp nsICODecoder.cpp nsIconDecoder.cpp \
+          nsWBMPDecoder.cpp \
           $(NULL)
 
 CSRCS   = iccjpeg.c \
           $(NULL)
 
 # Decoders need RasterImage.h
 LOCAL_INCLUDES += -I$(topsrcdir)/image/src/
 
new file mode 100644
--- /dev/null
+++ b/image/decoders/nsWBMPDecoder.cpp
@@ -0,0 +1,289 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWBMPDecoder.h"
+#include "RasterImage.h"
+#include "nspr.h"
+#include "nsRect.h"
+#include "gfxPlatform.h"
+
+#include "nsError.h"
+
+namespace mozilla {
+namespace image {
+
+static inline void SetPixel(uint32_t*& aDecoded, bool aPixelWhite)
+{
+  uint8_t pixelValue = aPixelWhite ? 255 : 0;
+
+  *aDecoded++ = gfxPackedPixel(0xFF, pixelValue, pixelValue, pixelValue);
+}
+
+/** Parses a WBMP encoded int field.  Returns IntParseInProgress (out of
+ *  data), IntParseSucceeded if the field was read OK or IntParseFailed
+ *  on an error.
+ *  The encoding used for WBMP ints is per byte.  The high bit is a
+ *  continuation flag saying (when set) that the next byte is part of the
+ *  field, and the low seven bits are data.  New data bits are added in the
+ *  low bit positions, i.e. the field is big-endian (ignoring the high bits).
+ * @param aField Variable holds current value of field.  When this function
+ *               returns IntParseInProgress, aField will hold the
+ *               intermediate result of the decoding, so this function can be
+ *               called repeatedly for new bytes on the same field and will
+ *               operate correctly.
+ * @param aBuffer Points to encoded field data.
+ * @param aCount Number of bytes in aBuffer. */
+static WbmpIntDecodeStatus DecodeEncodedInt (uint32_t& aField, const char*& aBuffer, uint32_t& aCount)
+{
+  while (aCount > 0) {
+    // Check if the result would overflow if another seven bits were added.
+    // The actual test performed is AND to check if any of the top seven bits are set.
+    if (aField & 0xFE000000) {
+      // Overflow :(
+      return IntParseFailed;
+    }
+
+    // Get next encoded byte.
+    char encodedByte = *aBuffer;
+
+    // Update buffer state variables now we have read this byte.
+    aBuffer++;
+    aCount--;
+
+    // Work out and store the new (valid) value of the encoded int with this byte added.
+    aField = (aField << 7) + (uint32_t)(encodedByte & 0x7F);
+
+    if (!(encodedByte & 0x80)) {
+      // No more bytes, value is complete.
+      return IntParseSucceeded;
+    }
+  }
+
+  // Out of data but in the middle of an encoded int.
+  return IntParseInProgress;
+}
+
+nsWBMPDecoder::nsWBMPDecoder(RasterImage &aImage, imgDecoderObserver* aObserver)
+ : Decoder(aImage, aObserver),
+   mWidth(0),
+   mHeight(0),
+   mImageData(nullptr),
+   mRow(nullptr),
+   mRowBytes(0),
+   mCurLine(0),
+   mState(WbmpStateStart)
+{
+  // Nothing to do
+}
+
+nsWBMPDecoder::~nsWBMPDecoder()
+{
+  moz_free(mRow);
+}
+
+void
+nsWBMPDecoder::WriteInternal(const char *aBuffer, uint32_t aCount)
+{
+  NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
+
+  // Loop until the input data is gone
+  while (aCount > 0) {
+    switch (mState) {
+      case WbmpStateStart:
+      {
+        // Since we only accept a type 0 WBMP we can just check the first byte is 0.
+        // (The specification says a well defined type 0 bitmap will start with a 0x00 byte).
+        if (*aBuffer++ == 0x00) {
+          // This is a type 0 WBMP.
+          aCount--;
+          mState = DecodingFixHeader;
+        } else {
+          // This is a new type of WBMP or a type 0 WBMP defined oddly (e.g. 0x80 0x00)
+          PostDataError();
+          mState = DecodingFailed;
+          return;
+        }
+        break;
+      }
+
+      case DecodingFixHeader:
+      {
+        if ((*aBuffer++ & 0x9F) == 0x00) {
+          // Fix header field is as expected
+          aCount--;
+          // For now, we skip the ext header field as it is not in a well-defined type 0 WBMP.
+          mState = DecodingWidth;
+        } else {
+          // Can't handle this fix header field.
+          PostDataError();
+          mState = DecodingFailed;
+          return;
+        }
+        break;
+      }
+
+      case DecodingWidth:
+      {
+        WbmpIntDecodeStatus widthReadResult = DecodeEncodedInt (mWidth, aBuffer, aCount);
+
+        if (widthReadResult == IntParseSucceeded) {
+          mState = DecodingHeight;
+        } else if (widthReadResult == IntParseFailed) {
+          // Encoded width was bigger than a uint32_t or equal to 0.
+          PostDataError();
+          mState = DecodingFailed;
+          return;
+        } else {
+          // We are still parsing the encoded int field.
+          NS_ABORT_IF_FALSE((widthReadResult == IntParseInProgress),
+                            "nsWBMPDecoder got bad result from an encoded width field");
+          return;
+        }
+        break;
+      }
+
+      case DecodingHeight:
+      {
+        WbmpIntDecodeStatus heightReadResult = DecodeEncodedInt (mHeight, aBuffer, aCount);
+
+        if (heightReadResult == IntParseSucceeded) {
+          // The header has now been entirely read.
+          const uint32_t k64KWidth = 0x0000FFFF;
+          if (mWidth == 0 || mWidth > k64KWidth
+              || mHeight == 0 || mHeight > k64KWidth) {
+            // consider 0 as an incorrect image size
+            // reject the extremely wide/high images to keep the math sane
+            PostDataError();
+            mState = DecodingFailed;
+            return;
+          }
+
+          // Post our size to the superclass
+          PostSize(mWidth, mHeight);
+          if (HasError()) {
+            // Setting the size led to an error.
+            mState = DecodingFailed;
+            return;
+          }
+
+          // If We're doing a size decode, we're done
+          if (IsSizeDecode()) {
+            mState = WbmpStateFinished;
+            return;
+          }
+
+          uint32_t imageLength;
+          // Add the frame and signal
+          nsresult rv = mImage.EnsureFrame(0, 0, 0, mWidth, mHeight,
+                                           gfxASurface::ImageFormatRGB24,
+                                           (uint8_t**)&mImageData, &imageLength);
+
+          if (NS_FAILED(rv) || !mImageData) {
+            PostDecoderError(NS_ERROR_FAILURE);
+            mState = DecodingFailed;
+            return;
+          }
+
+          // Create mRow, the buffer that holds one line of the raw image data
+          mRow = (uint8_t*)moz_malloc((mWidth + 7) / 8);
+          if (!mRow) {
+            PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
+            mState = DecodingFailed;
+            return;
+          }
+
+          // Tell the superclass we're starting a frame
+          PostFrameStart();
+
+          mState = DecodingImageData;
+
+        } else if (heightReadResult == IntParseFailed) {
+          // Encoded height was bigger than a uint32_t.
+          PostDataError();
+          mState = DecodingFailed;
+          return;
+        } else {
+          // We are still parsing the encoded int field.
+          NS_ABORT_IF_FALSE((heightReadResult == IntParseInProgress),
+                            "nsWBMPDecoder got bad result from an encoded height field");
+          return;
+        }
+        break;
+      }
+
+      case DecodingImageData:
+      {
+        uint32_t rowSize = (mWidth + 7) / 8; // +7 to round up to nearest byte
+        uint32_t top = mCurLine;
+
+        // Process up to one row of data at a time until there is no more data.
+        while ((aCount > 0) && (mCurLine < mHeight)) {
+          // Calculate if we need to copy data to fill the next buffered row of raw data.
+          uint32_t toCopy = rowSize - mRowBytes;
+
+          // If required, copy raw data to fill a buffered row of raw data.
+          if (toCopy) {
+            if (toCopy > aCount)
+              toCopy = aCount;
+            memcpy(mRow + mRowBytes, aBuffer, toCopy);
+            aCount -= toCopy;
+            aBuffer += toCopy;
+            mRowBytes += toCopy;
+          }
+
+          // If there is a filled buffered row of raw data, process the row.
+          if (rowSize == mRowBytes) {
+            uint8_t *p = mRow;
+            uint32_t *d = mImageData + (mWidth * mCurLine); // position of the first pixel at mCurLine
+            uint32_t lpos = 0;
+
+            while (lpos < mWidth) {
+              for (int8_t bit = 7; bit >= 0; bit--) {
+                if (lpos >= mWidth)
+                  break;
+                bool pixelWhite = (*p >> bit) & 1;
+                SetPixel(d, pixelWhite);
+                ++lpos;
+              }
+              ++p;
+            }
+
+            mCurLine++;
+            mRowBytes = 0;
+          }
+        }
+
+        nsIntRect r(0, top, mWidth, mCurLine - top);
+        // Invalidate
+        PostInvalidation(r);
+
+        // If we've got all the pixel bytes, we're finished
+        if (mCurLine == mHeight) {
+          PostFrameStop();
+          PostDecodeDone();
+          mState = WbmpStateFinished;
+        }
+        break;
+      }
+
+      case WbmpStateFinished:
+      {
+        // Consume all excess data silently
+        aCount = 0;
+        break;
+      }
+
+      case DecodingFailed:
+      {
+        NS_ABORT_IF_FALSE(0, "Shouldn't process any data after decode failed!");
+        return;
+      }
+    }
+  }
+}
+
+} // namespace image
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/image/decoders/nsWBMPDecoder.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWBMPDecoder_h__
+#define nsWBMPDecoder_h__
+
+#include "Decoder.h"
+#include "nsCOMPtr.h"
+#include "imgDecoderObserver.h"
+#include "gfxColor.h"
+
+namespace mozilla {
+namespace image {
+class RasterImage;
+
+/* WBMP is a monochrome graphics file format optimized for mobile computing devices.
+ * Format description from http://www.wapforum.org/what/technical/SPEC-WAESpec-19990524.pdf
+ */
+
+typedef enum {
+  WbmpStateStart,
+  DecodingFixHeader,
+  DecodingWidth,
+  DecodingHeight,
+  DecodingImageData,
+  DecodingFailed,
+  WbmpStateFinished
+} WbmpDecodingState;
+
+typedef enum {
+  IntParseSucceeded,
+  IntParseFailed,
+  IntParseInProgress
+} WbmpIntDecodeStatus;
+
+class nsWBMPDecoder : public Decoder
+{
+public:
+
+  nsWBMPDecoder(RasterImage &aImage, imgDecoderObserver* aObserver);
+  virtual ~nsWBMPDecoder();
+
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
+
+private:
+  uint32_t mWidth;
+  uint32_t mHeight;
+
+  uint32_t *mImageData;
+
+  uint8_t* mRow;                    // Holds one raw line of the image
+  uint32_t mRowBytes;               // How many bytes of the row were already received
+  uint32_t mCurLine;                // The current line being decoded (0 to mHeight - 1)
+
+  WbmpDecodingState mState;         // Describes what part of the file we are decoding now.
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // nsWBMPDecoder_h__
--- a/image/src/Image.cpp
+++ b/image/src/Image.cpp
@@ -81,16 +81,20 @@ Image::GetDecoderType(const char *aMimeT
     rv = eDecoderType_ico;
   else if (!strcmp(aMimeType, IMAGE_ICO_MS))
     rv = eDecoderType_ico;
 
   // Icon
   else if (!strcmp(aMimeType, IMAGE_ICON_MS))
     rv = eDecoderType_icon;
 
+  // WBMP
+  else if (!strcmp(aMimeType, IMAGE_WBMP))
+    rv = eDecoderType_wbmp;
+
   return rv;
 }
 
 void
 ImageResource::IncrementAnimationConsumers()
 {
   mAnimationConsumers++;
   EvaluateAnimation();
--- a/image/src/Image.h
+++ b/image/src/Image.h
@@ -21,17 +21,18 @@ public:
   // Mimetype translation
   enum eDecoderType {
     eDecoderType_png     = 0,
     eDecoderType_gif     = 1,
     eDecoderType_jpeg    = 2,
     eDecoderType_bmp     = 3,
     eDecoderType_ico     = 4,
     eDecoderType_icon    = 5,
-    eDecoderType_unknown = 6
+    eDecoderType_wbmp    = 6,
+    eDecoderType_unknown = 7
   };
   static eDecoderType GetDecoderType(const char *aMimeType);
 
   /**
    * Flags for Image initialization.
    *
    * Meanings:
    *
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -20,16 +20,17 @@
 #include "nsPresContext.h"
 
 #include "nsPNGDecoder.h"
 #include "nsGIFDecoder2.h"
 #include "nsJPEGDecoder.h"
 #include "nsBMPDecoder.h"
 #include "nsICODecoder.h"
 #include "nsIconDecoder.h"
+#include "nsWBMPDecoder.h"
 
 #include "gfxContext.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/StandardInteger.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/ClearOnShutdown.h"
@@ -2603,16 +2604,19 @@ RasterImage::InitDecoder(bool aDoSizeDec
       mDecoder = new nsBMPDecoder(*this, observer);
       break;
     case eDecoderType_ico:
       mDecoder = new nsICODecoder(*this, observer);
       break;
     case eDecoderType_icon:
       mDecoder = new nsIconDecoder(*this, observer);
       break;
+    case eDecoderType_wbmp:
+      mDecoder = new nsWBMPDecoder(*this, observer);
+      break;
     default:
       NS_ABORT_IF_FALSE(0, "Shouldn't get here!");
   }
 
   // Initialize the decoder
   mDecoder->SetSizeDecode(aDoSizeDecode);
   mDecoder->SetDecodeFlags(mFrameDecodeFlags);
   mDecoder->Init();
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -2041,16 +2041,24 @@ nsresult imgLoader::GetMimeTypeFromConte
 
   // ICOs always begin with a 2-byte 0 followed by a 2-byte 1.
   // CURs begin with 2-byte 0 followed by 2-byte 2.
   else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) ||
                             !memcmp(aContents, "\000\000\002\000", 4))) {
     aContentType.AssignLiteral(IMAGE_ICO);
   }
 
+  // A well-defined type 0 WBMP file starts with an "0000 0000b" byte followed
+  // by an "0xx0 0000b" byte (x = don't care).
+  else if (aLength >= 2 && (static_cast<unsigned char>(aContents[0]) == 0x00 &&
+                            (static_cast<unsigned char>(aContents[1]) & 0x9F) == 0x00))
+  {
+    aContentType.AssignLiteral(IMAGE_WBMP);
+  }
+
   else {
     /* none of the above?  I give up */
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return NS_OK;
 }
 
--- a/netwerk/mime/nsMimeTypes.h
+++ b/netwerk/mime/nsMimeTypes.h
@@ -96,16 +96,17 @@
 #define IMAGE_BMP                           "image/bmp"
 #define IMAGE_BMP_MS                        "image/x-ms-bmp"
 #define IMAGE_ICO                           "image/x-icon"
 #define IMAGE_ICO_MS                        "image/vnd.microsoft.icon"
 #define IMAGE_ICON_MS                       "image/icon"
 #define IMAGE_MNG                           "video/x-mng"
 #define IMAGE_JNG                           "image/x-jng"
 #define IMAGE_SVG_XML                       "image/svg+xml"
+#define IMAGE_WBMP                          "image/vnd.wap.wbmp"
 
 #define MESSAGE_EXTERNAL_BODY               "message/external-body"
 #define MESSAGE_NEWS                        "message/news"
 #define MESSAGE_RFC822                      "message/rfc822"
 
 #define MULTIPART_ALTERNATIVE               "multipart/alternative"
 #define MULTIPART_APPLEDOUBLE               "multipart/appledouble"
 #define MULTIPART_DIGEST                    "multipart/digest"
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -459,16 +459,17 @@ static nsExtraMimeTypeEntry extraMimeEnt
   { IMAGE_ART, "art", "ART Image" },
   { IMAGE_BMP, "bmp", "BMP Image" },
   { IMAGE_GIF, "gif", "GIF Image" },
   { IMAGE_ICO, "ico,cur", "ICO Image" },
   { IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" },
   { IMAGE_PNG, "png", "PNG Image" },
   { IMAGE_TIFF, "tiff,tif", "TIFF Image" },
   { IMAGE_XBM, "xbm", "XBM Image" },
+  { IMAGE_WBMP, "wbmp", "WBMP Image" },
   { "image/svg+xml", "svg", "Scalable Vector Graphics" },
   { MESSAGE_RFC822, "eml", "RFC-822 data" },
   { TEXT_PLAIN, "txt,text", "Text File" },
   { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },
   { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" },
   { APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language" },
   { APPLICATION_RDF, "rdf", "Resource Description Framework" },
   { TEXT_XUL, "xul", "XML-Based User Interface Language" },