From e51f538dd6f32ee45a170ba024e46f2ad13c7a66 Mon Sep 17 00:00:00 2001 From: Shih-Chiang Chien Date: Fri, 8 Mar 2013 11:17:29 +0800 Subject: [PATCH] Bug 847310 - WBMP decoder implementation. r=joe --- image/build/nsImageModule.cpp | 1 + image/decoders/Makefile.in | 1 + image/decoders/nsWBMPDecoder.cpp | 289 ++++++++++++++++++ image/decoders/nsWBMPDecoder.h | 64 ++++ image/src/Image.cpp | 4 + image/src/Image.h | 3 +- image/src/RasterImage.cpp | 4 + image/src/imgLoader.cpp | 8 + netwerk/mime/nsMimeTypes.h | 1 + .../exthandler/nsExternalHelperAppService.cpp | 1 + 10 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 image/decoders/nsWBMPDecoder.cpp create mode 100644 image/decoders/nsWBMPDecoder.h diff --git a/image/build/nsImageModule.cpp b/image/build/nsImageModule.cpp index f20eaedf9ad7..a0037adbe881 100644 --- a/image/build/nsImageModule.cpp +++ b/image/build/nsImageModule.cpp @@ -81,6 +81,7 @@ static const mozilla::Module::CategoryEntry kImageCategories[] = { { "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 } }; diff --git a/image/decoders/Makefile.in b/image/decoders/Makefile.in index d75d0938a3f8..b5c074b1a0fd 100644 --- a/image/decoders/Makefile.in +++ b/image/decoders/Makefile.in @@ -22,6 +22,7 @@ endif # !_MSC_VER CPPSRCS = nsPNGDecoder.cpp nsJPEGDecoder.cpp nsGIFDecoder2.cpp \ nsBMPDecoder.cpp nsICODecoder.cpp nsIconDecoder.cpp \ + nsWBMPDecoder.cpp \ $(NULL) CSRCS = iccjpeg.c \ diff --git a/image/decoders/nsWBMPDecoder.cpp b/image/decoders/nsWBMPDecoder.cpp new file mode 100644 index 000000000000..51bee1b52740 --- /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 diff --git a/image/decoders/nsWBMPDecoder.h b/image/decoders/nsWBMPDecoder.h new file mode 100644 index 000000000000..ea38b6703558 --- /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__ diff --git a/image/src/Image.cpp b/image/src/Image.cpp index ddb56692cdef..5f24997d0e7f 100644 --- a/image/src/Image.cpp +++ b/image/src/Image.cpp @@ -86,6 +86,10 @@ Image::GetDecoderType(const char *aMimeType) else if (!strcmp(aMimeType, IMAGE_ICON_MS)) rv = eDecoderType_icon; + // WBMP + else if (!strcmp(aMimeType, IMAGE_WBMP)) + rv = eDecoderType_wbmp; + return rv; } diff --git a/image/src/Image.h b/image/src/Image.h index c89aa0e8ce1e..ab37fcffdcec 100644 --- a/image/src/Image.h +++ b/image/src/Image.h @@ -26,7 +26,8 @@ public: eDecoderType_bmp = 3, eDecoderType_ico = 4, eDecoderType_icon = 5, - eDecoderType_unknown = 6 + eDecoderType_wbmp = 6, + eDecoderType_unknown = 7 }; static eDecoderType GetDecoderType(const char *aMimeType); diff --git a/image/src/RasterImage.cpp b/image/src/RasterImage.cpp index e82107550941..eb4a12377385 100644 --- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -25,6 +25,7 @@ #include "nsBMPDecoder.h" #include "nsICODecoder.h" #include "nsIconDecoder.h" +#include "nsWBMPDecoder.h" #include "gfxContext.h" @@ -2608,6 +2609,9 @@ RasterImage::InitDecoder(bool aDoSizeDecode) 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!"); } diff --git a/image/src/imgLoader.cpp b/image/src/imgLoader.cpp index 74132b67387c..3965564e73de 100644 --- a/image/src/imgLoader.cpp +++ b/image/src/imgLoader.cpp @@ -2046,6 +2046,14 @@ nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, uint32_t aLeng 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(aContents[0]) == 0x00 && + (static_cast(aContents[1]) & 0x9F) == 0x00)) + { + aContentType.AssignLiteral(IMAGE_WBMP); + } + else { /* none of the above? I give up */ return NS_ERROR_NOT_AVAILABLE; diff --git a/netwerk/mime/nsMimeTypes.h b/netwerk/mime/nsMimeTypes.h index c4c1b12a303a..c5e8a16c7632 100644 --- a/netwerk/mime/nsMimeTypes.h +++ b/netwerk/mime/nsMimeTypes.h @@ -101,6 +101,7 @@ #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" diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index ae0e0ae611c4..7bf3d1a4bd03 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -464,6 +464,7 @@ static nsExtraMimeTypeEntry extraMimeEntries [] = { 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" },