2007-03-22 10:30:00 -07:00
|
|
|
/* vim:set tw=80 expandtab softtabstop=4 ts=4 sw=4: */
|
2012-05-21 04:12:37 -07:00
|
|
|
/* 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/. */
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
/* This is a Cross-Platform ICO Decoder, which should work everywhere, including
|
|
|
|
* Big-Endian machines like the PowerPC. */
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
2013-09-05 15:55:13 -07:00
|
|
|
#include "mozilla/Endian.h"
|
2014-11-26 13:22:10 -08:00
|
|
|
#include "mozilla/Move.h"
|
2007-03-22 10:30:00 -07:00
|
|
|
#include "nsICODecoder.h"
|
|
|
|
|
2010-08-13 21:09:49 -07:00
|
|
|
#include "RasterImage.h"
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2010-08-22 19:30:46 -07:00
|
|
|
namespace mozilla {
|
2012-01-06 08:02:27 -08:00
|
|
|
namespace image {
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
#define ICONCOUNTOFFSET 4
|
|
|
|
#define DIRENTRYOFFSET 6
|
|
|
|
#define BITMAPINFOSIZE 40
|
|
|
|
#define PREFICONSIZE 16
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// Actual Data Processing
|
|
|
|
// ----------------------------------------
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t
|
2014-11-14 09:59:00 -08:00
|
|
|
nsICODecoder::CalcAlphaRowSize()
|
2007-03-22 10:30:00 -07:00
|
|
|
{
|
2007-07-17 14:08:46 -07:00
|
|
|
// Calculate rowsize in DWORD's and then return in # of bytes
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t rowSize = (GetRealWidth() + 31) / 32; // + 31 to round up
|
2011-08-25 13:09:01 -07:00
|
|
|
return rowSize * 4; // Return rowSize in bytes
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// Obtains the number of colors from the bits per pixel
|
2012-08-22 08:56:38 -07:00
|
|
|
uint16_t
|
2014-11-14 09:59:00 -08:00
|
|
|
nsICODecoder::GetNumColors()
|
2011-08-25 13:09:01 -07:00
|
|
|
{
|
2012-08-22 08:56:38 -07:00
|
|
|
uint16_t numColors = 0;
|
2011-08-25 13:09:01 -07:00
|
|
|
if (mBPP <= 8) {
|
|
|
|
switch (mBPP) {
|
|
|
|
case 1:
|
|
|
|
numColors = 2;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
numColors = 16;
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
numColors = 256;
|
|
|
|
break;
|
|
|
|
default:
|
2012-08-22 08:56:38 -07:00
|
|
|
numColors = (uint16_t)-1;
|
2011-08-25 13:09:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return numColors;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-01-15 15:11:35 -08:00
|
|
|
nsICODecoder::nsICODecoder(RasterImage* aImage)
|
2013-01-18 13:47:18 -08:00
|
|
|
: Decoder(aImage)
|
2007-03-22 10:30:00 -07:00
|
|
|
{
|
2011-08-25 13:09:01 -07:00
|
|
|
mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0;
|
2011-10-17 07:59:28 -07:00
|
|
|
mIsPNG = false;
|
2012-07-30 07:20:58 -07:00
|
|
|
mRow = nullptr;
|
2011-08-25 13:09:01 -07:00
|
|
|
mOldLine = mCurLine = 1; // Otherwise decoder will never start
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
nsICODecoder::~nsICODecoder()
|
|
|
|
{
|
2010-08-22 19:30:46 -07:00
|
|
|
if (mRow) {
|
2011-08-25 13:09:01 -07:00
|
|
|
moz_free(mRow);
|
2010-08-22 19:30:46 -07:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2010-09-12 08:22:30 -07:00
|
|
|
void
|
2010-08-22 19:30:46 -07:00
|
|
|
nsICODecoder::FinishInternal()
|
2007-03-22 10:30:00 -07:00
|
|
|
{
|
2010-09-12 08:22:31 -07:00
|
|
|
// We shouldn't be called in error cases
|
2015-02-09 14:34:50 -08:00
|
|
|
MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
|
2010-09-12 08:22:31 -07:00
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// Finish the internally used decoder as well
|
|
|
|
if (mContainedDecoder) {
|
2015-01-11 11:43:32 -08:00
|
|
|
mContainedDecoder->FinishSharedDecoder();
|
2011-08-25 13:09:01 -07:00
|
|
|
mDecodeDone = mContainedDecoder->GetDecodeDone();
|
2014-11-18 01:48:49 -08:00
|
|
|
mProgress |= mContainedDecoder->TakeProgress();
|
2014-11-18 01:48:48 -08:00
|
|
|
mInvalidRect.Union(mContainedDecoder->TakeInvalidRect());
|
2011-08-25 13:09:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns a buffer filled with the bitmap file header in little endian:
|
|
|
|
// Signature 2 bytes 'BM'
|
2014-11-14 09:59:00 -08:00
|
|
|
// FileSize 4 bytes File size in bytes
|
|
|
|
// reserved 4 bytes unused (=0)
|
|
|
|
// DataOffset 4 bytes File offset to Raster Data
|
2011-10-17 07:59:28 -07:00
|
|
|
// Returns true if successful
|
2014-11-14 09:59:00 -08:00
|
|
|
bool
|
|
|
|
nsICODecoder::FillBitmapFileHeaderBuffer(int8_t* bfh)
|
2011-08-25 13:09:01 -07:00
|
|
|
{
|
|
|
|
memset(bfh, 0, 14);
|
|
|
|
bfh[0] = 'B';
|
|
|
|
bfh[1] = 'M';
|
2012-08-22 08:56:38 -07:00
|
|
|
int32_t dataOffset = 0;
|
|
|
|
int32_t fileSize = 0;
|
2011-08-25 13:09:01 -07:00
|
|
|
dataOffset = BFH_LENGTH + BITMAPINFOSIZE;
|
|
|
|
|
|
|
|
// The color table is present only if BPP is <= 8
|
|
|
|
if (mDirEntry.mBitCount <= 8) {
|
2012-08-22 08:56:38 -07:00
|
|
|
uint16_t numColors = GetNumColors();
|
|
|
|
if (numColors == (uint16_t)-1) {
|
2011-10-17 07:59:28 -07:00
|
|
|
return false;
|
2011-08-25 13:09:01 -07:00
|
|
|
}
|
|
|
|
dataOffset += 4 * numColors;
|
2011-09-22 06:43:13 -07:00
|
|
|
fileSize = dataOffset + GetRealWidth() * GetRealHeight();
|
2011-08-25 13:09:01 -07:00
|
|
|
} else {
|
2014-11-14 09:59:00 -08:00
|
|
|
fileSize = dataOffset + (mDirEntry.mBitCount * GetRealWidth() *
|
2011-09-22 06:43:13 -07:00
|
|
|
GetRealHeight()) / 8;
|
2011-08-25 13:09:01 -07:00
|
|
|
}
|
|
|
|
|
2013-09-05 15:55:13 -07:00
|
|
|
NativeEndian::swapToLittleEndianInPlace(&fileSize, 1);
|
2011-08-25 13:09:01 -07:00
|
|
|
memcpy(bfh + 2, &fileSize, sizeof(fileSize));
|
2013-09-05 15:55:13 -07:00
|
|
|
NativeEndian::swapToLittleEndianInPlace(&dataOffset, 1);
|
2011-08-25 13:09:01 -07:00
|
|
|
memcpy(bfh + 10, &dataOffset, sizeof(dataOffset));
|
2011-10-17 07:59:28 -07:00
|
|
|
return true;
|
2011-08-25 13:09:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2011-11-04 06:56:17 -07:00
|
|
|
bool
|
2014-11-14 09:59:00 -08:00
|
|
|
nsICODecoder::FixBitmapHeight(int8_t* bih)
|
2011-08-25 13:09:01 -07:00
|
|
|
{
|
2011-11-04 06:56:17 -07:00
|
|
|
// Get the height from the BMP file information header
|
2012-08-22 08:56:38 -07:00
|
|
|
int32_t height;
|
2011-11-04 06:56:17 -07:00
|
|
|
memcpy(&height, bih + 8, sizeof(height));
|
2013-09-05 15:55:13 -07:00
|
|
|
NativeEndian::swapFromLittleEndianInPlace(&height, 1);
|
2012-07-21 10:57:35 -07:00
|
|
|
// BMPs can be stored inverted by having a negative height
|
|
|
|
height = abs(height);
|
2011-11-04 06:56:17 -07:00
|
|
|
|
|
|
|
// The bitmap height is by definition * 2 what it should be to account for
|
|
|
|
// the 'AND mask'. It is * 2 even if the `AND mask` is not present.
|
|
|
|
height /= 2;
|
|
|
|
|
|
|
|
if (height > 256) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-11-14 09:59:00 -08:00
|
|
|
// We should always trust the height from the bitmap itself instead of
|
2011-11-04 06:56:17 -07:00
|
|
|
// the ICO height. So fix the ICO height.
|
|
|
|
if (height == 256) {
|
|
|
|
mDirEntry.mHeight = 0;
|
|
|
|
} else {
|
2012-08-22 08:56:38 -07:00
|
|
|
mDirEntry.mHeight = (int8_t)height;
|
2011-11-04 06:56:17 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fix the BMP height in the BIH so that the BMP decoder can work properly
|
2013-09-05 15:55:13 -07:00
|
|
|
NativeEndian::swapToLittleEndianInPlace(&height, 1);
|
2011-08-25 13:09:01 -07:00
|
|
|
memcpy(bih + 8, &height, sizeof(height));
|
2011-11-04 06:56:17 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We should always trust the contained resource for the width
|
|
|
|
// information over our own information.
|
|
|
|
bool
|
2014-11-14 09:59:00 -08:00
|
|
|
nsICODecoder::FixBitmapWidth(int8_t* bih)
|
2011-11-04 06:56:17 -07:00
|
|
|
{
|
|
|
|
// Get the width from the BMP file information header
|
2012-08-22 08:56:38 -07:00
|
|
|
int32_t width;
|
2011-11-04 06:56:17 -07:00
|
|
|
memcpy(&width, bih + 4, sizeof(width));
|
2013-09-05 15:55:13 -07:00
|
|
|
NativeEndian::swapFromLittleEndianInPlace(&width, 1);
|
2011-11-04 06:56:17 -07:00
|
|
|
if (width > 256) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-11-14 09:59:00 -08:00
|
|
|
// We should always trust the width from the bitmap itself instead of
|
2011-11-04 06:56:17 -07:00
|
|
|
// the ICO width.
|
|
|
|
if (width == 256) {
|
|
|
|
mDirEntry.mWidth = 0;
|
|
|
|
} else {
|
2012-08-22 08:56:38 -07:00
|
|
|
mDirEntry.mWidth = (int8_t)width;
|
2011-11-04 06:56:17 -07:00
|
|
|
}
|
|
|
|
return true;
|
2011-08-25 13:09:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2014-11-14 09:59:00 -08:00
|
|
|
int32_t
|
|
|
|
nsICODecoder::ExtractBPPFromBitmap(int8_t* bih)
|
2011-08-25 13:09:01 -07:00
|
|
|
{
|
2012-08-22 08:56:38 -07:00
|
|
|
int32_t bitsPerPixel;
|
2011-08-25 13:09:01 -07:00
|
|
|
memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel));
|
2013-09-05 15:55:13 -07:00
|
|
|
NativeEndian::swapFromLittleEndianInPlace(&bitsPerPixel, 1);
|
2011-08-25 13:09:01 -07:00
|
|
|
return bitsPerPixel;
|
|
|
|
}
|
2010-08-22 19:30:46 -07:00
|
|
|
|
2014-11-14 09:59:00 -08:00
|
|
|
int32_t
|
|
|
|
nsICODecoder::ExtractBIHSizeFromBitmap(int8_t* bih)
|
2011-08-29 22:12:59 -07:00
|
|
|
{
|
2012-08-22 08:56:38 -07:00
|
|
|
int32_t headerSize;
|
2011-08-29 22:12:59 -07:00
|
|
|
memcpy(&headerSize, bih, sizeof(headerSize));
|
2013-09-05 15:55:13 -07:00
|
|
|
NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1);
|
2011-08-29 22:12:59 -07:00
|
|
|
return headerSize;
|
|
|
|
}
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
void
|
2014-11-14 09:59:00 -08:00
|
|
|
nsICODecoder::SetHotSpotIfCursor()
|
|
|
|
{
|
2011-08-25 13:09:01 -07:00
|
|
|
if (!mIsCursor) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-20 08:49:25 -08:00
|
|
|
mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2010-09-12 08:22:30 -07:00
|
|
|
void
|
2015-01-08 00:04:31 -08:00
|
|
|
nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
|
2007-03-22 10:30:00 -07:00
|
|
|
{
|
2015-02-09 14:34:50 -08:00
|
|
|
MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2013-02-27 11:23:08 -08:00
|
|
|
if (!aCount) {
|
|
|
|
if (mContainedDecoder) {
|
2015-01-08 00:04:31 -08:00
|
|
|
WriteToContainedDecoder(aBuffer, aCount);
|
2013-02-27 11:23:08 -08:00
|
|
|
}
|
2010-09-12 08:22:30 -07:00
|
|
|
return;
|
2013-02-27 11:23:08 -08:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
|
|
|
|
if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
|
|
|
|
if ((*aBuffer != 1) && (*aBuffer != 2)) {
|
2010-09-12 08:22:27 -07:00
|
|
|
PostDataError();
|
2010-09-12 08:22:30 -07:00
|
|
|
return;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
mIsCursor = (*aBuffer == 2);
|
|
|
|
}
|
|
|
|
mPos++; aBuffer++; aCount--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mPos == ICONCOUNTOFFSET && aCount >= 2) {
|
2014-11-14 09:59:00 -08:00
|
|
|
mNumIcons =
|
|
|
|
LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aBuffer));
|
2007-03-22 10:30:00 -07:00
|
|
|
aBuffer += 2;
|
|
|
|
mPos += 2;
|
|
|
|
aCount -= 2;
|
|
|
|
}
|
|
|
|
|
2014-11-14 09:59:00 -08:00
|
|
|
if (mNumIcons == 0) {
|
2010-09-12 08:22:30 -07:00
|
|
|
return; // Nothing to do.
|
2014-11-14 09:59:00 -08:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
uint16_t colorDepth = 0;
|
2015-01-15 15:11:35 -08:00
|
|
|
nsIntSize prefSize = mImage->GetRequestedResolution();
|
2013-03-22 16:12:40 -07:00
|
|
|
if (prefSize.width == 0 && prefSize.height == 0) {
|
2013-04-02 00:18:09 -07:00
|
|
|
prefSize.SizeTo(PREFICONSIZE, PREFICONSIZE);
|
2013-03-22 16:12:40 -07:00
|
|
|
}
|
|
|
|
|
2013-04-02 00:18:09 -07:00
|
|
|
// A measure of the difference in size between the entry we've found
|
|
|
|
// and the requested size. We will choose the smallest image that is
|
|
|
|
// >= requested size (i.e. we assume it's better to downscale a larger
|
|
|
|
// icon than to upscale a smaller one).
|
|
|
|
int32_t diff = INT_MIN;
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// Loop through each entry's dir entry
|
2014-11-14 09:59:00 -08:00
|
|
|
while (mCurrIcon < mNumIcons) {
|
|
|
|
if (mPos >= DIRENTRYOFFSET + (mCurrIcon * sizeof(mDirEntryArray)) &&
|
2011-08-25 13:09:01 -07:00
|
|
|
mPos < DIRENTRYOFFSET + ((mCurrIcon + 1) * sizeof(mDirEntryArray))) {
|
2014-11-14 09:59:00 -08:00
|
|
|
uint32_t toCopy = sizeof(mDirEntryArray) -
|
|
|
|
(mPos - DIRENTRYOFFSET - mCurrIcon *
|
|
|
|
sizeof(mDirEntryArray));
|
2011-08-25 13:09:01 -07:00
|
|
|
if (toCopy > aCount) {
|
2007-03-22 10:30:00 -07:00
|
|
|
toCopy = aCount;
|
2011-08-25 13:09:01 -07:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
memcpy(mDirEntryArray + sizeof(mDirEntryArray) - toCopy, aBuffer, toCopy);
|
|
|
|
mPos += toCopy;
|
|
|
|
aCount -= toCopy;
|
|
|
|
aBuffer += toCopy;
|
|
|
|
}
|
2014-11-14 09:59:00 -08:00
|
|
|
if (aCount == 0) {
|
2010-09-12 08:22:30 -07:00
|
|
|
return; // Need more data
|
2014-11-14 09:59:00 -08:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
IconDirEntry e;
|
2014-11-14 09:59:00 -08:00
|
|
|
if (mPos == (DIRENTRYOFFSET + ICODIRENTRYSIZE) +
|
2011-08-25 13:09:01 -07:00
|
|
|
(mCurrIcon * sizeof(mDirEntryArray))) {
|
2007-03-22 10:30:00 -07:00
|
|
|
mCurrIcon++;
|
|
|
|
ProcessDirEntry(e);
|
2011-09-22 06:43:13 -07:00
|
|
|
// We can't use GetRealWidth and GetRealHeight here because those operate
|
2013-04-02 00:18:09 -07:00
|
|
|
// on mDirEntry, here we are going through each item in the directory.
|
|
|
|
// Calculate the delta between this image's size and the desired size,
|
|
|
|
// so we can see if it is better than our current-best option.
|
|
|
|
// In the case of several equally-good images, we use the last one.
|
|
|
|
int32_t delta = (e.mWidth == 0 ? 256 : e.mWidth) - prefSize.width +
|
|
|
|
(e.mHeight == 0 ? 256 : e.mHeight) - prefSize.height;
|
|
|
|
if (e.mBitCount >= colorDepth &&
|
|
|
|
((diff < 0 && delta >= diff) || (delta >= 0 && delta <= diff))) {
|
2013-03-22 16:12:40 -07:00
|
|
|
diff = delta;
|
2007-03-22 10:30:00 -07:00
|
|
|
mImageOffset = e.mImageOffset;
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// ensure mImageOffset is >= size of the direntry headers (bug #245631)
|
2014-11-14 09:59:00 -08:00
|
|
|
uint32_t minImageOffset = DIRENTRYOFFSET +
|
2011-08-25 13:09:01 -07:00
|
|
|
mNumIcons * sizeof(mDirEntryArray);
|
2009-10-15 19:54:44 -07:00
|
|
|
if (mImageOffset < minImageOffset) {
|
2010-09-12 08:22:27 -07:00
|
|
|
PostDataError();
|
2010-09-12 08:22:30 -07:00
|
|
|
return;
|
2009-10-15 19:54:44 -07:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
colorDepth = e.mBitCount;
|
|
|
|
memcpy(&mDirEntry, &e, sizeof(IconDirEntry));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-19 15:40:04 -08:00
|
|
|
if (mPos < mImageOffset) {
|
|
|
|
// Skip to (or at least towards) the desired image offset
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t toSkip = mImageOffset - mPos;
|
2014-11-14 09:59:00 -08:00
|
|
|
if (toSkip > aCount) {
|
2008-02-19 15:40:04 -08:00
|
|
|
toSkip = aCount;
|
2014-11-14 09:59:00 -08:00
|
|
|
}
|
2008-02-19 15:40:04 -08:00
|
|
|
|
|
|
|
mPos += toSkip;
|
|
|
|
aBuffer += toSkip;
|
|
|
|
aCount -= toSkip;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// 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.
|
2014-11-14 09:59:00 -08:00
|
|
|
if (mCurrIcon == mNumIcons && mPos >= mImageOffset &&
|
|
|
|
mPos < mImageOffset + PNGSIGNATURESIZE) {
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t toCopy = PNGSIGNATURESIZE - (mPos - mImageOffset);
|
2011-08-25 13:09:01 -07:00
|
|
|
if (toCopy > aCount) {
|
|
|
|
toCopy = aCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(mSignature + (mPos - mImageOffset), aBuffer, toCopy);
|
|
|
|
mPos += toCopy;
|
|
|
|
aCount -= toCopy;
|
|
|
|
aBuffer += toCopy;
|
|
|
|
|
2014-11-14 09:59:00 -08:00
|
|
|
mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
|
2011-08-25 13:09:01 -07:00
|
|
|
PNGSIGNATURESIZE);
|
|
|
|
if (mIsPNG) {
|
2013-01-18 13:47:18 -08:00
|
|
|
mContainedDecoder = new nsPNGDecoder(mImage);
|
2013-01-31 10:38:24 -08:00
|
|
|
mContainedDecoder->SetSizeDecode(IsSizeDecode());
|
2015-01-11 22:29:32 -08:00
|
|
|
mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
|
2015-01-11 11:43:32 -08:00
|
|
|
mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
|
|
|
|
mColormap, mColormapSize,
|
|
|
|
Move(mRefForContainedDecoder));
|
2015-01-08 00:04:31 -08:00
|
|
|
if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
|
2011-08-25 13:09:01 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have a PNG, let the PNG decoder do all of the rest of the work
|
|
|
|
if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
|
2015-01-08 00:04:31 -08:00
|
|
|
if (!WriteToContainedDecoder(aBuffer, aCount)) {
|
2011-08-25 13:09:01 -07:00
|
|
|
return;
|
|
|
|
}
|
2013-02-27 11:23:08 -08:00
|
|
|
|
2013-04-25 13:34:30 -07:00
|
|
|
if (!HasSize() && mContainedDecoder->HasSize()) {
|
2013-02-27 11:23:08 -08:00
|
|
|
PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
|
|
|
|
mContainedDecoder->GetImageMetadata().GetHeight());
|
|
|
|
}
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
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
|
2013-01-31 10:38:24 -08:00
|
|
|
if (!IsSizeDecode() &&
|
|
|
|
!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
|
2011-08-25 13:09:01 -07:00
|
|
|
PostDataError();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-11-14 09:59:00 -08:00
|
|
|
// We've processed all of the icon dir entries and are within the
|
2011-08-25 13:09:01 -07:00
|
|
|
// bitmap info size
|
2014-11-14 09:59:00 -08:00
|
|
|
if (!mIsPNG && mCurrIcon == mNumIcons && mPos >= mImageOffset &&
|
|
|
|
mPos >= mImageOffset + PNGSIGNATURESIZE &&
|
2011-08-25 13:09:01 -07:00
|
|
|
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);
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
// We've found the icon.
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t toCopy = sizeof(mBIHraw) - (mPos - mImageOffset);
|
2014-11-14 09:59:00 -08:00
|
|
|
if (toCopy > aCount) {
|
2007-03-22 10:30:00 -07:00
|
|
|
toCopy = aCount;
|
2014-11-14 09:59:00 -08:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
memcpy(mBIHraw + (mPos - mImageOffset), aBuffer, toCopy);
|
|
|
|
mPos += toCopy;
|
|
|
|
aCount -= toCopy;
|
|
|
|
aBuffer += toCopy;
|
|
|
|
}
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// If we have a BMP inside the ICO and we have read the BIH header
|
|
|
|
if (!mIsPNG && mPos == mImageOffset + BITMAPINFOSIZE) {
|
2011-08-29 22:12:59 -07:00
|
|
|
|
|
|
|
// Make sure we have a sane value for the bitmap information header
|
2014-11-14 09:59:00 -08:00
|
|
|
int32_t bihSize = ExtractBIHSizeFromBitmap(reinterpret_cast<int8_t*>
|
|
|
|
(mBIHraw));
|
2011-08-29 22:12:59 -07:00
|
|
|
if (bihSize != BITMAPINFOSIZE) {
|
|
|
|
PostDataError();
|
|
|
|
return;
|
|
|
|
}
|
2014-11-14 09:59:00 -08:00
|
|
|
// We are extracting the BPP from the BIH header as it should be trusted
|
2011-08-25 13:09:01 -07:00
|
|
|
// over the one we have from the icon header
|
2012-08-22 08:56:38 -07:00
|
|
|
mBPP = ExtractBPPFromBitmap(reinterpret_cast<int8_t*>(mBIHraw));
|
2014-11-14 09:59:00 -08:00
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// 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
|
2014-11-14 09:59:00 -08:00
|
|
|
nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
|
2011-08-25 13:09:01 -07:00
|
|
|
mContainedDecoder = bmpDecoder;
|
2011-10-17 07:59:28 -07:00
|
|
|
bmpDecoder->SetUseAlphaData(true);
|
2011-08-25 13:09:01 -07:00
|
|
|
mContainedDecoder->SetSizeDecode(IsSizeDecode());
|
2015-01-11 22:29:32 -08:00
|
|
|
mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
|
2015-01-11 11:43:32 -08:00
|
|
|
mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
|
|
|
|
mColormap, mColormapSize,
|
|
|
|
Move(mRefForContainedDecoder));
|
2011-08-25 13:09:01 -07:00
|
|
|
|
|
|
|
// The ICO format when containing a BMP does not include the 14 byte
|
2014-11-14 09:59:00 -08:00
|
|
|
// bitmap file header. To use the code of the BMP decoder we need to
|
2011-08-25 13:09:01 -07:00
|
|
|
// generate this header ourselves and feed it to the BMP decoder.
|
2012-08-22 08:56:38 -07:00
|
|
|
int8_t bfhBuffer[BMPFILEHEADERSIZE];
|
2011-08-25 13:09:01 -07:00
|
|
|
if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
|
|
|
|
PostDataError();
|
|
|
|
return;
|
|
|
|
}
|
2015-01-08 00:04:31 -08:00
|
|
|
if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer))) {
|
2010-09-12 08:22:30 -07:00
|
|
|
return;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// Setup the cursor hot spot if one is present
|
|
|
|
SetHotSpotIfCursor();
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2011-11-04 06:56:17 -07:00
|
|
|
// Fix the ICO height from the BIH.
|
|
|
|
// Fix the height on the BIH to be /2 so our BMP decoder will understand.
|
2012-08-22 08:56:38 -07:00
|
|
|
if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
|
2011-11-04 06:56:17 -07:00
|
|
|
PostDataError();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix the ICO width from the BIH.
|
2012-08-22 08:56:38 -07:00
|
|
|
if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
|
2011-11-04 06:56:17 -07:00
|
|
|
PostDataError();
|
|
|
|
return;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// Write out the BMP's bitmap info header
|
2015-01-08 00:04:31 -08:00
|
|
|
if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
|
2011-08-25 13:09:01 -07:00
|
|
|
return;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2013-02-27 11:23:08 -08:00
|
|
|
PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
|
|
|
|
mContainedDecoder->GetImageMetadata().GetHeight());
|
|
|
|
|
2011-08-29 22:12:59 -07:00
|
|
|
// We have the size. If we're doing a size decode, we got what
|
|
|
|
// we came for.
|
2014-11-14 09:59:00 -08:00
|
|
|
if (IsSizeDecode()) {
|
2011-08-29 22:12:59 -07:00
|
|
|
return;
|
2014-11-14 09:59:00 -08:00
|
|
|
}
|
2011-08-29 22:12:59 -07:00
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// Sometimes the ICO BPP header field is not filled out
|
|
|
|
// so we should trust the contained resource over our own
|
|
|
|
// information.
|
|
|
|
mBPP = bmpDecoder->GetBitsPerPixel();
|
|
|
|
|
|
|
|
// Check to make sure we have valid color settings
|
2012-08-22 08:56:38 -07:00
|
|
|
uint16_t numColors = GetNumColors();
|
|
|
|
if (numColors == (uint16_t)-1) {
|
2011-08-25 13:09:01 -07:00
|
|
|
PostDataError();
|
2010-09-12 08:22:30 -07:00
|
|
|
return;
|
2009-10-15 19:54:44 -07:00
|
|
|
}
|
2011-08-25 13:09:01 -07:00
|
|
|
}
|
2009-10-15 19:54:44 -07:00
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// If we have a BMP
|
|
|
|
if (!mIsPNG && mContainedDecoder && mPos >= mImageOffset + BITMAPINFOSIZE) {
|
2012-08-22 08:56:38 -07:00
|
|
|
uint16_t numColors = GetNumColors();
|
|
|
|
if (numColors == (uint16_t)-1) {
|
2011-08-25 13:09:01 -07:00
|
|
|
PostDataError();
|
2010-09-12 08:22:30 -07:00
|
|
|
return;
|
|
|
|
}
|
2011-08-25 13:09:01 -07:00
|
|
|
// Feed the actual image data (not including headers) into the BMP decoder
|
2012-10-12 09:11:20 -07:00
|
|
|
uint32_t bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE;
|
2014-11-14 09:59:00 -08:00
|
|
|
uint32_t bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE +
|
|
|
|
static_cast<nsBMPDecoder*>(mContainedDecoder.get())->
|
|
|
|
GetCompressedImageSize() +
|
2012-10-12 09:11:20 -07:00
|
|
|
4 * numColors;
|
2011-08-25 13:09:01 -07:00
|
|
|
|
|
|
|
// 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
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t toFeed = bmpDataEnd - mPos;
|
2011-08-25 13:09:01 -07:00
|
|
|
if (toFeed > aCount) {
|
|
|
|
toFeed = aCount;
|
|
|
|
}
|
2007-07-17 14:08:46 -07:00
|
|
|
|
2015-01-08 00:04:31 -08:00
|
|
|
if (!WriteToContainedDecoder(aBuffer, toFeed)) {
|
2011-08-25 13:09:01 -07:00
|
|
|
return;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
mPos += toFeed;
|
|
|
|
aCount -= toFeed;
|
|
|
|
aBuffer += toFeed;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
2014-11-14 09:59:00 -08:00
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// 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
|
2014-11-14 09:59:00 -08:00
|
|
|
// only used if the alpha data is not already set. The alpha data
|
2011-08-25 13:09:01 -07:00
|
|
|
// is used for 32bpp bitmaps as per the comment in ICODecoder.h
|
|
|
|
// The alpha mask should be checked in all other cases.
|
2014-11-14 09:59:00 -08:00
|
|
|
if (static_cast<nsBMPDecoder*>(mContainedDecoder.get())->
|
|
|
|
GetBitsPerPixel() != 32 ||
|
|
|
|
!static_cast<nsBMPDecoder*>(mContainedDecoder.get())->
|
|
|
|
HasAlphaData()) {
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
|
2011-08-25 13:09:01 -07:00
|
|
|
if (mPos == bmpDataEnd) {
|
|
|
|
mPos++;
|
|
|
|
mRowBytes = 0;
|
2011-09-22 06:43:13 -07:00
|
|
|
mCurLine = GetRealHeight();
|
2012-08-22 08:56:38 -07:00
|
|
|
mRow = (uint8_t*)moz_realloc(mRow, rowSize);
|
2011-08-25 13:09:01 -07:00
|
|
|
if (!mRow) {
|
|
|
|
PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
// Ensure memory has been allocated before decoding.
|
2015-02-09 14:34:50 -08:00
|
|
|
MOZ_ASSERT(mRow, "mRow is null");
|
2011-09-29 06:17:13 -07:00
|
|
|
if (!mRow) {
|
2011-08-25 13:09:01 -07:00
|
|
|
PostDataError();
|
|
|
|
return;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2014-11-17 11:16:45 -08:00
|
|
|
uint8_t sawTransparency = 0;
|
|
|
|
|
2011-08-25 13:09:01 -07:00
|
|
|
while (mCurLine > 0 && aCount > 0) {
|
2013-01-15 04:22:03 -08:00
|
|
|
uint32_t toCopy = std::min(rowSize - mRowBytes, aCount);
|
2011-08-25 13:09:01 -07:00
|
|
|
if (toCopy) {
|
2007-03-22 10:30:00 -07:00
|
|
|
memcpy(mRow + mRowBytes, aBuffer, toCopy);
|
|
|
|
aCount -= toCopy;
|
|
|
|
aBuffer += toCopy;
|
|
|
|
mRowBytes += toCopy;
|
2011-08-25 13:09:01 -07:00
|
|
|
}
|
|
|
|
if (rowSize == mRowBytes) {
|
2007-03-22 10:30:00 -07:00
|
|
|
mCurLine--;
|
|
|
|
mRowBytes = 0;
|
|
|
|
|
2014-11-14 09:59:00 -08:00
|
|
|
uint32_t* imageData =
|
|
|
|
static_cast<nsBMPDecoder*>(mContainedDecoder.get())->
|
|
|
|
GetImageData();
|
2011-08-29 22:12:59 -07:00
|
|
|
if (!imageData) {
|
|
|
|
PostDataError();
|
|
|
|
return;
|
|
|
|
}
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t* decoded = imageData + mCurLine * GetRealWidth();
|
|
|
|
uint32_t* decoded_end = decoded + GetRealWidth();
|
2014-08-08 07:04:45 -07:00
|
|
|
uint8_t* p = mRow;
|
|
|
|
uint8_t* p_end = mRow + rowSize;
|
2011-08-25 13:09:01 -07:00
|
|
|
while (p < p_end) {
|
2012-08-22 08:56:38 -07:00
|
|
|
uint8_t idx = *p++;
|
2014-11-17 11:16:45 -08:00
|
|
|
sawTransparency |= idx;
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint8_t bit = 0x80; bit && decoded<decoded_end; bit >>= 1) {
|
2011-08-25 13:09:01 -07:00
|
|
|
// Clear pixel completely for transparency.
|
|
|
|
if (idx & bit) {
|
|
|
|
*decoded = 0;
|
|
|
|
}
|
|
|
|
decoded++;
|
|
|
|
}
|
|
|
|
}
|
2007-07-17 14:08:46 -07:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
2014-11-17 11:16:45 -08:00
|
|
|
|
|
|
|
// If any bits are set in sawTransparency, then we know at least one
|
|
|
|
// pixel was transparent.
|
|
|
|
if (sawTransparency) {
|
|
|
|
PostHasTransparency();
|
|
|
|
}
|
2007-07-17 14:08:46 -07:00
|
|
|
}
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
2012-05-08 05:38:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2015-01-08 00:04:31 -08:00
|
|
|
nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
|
2012-05-08 05:38:43 -07:00
|
|
|
{
|
2015-01-08 00:04:31 -08:00
|
|
|
mContainedDecoder->Write(aBuffer, aCount);
|
2014-11-18 01:48:49 -08:00
|
|
|
mProgress |= mContainedDecoder->TakeProgress();
|
2014-11-18 01:48:48 -08:00
|
|
|
mInvalidRect.Union(mContainedDecoder->TakeInvalidRect());
|
2012-05-08 05:38:43 -07:00
|
|
|
if (mContainedDecoder->HasDataError()) {
|
|
|
|
mDataError = mContainedDecoder->HasDataError();
|
|
|
|
}
|
|
|
|
if (mContainedDecoder->HasDecoderError()) {
|
|
|
|
PostDecoderError(mContainedDecoder->GetDecoderError());
|
|
|
|
}
|
|
|
|
return !HasError();
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsICODecoder::ProcessDirEntry(IconDirEntry& aTarget)
|
|
|
|
{
|
|
|
|
memset(&aTarget, 0, sizeof(aTarget));
|
|
|
|
memcpy(&aTarget.mWidth, mDirEntryArray, sizeof(aTarget.mWidth));
|
2011-08-25 13:09:01 -07:00
|
|
|
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));
|
2013-09-05 15:55:13 -07:00
|
|
|
aTarget.mPlanes = LittleEndian::readUint16(&aTarget.mPlanes);
|
2011-08-25 13:09:01 -07:00
|
|
|
memcpy(&aTarget.mBitCount, mDirEntryArray + 6, sizeof(aTarget.mBitCount));
|
2013-09-05 15:55:13 -07:00
|
|
|
aTarget.mBitCount = LittleEndian::readUint16(&aTarget.mBitCount);
|
2011-08-25 13:09:01 -07:00
|
|
|
memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes));
|
2013-09-05 15:55:13 -07:00
|
|
|
aTarget.mBytesInRes = LittleEndian::readUint32(&aTarget.mBytesInRes);
|
2014-11-14 09:59:00 -08:00
|
|
|
memcpy(&aTarget.mImageOffset, mDirEntryArray + 12,
|
2011-08-25 13:09:01 -07:00
|
|
|
sizeof(aTarget.mImageOffset));
|
2013-09-05 15:55:13 -07:00
|
|
|
aTarget.mImageOffset = LittleEndian::readUint32(&aTarget.mImageOffset);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2015-01-11 11:43:32 -08:00
|
|
|
bool
|
|
|
|
nsICODecoder::NeedsNewFrame() const
|
|
|
|
{
|
|
|
|
if (mContainedDecoder) {
|
|
|
|
return mContainedDecoder->NeedsNewFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Decoder::NeedsNewFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2015-01-18 14:02:14 -08:00
|
|
|
nsICODecoder::AllocateFrame(const nsIntSize& aTargetSize /* = nsIntSize() */)
|
2015-01-11 11:43:32 -08:00
|
|
|
{
|
|
|
|
nsresult rv;
|
|
|
|
|
|
|
|
if (mContainedDecoder) {
|
2015-01-18 14:02:14 -08:00
|
|
|
rv = mContainedDecoder->AllocateFrame(aTargetSize);
|
2015-01-11 11:43:32 -08:00
|
|
|
mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
|
|
|
|
mProgress |= mContainedDecoder->TakeProgress();
|
|
|
|
mInvalidRect.Union(mContainedDecoder->TakeInvalidRect());
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Grab a strong ref that we'll later hand over to the contained decoder. This
|
|
|
|
// lets us avoid creating a RawAccessFrameRef off-main-thread.
|
2015-01-18 14:02:14 -08:00
|
|
|
rv = Decoder::AllocateFrame(aTargetSize);
|
2015-01-11 11:43:32 -08:00
|
|
|
mRefForContainedDecoder = GetCurrentFrameRef();
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2012-01-06 08:02:27 -08:00
|
|
|
} // namespace image
|
2010-08-22 19:30:46 -07:00
|
|
|
} // namespace mozilla
|