/* * Defines the version and type of the second bitmap header
*/ enum BmpHeaderType {
kInfoV1_BmpHeaderType,
kInfoV2_BmpHeaderType,
kInfoV3_BmpHeaderType,
kInfoV4_BmpHeaderType,
kInfoV5_BmpHeaderType,
kOS2V1_BmpHeaderType,
kOS2VX_BmpHeaderType,
kUnknown_BmpHeaderType
};
/* * Used to define the input format of the bmp
*/ enum BmpInputFormat {
kStandard_BmpInputFormat,
kRLE_BmpInputFormat,
kBitMask_BmpInputFormat,
kUnknown_BmpInputFormat
};
/* * Checks the start of the stream to see if the image is a bitmap
*/ bool SkBmpCodec::IsBmp(constvoid* buffer, size_t bytesRead) { // TODO: Support "IC", "PT", "CI", "CP", "BA" constchar bmpSig[] = { 'B', 'M' }; return bytesRead >= 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
*/
std::unique_ptr<SkCodec> SkBmpCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
Result* result) { return SkBmpCodec::MakeFromStream(std::move(stream), result, false);
}
/* * Creates a bmp decoder for a bmp embedded in ico * Reads enough of the stream to determine the image format
*/
std::unique_ptr<SkCodec> SkBmpCodec::MakeFromIco(std::unique_ptr<SkStream> stream, Result* result) { return SkBmpCodec::MakeFromStream(std::move(stream), result, true);
}
static BmpHeaderType get_header_type(size_t infoBytes) { if (infoBytes >= kBmpInfoBaseBytes) { // Check the version of the header switch (infoBytes) { case kBmpInfoV1Bytes: return kInfoV1_BmpHeaderType; case kBmpInfoV2Bytes: return kInfoV2_BmpHeaderType; case kBmpInfoV3Bytes: return kInfoV3_BmpHeaderType; case kBmpInfoV4Bytes: return kInfoV4_BmpHeaderType; case kBmpInfoV5Bytes: return kInfoV5_BmpHeaderType; case 16: case 20: case 24: case 28: case 32: case 36: case 42: case 46: case 48: case 60: case kBmpOS2V2Bytes: return kOS2VX_BmpHeaderType; default:
SkCodecPrintf("Error: unknown bmp header format.\n"); return kUnknown_BmpHeaderType;
}
} if (infoBytes >= kBmpOS2V1Bytes) { // The OS2V1 is treated separately because it has a unique format return kOS2V1_BmpHeaderType;
} else { // There are no valid bmp headers
SkCodecPrintf("Error: second bitmap header size is invalid.\n"); return kUnknown_BmpHeaderType;
}
}
SkCodec::Result SkBmpCodec::ReadHeader(SkStream* stream, bool inIco,
std::unique_ptr<SkCodec>* codecOut) { // 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 (!inIco) { // Read the first header and the size of the second header
uint8_t hBuffer[kBmpHeaderBytesPlusFour]; if (stream->read(hBuffer, kBmpHeaderBytesPlusFour) !=
kBmpHeaderBytesPlusFour) {
SkCodecPrintf("Error: unable to read first bitmap header.\n"); return kIncompleteInput;
}
// 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, 14); if (infoBytes < kBmpOS2V1Bytes) {
SkCodecPrintf("Error: invalid second header size.\n"); return kInvalidInput;
}
} 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
uint8_t hBuffer[4]; if (stream->read(hBuffer, 4) != 4) {
SkCodecPrintf("Error: unable to read size of second bitmap header.\n"); return kIncompleteInput;
}
infoBytes = get_int(hBuffer, 0); if (infoBytes < kBmpOS2V1Bytes) {
SkCodecPrintf("Error: invalid second header size.\n"); return kInvalidInput;
}
}
// Determine image information depending on second header format const BmpHeaderType headerType = get_header_type(infoBytes); if (kUnknown_BmpHeaderType == headerType) { return kInvalidInput;
}
// 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
std::unique_ptr<uint8_t[]> iBuffer(new uint8_t[infoBytesRemaining]); if (stream->read(iBuffer.get(), infoBytesRemaining) != infoBytesRemaining) {
SkCodecPrintf("Error: unable to read second bitmap header.\n"); return kIncompleteInput;
}
// 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_BmpCompressionMethod;
// 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;
switch (headerType) { case kInfoV1_BmpHeaderType: case kInfoV2_BmpHeaderType: case kInfoV3_BmpHeaderType: case kInfoV4_BmpHeaderType: case kInfoV5_BmpHeaderType: case kOS2VX_BmpHeaderType: // 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; break; case kOS2V1_BmpHeaderType: // The OS2V1 is treated separately because it has a unique format
width = (int) get_short(iBuffer.get(), 0);
height = (int) get_short(iBuffer.get(), 2);
bitsPerPixel = get_short(iBuffer.get(), 6);
bytesPerColor = 3; break; case kUnknown_BmpHeaderType: // We'll exit above in this case.
SkASSERT(false); return kInvalidInput;
}
// Check for valid dimensions from header
SkCodec::SkScanlineOrder rowOrder = SkCodec::kBottomUp_SkScanlineOrder; if (height < 0) { // We can't negate INT32_MIN. if (height == INT32_MIN) { return kInvalidInput;
}
height = -height;
rowOrder = SkCodec::kTopDown_SkScanlineOrder;
} // The height field for bmp in ico is double the actual height because they // contain an XOR mask followed by an AND mask if (inIco) {
height /= 2;
}
// Determine the input compression format and set bit masks if necessary
uint32_t maskBytes = 0;
BmpInputFormat inputFormat = kUnknown_BmpInputFormat; switch (compression) { case kNone_BmpCompressionMethod:
inputFormat = kStandard_BmpInputFormat;
// 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. if (16 == bitsPerPixel) {
inputMasks.red = 0x7C00;
inputMasks.green = 0x03E0;
inputMasks.blue = 0x001F;
inputFormat = kBitMask_BmpInputFormat;
} break; case k8BitRLE_BmpCompressionMethod: if (bitsPerPixel != 8) {
SkCodecPrintf("Warning: correcting invalid bitmap format.\n");
bitsPerPixel = 8;
}
inputFormat = kRLE_BmpInputFormat; break; case k4BitRLE_BmpCompressionMethod: if (bitsPerPixel != 4) {
SkCodecPrintf("Warning: correcting invalid bitmap format.\n");
bitsPerPixel = 4;
}
inputFormat = kRLE_BmpInputFormat; break; case kAlphaBitMasks_BmpCompressionMethod: case kBitMasks_BmpCompressionMethod: // Load the masks
inputFormat = kBitMask_BmpInputFormat; switch (headerType) { case kInfoV1_BmpHeaderType: { // The V1 header stores the bit masks after the header
uint8_t buffer[kBmpMaskBytes]; if (stream->read(buffer, kBmpMaskBytes) != kBmpMaskBytes) {
SkCodecPrintf("Error: unable to read bit inputMasks.\n"); return kIncompleteInput;
}
maskBytes = kBmpMaskBytes;
inputMasks.red = get_int(buffer, 0);
inputMasks.green = get_int(buffer, 4);
inputMasks.blue = get_int(buffer, 8); break;
} case kInfoV2_BmpHeaderType: case kInfoV3_BmpHeaderType: case kInfoV4_BmpHeaderType: case kInfoV5_BmpHeaderType: // 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);
// 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 ignore the alpha mask. For V4+ // images in kMask mode, we will use the alpha mask. Additionally, V3 // bmp-in-ico expect us to use the alpha mask. // // skbug.com/4116: We should perhaps also apply the alpha mask in kStandard // mode. We just haven't seen any images that expect this // behavior. // // 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); break; case kOS2VX_BmpHeaderType: // 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 kUnimplemented; default:
SkCodecPrintf("Error: invalid bmp bit masks header.\n"); return kInvalidInput;
} break; case kJpeg_BmpCompressionMethod: if (24 == bitsPerPixel) {
inputFormat = kRLE_BmpInputFormat; break;
}
[[fallthrough]]; case kPng_BmpCompressionMethod: // 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 kUnimplemented; case kCMYK_BmpCompressionMethod: case kCMYK8BitRLE_BmpCompressionMethod: case kCMYK4BitRLE_BmpCompressionMethod: // TODO: Same as above.
SkCodecPrintf("Error: CMYK not supported for bitmap decoding.\n"); return kUnimplemented; default:
SkCodecPrintf("Error: invalid format for bitmap decoding.\n"); return kInvalidInput;
}
iBuffer.reset();
// Calculate the number of bytes read so far const uint32_t bytesRead = kBmpHeaderBytes + infoBytes + maskBytes; if (!inIco && offset < bytesRead) { // TODO (msarett): Do we really want to fail if the offset in the header is invalid? // Seems like we can just assume that the offset is zero and try to decode? // Maybe we don't want to try to decode corrupt images?
SkCodecPrintf("Error: pixel data offset less than header size.\n"); return kInvalidInput;
}
switch (inputFormat) { case kStandard_BmpInputFormat: { // BMPs are generally opaque, however BMPs-in-ICOs may contain // a transparency mask after the image. Therefore, we mark the // alpha as kBinary if the BMP is contained in an ICO. // We use |isOpaque| to indicate if the BMP itself is opaque.
SkEncodedInfo::Alpha alpha = inIco ? SkEncodedInfo::kBinary_Alpha :
SkEncodedInfo::kOpaque_Alpha; bool isOpaque = true;
SkEncodedInfo::Color color;
uint8_t bitsPerComponent; switch (bitsPerPixel) { // Palette formats case 1: case 2: case 4: case 8: // In the case of ICO, kBGRA is actually the closest match, // since we will need to apply a transparency mask. if (inIco) {
color = SkEncodedInfo::kBGRA_Color;
bitsPerComponent = 8;
} else {
color = SkEncodedInfo::kPalette_Color;
bitsPerComponent = (uint8_t) bitsPerPixel;
} break; case 24: // In the case of ICO, kBGRA is actually the closest match, // since we will need to apply a transparency mask.
color = inIco ? SkEncodedInfo::kBGRA_Color : SkEncodedInfo::kBGR_Color;
bitsPerComponent = 8; break; case 32: // 32-bit BMP-in-ICOs actually use the alpha channel in place of a // transparency mask. if (inIco) {
isOpaque = false;
alpha = SkEncodedInfo::kUnpremul_Alpha;
color = SkEncodedInfo::kBGRA_Color;
} else {
color = SkEncodedInfo::kBGRX_Color;
}
bitsPerComponent = 8; break; default:
SkCodecPrintf("Error: invalid input value for bits per pixel.\n"); return kInvalidInput;
}
if (codecOut) { // We require streams to have a memory base for Bmp-in-Ico decodes.
SkASSERT(!inIco || nullptr != stream->getMemoryBase());
// Set the image info and create a codec. auto info = SkEncodedInfo::Make(width, height, color, alpha, bitsPerComponent);
*codecOut = std::make_unique<SkBmpStandardCodec>(std::move(info),
std::unique_ptr<SkStream>(stream),
bitsPerPixel, numColors, bytesPerColor,
offset - bytesRead, rowOrder, isOpaque,
inIco); returnstatic_cast<SkBmpStandardCodec*>(codecOut->get())->didCreateSrcBuffer()
? kSuccess : kInvalidInput;
} return kSuccess;
}
case kBitMask_BmpInputFormat: { // Bmp-in-Ico must be standard mode if (inIco) {
SkCodecPrintf("Error: Icos may not use bit mask format.\n"); return kInvalidInput;
}
switch (bitsPerPixel) { case 16: case 24: case 32: break; default:
SkCodecPrintf("Error: invalid input value for bits per pixel.\n"); return kInvalidInput;
}
// Skip to the start of the pixel array. // We can do this here because there is no color table to read // in bit mask mode. if (stream->skip(offset - bytesRead) != offset - bytesRead) {
SkCodecPrintf("Error: unable to skip to image data.\n"); return kIncompleteInput;
}
if (codecOut) { // Check that input bit masks are valid and create the masks object
SkASSERT(bitsPerPixel % 8 == 0);
std::unique_ptr<SkMasks> masks(SkMasks::CreateMasks(inputMasks, bitsPerPixel/8)); if (nullptr == masks) {
SkCodecPrintf("Error: invalid input masks.\n"); return kInvalidInput;
}
// Masked bmps are not a great fit for SkEncodedInfo, since they have // arbitrary component orderings and bits per component. Here we choose // somewhat reasonable values - it's ok that we don't match exactly // because SkBmpMaskCodec has its own mask swizzler anyway.
SkEncodedInfo::Color color;
SkEncodedInfo::Alpha alpha; if (masks->getAlphaMask()) {
color = SkEncodedInfo::kBGRA_Color;
alpha = SkEncodedInfo::kUnpremul_Alpha;
} else {
color = SkEncodedInfo::kBGR_Color;
alpha = SkEncodedInfo::kOpaque_Alpha;
} auto info = SkEncodedInfo::Make(width, height, color, alpha, 8);
*codecOut = std::make_unique<SkBmpMaskCodec>(std::move(info),
std::unique_ptr<SkStream>(stream), bitsPerPixel,
masks.release(), rowOrder); returnstatic_cast<SkBmpMaskCodec*>(codecOut->get())->didCreateSrcBuffer()
? kSuccess : kInvalidInput;
} return kSuccess;
}
case kRLE_BmpInputFormat: { // We should not reach this point without a valid value of bitsPerPixel.
SkASSERT(4 == bitsPerPixel || 8 == bitsPerPixel || 24 == bitsPerPixel);
// Check for a valid number of total bytes when in RLE mode if (totalBytes <= offset) {
SkCodecPrintf("Error: RLE requires valid input size.\n"); return kInvalidInput;
}
// Bmp-in-Ico must be standard mode // When inIco is true, this line cannot be reached, since we // require that RLE Bmps have a valid number of totalBytes, and // Icos skip the header that contains totalBytes.
SkASSERT(!inIco);
if (codecOut) { // RLE inputs may skip pixels, leaving them as transparent. This // is uncommon, but we cannot be certain that an RLE bmp will be // opaque or that we will be able to represent it with a palette. // For that reason, we always indicate that we are kBGRA. auto info = SkEncodedInfo::Make(width, height, SkEncodedInfo::kBGRA_Color,
SkEncodedInfo::kBinary_Alpha, 8);
*codecOut = std::make_unique<SkBmpRLECodec>(std::move(info),
std::unique_ptr<SkStream>(stream), bitsPerPixel,
numColors, bytesPerColor, offset - bytesRead,
rowOrder);
} return kSuccess;
} default:
SkASSERT(false); return kInvalidInput;
}
}
/* * Creates a bmp decoder * Reads enough of the stream to determine the image format
*/
std::unique_ptr<SkCodec> SkBmpCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
Result* result, bool inIco) {
SkASSERT(result); if (!stream) {
*result = SkCodec::kInvalidInput; return nullptr;
}
std::unique_ptr<SkCodec> codec;
*result = ReadHeader(stream.get(), inIco, &codec); if (codec) { // codec has taken ownership of stream, so we do not need to delete it.
stream.release();
} return kSuccess == *result ? std::move(codec) : nullptr;
}
int SkBmpCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { // Create a new image info representing the portion of the image to decode
SkImageInfo rowInfo = this->dstInfo().makeWH(this->dstInfo().width(), count);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.