michael@0: /* vim:set tw=80 expandtab softtabstop=4 ts=4 sw=4: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: /* I got the format description from http://www.daubnet.com/formats/BMP.html */ michael@0: michael@0: /* This is a Cross-Platform BMP Decoder, which should work everywhere, including michael@0: * Big-Endian machines like the PowerPC. */ michael@0: michael@0: #include michael@0: michael@0: #include "ImageLogging.h" michael@0: #include "mozilla/Endian.h" michael@0: #include "nsBMPDecoder.h" michael@0: michael@0: #include "nsIInputStream.h" michael@0: #include "RasterImage.h" michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace image { michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo * michael@0: GetBMPLog() michael@0: { michael@0: static PRLogModuleInfo *sBMPLog; michael@0: if (!sBMPLog) michael@0: sBMPLog = PR_NewLogModule("BMPDecoder"); michael@0: return sBMPLog; michael@0: } michael@0: #endif michael@0: michael@0: // Convert from row (1..height) to absolute line (0..height-1) michael@0: #define LINE(row) ((mBIH.height < 0) ? (-mBIH.height - (row)) : ((row) - 1)) michael@0: #define PIXEL_OFFSET(row, col) (LINE(row) * mBIH.width + col) michael@0: michael@0: nsBMPDecoder::nsBMPDecoder(RasterImage &aImage) michael@0: : Decoder(aImage) michael@0: { michael@0: mColors = nullptr; michael@0: mRow = nullptr; michael@0: mCurPos = mPos = mNumColors = mRowBytes = 0; michael@0: mOldLine = mCurLine = 1; // Otherwise decoder will never start michael@0: mState = eRLEStateInitial; michael@0: mStateData = 0; michael@0: mLOH = WIN_V3_HEADER_LENGTH; michael@0: mUseAlphaData = mHaveAlphaData = false; michael@0: } michael@0: michael@0: nsBMPDecoder::~nsBMPDecoder() michael@0: { michael@0: delete[] mColors; michael@0: if (mRow) { michael@0: moz_free(mRow); michael@0: } michael@0: } michael@0: michael@0: // Sets whether or not the BMP will use alpha data michael@0: void michael@0: nsBMPDecoder::SetUseAlphaData(bool useAlphaData) michael@0: { michael@0: mUseAlphaData = useAlphaData; michael@0: } michael@0: michael@0: // Obtains the bits per pixel from the internal BIH header michael@0: int32_t michael@0: nsBMPDecoder::GetBitsPerPixel() const michael@0: { michael@0: return mBIH.bpp; michael@0: } michael@0: michael@0: // Obtains the width from the internal BIH header michael@0: int32_t michael@0: nsBMPDecoder::GetWidth() const michael@0: { michael@0: return mBIH.width; michael@0: } michael@0: michael@0: // Obtains the abs-value of the height from the internal BIH header michael@0: int32_t michael@0: nsBMPDecoder::GetHeight() const michael@0: { michael@0: return abs(mBIH.height); michael@0: } michael@0: michael@0: // Obtains the internal output image buffer michael@0: uint32_t* michael@0: nsBMPDecoder::GetImageData() michael@0: { michael@0: return reinterpret_cast(mImageData); michael@0: } michael@0: michael@0: // Obtains the size of the compressed image resource michael@0: int32_t michael@0: nsBMPDecoder::GetCompressedImageSize() const michael@0: { michael@0: // For everything except BI_RGB the header field must be defined michael@0: if (mBIH.compression != BI_RGB) { michael@0: return mBIH.image_size; michael@0: } michael@0: michael@0: // mBIH.image_size isn't always filled for BI_RGB so calculate it manually michael@0: // The pixel array size is calculated based on extra 4 byte boundary padding michael@0: uint32_t rowSize = (mBIH.bpp * mBIH.width + 7) / 8; // + 7 to round up michael@0: // Pad to DWORD Boundary michael@0: if (rowSize % 4) { michael@0: rowSize += (4 - (rowSize % 4)); michael@0: } michael@0: michael@0: // The height should be the absolute value of what the height is in the BIH. michael@0: // If positive the bitmap is stored bottom to top, otherwise top to bottom michael@0: int32_t pixelArraySize = rowSize * GetHeight(); michael@0: return pixelArraySize; michael@0: } michael@0: michael@0: // Obtains whether or not a BMP file had alpha data in its 4th byte michael@0: // for 32BPP bitmaps. Only use after the bitmap has been processed. michael@0: bool michael@0: nsBMPDecoder::HasAlphaData() const michael@0: { michael@0: return mHaveAlphaData; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsBMPDecoder::FinishInternal() michael@0: { michael@0: // We shouldn't be called in error cases michael@0: NS_ABORT_IF_FALSE(!HasError(), "Can't call FinishInternal on error!"); michael@0: michael@0: // We should never make multiple frames michael@0: NS_ABORT_IF_FALSE(GetFrameCount() <= 1, "Multiple BMP frames?"); michael@0: michael@0: // Send notifications if appropriate michael@0: if (!IsSizeDecode() && HasSize()) { michael@0: michael@0: // Invalidate michael@0: nsIntRect r(0, 0, mBIH.width, GetHeight()); michael@0: PostInvalidation(r); michael@0: michael@0: if (mUseAlphaData) { michael@0: PostFrameStop(FrameBlender::kFrameHasAlpha); michael@0: } else { michael@0: PostFrameStop(FrameBlender::kFrameOpaque); michael@0: } michael@0: PostDecodeDone(); michael@0: } michael@0: } michael@0: michael@0: // ---------------------------------------- michael@0: // Actual Data Processing michael@0: // ---------------------------------------- michael@0: michael@0: static void calcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength) michael@0: { michael@0: // find the rightmost 1 michael@0: uint8_t pos; michael@0: bool started = false; michael@0: aBegin = aLength = 0; michael@0: for (pos = 0; pos <= 31; pos++) { michael@0: if (!started && (aMask & (1 << pos))) { michael@0: aBegin = pos; michael@0: started = true; michael@0: } michael@0: else if (started && !(aMask & (1 << pos))) { michael@0: aLength = pos - aBegin; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_METHOD nsBMPDecoder::CalcBitShift() michael@0: { michael@0: uint8_t begin, length; michael@0: // red michael@0: calcBitmask(mBitFields.red, begin, length); michael@0: mBitFields.redRightShift = begin; michael@0: mBitFields.redLeftShift = 8 - length; michael@0: // green michael@0: calcBitmask(mBitFields.green, begin, length); michael@0: mBitFields.greenRightShift = begin; michael@0: mBitFields.greenLeftShift = 8 - length; michael@0: // blue michael@0: calcBitmask(mBitFields.blue, begin, length); michael@0: mBitFields.blueRightShift = begin; michael@0: mBitFields.blueLeftShift = 8 - length; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy) michael@0: { michael@0: NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!"); michael@0: michael@0: // aCount=0 means EOF, mCurLine=0 means we're past end of image michael@0: if (!aCount || !mCurLine) michael@0: return; michael@0: michael@0: if (mPos < BFH_INTERNAL_LENGTH) { /* In BITMAPFILEHEADER */ michael@0: uint32_t toCopy = BFH_INTERNAL_LENGTH - mPos; michael@0: if (toCopy > aCount) michael@0: toCopy = aCount; michael@0: memcpy(mRawBuf + mPos, aBuffer, toCopy); michael@0: mPos += toCopy; michael@0: aCount -= toCopy; michael@0: aBuffer += toCopy; michael@0: } michael@0: if (mPos == BFH_INTERNAL_LENGTH) { michael@0: ProcessFileHeader(); michael@0: if (mBFH.signature[0] != 'B' || mBFH.signature[1] != 'M') { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: if (mBFH.bihsize == OS2_BIH_LENGTH) michael@0: mLOH = OS2_HEADER_LENGTH; michael@0: } michael@0: if (mPos >= BFH_INTERNAL_LENGTH && mPos < mLOH) { /* In BITMAPINFOHEADER */ michael@0: uint32_t toCopy = mLOH - mPos; michael@0: if (toCopy > aCount) michael@0: toCopy = aCount; michael@0: memcpy(mRawBuf + (mPos - BFH_INTERNAL_LENGTH), aBuffer, toCopy); michael@0: mPos += toCopy; michael@0: aCount -= toCopy; michael@0: aBuffer += toCopy; michael@0: } michael@0: michael@0: // HasSize is called to ensure that if at this point mPos == mLOH but michael@0: // we have no data left to process, the next time WriteInternal is called michael@0: // we won't enter this condition again. michael@0: if (mPos == mLOH && !HasSize()) { michael@0: ProcessInfoHeader(); michael@0: PR_LOG(GetBMPLog(), PR_LOG_DEBUG, ("BMP is %lix%lix%lu. compression=%lu\n", michael@0: mBIH.width, mBIH.height, mBIH.bpp, mBIH.compression)); michael@0: // Verify we support this bit depth michael@0: if (mBIH.bpp != 1 && mBIH.bpp != 4 && mBIH.bpp != 8 && michael@0: mBIH.bpp != 16 && mBIH.bpp != 24 && mBIH.bpp != 32) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: michael@0: // BMPs with negative width are invalid michael@0: // Reject extremely wide images to keep the math sane michael@0: const int32_t k64KWidth = 0x0000FFFF; michael@0: if (mBIH.width < 0 || mBIH.width > k64KWidth) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: michael@0: if (mBIH.height == INT_MIN) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: michael@0: uint32_t real_height = GetHeight(); michael@0: michael@0: // Post our size to the superclass michael@0: PostSize(mBIH.width, real_height); michael@0: if (HasError()) { michael@0: // Setting the size led to an error. michael@0: return; michael@0: } michael@0: michael@0: // We have the size. If we're doing a size decode, we got what michael@0: // we came for. michael@0: if (IsSizeDecode()) michael@0: return; michael@0: michael@0: // We're doing a real decode. michael@0: mOldLine = mCurLine = real_height; michael@0: michael@0: if (mBIH.bpp <= 8) { michael@0: mNumColors = 1 << mBIH.bpp; michael@0: if (mBIH.colors && mBIH.colors < mNumColors) michael@0: mNumColors = mBIH.colors; michael@0: michael@0: // Always allocate 256 even though mNumColors might be smaller michael@0: mColors = new colorTable[256]; michael@0: memset(mColors, 0, 256 * sizeof(colorTable)); michael@0: } michael@0: else if (mBIH.compression != BI_BITFIELDS && mBIH.bpp == 16) { michael@0: // Use default 5-5-5 format michael@0: mBitFields.red = 0x7C00; michael@0: mBitFields.green = 0x03E0; michael@0: mBitFields.blue = 0x001F; michael@0: CalcBitShift(); michael@0: } michael@0: michael@0: // Make sure we have a valid value for our supported compression modes michael@0: // before adding the frame michael@0: if (mBIH.compression != BI_RGB && mBIH.compression != BI_RLE8 && michael@0: mBIH.compression != BI_RLE4 && mBIH.compression != BI_BITFIELDS) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: michael@0: // If we have RLE4 or RLE8 or BI_ALPHABITFIELDS, then ensure we michael@0: // have valid BPP values before adding the frame michael@0: if (mBIH.compression == BI_RLE8 && mBIH.bpp != 8) { michael@0: PR_LOG(GetBMPLog(), PR_LOG_DEBUG, michael@0: ("BMP RLE8 compression only supports 8 bits per pixel\n")); michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: if (mBIH.compression == BI_RLE4 && mBIH.bpp != 4 && mBIH.bpp != 1) { michael@0: PR_LOG(GetBMPLog(), PR_LOG_DEBUG, michael@0: ("BMP RLE4 compression only supports 4 bits per pixel\n")); michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: if (mBIH.compression == BI_ALPHABITFIELDS && michael@0: mBIH.bpp != 16 && mBIH.bpp != 32) { michael@0: PR_LOG(GetBMPLog(), PR_LOG_DEBUG, michael@0: ("BMP ALPHABITFIELDS only supports 16 or 32 bits per pixel\n")); michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: michael@0: if (mBIH.compression != BI_RLE8 && mBIH.compression != BI_RLE4 && michael@0: mBIH.compression != BI_ALPHABITFIELDS) { michael@0: // mRow is not used for RLE encoded images michael@0: mRow = (uint8_t*)moz_malloc((mBIH.width * mBIH.bpp) / 8 + 4); michael@0: // + 4 because the line is padded to a 4 bit boundary, but I don't want michael@0: // to make exact calculations here, that's unnecessary. michael@0: // Also, it compensates rounding error. michael@0: if (!mRow) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: } michael@0: if (!mImageData) { michael@0: PostDecoderError(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: // Prepare for transparency michael@0: if ((mBIH.compression == BI_RLE8) || (mBIH.compression == BI_RLE4)) { michael@0: // Clear the image, as the RLE may jump over areas michael@0: memset(mImageData, 0, mImageDataLength); michael@0: } michael@0: } michael@0: michael@0: if (mColors && mPos >= mLOH) { michael@0: // OS/2 Bitmaps have no padding byte michael@0: uint8_t bytesPerColor = (mBFH.bihsize == OS2_BIH_LENGTH) ? 3 : 4; michael@0: if (mPos < (mLOH + mNumColors * bytesPerColor)) { michael@0: // Number of bytes already received michael@0: uint32_t colorBytes = mPos - mLOH; michael@0: // Color which is currently received michael@0: uint8_t colorNum = colorBytes / bytesPerColor; michael@0: uint8_t at = colorBytes % bytesPerColor; michael@0: while (aCount && (mPos < (mLOH + mNumColors * bytesPerColor))) { michael@0: switch (at) { michael@0: case 0: michael@0: mColors[colorNum].blue = *aBuffer; michael@0: break; michael@0: case 1: michael@0: mColors[colorNum].green = *aBuffer; michael@0: break; michael@0: case 2: michael@0: mColors[colorNum].red = *aBuffer; michael@0: // If there is no padding byte, increment the color index michael@0: // since we're done with the current color. michael@0: if (bytesPerColor == 3) michael@0: colorNum++; michael@0: break; michael@0: case 3: michael@0: // This is a padding byte only in Windows BMPs. Increment michael@0: // the color index since we're done with the current color. michael@0: colorNum++; michael@0: break; michael@0: } michael@0: mPos++; aBuffer++; aCount--; michael@0: at = (at + 1) % bytesPerColor; michael@0: } michael@0: } michael@0: } michael@0: else if (aCount && mBIH.compression == BI_BITFIELDS && mPos < (WIN_V3_HEADER_LENGTH + BITFIELD_LENGTH)) { michael@0: // If compression is used, this is a windows bitmap, hence we can michael@0: // use WIN_HEADER_LENGTH instead of mLOH michael@0: uint32_t toCopy = (WIN_V3_HEADER_LENGTH + BITFIELD_LENGTH) - mPos; michael@0: if (toCopy > aCount) michael@0: toCopy = aCount; michael@0: memcpy(mRawBuf + (mPos - WIN_V3_HEADER_LENGTH), aBuffer, toCopy); michael@0: mPos += toCopy; michael@0: aBuffer += toCopy; michael@0: aCount -= toCopy; michael@0: } michael@0: if (mPos == WIN_V3_HEADER_LENGTH + BITFIELD_LENGTH && michael@0: mBIH.compression == BI_BITFIELDS) { michael@0: mBitFields.red = LittleEndian::readUint32(reinterpret_cast(mRawBuf)); michael@0: mBitFields.green = LittleEndian::readUint32(reinterpret_cast(mRawBuf + 4)); michael@0: mBitFields.blue = LittleEndian::readUint32(reinterpret_cast(mRawBuf + 8)); michael@0: CalcBitShift(); michael@0: } michael@0: while (aCount && (mPos < mBFH.dataoffset)) { // Skip whatever is between header and data michael@0: mPos++; aBuffer++; aCount--; michael@0: } michael@0: if (aCount && ++mPos >= mBFH.dataoffset) { michael@0: // Need to increment mPos, else we might get to mPos==mLOH again michael@0: // From now on, mPos is irrelevant michael@0: if (!mBIH.compression || mBIH.compression == BI_BITFIELDS) { michael@0: uint32_t rowSize = (mBIH.bpp * mBIH.width + 7) / 8; // + 7 to round up michael@0: if (rowSize % 4) { michael@0: rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary michael@0: } michael@0: uint32_t toCopy; michael@0: do { michael@0: toCopy = rowSize - mRowBytes; michael@0: if (toCopy) { michael@0: if (toCopy > aCount) michael@0: toCopy = aCount; michael@0: memcpy(mRow + mRowBytes, aBuffer, toCopy); michael@0: aCount -= toCopy; michael@0: aBuffer += toCopy; michael@0: mRowBytes += toCopy; michael@0: } michael@0: if (rowSize == mRowBytes) { michael@0: // Collected a whole row into mRow, process it michael@0: uint8_t* p = mRow; michael@0: uint32_t* d = reinterpret_cast(mImageData) + PIXEL_OFFSET(mCurLine, 0); michael@0: uint32_t lpos = mBIH.width; michael@0: switch (mBIH.bpp) { michael@0: case 1: michael@0: while (lpos > 0) { michael@0: int8_t bit; michael@0: uint8_t idx; michael@0: for (bit = 7; bit >= 0 && lpos > 0; bit--) { michael@0: idx = (*p >> bit) & 1; michael@0: SetPixel(d, idx, mColors); michael@0: --lpos; michael@0: } michael@0: ++p; michael@0: } michael@0: break; michael@0: case 4: michael@0: while (lpos > 0) { michael@0: Set4BitPixel(d, *p, lpos, mColors); michael@0: ++p; michael@0: } michael@0: break; michael@0: case 8: michael@0: while (lpos > 0) { michael@0: SetPixel(d, *p, mColors); michael@0: --lpos; michael@0: ++p; michael@0: } michael@0: break; michael@0: case 16: michael@0: while (lpos > 0) { michael@0: uint16_t val = LittleEndian::readUint16(reinterpret_cast(p)); michael@0: SetPixel(d, michael@0: (val & mBitFields.red) >> mBitFields.redRightShift << mBitFields.redLeftShift, michael@0: (val & mBitFields.green) >> mBitFields.greenRightShift << mBitFields.greenLeftShift, michael@0: (val & mBitFields.blue) >> mBitFields.blueRightShift << mBitFields.blueLeftShift); michael@0: --lpos; michael@0: p+=2; michael@0: } michael@0: break; michael@0: case 24: michael@0: while (lpos > 0) { michael@0: SetPixel(d, p[2], p[1], p[0]); michael@0: p += 2; michael@0: --lpos; michael@0: ++p; michael@0: } michael@0: break; michael@0: case 32: michael@0: while (lpos > 0) { michael@0: if (mUseAlphaData) { michael@0: if (!mHaveAlphaData && p[3]) { michael@0: // Non-zero alpha byte detected! Clear previous michael@0: // pixels that we have already processed. michael@0: // This works because we know that if we michael@0: // are reaching here then the alpha data in byte michael@0: // 4 has been right all along. And we know it michael@0: // has been set to 0 the whole time, so that michael@0: // means that everything is transparent so far. michael@0: uint32_t* start = reinterpret_cast(mImageData) + GetWidth() * (mCurLine - 1); michael@0: uint32_t heightDifference = GetHeight() - mCurLine + 1; michael@0: uint32_t pixelCount = GetWidth() * heightDifference; michael@0: michael@0: memset(start, 0, pixelCount * sizeof(uint32_t)); michael@0: michael@0: mHaveAlphaData = true; michael@0: } michael@0: SetPixel(d, p[2], p[1], p[0], mHaveAlphaData ? p[3] : 0xFF); michael@0: } else { michael@0: SetPixel(d, p[2], p[1], p[0]); michael@0: } michael@0: p += 4; michael@0: --lpos; michael@0: } michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("Unsupported color depth, but earlier check didn't catch it"); michael@0: } michael@0: mCurLine --; michael@0: if (mCurLine == 0) { // Finished last line michael@0: break; michael@0: } michael@0: mRowBytes = 0; michael@0: michael@0: } michael@0: } while (aCount > 0); michael@0: } michael@0: else if ((mBIH.compression == BI_RLE8) || (mBIH.compression == BI_RLE4)) { michael@0: if (((mBIH.compression == BI_RLE8) && (mBIH.bpp != 8)) || michael@0: ((mBIH.compression == BI_RLE4) && (mBIH.bpp != 4) && (mBIH.bpp != 1))) { michael@0: PR_LOG(GetBMPLog(), PR_LOG_DEBUG, ("BMP RLE8/RLE4 compression only supports 8/4 bits per pixel\n")); michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: michael@0: while (aCount > 0) { michael@0: uint8_t byte; michael@0: michael@0: switch(mState) { michael@0: case eRLEStateInitial: michael@0: mStateData = (uint8_t)*aBuffer++; michael@0: aCount--; michael@0: michael@0: mState = eRLEStateNeedSecondEscapeByte; michael@0: continue; michael@0: michael@0: case eRLEStateNeedSecondEscapeByte: michael@0: byte = *aBuffer++; michael@0: aCount--; michael@0: if (mStateData != RLE_ESCAPE) { // encoded mode michael@0: // Encoded mode consists of two bytes: michael@0: // the first byte (mStateData) specifies the michael@0: // number of consecutive pixels to be drawn michael@0: // using the color index contained in michael@0: // the second byte michael@0: // Work around bitmaps that specify too many pixels michael@0: mState = eRLEStateInitial; michael@0: uint32_t pixelsNeeded = std::min(mBIH.width - mCurPos, mStateData); michael@0: if (pixelsNeeded) { michael@0: uint32_t* d = reinterpret_cast(mImageData) + PIXEL_OFFSET(mCurLine, mCurPos); michael@0: mCurPos += pixelsNeeded; michael@0: if (mBIH.compression == BI_RLE8) { michael@0: do { michael@0: SetPixel(d, byte, mColors); michael@0: pixelsNeeded --; michael@0: } while (pixelsNeeded); michael@0: } else { michael@0: do { michael@0: Set4BitPixel(d, byte, pixelsNeeded, mColors); michael@0: } while (pixelsNeeded); michael@0: } michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: switch(byte) { michael@0: case RLE_ESCAPE_EOL: michael@0: // End of Line: Go to next row michael@0: mCurLine --; michael@0: mCurPos = 0; michael@0: mState = eRLEStateInitial; michael@0: break; michael@0: michael@0: case RLE_ESCAPE_EOF: // EndOfFile michael@0: mCurPos = mCurLine = 0; michael@0: break; michael@0: michael@0: case RLE_ESCAPE_DELTA: michael@0: mState = eRLEStateNeedXDelta; michael@0: continue; michael@0: michael@0: default : // absolute mode michael@0: // Save the number of pixels to read michael@0: mStateData = byte; michael@0: if (mCurPos + mStateData > (uint32_t)mBIH.width) { michael@0: // We can work around bitmaps that specify one michael@0: // pixel too many, but only if their width is odd. michael@0: mStateData -= mBIH.width & 1; michael@0: if (mCurPos + mStateData > (uint32_t)mBIH.width) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // See if we will need to skip a byte michael@0: // to word align the pixel data michael@0: // mStateData is a number of pixels michael@0: // so allow for the RLE compression type michael@0: // Pixels RLE8=1 RLE4=2 michael@0: // 1 Pad Pad michael@0: // 2 No Pad michael@0: // 3 Pad No michael@0: // 4 No No michael@0: if (((mStateData - 1) & mBIH.compression) != 0) michael@0: mState = eRLEStateAbsoluteMode; michael@0: else michael@0: mState = eRLEStateAbsoluteModePadded; michael@0: continue; michael@0: } michael@0: break; michael@0: michael@0: case eRLEStateNeedXDelta: michael@0: // Handle the XDelta and proceed to get Y Delta michael@0: byte = *aBuffer++; michael@0: aCount--; michael@0: mCurPos += byte; michael@0: if (mCurPos > mBIH.width) michael@0: mCurPos = mBIH.width; michael@0: michael@0: mState = eRLEStateNeedYDelta; michael@0: continue; michael@0: michael@0: case eRLEStateNeedYDelta: michael@0: // Get the Y Delta and then "handle" the move michael@0: byte = *aBuffer++; michael@0: aCount--; michael@0: mState = eRLEStateInitial; michael@0: mCurLine -= std::min(byte, mCurLine); michael@0: break; michael@0: michael@0: case eRLEStateAbsoluteMode: // Absolute Mode michael@0: case eRLEStateAbsoluteModePadded: michael@0: if (mStateData) { michael@0: // In absolute mode, the second byte (mStateData) michael@0: // represents the number of pixels michael@0: // that follow, each of which contains michael@0: // the color index of a single pixel. michael@0: uint32_t* d = reinterpret_cast(mImageData) + PIXEL_OFFSET(mCurLine, mCurPos); michael@0: uint32_t* oldPos = d; michael@0: if (mBIH.compression == BI_RLE8) { michael@0: while (aCount > 0 && mStateData > 0) { michael@0: byte = *aBuffer++; michael@0: aCount--; michael@0: SetPixel(d, byte, mColors); michael@0: mStateData--; michael@0: } michael@0: } else { michael@0: while (aCount > 0 && mStateData > 0) { michael@0: byte = *aBuffer++; michael@0: aCount--; michael@0: Set4BitPixel(d, byte, mStateData, mColors); michael@0: } michael@0: } michael@0: mCurPos += d - oldPos; michael@0: } michael@0: michael@0: if (mStateData == 0) { michael@0: // In absolute mode, each run must michael@0: // be aligned on a word boundary michael@0: michael@0: if (mState == eRLEStateAbsoluteMode) { // Word Aligned michael@0: mState = eRLEStateInitial; michael@0: } else if (aCount > 0) { // Not word Aligned michael@0: // "next" byte is just a padding byte michael@0: // so "move" past it and we can continue michael@0: aBuffer++; michael@0: aCount--; michael@0: mState = eRLEStateInitial; michael@0: } michael@0: } michael@0: // else state is still eRLEStateAbsoluteMode michael@0: continue; michael@0: michael@0: default : michael@0: NS_ABORT_IF_FALSE(0, "BMP RLE decompression: unknown state!"); michael@0: PostDecoderError(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: // Because of the use of the continue statement michael@0: // we only get here for eol, eof or y delta michael@0: if (mCurLine == 0) { // Finished last line michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: const uint32_t rows = mOldLine - mCurLine; michael@0: if (rows) { michael@0: michael@0: // Invalidate michael@0: nsIntRect r(0, mBIH.height < 0 ? -mBIH.height - mOldLine : mCurLine, michael@0: mBIH.width, rows); michael@0: PostInvalidation(r); michael@0: michael@0: mOldLine = mCurLine; michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: void nsBMPDecoder::ProcessFileHeader() michael@0: { michael@0: memset(&mBFH, 0, sizeof(mBFH)); michael@0: memcpy(&mBFH.signature, mRawBuf, sizeof(mBFH.signature)); michael@0: memcpy(&mBFH.filesize, mRawBuf + 2, sizeof(mBFH.filesize)); michael@0: memcpy(&mBFH.reserved, mRawBuf + 6, sizeof(mBFH.reserved)); michael@0: memcpy(&mBFH.dataoffset, mRawBuf + 10, sizeof(mBFH.dataoffset)); michael@0: memcpy(&mBFH.bihsize, mRawBuf + 14, sizeof(mBFH.bihsize)); michael@0: michael@0: // Now correct the endianness of the header michael@0: mBFH.filesize = LittleEndian::readUint32(&mBFH.filesize); michael@0: mBFH.dataoffset = LittleEndian::readUint32(&mBFH.dataoffset); michael@0: mBFH.bihsize = LittleEndian::readUint32(&mBFH.bihsize); michael@0: } michael@0: michael@0: void nsBMPDecoder::ProcessInfoHeader() michael@0: { michael@0: memset(&mBIH, 0, sizeof(mBIH)); michael@0: if (mBFH.bihsize == 12) { // OS/2 Bitmap michael@0: memcpy(&mBIH.width, mRawBuf, 2); michael@0: memcpy(&mBIH.height, mRawBuf + 2, 2); michael@0: memcpy(&mBIH.planes, mRawBuf + 4, sizeof(mBIH.planes)); michael@0: memcpy(&mBIH.bpp, mRawBuf + 6, sizeof(mBIH.bpp)); michael@0: } michael@0: else { michael@0: memcpy(&mBIH.width, mRawBuf, sizeof(mBIH.width)); michael@0: memcpy(&mBIH.height, mRawBuf + 4, sizeof(mBIH.height)); michael@0: memcpy(&mBIH.planes, mRawBuf + 8, sizeof(mBIH.planes)); michael@0: memcpy(&mBIH.bpp, mRawBuf + 10, sizeof(mBIH.bpp)); michael@0: memcpy(&mBIH.compression, mRawBuf + 12, sizeof(mBIH.compression)); michael@0: memcpy(&mBIH.image_size, mRawBuf + 16, sizeof(mBIH.image_size)); michael@0: memcpy(&mBIH.xppm, mRawBuf + 20, sizeof(mBIH.xppm)); michael@0: memcpy(&mBIH.yppm, mRawBuf + 24, sizeof(mBIH.yppm)); michael@0: memcpy(&mBIH.colors, mRawBuf + 28, sizeof(mBIH.colors)); michael@0: memcpy(&mBIH.important_colors, mRawBuf + 32, sizeof(mBIH.important_colors)); michael@0: } michael@0: michael@0: // Convert endianness michael@0: mBIH.width = LittleEndian::readUint32(&mBIH.width); michael@0: mBIH.height = LittleEndian::readUint32(&mBIH.height); michael@0: mBIH.planes = LittleEndian::readUint16(&mBIH.planes); michael@0: mBIH.bpp = LittleEndian::readUint16(&mBIH.bpp); michael@0: michael@0: mBIH.compression = LittleEndian::readUint32(&mBIH.compression); michael@0: mBIH.image_size = LittleEndian::readUint32(&mBIH.image_size); michael@0: mBIH.xppm = LittleEndian::readUint32(&mBIH.xppm); michael@0: mBIH.yppm = LittleEndian::readUint32(&mBIH.yppm); michael@0: mBIH.colors = LittleEndian::readUint32(&mBIH.colors); michael@0: mBIH.important_colors = LittleEndian::readUint32(&mBIH.important_colors); michael@0: } michael@0: michael@0: } // namespace image michael@0: } // namespace mozilla