/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkCodec_libbmp.h"
#include "SkCodecPriv.h"
#include "SkColorPriv.h"
#include "SkStream.h"
/*
*
* Checks if the conversion between the input image and the requested output
* image has been implemented
*
*/
static bool conversion_possible(const SkImageInfo& dst,
const SkImageInfo& src) {
// Ensure that the profile type is unchanged
if (dst.profileType() != src.profileType()) {
return false;
}
// Check for supported alpha types
if (src.alphaType() != dst.alphaType()) {
if (kOpaque_SkAlphaType == src.alphaType()) {
// If the source is opaque, we must decode to opaque
return false;
}
// The source is not opaque
switch (dst.alphaType()) {
case kPremul_SkAlphaType:
case kUnpremul_SkAlphaType:
// The source is not opaque, so either of these is okay
break;
default:
// We cannot decode a non-opaque image to opaque (or unknown)
return false;
}
}
// Check for supported color types
switch (dst.colorType()) {
// Allow output to kN32 from any type of input
case kN32_SkColorType:
return true;
// Allow output to kIndex_8 from compatible inputs
case kIndex_8_SkColorType:
return kIndex_8_SkColorType == src.colorType();
default:
return false;
}
}
/*
*
* Defines the version and type of the second bitmap header
*
*/
enum BitmapHeaderType {
kInfoV1_BitmapHeaderType,
kInfoV2_BitmapHeaderType,
kInfoV3_BitmapHeaderType,
kInfoV4_BitmapHeaderType,
kInfoV5_BitmapHeaderType,
kOS2V1_BitmapHeaderType,
kOS2VX_BitmapHeaderType,
kUnknown_BitmapHeaderType
};
/*
*
* Possible bitmap compression types
*
*/
enum BitmapCompressionMethod {
kNone_BitmapCompressionMethod = 0,
k8BitRLE_BitmapCompressionMethod = 1,
k4BitRLE_BitmapCompressionMethod = 2,
kBitMasks_BitmapCompressionMethod = 3,
kJpeg_BitmapCompressionMethod = 4,
kPng_BitmapCompressionMethod = 5,
kAlphaBitMasks_BitmapCompressionMethod = 6,
kCMYK_BitmapCompressionMethod = 11,
kCMYK8BitRLE_BitmapCompressionMethod = 12,
kCMYK4BitRLE_BitmapCompressionMethod = 13
};
/*
*
* Checks the start of the stream to see if the image is a bitmap
*
*/
bool SkBmpCodec::IsBmp(SkStream* stream) {
// TODO: Support "IC", "PT", "CI", "CP", "BA"
const char bmpSig[] = { 'B', 'M' };
char buffer[sizeof(bmpSig)];
return stream->read(buffer, sizeof(bmpSig)) == sizeof(bmpSig) &&
!memcmp(buffer, bmpSig, sizeof(bmpSig));
}
/*
*
* Assumes IsBmp was called and returned true
* Creates a bmp decoder
* Reads enough of the stream to determine the image format
*
*/
SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) {
return SkBmpCodec::NewFromStream(stream, false);
}
/*
*
* Creates a bmp decoder for a bmp embedded in ico
* Reads enough of the stream to determine the image format
*
*/
SkCodec* SkBmpCodec::NewFromIco(SkStream* stream) {
return SkBmpCodec::NewFromStream(stream, true);
}
/*
*
* Read enough of the stream to initialize the SkBmpCodec. Returns a bool
* representing success or failure. If it returned true, and codecOut was
* not NULL, it will be set to a new SkBmpCodec.
* Does *not* take ownership of the passed in SkStream.
*
*/
bool SkBmpCodec::ReadHeader(SkStream* stream, bool isIco, SkCodec** codecOut) {
// Header size constants
static const uint32_t kBmpHeaderBytes = 14;
static const uint32_t kBmpHeaderBytesPlusFour = kBmpHeaderBytes + 4;
static const uint32_t kBmpOS2V1Bytes = 12;
static const uint32_t kBmpOS2V2Bytes = 64;
static const uint32_t kBmpInfoBaseBytes = 16;
static const uint32_t kBmpInfoV1Bytes = 40;
static const uint32_t kBmpInfoV2Bytes = 52;
static const uint32_t kBmpInfoV3Bytes = 56;
static const uint32_t kBmpInfoV4Bytes = 108;
static const uint32_t kBmpInfoV5Bytes = 124;
static const uint32_t kBmpMaskBytes = 12;
// The total bytes in the bmp file
// We only need to use this value for RLE decoding, so we will only
// check that it is valid in the RLE case.
uint32_t totalBytes;
// The offset from the start of the file where the pixel data begins
uint32_t offset;
// The size of the second (info) header in bytes
uint32_t infoBytes;
// Bmps embedded in Icos skip the first Bmp header
if (!isIco) {
// Read the first header and the size of the second header
SkAutoTDeleteArray<uint8_t> hBuffer(
SkNEW_ARRAY(uint8_t, kBmpHeaderBytesPlusFour));
if (stream->read(hBuffer.get(), kBmpHeaderBytesPlusFour) !=
kBmpHeaderBytesPlusFour) {
SkCodecPrintf("Error: unable to read first bitmap header.\n");
return false;
}
totalBytes = get_int(hBuffer.get(), 2);
offset = get_int(hBuffer.get(), 10);
if (offset < kBmpHeaderBytes + kBmpOS2V1Bytes) {
SkCodecPrintf("Error: invalid starting location for pixel data\n");
return false;
}
// The size of the second (info) header in bytes
// The size is the first field of the second header, so we have already
// read the first four infoBytes.
infoBytes = get_int(hBuffer.get(), 14);
if (infoBytes < kBmpOS2V1Bytes) {
SkCodecPrintf("Error: invalid second header size.\n");
return false;
}
} else {
// This value is only used by RLE compression. Bmp in Ico files do not
// use RLE. If the compression field is incorrectly signaled as RLE,
// we will catch this and signal an error below.
totalBytes = 0;
// Bmps in Ico cannot specify an offset. We will always assume that
// pixel data begins immediately after the color table. This value
// will be corrected below.
offset = 0;
// Read the size of the second header
SkAutoTDeleteArray<uint8_t> hBuffer(
SkNEW_ARRAY(uint8_t, 4));
if (stream->read(hBuffer.get(), 4) != 4) {
SkCodecPrintf("Error: unable to read size of second bitmap header.\n");
return false;
}
infoBytes = get_int(hBuffer.get(), 0);
if (infoBytes < kBmpOS2V1Bytes) {
SkCodecPrintf("Error: invalid second header size.\n");
return false;
}
}
// We already read the first four bytes of the info header to get the size
const uint32_t infoBytesRemaining = infoBytes - 4;
// Read the second header
SkAutoTDeleteArray<uint8_t> iBuffer(
SkNEW_ARRAY(uint8_t, infoBytesRemaining));
if (stream->read(iBuffer.get(), infoBytesRemaining) != infoBytesRemaining) {
SkCodecPrintf("Error: unable to read second bitmap header.\n");
return false;
}
// The number of bits used per pixel in the pixel data
uint16_t bitsPerPixel;
// The compression method for the pixel data
uint32_t compression = kNone_BitmapCompressionMethod;
// Number of colors in the color table, defaults to 0 or max (see below)
uint32_t numColors = 0;
// Bytes per color in the color table, early versions use 3, most use 4
uint32_t bytesPerColor;
// The image width and height
int width, height;
// Determine image information depending on second header format
BitmapHeaderType headerType;
if (infoBytes >= kBmpInfoBaseBytes) {
// Check the version of the header
switch (infoBytes) {
case kBmpInfoV1Bytes:
headerType = kInfoV1_BitmapHeaderType;
break;
case kBmpInfoV2Bytes:
headerType = kInfoV2_BitmapHeaderType;
break;
case kBmpInfoV3Bytes:
headerType = kInfoV3_BitmapHeaderType;
break;
case kBmpInfoV4Bytes:
headerType = kInfoV4_BitmapHeaderType;
break;
case kBmpInfoV5Bytes:
headerType = kInfoV5_BitmapHeaderType;
break;
case 16:
case 20:
case 24:
case 28:
case 32:
case 36:
case 42:
case 46:
case 48:
case 60:
case kBmpOS2V2Bytes:
headerType = kOS2VX_BitmapHeaderType;
break;
default:
// We do not signal an error here because there is the
// possibility of new or undocumented bmp header types. Most
// of the newer versions of bmp headers are similar to and
// build off of the older versions, so we may still be able to
// decode the bmp.
SkCodecPrintf("Warning: unknown bmp header format.\n");
headerType = kUnknown_BitmapHeaderType;
break;
}
// We check the size of the header before entering the if statement.
// We should not reach this point unless the size is large enough for
// these required fields.
SkASSERT(infoBytesRemaining >= 12);
width = get_int(iBuffer.get(), 0);
height = get_int(iBuffer.get(), 4);
bitsPerPixel = get_short(iBuffer.get(), 10);
// Some versions do not have these fields, so we check before
// overwriting the default value.
if (infoBytesRemaining >= 16) {
compression = get_int(iBuffer.get(), 12);
if (infoBytesRemaining >= 32) {
numColors = get_int(iBuffer.get(), 28);
}
}
// All of the headers that reach this point, store color table entries
// using 4 bytes per pixel.
bytesPerColor = 4;
} else if (infoBytes >= kBmpOS2V1Bytes) {
// The OS2V1 is treated separately because it has a unique format
headerType = kOS2V1_BitmapHeaderType;
width = (int) get_short(iBuffer.get(), 0);
height = (int) get_short(iBuffer.get(), 2);
bitsPerPixel = get_short(iBuffer.get(), 6);
bytesPerColor = 3;
} else {
// There are no valid bmp headers
SkCodecPrintf("Error: second bitmap header size is invalid.\n");
return false;
}
// Check for valid dimensions from header
RowOrder rowOrder = kBottomUp_RowOrder;
if (height < 0) {
height = -height;
rowOrder = kTopDown_RowOrder;
}
// The height field for bmp in ico is double the actual height because they
// contain an XOR mask followed by an AND mask
if (isIco) {
height /= 2;
}
if (width <= 0 || height <= 0) {
// TODO: Decide if we want to disable really large bmps as well.
// https://code.google.com/p/skia/issues/detail?id=3617
SkCodecPrintf("Error: invalid bitmap dimensions.\n");
return false;
}
// Create mask struct
SkMasks::InputMasks inputMasks;
memset(&inputMasks, 0, sizeof(SkMasks::InputMasks));
// Determine the input compression format and set bit masks if necessary
uint32_t maskBytes = 0;
BitmapInputFormat inputFormat = kUnknown_BitmapInputFormat;
switch (compression) {
case kNone_BitmapCompressionMethod:
inputFormat = kStandard_BitmapInputFormat;
break;
case k8BitRLE_BitmapCompressionMethod:
if (bitsPerPixel != 8) {
SkCodecPrintf("Warning: correcting invalid bitmap format.\n");
bitsPerPixel = 8;
}
inputFormat = kRLE_BitmapInputFormat;
break;
case k4BitRLE_BitmapCompressionMethod:
if (bitsPerPixel != 4) {
SkCodecPrintf("Warning: correcting invalid bitmap format.\n");
bitsPerPixel = 4;
}
inputFormat = kRLE_BitmapInputFormat;
break;
case kAlphaBitMasks_BitmapCompressionMethod:
case kBitMasks_BitmapCompressionMethod:
// Load the masks
inputFormat = kBitMask_BitmapInputFormat;
switch (headerType) {
case kInfoV1_BitmapHeaderType: {
// The V1 header stores the bit masks after the header
SkAutoTDeleteArray<uint8_t> mBuffer(
SkNEW_ARRAY(uint8_t, kBmpMaskBytes));
if (stream->read(mBuffer.get(), kBmpMaskBytes) !=
kBmpMaskBytes) {
SkCodecPrintf("Error: unable to read bit inputMasks.\n");
return false;
}
maskBytes = kBmpMaskBytes;
inputMasks.red = get_int(mBuffer.get(), 0);
inputMasks.green = get_int(mBuffer.get(), 4);
inputMasks.blue = get_int(mBuffer.get(), 8);
break;
}
case kInfoV2_BitmapHeaderType:
case kInfoV3_BitmapHeaderType:
case kInfoV4_BitmapHeaderType:
case kInfoV5_BitmapHeaderType:
// Header types are matched based on size. If the header
// is V2+, we are guaranteed to be able to read at least
// this size.
SkASSERT(infoBytesRemaining >= 48);
inputMasks.red = get_int(iBuffer.get(), 36);
inputMasks.green = get_int(iBuffer.get(), 40);
inputMasks.blue = get_int(iBuffer.get(), 44);
break;
case kOS2VX_BitmapHeaderType:
// TODO: Decide if we intend to support this.
// It is unsupported in the previous version and
// in chromium. I have not come across a test case
// that uses this format.
SkCodecPrintf("Error: huffman format unsupported.\n");
return false;
default:
SkCodecPrintf("Error: invalid bmp bit masks header.\n");
return false;
}
break;
case kJpeg_BitmapCompressionMethod:
if (24 == bitsPerPixel) {
inputFormat = kRLE_BitmapInputFormat;
break;
}
// Fall through
case kPng_BitmapCompressionMethod:
// TODO: Decide if we intend to support this.
// It is unsupported in the previous version and
// in chromium. I think it is used mostly for printers.
SkCodecPrintf("Error: compression format not supported.\n");
return false;
case kCMYK_BitmapCompressionMethod:
case kCMYK8BitRLE_BitmapCompressionMethod:
case kCMYK4BitRLE_BitmapCompressionMethod:
// TODO: Same as above.
SkCodecPrintf("Error: CMYK not supported for bitmap decoding.\n");
return false;
default:
SkCodecPrintf("Error: invalid format for bitmap decoding.\n");
return false;
}
// Most versions of bmps should be rendered as opaque. Either they do
// not have an alpha channel, or they expect the alpha channel to be
// ignored. V3+ bmp files introduce an alpha mask and allow the creator
// of the image to use the alpha channels. However, many of these images
// leave the alpha channel blank and expect to be rendered as opaque. This
// is the case for almost all V3 images, so we render these as opaque. For
// V4+, we will use the alpha channel, and fix the image later if it turns
// out to be fully transparent.
// As an exception, V3 bmp-in-ico may use an alpha mask.
SkAlphaType alphaType = kOpaque_SkAlphaType;
if ((kInfoV3_BitmapHeaderType == headerType && isIco) ||
kInfoV4_BitmapHeaderType == headerType ||
kInfoV5_BitmapHeaderType == headerType) {
// Header types are matched based on size. If the header is
// V3+, we are guaranteed to be able to read at least this size.
SkASSERT(infoBytesRemaining > 52);
inputMasks.alpha = get_int(iBuffer.get(), 48);
if (inputMasks.alpha != 0) {
alphaType = kUnpremul_SkAlphaType;
}
}
iBuffer.free();
// Additionally, 32 bit bmp-in-icos use the alpha channel.
// And, RLE inputs may skip pixels, leaving them as transparent. This
// is uncommon, but we cannot be certain that an RLE bmp will be opaque.
if ((isIco && 32 == bitsPerPixel) || (kRLE_BitmapInputFormat == inputFormat)) {
alphaType = kUnpremul_SkAlphaType;
}
// Check for valid bits per pixel.
// At the same time, use this information to choose a suggested color type
// and to set default masks.
SkColorType colorType = kN32_SkColorType;
switch (bitsPerPixel) {
// In addition to more standard pixel compression formats, bmp supports
// the use of bit masks to determine pixel components. The standard
// format for representing 16-bit colors is 555 (XRRRRRGGGGGBBBBB),
// which does not map well to any Skia color formats. For this reason,
// we will always enable mask mode with 16 bits per pixel.
case 16:
if (kBitMask_BitmapInputFormat != inputFormat) {
inputMasks.red = 0x7C00;
inputMasks.green = 0x03E0;
inputMasks.blue = 0x001F;
inputFormat = kBitMask_BitmapInputFormat;
}
break;
// We want to decode to kIndex_8 for input formats that are already
// designed in index format.
case 1:
case 2:
case 4:
case 8:
// However, we cannot in RLE format since we may need to leave some
// pixels as transparent. Similarly, we also cannot for ICO images
// since we may need to apply a transparent mask.
if (kRLE_BitmapInputFormat != inputFormat && !isIco) {
colorType = kIndex_8_SkColorType;
}
case 24:
case 32:
break;
default:
SkCodecPrintf("Error: invalid input value for bits per pixel.\n");
return false;
}
// Check that input bit masks are valid and create the masks object
SkAutoTDelete<SkMasks>
masks(SkMasks::CreateMasks(inputMasks, bitsPerPixel));
if (NULL == masks) {
SkCodecPrintf("Error: invalid input masks.\n");
return false;
}
// Check for a valid number of total bytes when in RLE mode
if (totalBytes <= offset && kRLE_BitmapInputFormat == inputFormat) {
SkCodecPrintf("Error: RLE requires valid input size.\n");
return false;
}
const size_t RLEBytes = totalBytes - offset;
// Calculate the number of bytes read so far
const uint32_t bytesRead = kBmpHeaderBytes + infoBytes + maskBytes;
if (!isIco && offset < bytesRead) {
SkCodecPrintf("Error: pixel data offset less than header size.\n");
return false;
}
if (codecOut) {
// Return the codec
// We will use ImageInfo to store width, height, suggested color type, and
// suggested alpha type.
const SkImageInfo& imageInfo = SkImageInfo::Make(width, height,
colorType, alphaType);
*codecOut = SkNEW_ARGS(SkBmpCodec, (imageInfo, stream, bitsPerPixel,
inputFormat, masks.detach(),
numColors, bytesPerColor,
offset - bytesRead, rowOrder,
RLEBytes, isIco));
}
return true;
}
/*
*
* Creates a bmp decoder
* Reads enough of the stream to determine the image format
*
*/
SkCodec* SkBmpCodec::NewFromStream(SkStream* stream, bool isIco) {
SkAutoTDelete<SkStream> streamDeleter(stream);
SkCodec* codec = NULL;
if (ReadHeader(stream, isIco, &codec)) {
// codec has taken ownership of stream, so we do not need to
// delete it.
SkASSERT(codec);
streamDeleter.detach();
return codec;
}
return NULL;
}
/*
*
* Creates an instance of the decoder
* Called only by NewFromStream
*
*/
SkBmpCodec::SkBmpCodec(const SkImageInfo& info, SkStream* stream,
uint16_t bitsPerPixel, BitmapInputFormat inputFormat,
SkMasks* masks, uint32_t numColors,
uint32_t bytesPerColor, uint32_t offset,
RowOrder rowOrder, size_t RLEBytes, bool isIco)
: INHERITED(info, stream)
, fBitsPerPixel(bitsPerPixel)
, fInputFormat(inputFormat)
, fMasks(masks)
, fColorTable(NULL)
, fNumColors(numColors)
, fBytesPerColor(bytesPerColor)
, fOffset(offset)
, fRowOrder(rowOrder)
, fRLEBytes(RLEBytes)
, fIsIco(isIco)
{}
/*
*
* Initiates the bitmap decode
*
*/
SkCodec::Result SkBmpCodec::onGetPixels(const SkImageInfo& dstInfo,
void* dst, size_t dstRowBytes,
const Options& opts,
SkPMColor* inputColorPtr,
int* inputColorCount) {
// Check for proper input and output formats
SkCodec::RewindState rewindState = this->rewindIfNeeded();
if (rewindState == kCouldNotRewind_RewindState) {
return kCouldNotRewind;
} else if (rewindState == kRewound_RewindState) {
if (!ReadHeader(this->stream(), fIsIco, NULL)) {
return kCouldNotRewind;
}
}
if (dstInfo.dimensions() != this->getInfo().dimensions()) {
SkCodecPrintf("Error: scaling not supported.\n");
return kInvalidScale;
}
if (!conversion_possible(dstInfo, this->getInfo())) {
SkCodecPrintf("Error: cannot convert input type to output type.\n");
return kInvalidConversion;
}
// Create the color table if necessary and prepare the stream for decode
// Note that if it is non-NULL, inputColorCount will be modified
if (!createColorTable(dstInfo.alphaType(), inputColorCount)) {
SkCodecPrintf("Error: could not create color table.\n");
return kInvalidInput;
}
// Copy the color table to the client if necessary
copy_color_table(dstInfo, fColorTable, inputColorPtr, inputColorCount);
// Perform the decode
switch (fInputFormat) {
case kBitMask_BitmapInputFormat:
return decodeMask(dstInfo, dst, dstRowBytes, opts);
case kRLE_BitmapInputFormat:
return decodeRLE(dstInfo, dst, dstRowBytes, opts);
case kStandard_BitmapInputFormat:
return decode(dstInfo, dst, dstRowBytes, opts);
default:
SkASSERT(false);
return kInvalidInput;
}
}
/*
*
* Process the color table for the bmp input
*
*/
bool SkBmpCodec::createColorTable(SkAlphaType alphaType, int* numColors) {
// Allocate memory for color table
uint32_t colorBytes = 0;
uint32_t maxColors = 0;
SkPMColor colorTable[256];
if (fBitsPerPixel <= 8) {
// Zero is a default for maxColors
// Also set fNumColors to maxColors when it is too large
maxColors = 1 << fBitsPerPixel;
if (fNumColors == 0 || fNumColors >= maxColors) {
fNumColors = maxColors;
}
// Inform the caller of the number of colors
if (NULL != numColors) {
// We set the number of colors to maxColors in order to ensure
// safe memory accesses. Otherwise, an invalid pixel could
// access memory outside of our color table array.
*numColors = maxColors;
}
// Read the color table from the stream
colorBytes = fNumColors * fBytesPerColor;
SkAutoTDeleteArray<uint8_t> cBuffer(SkNEW_ARRAY(uint8_t, colorBytes));
if (stream()->read(cBuffer.get(), colorBytes) != colorBytes) {
SkCodecPrintf("Error: unable to read color table.\n");
return false;
}
// Choose the proper packing function
SkPMColor (*packARGB) (uint32_t, uint32_t, uint32_t, uint32_t);
switch (alphaType) {
case kOpaque_SkAlphaType:
case kUnpremul_SkAlphaType:
packARGB = &SkPackARGB32NoCheck;
break;
case kPremul_SkAlphaType:
packARGB = &SkPreMultiplyARGB;
break;
default:
// This should not be reached because conversion possible
// should fail if the alpha type is not one of the above
// values.
SkASSERT(false);
packARGB = NULL;
break;
}
// Fill in the color table
uint32_t i = 0;
for (; i < fNumColors; i++) {
uint8_t blue = get_byte(cBuffer.get(), i*fBytesPerColor);
uint8_t green = get_byte(cBuffer.get(), i*fBytesPerColor + 1);
uint8_t red = get_byte(cBuffer.get(), i*fBytesPerColor + 2);
uint8_t alpha;
if (kOpaque_SkAlphaType == alphaType || kRLE_BitmapInputFormat == fInputFormat) {
alpha = 0xFF;
} else {
alpha = (fMasks->getAlphaMask() >> 24) &
get_byte(cBuffer.get(), i*fBytesPerColor + 3);
}
colorTable[i] = packARGB(alpha, red, green, blue);
}
// To avoid segmentation faults on bad pixel data, fill the end of the
// color table with black. This is the same the behavior as the
// chromium decoder.
for (; i < maxColors; i++) {
colorTable[i] = SkPackARGB32NoCheck(0xFF, 0, 0, 0);
}
// Set the color table
fColorTable.reset(SkNEW_ARGS(SkColorTable, (colorTable, maxColors)));
}
// Bmp-in-Ico files do not use an offset to indicate where the pixel data
// begins. Pixel data always begins immediately after the color table.
if (!fIsIco) {
// Check that we have not read past the pixel array offset
if(fOffset < colorBytes) {
// This may occur on OS 2.1 and other old versions where the color
// table defaults to max size, and the bmp tries to use a smaller
// color table. This is invalid, and our decision is to indicate
// an error, rather than try to guess the intended size of the
// color table.
SkCodecPrintf("Error: pixel data offset less than color table size.\n");
return false;
}
// After reading the color table, skip to the start of the pixel array
if (stream()->skip(fOffset - colorBytes) != fOffset - colorBytes) {
SkCodecPrintf("Error: unable to skip to image data.\n");
return false;
}
}
// Return true on success
return true;
}
/*
*
* Get the destination row to start filling from
* Used to fill the remainder of the image on incomplete input
*
*/
static inline void* get_dst_start_row(void* dst, size_t dstRowBytes, int32_t y,
SkBmpCodec::RowOrder rowOrder) {
return (SkBmpCodec::kTopDown_RowOrder == rowOrder) ?
SkTAddOffset<void*>(dst, y * dstRowBytes) : dst;
}
/*
*
* Performs the bitmap decoding for bit masks input format
*
*/
SkCodec::Result SkBmpCodec::decodeMask(const SkImageInfo& dstInfo,
void* dst, size_t dstRowBytes,
const Options& opts) {
// Set constant values
const int width = dstInfo.width();
const int height = dstInfo.height();
const size_t rowBytes = SkAlign4(compute_row_bytes(width, fBitsPerPixel));
// Allocate a buffer large enough to hold the full image
SkAutoTDeleteArray<uint8_t>
srcBuffer(SkNEW_ARRAY(uint8_t, height*rowBytes));
uint8_t* srcRow = srcBuffer.get();
// Create the swizzler
SkAutoTDelete<SkMaskSwizzler> maskSwizzler(
SkMaskSwizzler::CreateMaskSwizzler(dstInfo, dst, dstRowBytes,
fMasks, fBitsPerPixel));
// Iterate over rows of the image
bool transparent = true;
for (int y = 0; y < height; y++) {
// Read a row of the input
if (stream()->read(srcRow, rowBytes) != rowBytes) {
SkCodecPrintf("Warning: incomplete input stream.\n");
// Fill the destination image on failure
SkPMColor fillColor = dstInfo.alphaType() == kOpaque_SkAlphaType ?
SK_ColorBLACK : SK_ColorTRANSPARENT;
if (kNo_ZeroInitialized == opts.fZeroInitialized || 0 != fillColor) {
void* dstStart = get_dst_start_row(dst, dstRowBytes, y, fRowOrder);
SkSwizzler::Fill(dstStart, dstInfo, dstRowBytes, dstInfo.height() - y, fillColor,
NULL);
}
return kIncompleteInput;
}
// Decode the row in destination format
int row = kBottomUp_RowOrder == fRowOrder ? height - 1 - y : y;
SkSwizzler::ResultAlpha r = maskSwizzler->next(srcRow, row);
transparent &= SkSwizzler::IsTransparent(r);
// Move to the next row
srcRow = SkTAddOffset<uint8_t>(srcRow, rowBytes);
}
// Some fully transparent bmp images are intended to be opaque. Here, we
// correct for this possibility.
if (transparent) {
const SkImageInfo& opaqueInfo =
dstInfo.makeAlphaType(kOpaque_SkAlphaType);
SkAutoTDelete<SkMaskSwizzler> opaqueSwizzler(
SkMaskSwizzler::CreateMaskSwizzler(opaqueInfo, dst, dstRowBytes,
fMasks, fBitsPerPixel));
srcRow = srcBuffer.get();
for (int y = 0; y < height; y++) {
// Decode the row in opaque format
int row = kBottomUp_RowOrder == fRowOrder ? height - 1 - y : y;
opaqueSwizzler->next(srcRow, row);
// Move to the next row
srcRow = SkTAddOffset<uint8_t>(srcRow, rowBytes);
}
}
// Finished decoding the entire image
return kSuccess;
}
/*
*
* Set an RLE pixel using the color table
*
*/
void SkBmpCodec::setRLEPixel(void* dst, size_t dstRowBytes,
const SkImageInfo& dstInfo, uint32_t x, uint32_t y,
uint8_t index) {
// Set the row
int height = dstInfo.height();
int row;
if (kBottomUp_RowOrder == fRowOrder) {
row = height - y - 1;
} else {
row = y;
}
// Set the pixel based on destination color type
switch (dstInfo.colorType()) {
case kN32_SkColorType: {
SkPMColor* dstRow = SkTAddOffset<SkPMColor>((SkPMColor*) dst,
row * (int) dstRowBytes);
dstRow[x] = fColorTable->operator[](index);
break;
}
default:
// This case should not be reached. We should catch an invalid
// color type when we check that the conversion is possible.
SkASSERT(false);
break;
}
}
/*
*
* Set an RLE pixel from R, G, B values
*
*/
void SkBmpCodec::setRLE24Pixel(void* dst, size_t dstRowBytes,
const SkImageInfo& dstInfo, uint32_t x,
uint32_t y, uint8_t red, uint8_t green,
uint8_t blue) {
// Set the row
int height = dstInfo.height();
int row;
if (kBottomUp_RowOrder == fRowOrder) {
row = height - y - 1;
} else {
row = y;
}
// Set the pixel based on destination color type
switch (dstInfo.colorType()) {
case kN32_SkColorType: {
SkPMColor* dstRow = SkTAddOffset<SkPMColor>((SkPMColor*) dst,
row * (int) dstRowBytes);
dstRow[x] = SkPackARGB32NoCheck(0xFF, red, green, blue);
break;
}
default:
// This case should not be reached. We should catch an invalid
// color type when we check that the conversion is possible.
SkASSERT(false);
break;
}
}
/*
*
* Performs the bitmap decoding for RLE input format
* RLE decoding is performed all at once, rather than a one row at a time
*
*/
SkCodec::Result SkBmpCodec::decodeRLE(const SkImageInfo& dstInfo,
void* dst, size_t dstRowBytes,
const Options& opts) {
// Set RLE flags
static const uint8_t RLE_ESCAPE = 0;
static const uint8_t RLE_EOL = 0;
static const uint8_t RLE_EOF = 1;
static const uint8_t RLE_DELTA = 2;
// Set constant values
const int width = dstInfo.width();
const int height = dstInfo.height();
// Input buffer parameters
uint32_t currByte = 0;
SkAutoTDeleteArray<uint8_t> buffer(SkNEW_ARRAY(uint8_t, fRLEBytes));
size_t totalBytes = stream()->read(buffer.get(), fRLEBytes);
if (totalBytes < fRLEBytes) {
SkCodecPrintf("Warning: incomplete RLE file.\n");
} else if (totalBytes <= 0) {
SkCodecPrintf("Error: could not read RLE image data.\n");
return kInvalidInput;
}
// Destination parameters
int x = 0;
int y = 0;
// Set the background as transparent. Then, if the RLE code skips pixels,
// the skipped pixels will be transparent.
// Because of the need for transparent pixels, kN32 is the only color
// type that makes sense for the destination format.
SkASSERT(kN32_SkColorType == dstInfo.colorType());
if (kNo_ZeroInitialized == opts.fZeroInitialized) {
SkSwizzler::Fill(dst, dstInfo, dstRowBytes, height, SK_ColorTRANSPARENT, NULL);
}
while (true) {
// Every entry takes at least two bytes
if ((int) totalBytes - currByte < 2) {
SkCodecPrintf("Warning: incomplete RLE input.\n");
return kIncompleteInput;
}
// Read the next two bytes. These bytes have different meanings
// depending on their values. In the first interpretation, the first
// byte is an escape flag and the second byte indicates what special
// task to perform.
const uint8_t flag = buffer.get()[currByte++];
const uint8_t task = buffer.get()[currByte++];
// If we have reached a row that is beyond the image size, and the RLE
// code does not indicate end of file, abort and signal a warning.
if (y >= height && (flag != RLE_ESCAPE || (task != RLE_EOF))) {
SkCodecPrintf("Warning: invalid RLE input.\n");
return kIncompleteInput;
}
// Perform decoding
if (RLE_ESCAPE == flag) {
switch (task) {
case RLE_EOL:
x = 0;
y++;
break;
case RLE_EOF:
return kSuccess;
case RLE_DELTA: {
// Two bytes are needed to specify delta
if ((int) totalBytes - currByte < 2) {
SkCodecPrintf("Warning: incomplete RLE input\n");
return kIncompleteInput;
}
// Modify x and y
const uint8_t dx = buffer.get()[currByte++];
const uint8_t dy = buffer.get()[currByte++];
x += dx;
y += dy;
if (x > width || y > height) {
SkCodecPrintf("Warning: invalid RLE input.\n");
return kIncompleteInput;
}
break;
}
default: {
// If task does not match any of the above signals, it
// indicates that we have a sequence of non-RLE pixels.
// Furthermore, the value of task is equal to the number
// of pixels to interpret.
uint8_t numPixels = task;
const size_t rowBytes = compute_row_bytes(numPixels,
fBitsPerPixel);
// Abort if setting numPixels moves us off the edge of the
// image. Also abort if there are not enough bytes
// remaining in the stream to set numPixels.
if (x + numPixels > width ||
(int) totalBytes - currByte < SkAlign2(rowBytes)) {
SkCodecPrintf("Warning: invalid RLE input.\n");
return kIncompleteInput;
}
// Set numPixels number of pixels
while (numPixels > 0) {
switch(fBitsPerPixel) {
case 4: {
SkASSERT(currByte < totalBytes);
uint8_t val = buffer.get()[currByte++];
setRLEPixel(dst, dstRowBytes, dstInfo, x++,
y, val >> 4);
numPixels--;
if (numPixels != 0) {
setRLEPixel(dst, dstRowBytes, dstInfo,
x++, y, val & 0xF);
numPixels--;
}
break;
}
case 8:
SkASSERT(currByte < totalBytes);
setRLEPixel(dst, dstRowBytes, dstInfo, x++,
y, buffer.get()[currByte++]);
numPixels--;
break;
case 24: {
SkASSERT(currByte + 2 < totalBytes);
uint8_t blue = buffer.get()[currByte++];
uint8_t green = buffer.get()[currByte++];
uint8_t red = buffer.get()[currByte++];
setRLE24Pixel(dst, dstRowBytes, dstInfo,
x++, y, red, green, blue);
numPixels--;
}
default:
SkASSERT(false);
return kInvalidInput;
}
}
// Skip a byte if necessary to maintain alignment
if (!SkIsAlign2(rowBytes)) {
currByte++;
}
break;
}
}
} else {
// If the first byte read is not a flag, it indicates the number of
// pixels to set in RLE mode.
const uint8_t numPixels = flag;
const int endX = SkTMin<int>(x + numPixels, width);
if (24 == fBitsPerPixel) {
// In RLE24, the second byte read is part of the pixel color.
// There are two more required bytes to finish encoding the
// color.
if ((int) totalBytes - currByte < 2) {
SkCodecPrintf("Warning: incomplete RLE input\n");
return kIncompleteInput;
}
// Fill the pixels up to endX with the specified color
uint8_t blue = task;
uint8_t green = buffer.get()[currByte++];
uint8_t red = buffer.get()[currByte++];
while (x < endX) {
setRLE24Pixel(dst, dstRowBytes, dstInfo, x++, y, red,
green, blue);
}
} else {
// In RLE8 or RLE4, the second byte read gives the index in the
// color table to look up the pixel color.
// RLE8 has one color index that gets repeated
// RLE4 has two color indexes in the upper and lower 4 bits of
// the bytes, which are alternated
uint8_t indices[2] = { task, task };
if (4 == fBitsPerPixel) {
indices[0] >>= 4;
indices[1] &= 0xf;
}
// Set the indicated number of pixels
for (int which = 0; x < endX; x++) {
setRLEPixel(dst, dstRowBytes, dstInfo, x, y,
indices[which]);
which = !which;
}
}
}
}
}
/*
*
* Performs the bitmap decoding for standard input format
*
*/
SkCodec::Result SkBmpCodec::decode(const SkImageInfo& dstInfo,
void* dst, size_t dstRowBytes,
const Options& opts) {
// Set constant values
const int width = dstInfo.width();
const int height = dstInfo.height();
const size_t rowBytes = SkAlign4(compute_row_bytes(width, fBitsPerPixel));
// Get swizzler configuration and choose the fill value for failures. We will use
// zero as the default palette index, black for opaque images, and transparent for
// non-opaque images.
SkSwizzler::SrcConfig config;
uint32_t fillColorOrIndex;
bool zeroFill = true;
switch (fBitsPerPixel) {
case 1:
config = SkSwizzler::kIndex1;
fillColorOrIndex = 0;
break;
case 2:
config = SkSwizzler::kIndex2;
fillColorOrIndex = 0;
break;
case 4:
config = SkSwizzler::kIndex4;
fillColorOrIndex = 0;
break;
case 8:
config = SkSwizzler::kIndex;
fillColorOrIndex = 0;
break;
case 24:
config = SkSwizzler::kBGR;
fillColorOrIndex = SK_ColorBLACK;
zeroFill = false;
break;
case 32:
if (kOpaque_SkAlphaType == dstInfo.alphaType()) {
config = SkSwizzler::kBGRX;
fillColorOrIndex = SK_ColorBLACK;
zeroFill = false;
} else {
config = SkSwizzler::kBGRA;
fillColorOrIndex = SK_ColorTRANSPARENT;
}
break;
default:
SkASSERT(false);
return kInvalidInput;
}
// Get a pointer to the color table if it exists
const SkPMColor* colorPtr = NULL != fColorTable.get() ? fColorTable->readColors() : NULL;
// Create swizzler
SkAutoTDelete<SkSwizzler> swizzler(SkSwizzler::CreateSwizzler(config,
colorPtr, dstInfo, dst, dstRowBytes,
SkImageGenerator::kNo_ZeroInitialized));
// Allocate space for a row buffer and a source for the swizzler
SkAutoTDeleteArray<uint8_t> srcBuffer(SkNEW_ARRAY(uint8_t, rowBytes));
// Iterate over rows of the image
// FIXME: bool transparent = true;
for (int y = 0; y < height; y++) {
// Read a row of the input
if (stream()->read(srcBuffer.get(), rowBytes) != rowBytes) {
SkCodecPrintf("Warning: incomplete input stream.\n");
// Fill the destination image on failure
if (kNo_ZeroInitialized == opts.fZeroInitialized || !zeroFill) {
void* dstStart = get_dst_start_row(dst, dstRowBytes, y, fRowOrder);
SkSwizzler::Fill(dstStart, dstInfo, dstRowBytes, dstInfo.height() - y,
fillColorOrIndex, colorPtr);
}
return kIncompleteInput;
}
// Decode the row in destination format
uint32_t row;
if (kTopDown_RowOrder == fRowOrder) {
row = y;
} else {
row = height - 1 - y;
}
swizzler->next(srcBuffer.get(), row);
// FIXME: SkSwizzler::ResultAlpha r =
// swizzler->next(srcBuffer.get(), row);
// FIXME: transparent &= SkSwizzler::IsTransparent(r);
}
// FIXME: This code exists to match the behavior in the chromium decoder
// and to follow the bmp specification as it relates to alpha masks. It is
// commented out because we have yet to discover a test image that provides
// an alpha mask and uses this decode mode.
// Now we adjust the output image with some additional behavior that
// SkSwizzler does not support. Firstly, all bmp images that contain
// alpha are masked by the alpha mask. Secondly, many fully transparent
// bmp images are intended to be opaque. Here, we make those corrections
// in the kN32 case.
/*
SkPMColor* dstRow = (SkPMColor*) dst;
if (SkSwizzler::kBGRA == config) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (transparent) {
dstRow[x] |= 0xFF000000;
} else {
dstRow[x] &= alphaMask;
}
dstRow = SkTAddOffset<SkPMColor>(dstRow, dstRowBytes);
}
}
}
*/
// Finally, apply the AND mask for bmp-in-ico images
if (fIsIco) {
// The AND mask is always 1 bit per pixel
const size_t rowBytes = SkAlign4(compute_row_bytes(width, 1));
SkPMColor* dstPtr = (SkPMColor*) dst;
for (int y = 0; y < height; y++) {
// The srcBuffer will at least be large enough
if (stream()->read(srcBuffer.get(), rowBytes) != rowBytes) {
SkCodecPrintf("Warning: incomplete AND mask for bmp-in-ico.\n");
return kIncompleteInput;
}
int row;
if (kBottomUp_RowOrder == fRowOrder) {
row = height - y - 1;
} else {
row = y;
}
SkPMColor* dstRow =
SkTAddOffset<SkPMColor>(dstPtr, row * dstRowBytes);
for (int x = 0; x < width; x++) {
int quotient;
int modulus;
SkTDivMod(x, 8, "ient, &modulus);
uint32_t shift = 7 - modulus;
uint32_t alphaBit =
(srcBuffer.get()[quotient] >> shift) & 0x1;
dstRow[x] &= alphaBit - 1;
}
}
}
// Finished decoding the entire image
return kSuccess;
}