/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ // This is a cross-platform BMP Decoder, which should work everywhere, // including big-endian machines like the PowerPC. // // BMP is a format that has been extended multiple times. To understand the // decoder you need to understand this history. The summary of the history // below was determined from the following documents. // // - http://www.fileformat.info/format/bmp/egff.htm // - http://www.fileformat.info/format/os2bmp/egff.htm // - http://fileformats.archiveteam.org/wiki/BMP // - http://fileformats.archiveteam.org/wiki/OS/2_BMP // - https://en.wikipedia.org/wiki/BMP_file_format // - https://upload.wikimedia.org/wikipedia/commons/c/c4/BMPfileFormat.png // // WINDOWS VERSIONS OF THE BMP FORMAT // ---------------------------------- // WinBMPv1. // - This version is no longer used and can be ignored. // // WinBMPv2. // - First is a 14 byte file header that includes: the magic number ("BM"), // file size, and offset to the pixel data (|mDataOffset|). // - Next is a 12 byte info header which includes: the info header size // (mBIHSize), width, height, number of color planes, and bits-per-pixel // (|mBpp|) which must be 1, 4, 8 or 24. // - Next is the semi-optional color table, which has length 2^|mBpp| and has 3 // bytes per value (BGR). The color table is required if |mBpp| is 1, 4, or 8. // - Next is an optional gap. // - Next is the pixel data, which is pointed to by |mDataOffset|. // // WinBMPv3. This is the most widely used version. // - It changed the info header to 40 bytes by taking the WinBMPv2 info // header, enlargening its width and height fields, and adding more fields // including: a compression type (|mCompression|) and number of colors // (|mNumColors|). // - The semi-optional color table is now 4 bytes per value (BGR0), and its // length is |mNumColors|, or 2^|mBpp| if |mNumColors| is zero. // - |mCompression| can be RGB (i.e. no compression), RLE4 (if |mBpp|==4) or // RLE8 (if |mBpp|==8) values. // // WinBMPv3-NT. A variant of WinBMPv3. // - It did not change the info header layout from WinBMPv3. // - |mBpp| can now be 16 or 32, in which case |mCompression| can be RGB or the // new BITFIELDS value; in the latter case an additional 12 bytes of color // bitfields follow the info header. // // WinBMPv4. // - It extended the info header to 108 bytes, including the 12 bytes of color // mask data from WinBMPv3-NT, plus alpha mask data, and also color-space and // gamma correction fields. // // WinBMPv5. // - It extended the info header to 124 bytes, adding color profile data. // - It also added an optional color profile table after the pixel data (and // another optional gap). // // WinBMPv3-ICO. This is a variant of WinBMPv3. // - It's the BMP format used for BMP images within ICO files. // - The only difference with WinBMPv3 is that if an image is 32bpp and has no // compression, then instead of treating the pixel data as 0RGB it is treated // as ARGB, but only if one or more of the A values are non-zero. // // Clipboard variants. // - It's the BMP format used for BMP images captured from the clipboard. // - It is missing the file header, containing the BM signature and the data // offset. Instead the data begins after the header. // - If it uses BITFIELDS compression, then there is always an additional 12 // bytes of data after the header that must be read. In WinBMPv4+, the masks // are supposed to be included in the header size, which are the values we use // for decoding purposes, but there is additional three masks following the // header which must be skipped to get to the pixel data. // // OS/2 VERSIONS OF THE BMP FORMAT // ------------------------------- // OS2-BMPv1. // - Almost identical to WinBMPv2; the differences are basically ignorable. // // OS2-BMPv2. // - Similar to WinBMPv3. // - The info header is 64 bytes but can be reduced to as little as 16; any // omitted fields are treated as zero. The first 40 bytes of these fields are // nearly identical to the WinBMPv3 info header; the remaining 24 bytes are // different. // - Also adds compression types "Huffman 1D" and "RLE24", which we don't // support. // - We treat OS2-BMPv2 files as if they are WinBMPv3 (i.e. ignore the extra 24 // bytes in the info header), which in practice is good enough. #include "ImageLogging.h" #include "nsBMPDecoder.h" #include #include "mozilla/Attributes.h" #include "mozilla/EndianUtils.h" #include "mozilla/Likely.h" #include "mozilla/UniquePtrExtensions.h" #include "RasterImage.h" #include "SurfacePipeFactory.h" #include "gfxPlatform.h" #include using namespace mozilla::gfx; namespace mozilla { namespace image { namespace bmp { struct Compression { enum { RGB = 0, RLE8 = 1, RLE4 = 2, BITFIELDS = 3 }; }; // RLE escape codes and constants. struct RLE { enum { ESCAPE = 0, ESCAPE_EOL = 0, ESCAPE_EOF = 1, ESCAPE_DELTA = 2, SEGMENT_LENGTH = 2, DELTA_LENGTH = 2 }; }; } // namespace bmp using namespace bmp; static double FixedPoint2Dot30_To_Double(uint32_t aFixed) { constexpr double factor = 1.0 / 1073741824.0; // 2^-30 return double(aFixed) * factor; } static float FixedPoint16Dot16_To_Float(uint32_t aFixed) { constexpr double factor = 1.0 / 65536.0; // 2^-16 return double(aFixed) * factor; } static float CalRbgEndpointToQcms(const CalRgbEndpoint& aIn, qcms_CIE_xyY& aOut) { aOut.x = FixedPoint2Dot30_To_Double(aIn.mX); aOut.y = FixedPoint2Dot30_To_Double(aIn.mY); aOut.Y = FixedPoint2Dot30_To_Double(aIn.mZ); return FixedPoint16Dot16_To_Float(aIn.mGamma); } static void ReadCalRgbEndpoint(const char* aData, uint32_t aEndpointOffset, uint32_t aGammaOffset, CalRgbEndpoint& aOut) { aOut.mX = LittleEndian::readUint32(aData + aEndpointOffset); aOut.mY = LittleEndian::readUint32(aData + aEndpointOffset + 4); aOut.mZ = LittleEndian::readUint32(aData + aEndpointOffset + 8); aOut.mGamma = LittleEndian::readUint32(aData + aGammaOffset); } /// Sets the pixel data in aDecoded to the given values. /// @param aDecoded pointer to pixel to be set, will be incremented to point to /// the next pixel. static void SetPixel(uint32_t*& aDecoded, uint8_t aRed, uint8_t aGreen, uint8_t aBlue, uint8_t aAlpha = 0xFF) { *aDecoded++ = gfxPackedPixelNoPreMultiply(aAlpha, aRed, aGreen, aBlue); } static void SetPixel(uint32_t*& aDecoded, uint8_t idx, const UniquePtr& aColors) { SetPixel(aDecoded, aColors[idx].mRed, aColors[idx].mGreen, aColors[idx].mBlue); } /// Sets two (or one if aCount = 1) pixels /// @param aDecoded where the data is stored. Will be moved 4 resp 8 bytes /// depending on whether one or two pixels are written. /// @param aData The values for the two pixels /// @param aCount Current count. Is decremented by one or two. static void Set4BitPixel(uint32_t*& aDecoded, uint8_t aData, uint32_t& aCount, const UniquePtr& aColors) { uint8_t idx = aData >> 4; SetPixel(aDecoded, idx, aColors); if (--aCount > 0) { idx = aData & 0xF; SetPixel(aDecoded, idx, aColors); --aCount; } } static mozilla::LazyLogModule sBMPLog("BMPDecoder"); // The length of the mBIHSize field in the info header. static const uint32_t BIHSIZE_FIELD_LENGTH = 4; nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength, bool aForClipboard) : Decoder(aImage), mLexer(Transition::To(aState, aLength), Transition::TerminateSuccess()), mIsWithinICO(false), mIsForClipboard(aForClipboard), mMayHaveTransparency(false), mDoesHaveTransparency(false), mNumColors(0), mColors(nullptr), mBytesPerColor(0), mPreGapLength(0), mPixelRowSize(0), mCurrentRow(0), mCurrentPos(0), mAbsoluteModeNumPixels(0) {} // Constructor for normal BMP files or from the clipboard. nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, bool aForClipboard) : nsBMPDecoder(aImage, aForClipboard ? State::INFO_HEADER_SIZE : State::FILE_HEADER, aForClipboard ? BIHSIZE_FIELD_LENGTH : FILE_HEADER_LENGTH, aForClipboard) {} // Constructor used for WinBMPv3-ICO files, which lack a file header. nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset) : nsBMPDecoder(aImage, State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH, /* aForClipboard */ false) { SetIsWithinICO(); // Even though the file header isn't present in this case, the dataOffset // field is set as if it is, and so we must increment mPreGapLength // accordingly. mPreGapLength += FILE_HEADER_LENGTH; // This is the one piece of data we normally get from a BMP file header, so // it must be provided via an argument. mH.mDataOffset = aDataOffset; } nsBMPDecoder::~nsBMPDecoder() {} // Obtains the size of the compressed image resource. int32_t nsBMPDecoder::GetCompressedImageSize() const { // In the RGB case mImageSize might not be set, so compute it manually. MOZ_ASSERT(mPixelRowSize != 0); return mH.mCompression == Compression::RGB ? mPixelRowSize * AbsoluteHeight() : mH.mImageSize; } nsresult nsBMPDecoder::BeforeFinishInternal() { if (!IsMetadataDecode() && !mImageData) { return NS_ERROR_FAILURE; // No image; something went wrong. } return NS_OK; } nsresult nsBMPDecoder::FinishInternal() { // We shouldn't be called in error cases. MOZ_ASSERT(!HasError(), "Can't call FinishInternal on error!"); // We should never make multiple frames. MOZ_ASSERT(GetFrameCount() <= 1, "Multiple BMP frames?"); // Send notifications if appropriate. if (!IsMetadataDecode() && HasSize()) { // We should have image data. MOZ_ASSERT(mImageData); // If it was truncated, fill in the missing pixels as black. while (mCurrentRow > 0) { uint32_t* dst = RowBuffer(); while (mCurrentPos < mH.mWidth) { SetPixel(dst, 0, 0, 0); mCurrentPos++; } mCurrentPos = 0; FinishRow(); } MOZ_ASSERT_IF(mDoesHaveTransparency, mMayHaveTransparency); // We have transparency if we either detected some in the image itself // (i.e., |mDoesHaveTransparency| is true) or we're in an ICO, which could // mean we have an AND mask that provides transparency (i.e., |mIsWithinICO| // is true). // XXX(seth): We can tell when we create the decoder if the AND mask is // present, so we could be more precise about this. const Opacity opacity = mDoesHaveTransparency || mIsWithinICO ? Opacity::SOME_TRANSPARENCY : Opacity::FULLY_OPAQUE; PostFrameStop(opacity); PostDecodeDone(); } return NS_OK; } // ---------------------------------------- // Actual Data Processing // ---------------------------------------- void BitFields::Value::Set(uint32_t aMask) { mMask = aMask; // Handle this exceptional case first. The chosen values don't matter // (because a mask of zero will always give a value of zero) except that // mBitWidth: // - shouldn't be zero, because that would cause an infinite loop in Get(); // - shouldn't be 5 or 8, because that could cause a false positive match in // IsR5G5B5() or IsR8G8B8(). if (mMask == 0x0) { mRightShift = 0; mBitWidth = 1; return; } // Find the rightmost 1. uint8_t i; for (i = 0; i < 32; i++) { if (mMask & (1 << i)) { break; } } mRightShift = i; // Now find the leftmost 1 in the same run of 1s. (If there are multiple runs // of 1s -- which isn't valid -- we'll behave as if only the lowest run was // present, which seems reasonable.) for (i = i + 1; i < 32; i++) { if (!(mMask & (1 << i))) { break; } } mBitWidth = i - mRightShift; } MOZ_ALWAYS_INLINE uint8_t BitFields::Value::Get(uint32_t aValue) const { // Extract the unscaled value. uint32_t v = (aValue & mMask) >> mRightShift; // Idea: to upscale v precisely we need to duplicate its bits, possibly // repeatedly, possibly partially in the last case, from bit 7 down to bit 0 // in v2. For example: // // - mBitWidth=1: v2 = v<<7 | v<<6 | ... | v<<1 | v>>0 k -> kkkkkkkk // - mBitWidth=2: v2 = v<<6 | v<<4 | v<<2 | v>>0 jk -> jkjkjkjk // - mBitWidth=3: v2 = v<<5 | v<<2 | v>>1 ijk -> ijkijkij // - mBitWidth=4: v2 = v<<4 | v>>0 hijk -> hijkhijk // - mBitWidth=5: v2 = v<<3 | v>>2 ghijk -> ghijkghi // - mBitWidth=6: v2 = v<<2 | v>>4 fghijk -> fghijkfg // - mBitWidth=7: v2 = v<<1 | v>>6 efghijk -> efghijke // - mBitWidth=8: v2 = v>>0 defghijk -> defghijk // - mBitWidth=9: v2 = v>>1 cdefghijk -> cdefghij // - mBitWidth=10: v2 = v>>2 bcdefghijk -> bcdefghi // - mBitWidth=11: v2 = v>>3 abcdefghijk -> abcdefgh // - etc. // uint8_t v2 = 0; int32_t i; // must be a signed integer for (i = 8 - mBitWidth; i > 0; i -= mBitWidth) { v2 |= v << uint32_t(i); } v2 |= v >> uint32_t(-i); return v2; } MOZ_ALWAYS_INLINE uint8_t BitFields::Value::GetAlpha(uint32_t aValue, bool& aHasAlphaOut) const { if (mMask == 0x0) { return 0xff; } aHasAlphaOut = true; return Get(aValue); } MOZ_ALWAYS_INLINE uint8_t BitFields::Value::Get5(uint32_t aValue) const { MOZ_ASSERT(mBitWidth == 5); uint32_t v = (aValue & mMask) >> mRightShift; return (v << 3u) | (v >> 2u); } MOZ_ALWAYS_INLINE uint8_t BitFields::Value::Get8(uint32_t aValue) const { MOZ_ASSERT(mBitWidth == 8); uint32_t v = (aValue & mMask) >> mRightShift; return v; } void BitFields::SetR5G5B5() { mRed.Set(0x7c00); mGreen.Set(0x03e0); mBlue.Set(0x001f); } void BitFields::SetR8G8B8() { mRed.Set(0xff0000); mGreen.Set(0xff00); mBlue.Set(0x00ff); } bool BitFields::IsR5G5B5() const { return mRed.mBitWidth == 5 && mGreen.mBitWidth == 5 && mBlue.mBitWidth == 5 && mAlpha.mMask == 0x0; } bool BitFields::IsR8G8B8() const { return mRed.mBitWidth == 8 && mGreen.mBitWidth == 8 && mBlue.mBitWidth == 8 && mAlpha.mMask == 0x0; } uint32_t* nsBMPDecoder::RowBuffer() { return mRowBuffer.get() + mCurrentPos; } void nsBMPDecoder::ClearRowBufferRemainder() { int32_t len = mH.mWidth - mCurrentPos; memset(RowBuffer(), mMayHaveTransparency ? 0 : 0xFF, len * sizeof(uint32_t)); } void nsBMPDecoder::FinishRow() { mPipe.WriteBuffer(mRowBuffer.get()); Maybe invalidRect = mPipe.TakeInvalidRect(); if (invalidRect) { PostInvalidation(invalidRect->mInputSpaceRect, Some(invalidRect->mOutputSpaceRect)); } mCurrentRow--; } LexerResult nsBMPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) { MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); return mLexer.Lex( aIterator, aOnResume, [=](State aState, const char* aData, size_t aLength) { switch (aState) { case State::FILE_HEADER: return ReadFileHeader(aData, aLength); case State::INFO_HEADER_SIZE: return ReadInfoHeaderSize(aData, aLength); case State::INFO_HEADER_REST: return ReadInfoHeaderRest(aData, aLength); case State::BITFIELDS: return ReadBitfields(aData, aLength); case State::SKIP_TO_COLOR_PROFILE: return Transition::ContinueUnbuffered(State::SKIP_TO_COLOR_PROFILE); case State::FOUND_COLOR_PROFILE: return Transition::To(State::COLOR_PROFILE, mH.mColorSpace.mProfile.mLength); case State::COLOR_PROFILE: return ReadColorProfile(aData, aLength); case State::ALLOCATE_SURFACE: return AllocateSurface(); case State::COLOR_TABLE: return ReadColorTable(aData, aLength); case State::GAP: return SkipGap(); case State::AFTER_GAP: return AfterGap(); case State::PIXEL_ROW: return ReadPixelRow(aData); case State::RLE_SEGMENT: return ReadRLESegment(aData); case State::RLE_DELTA: return ReadRLEDelta(aData); case State::RLE_ABSOLUTE: return ReadRLEAbsolute(aData, aLength); default: MOZ_CRASH("Unknown State"); } }); } LexerTransition nsBMPDecoder::ReadFileHeader( const char* aData, size_t aLength) { mPreGapLength += aLength; bool signatureOk = aData[0] == 'B' && aData[1] == 'M'; if (!signatureOk) { return Transition::TerminateFailure(); } // We ignore the filesize (aData + 2) and reserved (aData + 6) fields. mH.mDataOffset = LittleEndian::readUint32(aData + 10); return Transition::To(State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH); } // We read the info header in two steps: (a) read the mBIHSize field to // determine how long the header is; (b) read the rest of the header. LexerTransition nsBMPDecoder::ReadInfoHeaderSize( const char* aData, size_t aLength) { mH.mBIHSize = LittleEndian::readUint32(aData); // Data offset can be wrong so fix it using the BIH size. if (!mIsForClipboard && mH.mDataOffset < mPreGapLength + mH.mBIHSize) { mH.mDataOffset = mPreGapLength + mH.mBIHSize; } mPreGapLength += aLength; bool bihSizeOk = mH.mBIHSize == InfoHeaderLength::WIN_V2 || mH.mBIHSize == InfoHeaderLength::WIN_V3 || mH.mBIHSize == InfoHeaderLength::WIN_V4 || mH.mBIHSize == InfoHeaderLength::WIN_V5 || (mH.mBIHSize >= InfoHeaderLength::OS2_V2_MIN && mH.mBIHSize <= InfoHeaderLength::OS2_V2_MAX); if (!bihSizeOk) { return Transition::TerminateFailure(); } // ICO BMPs must have a WinBMPv3 header. nsICODecoder should have already // terminated decoding if this isn't the case. MOZ_ASSERT_IF(mIsWithinICO, mH.mBIHSize == InfoHeaderLength::WIN_V3); return Transition::To(State::INFO_HEADER_REST, mH.mBIHSize - BIHSIZE_FIELD_LENGTH); } LexerTransition nsBMPDecoder::ReadInfoHeaderRest( const char* aData, size_t aLength) { mPreGapLength += aLength; // |mWidth| and |mHeight| may be signed (Windows) or unsigned (OS/2). We just // read as unsigned because in practice that's good enough. if (mH.mBIHSize == InfoHeaderLength::WIN_V2) { mH.mWidth = LittleEndian::readUint16(aData + 0); mH.mHeight = LittleEndian::readUint16(aData + 2); // We ignore the planes (aData + 4) field; it should always be 1. mH.mBpp = LittleEndian::readUint16(aData + 6); } else { mH.mWidth = LittleEndian::readUint32(aData + 0); mH.mHeight = LittleEndian::readUint32(aData + 4); // We ignore the planes (aData + 4) field; it should always be 1. mH.mBpp = LittleEndian::readUint16(aData + 10); // For OS2-BMPv2 the info header may be as little as 16 bytes, so be // careful for these fields. mH.mCompression = aLength >= 16 ? LittleEndian::readUint32(aData + 12) : 0; mH.mImageSize = aLength >= 20 ? LittleEndian::readUint32(aData + 16) : 0; // We ignore the xppm (aData + 20) and yppm (aData + 24) fields. mH.mNumColors = aLength >= 32 ? LittleEndian::readUint32(aData + 28) : 0; // We ignore the important_colors (aData + 36) field. // Read color management properties we may need later. mH.mCsType = aLength >= 56 ? static_cast(LittleEndian::readUint32(aData + 52)) : InfoColorSpace::SRGB; mH.mCsIntent = aLength >= 108 ? static_cast( LittleEndian::readUint32(aData + 104)) : InfoColorIntent::IMAGES; switch (mH.mCsType) { case InfoColorSpace::CALIBRATED_RGB: if (aLength >= 104) { ReadCalRgbEndpoint(aData, 56, 92, mH.mColorSpace.mCalibrated.mRed); ReadCalRgbEndpoint(aData, 68, 96, mH.mColorSpace.mCalibrated.mGreen); ReadCalRgbEndpoint(aData, 80, 100, mH.mColorSpace.mCalibrated.mBlue); } else { mH.mCsType = InfoColorSpace::SRGB; } break; case InfoColorSpace::EMBEDDED: if (aLength >= 116) { mH.mColorSpace.mProfile.mOffset = LittleEndian::readUint32(aData + 108); mH.mColorSpace.mProfile.mLength = LittleEndian::readUint32(aData + 112); } else { mH.mCsType = InfoColorSpace::SRGB; } break; case InfoColorSpace::LINKED: case InfoColorSpace::SRGB: case InfoColorSpace::WIN: default: // Nothing to be done at this time. break; } // For WinBMPv4, WinBMPv5 and (possibly) OS2-BMPv2 there are additional // fields in the info header which we ignore, with the possible exception // of the color bitfields (see below). } // The height for BMPs embedded inside an ICO includes spaces for the AND // mask even if it is not present, thus we need to adjust for that here. if (mIsWithinICO) { // XXX(seth): Should we really be writing the absolute value from // the BIH below? Seems like this could be problematic for inverted BMPs. mH.mHeight = abs(mH.mHeight) / 2; } // Run with MOZ_LOG=BMPDecoder:5 set to see this output. MOZ_LOG(sBMPLog, LogLevel::Debug, ("BMP: bihsize=%u, %d x %d, bpp=%u, compression=%u, colors=%u, " "data-offset=%u\n", mH.mBIHSize, mH.mWidth, mH.mHeight, uint32_t(mH.mBpp), mH.mCompression, mH.mNumColors, mH.mDataOffset)); // BMPs with negative width are invalid. Also, reject extremely wide images // to keep the math sane. And reject INT_MIN as a height because you can't // get its absolute value (because -INT_MIN is one more than INT_MAX). const int32_t k64KWidth = 0x0000FFFF; bool sizeOk = 0 <= mH.mWidth && mH.mWidth <= k64KWidth && mH.mHeight != INT_MIN; if (!sizeOk) { return Transition::TerminateFailure(); } // Check mBpp and mCompression. bool bppCompressionOk = (mH.mCompression == Compression::RGB && (mH.mBpp == 1 || mH.mBpp == 4 || mH.mBpp == 8 || mH.mBpp == 16 || mH.mBpp == 24 || mH.mBpp == 32)) || (mH.mCompression == Compression::RLE8 && mH.mBpp == 8) || (mH.mCompression == Compression::RLE4 && mH.mBpp == 4) || (mH.mCompression == Compression::BITFIELDS && // For BITFIELDS compression we require an exact match for one of the // WinBMP BIH sizes; this clearly isn't an OS2 BMP. (mH.mBIHSize == InfoHeaderLength::WIN_V3 || mH.mBIHSize == InfoHeaderLength::WIN_V4 || mH.mBIHSize == InfoHeaderLength::WIN_V5) && (mH.mBpp == 16 || mH.mBpp == 32)); if (!bppCompressionOk) { return Transition::TerminateFailure(); } // Initialize our current row to the top of the image. mCurrentRow = AbsoluteHeight(); // Round it up to the nearest byte count, then pad to 4-byte boundary. // Compute this even for a metadate decode because GetCompressedImageSize() // relies on it. mPixelRowSize = (mH.mBpp * mH.mWidth + 7) / 8; uint32_t surplus = mPixelRowSize % 4; if (surplus != 0) { mPixelRowSize += 4 - surplus; } size_t bitFieldsLengthStillToRead = 0; if (mH.mCompression == Compression::BITFIELDS) { // Need to read bitfields. if (mH.mBIHSize >= InfoHeaderLength::WIN_V4) { // Bitfields are present in the info header, so we can read them // immediately. mBitFields.ReadFromHeader(aData + 36, /* aReadAlpha = */ true); // If this came from the clipboard, then we know that even if the header // explicitly includes the bitfield masks, we need to add an additional // offset for the start of the RGB data. if (mIsForClipboard) { mH.mDataOffset += BitFields::LENGTH; } } else { // Bitfields are present after the info header, so we will read them in // ReadBitfields(). bitFieldsLengthStillToRead = BitFields::LENGTH; } } else if (mH.mBpp == 16) { // No bitfields specified; use the default 5-5-5 values. mBitFields.SetR5G5B5(); } else if (mH.mBpp == 32) { // No bitfields specified; use the default 8-8-8 values. mBitFields.SetR8G8B8(); } return Transition::To(State::BITFIELDS, bitFieldsLengthStillToRead); } void BitFields::ReadFromHeader(const char* aData, bool aReadAlpha) { mRed.Set(LittleEndian::readUint32(aData + 0)); mGreen.Set(LittleEndian::readUint32(aData + 4)); mBlue.Set(LittleEndian::readUint32(aData + 8)); if (aReadAlpha) { mAlpha.Set(LittleEndian::readUint32(aData + 12)); } } LexerTransition nsBMPDecoder::ReadBitfields( const char* aData, size_t aLength) { mPreGapLength += aLength; // If aLength is zero there are no bitfields to read, or we already read them // in ReadInfoHeader(). if (aLength != 0) { mBitFields.ReadFromHeader(aData, /* aReadAlpha = */ false); } // Note that RLE-encoded BMPs might be transparent because the 'delta' mode // can skip pixels and cause implicit transparency. mMayHaveTransparency = mIsWithinICO || mH.mCompression == Compression::RLE8 || mH.mCompression == Compression::RLE4 || (mH.mCompression == Compression::BITFIELDS && mBitFields.mAlpha.IsPresent()); if (mMayHaveTransparency) { PostHasTransparency(); } // Post our size to the superclass. PostSize(mH.mWidth, AbsoluteHeight()); if (HasError()) { return Transition::TerminateFailure(); } // We've now read all the headers. If we're doing a metadata decode, we're // done. if (IsMetadataDecode()) { return Transition::TerminateSuccess(); } // Set up the color table, if present; it'll be filled in by ReadColorTable(). if (mH.mBpp <= 8) { mNumColors = 1 << mH.mBpp; if (0 < mH.mNumColors && mH.mNumColors < mNumColors) { mNumColors = mH.mNumColors; } // Always allocate and zero 256 entries, even though mNumColors might be // smaller, because the file might erroneously index past mNumColors. mColors = MakeUniqueFallible(256); if (NS_WARN_IF(!mColors)) { return Transition::TerminateFailure(); } memset(mColors.get(), 0, 256 * sizeof(ColorTableEntry)); // OS/2 Bitmaps have no padding byte. mBytesPerColor = (mH.mBIHSize == InfoHeaderLength::WIN_V2) ? 3 : 4; } if (mCMSMode != CMSMode::Off) { switch (mH.mCsType) { case InfoColorSpace::EMBEDDED: return SeekColorProfile(aLength); case InfoColorSpace::CALIBRATED_RGB: PrepareCalibratedColorProfile(); break; case InfoColorSpace::SRGB: case InfoColorSpace::WIN: MOZ_LOG(sBMPLog, LogLevel::Debug, ("using sRGB color profile\n")); if (mColors) { // We will transform the color table instead of the output pixels. mTransform = GetCMSsRGBTransform(SurfaceFormat::R8G8B8); } else { mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA); } break; case InfoColorSpace::LINKED: default: // Not supported, no color management. MOZ_LOG(sBMPLog, LogLevel::Debug, ("color space type not provided\n")); break; } } return Transition::To(State::ALLOCATE_SURFACE, 0); } void nsBMPDecoder::PrepareCalibratedColorProfile() { // BMP does not define a white point. Use the same as sRGB. This matches what // Chrome does as well. qcms_CIE_xyY white_point = qcms_white_point_sRGB(); qcms_CIE_xyYTRIPLE primaries; float redGamma = CalRbgEndpointToQcms(mH.mColorSpace.mCalibrated.mRed, primaries.red); float greenGamma = CalRbgEndpointToQcms(mH.mColorSpace.mCalibrated.mGreen, primaries.green); float blueGamma = CalRbgEndpointToQcms(mH.mColorSpace.mCalibrated.mBlue, primaries.blue); // Explicitly verify the profile because sometimes the values from the BMP // header are just garbage. mInProfile = qcms_profile_create_rgb_with_gamma_set( white_point, primaries, redGamma, greenGamma, blueGamma); if (mInProfile && qcms_profile_is_bogus(mInProfile)) { // Bad profile, just use sRGB instead. Release the profile here, so that // our destructor doesn't assume we are the owner for the transform. qcms_profile_release(mInProfile); mInProfile = nullptr; } if (mInProfile) { MOZ_LOG(sBMPLog, LogLevel::Debug, ("using calibrated RGB color profile\n")); PrepareColorProfileTransform(); } else { MOZ_LOG(sBMPLog, LogLevel::Debug, ("failed to create calibrated RGB color profile, using sRGB\n")); if (mColors) { // We will transform the color table instead of the output pixels. mTransform = GetCMSsRGBTransform(SurfaceFormat::R8G8B8); } else { mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA); } } } void nsBMPDecoder::PrepareColorProfileTransform() { if (!mInProfile || !GetCMSOutputProfile()) { return; } qcms_data_type inType; qcms_data_type outType; if (mColors) { // We will transform the color table instead of the output pixels. inType = QCMS_DATA_RGB_8; outType = QCMS_DATA_RGB_8; } else { inType = gfxPlatform::GetCMSOSRGBAType(); outType = inType; } qcms_intent intent; switch (mH.mCsIntent) { case InfoColorIntent::BUSINESS: intent = QCMS_INTENT_SATURATION; break; case InfoColorIntent::GRAPHICS: intent = QCMS_INTENT_RELATIVE_COLORIMETRIC; break; case InfoColorIntent::ABS_COLORIMETRIC: intent = QCMS_INTENT_ABSOLUTE_COLORIMETRIC; break; case InfoColorIntent::IMAGES: default: intent = QCMS_INTENT_PERCEPTUAL; break; } mTransform = qcms_transform_create(mInProfile, inType, GetCMSOutputProfile(), outType, intent); if (!mTransform) { MOZ_LOG(sBMPLog, LogLevel::Debug, ("failed to create color profile transform\n")); } } LexerTransition nsBMPDecoder::SeekColorProfile( size_t aLength) { // The offset needs to be at least after the color table. uint32_t offset = mH.mColorSpace.mProfile.mOffset; if (offset <= mH.mBIHSize + aLength + mNumColors * mBytesPerColor || mH.mColorSpace.mProfile.mLength == 0) { return Transition::To(State::ALLOCATE_SURFACE, 0); } // We have already read the header and bitfields. offset -= mH.mBIHSize + aLength; // We need to skip ahead to search for the embedded color profile. We want // to return to this point once we read it. mReturnIterator = mLexer.Clone(*mIterator, SIZE_MAX); if (!mReturnIterator) { return Transition::TerminateFailure(); } return Transition::ToUnbuffered(State::FOUND_COLOR_PROFILE, State::SKIP_TO_COLOR_PROFILE, offset); } LexerTransition nsBMPDecoder::ReadColorProfile( const char* aData, size_t aLength) { mInProfile = qcms_profile_from_memory(aData, aLength); if (mInProfile) { MOZ_LOG(sBMPLog, LogLevel::Debug, ("using embedded color profile\n")); PrepareColorProfileTransform(); } // Jump back to where we left off. mIterator = std::move(mReturnIterator); return Transition::To(State::ALLOCATE_SURFACE, 0); } LexerTransition nsBMPDecoder::AllocateSurface() { SurfaceFormat format; SurfacePipeFlags pipeFlags = SurfacePipeFlags(); if (mMayHaveTransparency) { format = SurfaceFormat::OS_RGBA; if (!(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) { pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA; } } else { format = SurfaceFormat::OS_RGBX; } if (mH.mHeight >= 0) { // BMPs store their rows in reverse order, so we may need to flip. pipeFlags |= SurfacePipeFlags::FLIP_VERTICALLY; } mRowBuffer.reset(new (fallible) uint32_t[mH.mWidth]); if (!mRowBuffer) { return Transition::TerminateFailure(); } // Only give the color transform to the SurfacePipe if we are not transforming // the color table in advance. qcms_transform* transform = mColors ? nullptr : mTransform; Maybe pipe = SurfacePipeFactory::CreateSurfacePipe( this, Size(), OutputSize(), FullFrame(), format, format, Nothing(), transform, pipeFlags); if (!pipe) { return Transition::TerminateFailure(); } mPipe = std::move(*pipe); ClearRowBufferRemainder(); return Transition::To(State::COLOR_TABLE, mNumColors * mBytesPerColor); } LexerTransition nsBMPDecoder::ReadColorTable( const char* aData, size_t aLength) { MOZ_ASSERT_IF(aLength != 0, mNumColors > 0 && mColors); mPreGapLength += aLength; for (uint32_t i = 0; i < mNumColors; i++) { // The format is BGR or BGR0. mColors[i].mBlue = uint8_t(aData[0]); mColors[i].mGreen = uint8_t(aData[1]); mColors[i].mRed = uint8_t(aData[2]); aData += mBytesPerColor; } // If we have a color table and a transform, we can avoid transforming each // pixel by doing the table in advance. We color manage every entry in the // table, even if it is smaller in case the BMP is malformed and overruns // its stated color range. if (mColors && mTransform) { qcms_transform_data(mTransform, mColors.get(), mColors.get(), 256); } // If we are decoding a BMP from the clipboard, we did not know the data // offset in advance. It is just defined as after the header and color table. if (mIsForClipboard) { mH.mDataOffset += mPreGapLength; } // We know how many bytes we've read so far (mPreGapLength) and we know the // offset of the pixel data (mH.mDataOffset), so we can determine the length // of the gap (possibly zero) between the color table and the pixel data. // // If the gap is negative the file must be malformed (e.g. mH.mDataOffset // points into the middle of the color palette instead of past the end) and // we give up. if (mPreGapLength > mH.mDataOffset) { return Transition::TerminateFailure(); } uint32_t gapLength = mH.mDataOffset - mPreGapLength; return Transition::ToUnbuffered(State::AFTER_GAP, State::GAP, gapLength); } LexerTransition nsBMPDecoder::SkipGap() { return Transition::ContinueUnbuffered(State::GAP); } LexerTransition nsBMPDecoder::AfterGap() { // If there are no pixels we can stop. // // XXX: normally, if there are no pixels we will have stopped decoding before // now, outside of this decoder. However, if the BMP is within an ICO file, // it's possible that the ICO claimed the image had a non-zero size while the // BMP claims otherwise. This test is to catch that awkward case. If we ever // come up with a more general solution to this ICO-and-BMP-disagree-on-size // problem, this test can be removed. if (mH.mWidth == 0 || mH.mHeight == 0) { return Transition::TerminateSuccess(); } bool hasRLE = mH.mCompression == Compression::RLE8 || mH.mCompression == Compression::RLE4; return hasRLE ? Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH) : Transition::To(State::PIXEL_ROW, mPixelRowSize); } LexerTransition nsBMPDecoder::ReadPixelRow( const char* aData) { MOZ_ASSERT(mCurrentRow > 0); MOZ_ASSERT(mCurrentPos == 0); const uint8_t* src = reinterpret_cast(aData); uint32_t* dst = RowBuffer(); uint32_t lpos = mH.mWidth; switch (mH.mBpp) { case 1: while (lpos > 0) { int8_t bit; uint8_t idx; for (bit = 7; bit >= 0 && lpos > 0; bit--) { idx = (*src >> bit) & 1; SetPixel(dst, idx, mColors); --lpos; } ++src; } break; case 4: while (lpos > 0) { Set4BitPixel(dst, *src, lpos, mColors); ++src; } break; case 8: while (lpos > 0) { SetPixel(dst, *src, mColors); --lpos; ++src; } break; case 16: if (mBitFields.IsR5G5B5()) { // Specialize this common case. while (lpos > 0) { uint16_t val = LittleEndian::readUint16(src); SetPixel(dst, mBitFields.mRed.Get5(val), mBitFields.mGreen.Get5(val), mBitFields.mBlue.Get5(val)); --lpos; src += 2; } } else { bool anyHasAlpha = false; while (lpos > 0) { uint16_t val = LittleEndian::readUint16(src); SetPixel(dst, mBitFields.mRed.Get(val), mBitFields.mGreen.Get(val), mBitFields.mBlue.Get(val), mBitFields.mAlpha.GetAlpha(val, anyHasAlpha)); --lpos; src += 2; } if (anyHasAlpha) { MOZ_ASSERT(mMayHaveTransparency); mDoesHaveTransparency = true; } } break; case 24: while (lpos > 0) { SetPixel(dst, src[2], src[1], src[0]); --lpos; src += 3; } break; case 32: if (mH.mCompression == Compression::RGB && mIsWithinICO && mH.mBpp == 32) { // This is a special case only used for 32bpp WinBMPv3-ICO files, which // could be in either 0RGB or ARGB format. We start by assuming it's // an 0RGB image. If we hit a non-zero alpha value, then we know it's // actually an ARGB image, and change tack accordingly. // (Note: a fully-transparent ARGB image is indistinguishable from a // 0RGB image, and we will render such an image as a 0RGB image, i.e. // opaquely. This is unlikely to be a problem in practice.) while (lpos > 0) { if (!mDoesHaveTransparency && src[3] != 0) { // Up until now this looked like an 0RGB image, but we now know // it's actually an ARGB image. Which means every pixel we've seen // so far has been fully transparent. So we go back and redo them. // Tell the SurfacePipe to go back to the start. mPipe.ResetToFirstRow(); // Redo the complete rows we've already done. MOZ_ASSERT(mCurrentPos == 0); int32_t currentRow = mCurrentRow; mCurrentRow = AbsoluteHeight(); ClearRowBufferRemainder(); while (mCurrentRow > currentRow) { FinishRow(); } // Reset the row pointer back to where we started. dst = RowBuffer() + (mH.mWidth - lpos); MOZ_ASSERT(mMayHaveTransparency); mDoesHaveTransparency = true; } // If mDoesHaveTransparency is false, treat this as an 0RGB image. // Otherwise, treat this as an ARGB image. SetPixel(dst, src[2], src[1], src[0], mDoesHaveTransparency ? src[3] : 0xff); src += 4; --lpos; } } else if (mBitFields.IsR8G8B8()) { // Specialize this common case. while (lpos > 0) { uint32_t val = LittleEndian::readUint32(src); SetPixel(dst, mBitFields.mRed.Get8(val), mBitFields.mGreen.Get8(val), mBitFields.mBlue.Get8(val)); --lpos; src += 4; } } else { bool anyHasAlpha = false; while (lpos > 0) { uint32_t val = LittleEndian::readUint32(src); SetPixel(dst, mBitFields.mRed.Get(val), mBitFields.mGreen.Get(val), mBitFields.mBlue.Get(val), mBitFields.mAlpha.GetAlpha(val, anyHasAlpha)); --lpos; src += 4; } if (anyHasAlpha) { MOZ_ASSERT(mMayHaveTransparency); mDoesHaveTransparency = true; } } break; default: MOZ_CRASH("Unsupported color depth; earlier check didn't catch it?"); } FinishRow(); return mCurrentRow == 0 ? Transition::TerminateSuccess() : Transition::To(State::PIXEL_ROW, mPixelRowSize); } LexerTransition nsBMPDecoder::ReadRLESegment( const char* aData) { if (mCurrentRow == 0) { return Transition::TerminateSuccess(); } uint8_t byte1 = uint8_t(aData[0]); uint8_t byte2 = uint8_t(aData[1]); if (byte1 != RLE::ESCAPE) { // Encoded mode consists of two bytes: byte1 specifies the number of // consecutive pixels to be drawn using the color index contained in // byte2. // // Work around bitmaps that specify too many pixels. uint32_t pixelsNeeded = std::min(mH.mWidth - mCurrentPos, byte1); if (pixelsNeeded) { uint32_t* dst = RowBuffer(); mCurrentPos += pixelsNeeded; if (mH.mCompression == Compression::RLE8) { do { SetPixel(dst, byte2, mColors); pixelsNeeded--; } while (pixelsNeeded); } else { do { Set4BitPixel(dst, byte2, pixelsNeeded, mColors); } while (pixelsNeeded); } } return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); } if (byte2 == RLE::ESCAPE_EOL) { ClearRowBufferRemainder(); mCurrentPos = 0; FinishRow(); return mCurrentRow == 0 ? Transition::TerminateSuccess() : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); } if (byte2 == RLE::ESCAPE_EOF) { return Transition::TerminateSuccess(); } if (byte2 == RLE::ESCAPE_DELTA) { return Transition::To(State::RLE_DELTA, RLE::DELTA_LENGTH); } // Absolute mode. |byte2| gives the number of pixels. The length depends on // whether it's 4-bit or 8-bit RLE. Also, the length must be even (and zero // padding is used to achieve this when necessary). MOZ_ASSERT(mAbsoluteModeNumPixels == 0); mAbsoluteModeNumPixels = byte2; uint32_t length = byte2; if (mH.mCompression == Compression::RLE4) { length = (length + 1) / 2; // halve, rounding up } if (length & 1) { length++; } return Transition::To(State::RLE_ABSOLUTE, length); } LexerTransition nsBMPDecoder::ReadRLEDelta( const char* aData) { // Delta encoding makes it possible to skip pixels making part of the image // transparent. MOZ_ASSERT(mMayHaveTransparency); mDoesHaveTransparency = true; // Clear the skipped pixels. (This clears to the end of the row, // which is perfect if there's a Y delta and harmless if not). ClearRowBufferRemainder(); // Handle the XDelta. mCurrentPos += uint8_t(aData[0]); if (mCurrentPos > mH.mWidth) { mCurrentPos = mH.mWidth; } // Handle the Y Delta. int32_t yDelta = std::min(uint8_t(aData[1]), mCurrentRow); if (yDelta > 0) { // Commit the current row (the first of the skipped rows). FinishRow(); // Clear and commit the remaining skipped rows. We want to be careful not // to change mCurrentPos here. memset(mRowBuffer.get(), 0, mH.mWidth * sizeof(uint32_t)); for (int32_t line = 1; line < yDelta; line++) { FinishRow(); } } return mCurrentRow == 0 ? Transition::TerminateSuccess() : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); } LexerTransition nsBMPDecoder::ReadRLEAbsolute( const char* aData, size_t aLength) { uint32_t n = mAbsoluteModeNumPixels; mAbsoluteModeNumPixels = 0; if (mCurrentPos + n > uint32_t(mH.mWidth)) { // Some DIB RLE8 encoders count a padding byte as the absolute mode // pixel number at the end of the row. if (mH.mCompression == Compression::RLE8 && n > 0 && (n & 1) == 0 && mCurrentPos + n - uint32_t(mH.mWidth) == 1 && aLength > 0 && aData[aLength - 1] == 0) { n--; } else { // Bad data. Stop decoding; at least part of the image may have been // decoded. return Transition::TerminateSuccess(); } } // In absolute mode, n represents the number of pixels that follow, each of // which contains the color index of a single pixel. uint32_t* dst = RowBuffer(); uint32_t iSrc = 0; uint32_t* oldPos = dst; if (mH.mCompression == Compression::RLE8) { while (n > 0) { SetPixel(dst, aData[iSrc], mColors); n--; iSrc++; } } else { while (n > 0) { Set4BitPixel(dst, aData[iSrc], n, mColors); iSrc++; } } mCurrentPos += dst - oldPos; // We should read all the data (unless the last byte is zero padding). MOZ_ASSERT(iSrc == aLength - 1 || iSrc == aLength); return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); } } // namespace image } // namespace mozilla