mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
3edae2885b
Differential Revision: https://phabricator.services.mozilla.com/D145569
1264 lines
44 KiB
C++
1264 lines
44 KiB
C++
/* -*- 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 <stdlib.h>
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/EndianUtils.h"
|
|
#include "mozilla/Likely.h"
|
|
|
|
#include "RasterImage.h"
|
|
#include "SurfacePipeFactory.h"
|
|
#include "gfxPlatform.h"
|
|
#include <algorithm>
|
|
|
|
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<ColorTableEntry[]>& 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<ColorTableEntry[]>& 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<SurfaceInvalidRect> 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::State> 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::State> 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::State> 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<InfoColorSpace>(LittleEndian::readUint32(aData + 52))
|
|
: InfoColorSpace::SRGB;
|
|
mH.mCsIntent = aLength >= 108 ? static_cast<InfoColorIntent>(
|
|
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::State> 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 = MakeUnique<ColorTableEntry[]>(256);
|
|
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::State> 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::State> 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::State> 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<SurfacePipe> 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::State> 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::State> nsBMPDecoder::SkipGap() {
|
|
return Transition::ContinueUnbuffered(State::GAP);
|
|
}
|
|
|
|
LexerTransition<nsBMPDecoder::State> 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::State> nsBMPDecoder::ReadPixelRow(
|
|
const char* aData) {
|
|
MOZ_ASSERT(mCurrentRow > 0);
|
|
MOZ_ASSERT(mCurrentPos == 0);
|
|
|
|
const uint8_t* src = reinterpret_cast<const uint8_t*>(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::State> 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<uint32_t>(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::State> 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<int32_t>(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::State> nsBMPDecoder::ReadRLEAbsolute(
|
|
const char* aData, size_t aLength) {
|
|
uint32_t n = mAbsoluteModeNumPixels;
|
|
mAbsoluteModeNumPixels = 0;
|
|
|
|
if (mCurrentPos + n > uint32_t(mH.mWidth)) {
|
|
// 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
|