Index: src/codec/SkCodec_libbmp.cpp |
diff --git a/src/codec/SkCodec_libbmp.cpp b/src/codec/SkCodec_libbmp.cpp |
index 5b9691c087c84ae9674b29e3b6a5cb1279b9ac1b..ef97082d61853735904ddd2ae643903a282c92d8 100644 |
--- a/src/codec/SkCodec_libbmp.cpp |
+++ b/src/codec/SkCodec_libbmp.cpp |
@@ -18,9 +18,9 @@ |
*/ |
static bool conversion_possible(const SkImageInfo& dst, |
const SkImageInfo& src) { |
- // All of the swizzles convert to kN32 |
- // TODO: Update this when more swizzles are supported |
- if (kN32_SkColorType != dst.colorType()) { |
+ // Check for supported color types |
+ if (kN32_SkColorType != dst.colorType() && |
+ kRGB_565_SkColorType != dst.colorType()) { |
return false; |
} |
// Support the swizzle if the requested alpha type is the same as our guess |
@@ -28,8 +28,9 @@ static bool conversion_possible(const SkImageInfo& dst, |
if (src.alphaType() == dst.alphaType()) { |
return true; |
} |
- // TODO: Support more swizzles, especially premul |
- return false; |
+ |
+ // Support premul as the destination when the source is unpremul |
+ return premul_and_unpremul(dst.alphaType(), src.alphaType()); |
} |
/* |
@@ -392,51 +393,23 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) { |
return NULL; |
} |
- // Process the color table |
- uint32_t colorBytes = 0; |
- SkPMColor* colorTable = NULL; |
- if (bitsPerPixel < 16) { |
- // Verify the number of colors for the color table |
- const uint32_t maxColors = 1 << bitsPerPixel; |
- // Zero is a default for maxColors |
- // Also set numColors to maxColors when input is too large |
- if (numColors <= 0 || numColors > maxColors) { |
- numColors = maxColors; |
- } |
- colorTable = SkNEW_ARRAY(SkPMColor, maxColors); |
+ // Verify the number of colors in the color table |
+ const uint32_t maxColors = 1 << bitsPerPixel; |
scroggo
2015/03/17 13:27:22
Could you move all of this into SkBmpCodec's onGet
msarett
2015/03/17 16:54:05
Doing these checks in onGetPixels actually causes
scroggo
2015/03/17 20:02:58
I think we can get around this...
How about this:
msarett
2015/03/18 13:02:52
Yes great! That's a much better way to do it.
|
+ // Zero is a default for maxColors |
+ // Also set numColors to maxColors when input is too large |
+ if (numColors <= 0 || numColors > maxColors) { |
+ numColors = maxColors; |
+ } |
- // Construct the color table |
+ // Account for the color table size |
+ uint32_t colorBytes; |
+ if (bitsPerPixel < 16) { |
colorBytes = numColors * bytesPerColor; |
- SkAutoTDeleteArray<uint8_t> cBuffer(SkNEW_ARRAY(uint8_t, colorBytes)); |
- if (stream->read(cBuffer.get(), colorBytes) != colorBytes) { |
- SkDebugf("Error: unable to read color table.\n"); |
- return NULL; |
- } |
- |
- // Fill in the color table (colors are stored unpremultiplied) |
- uint32_t i = 0; |
- for (; i < numColors; i++) { |
- uint8_t blue = get_byte(cBuffer.get(), i*bytesPerColor); |
- uint8_t green = get_byte(cBuffer.get(), i*bytesPerColor + 1); |
- uint8_t red = get_byte(cBuffer.get(), i*bytesPerColor + 2); |
- uint8_t alpha = 0xFF; |
- if (kOpaque_SkAlphaType != alphaType) { |
- alpha = (inputMasks.alpha >> 24) & |
- get_byte(cBuffer.get(), i*bytesPerColor + 3); |
- } |
- // Store the unpremultiplied color |
- colorTable[i] = SkPackARGB32NoCheck(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); |
- } |
+ } else { |
+ colorBytes = 0; |
} |
- // Ensure that the stream now points to the start of the pixel array |
+ // Calculate the number of bytes read so far (including the color table) |
uint32_t bytesRead = kBmpHeaderBytes + infoBytes + maskBytes + colorBytes; |
// Check that we have not read past the pixel array offset |
@@ -444,18 +417,11 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) { |
// 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 and |
- // rewind the stream to display the image. |
+ // rather than try to guess the intended size of the color table. |
SkDebugf("Error: pixel data offset less than header size.\n"); |
return NULL; |
} |
- // Skip to the start of the pixel array |
- if (stream->skip(offset - bytesRead) != offset - bytesRead) { |
- SkDebugf("Error: unable to skip to image data.\n"); |
- return NULL; |
- } |
- |
// Remaining bytes is only used for RLE |
const int remainingBytes = totalBytes - offset; |
if (remainingBytes <= 0 && kRLE_BitmapInputFormat == inputFormat) { |
@@ -465,13 +431,13 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) { |
// Return the codec |
// We will use ImageInfo to store width, height, and alpha type. We will |
- // choose kN32_SkColorType as the input color type because that is the |
- // expected choice for a destination color type. In reality, the input |
- // color type has many possible formats. |
+ // set color type to kN32_SkColorType because that should be the default |
+ // output. |
const SkImageInfo& imageInfo = SkImageInfo::Make(width, height, |
kN32_SkColorType, alphaType); |
return SkNEW_ARGS(SkBmpCodec, (imageInfo, stream, bitsPerPixel, |
- inputFormat, masks.detach(), colorTable, |
+ inputFormat, masks.detach(), numColors, |
+ bytesPerColor, offset - bytesRead, |
rowOrder, remainingBytes)); |
} |
@@ -483,14 +449,17 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) { |
*/ |
SkBmpCodec::SkBmpCodec(const SkImageInfo& info, SkStream* stream, |
uint16_t bitsPerPixel, BitmapInputFormat inputFormat, |
- SkMasks* masks, SkPMColor* colorTable, |
- RowOrder rowOrder, |
- const uint32_t remainingBytes) |
+ SkMasks* masks, uint32_t numColors, |
+ uint32_t bytesPerColor, uint32_t offset, |
+ RowOrder rowOrder, uint32_t remainingBytes) |
: INHERITED(info, stream) |
, fBitsPerPixel(bitsPerPixel) |
, fInputFormat(inputFormat) |
, fMasks(masks) |
- , fColorTable(colorTable) |
+ , fColorTable(NULL) |
+ , fNumColors(numColors) |
+ , fBytesPerColor(bytesPerColor) |
+ , fOffset(offset) |
, fRowOrder(rowOrder) |
, fRemainingBytes(remainingBytes) |
{} |
@@ -503,6 +472,7 @@ SkBmpCodec::SkBmpCodec(const SkImageInfo& info, SkStream* stream, |
SkCodec::Result SkBmpCodec::onGetPixels(const SkImageInfo& dstInfo, |
void* dst, size_t dstRowBytes, |
SkPMColor*, int*) { |
+ // Check for proper input and output formats |
if (!this->rewindIfNeeded()) { |
return kCouldNotRewind; |
} |
@@ -515,6 +485,13 @@ SkCodec::Result SkBmpCodec::onGetPixels(const SkImageInfo& dstInfo, |
return kInvalidConversion; |
} |
+ // Create the color table if necessary and prepare the stream for decode |
+ if (!createColorTable(dstInfo.alphaType())) { |
+ SkDebugf("Error: could not create color table.\n"); |
+ return kInvalidInput; |
+ } |
+ |
+ // Perform the decode |
switch (fInputFormat) { |
case kBitMask_BitmapInputFormat: |
return decodeMask(dstInfo, dst, dstRowBytes); |
@@ -530,6 +507,78 @@ SkCodec::Result SkBmpCodec::onGetPixels(const SkImageInfo& dstInfo, |
/* |
* |
+ * Process the color table for the bmp input |
+ * |
+ */ |
+ bool SkBmpCodec::createColorTable(const SkAlphaType alphaType) { |
+ SkPMColor* colorTable = NULL; |
+ if (fBitsPerPixel < 16) { |
+ // Allocate memory for a color table |
+ const uint32_t maxColors = 1 << fBitsPerPixel; |
+ colorTable = SkNEW_ARRAY(SkPMColor, maxColors); |
scroggo
2015/03/17 13:27:22
colorTable will leak if we return NULL below.
msarett
2015/03/17 16:54:06
Done.
|
+ |
+ // Read the color table from the stream |
+ uint32_t colorBytes = fNumColors * fBytesPerColor; |
+ SkAutoTDeleteArray<uint8_t> cBuffer(SkNEW_ARRAY(uint8_t, colorBytes)); |
+ if (stream()->read(cBuffer.get(), colorBytes) != colorBytes) { |
+ SkDebugf("Error: unable to read color table.\n"); |
+ return NULL; |
+ } |
+ |
+ // 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; |
+ switch (alphaType) { |
+ case kOpaque_SkAlphaType: |
+ alpha = 0xFF; |
+ colorTable[i] = SkPackARGB32NoCheck(alpha, red, green, |
scroggo
2015/03/17 13:27:22
nit: no need to update the variable alpha. You can
msarett
2015/03/17 16:54:05
Done.
|
+ blue); |
+ break; |
+ case kUnpremul_SkAlphaType: |
+ alpha = (fMasks->getAlphaMask() >> 24) & |
+ get_byte(cBuffer.get(), i*fBytesPerColor + 3); |
+ colorTable[i] = SkPackARGB32NoCheck(alpha, red, green, |
+ blue); |
+ break; |
+ case kPremul_SkAlphaType: |
+ alpha = (fMasks->getAlphaMask() >> 24) & |
+ get_byte(cBuffer.get(), i*fBytesPerColor + 3); |
+ colorTable[i] = SkPreMultiplyARGB(alpha, red, green, blue); |
+ 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); |
+ break; |
+ } |
+ } |
+ |
+ // 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); |
+ } |
+ } |
+ |
+ // After reading the color table, skip to the start of the pixel array |
+ if (stream()->skip(fOffset) != fOffset) { |
+ SkDebugf("Error: unable to skip to image data.\n"); |
+ return false; |
+ } |
+ |
+ // Set the color table and return true on success |
+ fColorTable.reset(colorTable); |
+ return true; |
+} |
+ |
+/* |
+ * |
* Performs the bitmap decoding for bit masks input format |
* |
*/ |
@@ -540,17 +589,22 @@ SkCodec::Result SkBmpCodec::decodeMask(const SkImageInfo& dstInfo, |
const int height = dstInfo.height(); |
const size_t rowBytes = SkAlign4(compute_row_bytes(width, fBitsPerPixel)); |
- // Allocate space for a row buffer and a source for the swizzler |
- SkAutoTDeleteArray<uint8_t> srcBuffer(SkNEW_ARRAY(uint8_t, rowBytes)); |
+ // Allocate a buffer large enough to hold the full input stream |
+ uint8_t* srcBuffer(SkNEW_ARRAY(uint8_t, height*rowBytes)); |
msarett
2015/03/16 20:21:53
I know that it is preferred to use an SkAutoTDelet
scroggo
2015/03/17 13:27:22
You should definitely use some sort of auto delete
msarett
2015/03/17 16:54:06
Agreed, not sure what I was thinking.
|
+ uint8_t* srcRow = srcBuffer; |
// Get the destination start row and delta |
+ SkPMColor* dstStart; |
SkPMColor* dstRow; |
int delta; |
if (kTopDown_RowOrder == fRowOrder) { |
- dstRow = (SkPMColor*) dst; |
+ dstStart = (SkPMColor*) dst; |
+ dstRow = dstStart; |
delta = (int) dstRowBytes; |
} else { |
- dstRow = (SkPMColor*) SkTAddOffset<void>(dst, (height-1) * dstRowBytes); |
+ dstStart = (SkPMColor*) SkTAddOffset<void>( |
+ dst, (height - 1) * dstRowBytes); |
+ dstRow = dstStart; |
delta = -((int) dstRowBytes); |
} |
@@ -562,31 +616,42 @@ SkCodec::Result SkBmpCodec::decodeMask(const SkImageInfo& dstInfo, |
bool transparent = true; |
for (int y = 0; y < height; y++) { |
// Read a row of the input |
- if (stream()->read(srcBuffer.get(), rowBytes) != rowBytes) { |
+ if (stream()->read(srcRow, rowBytes) != rowBytes) { |
SkDebugf("Warning: incomplete input stream.\n"); |
+ SkDELETE_ARRAY(srcBuffer); |
return kIncompleteInput; |
} |
// Decode the row in destination format |
- SkSwizzler::ResultAlpha r = swizzler->next(dstRow, srcBuffer.get()); |
+ SkSwizzler::ResultAlpha r = swizzler->next(dstRow, srcRow); |
transparent &= SkSwizzler::IsTransparent(r); |
// Move to the next row |
dstRow = SkTAddOffset<SkPMColor>(dstRow, delta); |
+ srcRow = SkTAddOffset<uint8_t>(srcRow, rowBytes); |
scroggo
2015/03/17 13:27:22
How did this work before?
msarett
2015/03/17 16:54:05
We used to overwrite the same row buffer for each
scroggo
2015/03/17 20:02:58
Ah, of course!
|
} |
// Some fully transparent bmp images are intended to be opaque. Here, we |
// correct for this possibility. |
- dstRow = (SkPMColor*) dst; |
if (transparent) { |
+ const SkImageInfo& newInfo = dstInfo.makeAlphaType(kOpaque_SkAlphaType); |
scroggo
2015/03/17 13:27:22
nit: Maybe this should be opaqueInfo? Or maskInfo?
msarett
2015/03/17 16:54:06
Done.
|
+ SkMaskSwizzler* newSwizzler = SkMaskSwizzler::CreateMaskSwizzler( |
scroggo
2015/03/17 13:27:22
This will leak.
scroggo
2015/03/17 13:27:22
nit: Maybe name this maskSwizzler?
msarett
2015/03/17 16:54:05
Yeah it looks like it. I think this is not the on
|
+ newInfo, fMasks, fBitsPerPixel); |
msarett
2015/03/16 20:21:53
Increase indent
|
+ srcRow = srcBuffer; |
+ dstRow = dstStart; |
for (int y = 0; y < height; y++) { |
- for (int x = 0; x < width; x++) { |
- dstRow[x] |= 0xFF000000; |
- } |
- dstRow = SkTAddOffset<SkPMColor>(dstRow, dstRowBytes); |
+ // Decode the row in new format |
+ newSwizzler->next(dstRow, srcRow); |
+ |
+ // Move to the next row |
+ dstRow = SkTAddOffset<SkPMColor>(dstRow, delta); |
+ srcRow = SkTAddOffset<uint8_t>(srcRow, rowBytes); |
} |
} |
+ // Clean up memory |
+ SkDELETE_ARRAY(srcBuffer); |
+ |
// Finished decoding the entire image |
return kSuccess; |
} |
@@ -596,13 +661,78 @@ SkCodec::Result SkBmpCodec::decodeMask(const SkImageInfo& dstInfo, |
* Set an RLE pixel using the color table |
* |
*/ |
-void SkBmpCodec::setRLEPixel(SkPMColor* dst, size_t dstRowBytes, int height, |
- uint32_t x, uint32_t y, uint8_t index) { |
+void SkBmpCodec::setRLEPixel(SkPMColor* 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>(dst, |
+ row * (int) dstRowBytes); |
+ dstRow[x] = fColorTable.get()[index]; |
+ break; |
+ } |
+ case kRGB_565_SkColorType: { |
+ uint16_t* dstRow = SkTAddOffset<uint16_t>(dst, |
+ row * (int) dstRowBytes); |
+ dstRow[x] = SkPixel32ToPixel16(fColorTable.get()[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(SkPMColor* 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) { |
- y = height - y - 1; |
+ 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>(dst, |
+ row * (int) dstRowBytes); |
+ dstRow[x] = SkPackARGB32NoCheck(0xFF, red, green, blue); |
+ break; |
+ } |
+ case kRGB_565_SkColorType: { |
+ uint16_t* dstRow = SkTAddOffset<uint16_t>(dst, |
+ row * (int) dstRowBytes); |
+ dstRow[x] = SkPack888ToRGB16(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; |
} |
- SkPMColor* dstRow = SkTAddOffset<SkPMColor>(dst, y * dstRowBytes); |
- dstRow[x] = fColorTable.get()[index]; |
} |
/* |
@@ -627,7 +757,7 @@ SkCodec::Result SkBmpCodec::decodeRLE(const SkImageInfo& dstInfo, |
uint32_t currByte = 0; |
SkAutoTDeleteArray<uint8_t> buffer(SkNEW_ARRAY(uint8_t, fRemainingBytes)); |
size_t totalBytes = stream()->read(buffer.get(), fRemainingBytes); |
- if ((uint32_t) totalBytes < fRemainingBytes) { |
+ if (totalBytes < fRemainingBytes) { |
SkDebugf("Warning: incomplete RLE file.\n"); |
} else if (totalBytes <= 0) { |
SkDebugf("Error: could not read RLE image data.\n"); |
@@ -706,18 +836,16 @@ SkCodec::Result SkBmpCodec::decodeRLE(const SkImageInfo& dstInfo, |
return kIncompleteInput; |
} |
// Set numPixels number of pixels |
- SkPMColor* dstRow = SkTAddOffset<SkPMColor>( |
- dstPtr, y * dstRowBytes); |
while (numPixels > 0) { |
switch(fBitsPerPixel) { |
case 4: { |
SkASSERT(currByte < totalBytes); |
uint8_t val = buffer.get()[currByte++]; |
- setRLEPixel(dstPtr, dstRowBytes, height, x++, y, |
- val >> 4); |
+ setRLEPixel(dstPtr, dstRowBytes, dstInfo, x++, |
+ y, val >> 4); |
numPixels--; |
if (numPixels != 0) { |
- setRLEPixel(dstPtr, dstRowBytes, height, |
+ setRLEPixel(dstPtr, dstRowBytes, dstInfo, |
x++, y, val & 0xF); |
numPixels--; |
} |
@@ -725,8 +853,8 @@ SkCodec::Result SkBmpCodec::decodeRLE(const SkImageInfo& dstInfo, |
} |
case 8: |
SkASSERT(currByte < totalBytes); |
- setRLEPixel(dstPtr, dstRowBytes, height, x++, y, |
- buffer.get()[currByte++]); |
+ setRLEPixel(dstPtr, dstRowBytes, dstInfo, x++, |
+ y, buffer.get()[currByte++]); |
numPixels--; |
break; |
case 24: { |
@@ -734,9 +862,8 @@ SkCodec::Result SkBmpCodec::decodeRLE(const SkImageInfo& dstInfo, |
uint8_t blue = buffer.get()[currByte++]; |
uint8_t green = buffer.get()[currByte++]; |
uint8_t red = buffer.get()[currByte++]; |
- SkPMColor color = SkPackARGB32NoCheck( |
- 0xFF, red, green, blue); |
- dstRow[x++] = color; |
+ setRLE24Pixel(dstPtr, dstRowBytes, dstInfo, |
+ x++, y, red, green, blue); |
numPixels--; |
} |
default: |
@@ -770,11 +897,9 @@ SkCodec::Result SkBmpCodec::decodeRLE(const SkImageInfo& dstInfo, |
uint8_t blue = task; |
uint8_t green = buffer.get()[currByte++]; |
uint8_t red = buffer.get()[currByte++]; |
- SkPMColor color = SkPackARGB32NoCheck(0xFF, red, green, blue); |
- SkPMColor* dstRow = |
- SkTAddOffset<SkPMColor>(dstPtr, y * dstRowBytes); |
while (x < endX) { |
- dstRow[x++] = color; |
+ setRLE24Pixel(dstPtr, dstRowBytes, dstInfo, x++, y, red, |
+ green, blue); |
} |
} else { |
// In RLE8 or RLE4, the second byte read gives the index in the |
@@ -790,7 +915,7 @@ SkCodec::Result SkBmpCodec::decodeRLE(const SkImageInfo& dstInfo, |
// Set the indicated number of pixels |
for (int which = 0; x < endX; x++) { |
- setRLEPixel(dstPtr, dstRowBytes, height, x, y, |
+ setRLEPixel(dstPtr, dstRowBytes, dstInfo, x, y, |
indices[which]); |
which = !which; |
} |