Index: src/codec/SkPngCodec.cpp |
diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp |
index 9691f12d4d6b210245d1a0992b5853a20b5fb3cc..c77f9212252c329194ae7e0b2d089dcbb9ac89a3 100644 |
--- a/src/codec/SkPngCodec.cpp |
+++ b/src/codec/SkPngCodec.cpp |
@@ -10,6 +10,7 @@ |
#include "SkColorTable.h" |
#include "SkBitmap.h" |
#include "SkMath.h" |
+#include "SkOpts.h" |
#include "SkPngCodec.h" |
#include "SkPngFilters.h" |
#include "SkSize.h" |
@@ -35,31 +36,6 @@ |
#endif |
/////////////////////////////////////////////////////////////////////////////// |
-// Helper macros |
-/////////////////////////////////////////////////////////////////////////////// |
- |
-#ifndef png_jmpbuf |
-# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) |
-#endif |
- |
-/* These were dropped in libpng >= 1.4 */ |
-#ifndef png_infopp_NULL |
- #define png_infopp_NULL nullptr |
-#endif |
- |
-#ifndef png_bytepp_NULL |
- #define png_bytepp_NULL nullptr |
-#endif |
- |
-#ifndef int_p_NULL |
- #define int_p_NULL nullptr |
-#endif |
- |
-#ifndef png_flush_ptr_NULL |
- #define png_flush_ptr_NULL nullptr |
-#endif |
- |
-/////////////////////////////////////////////////////////////////////////////// |
// Callback functions |
/////////////////////////////////////////////////////////////////////////////// |
@@ -106,7 +82,7 @@ public: |
// fInfo_ptr will never be non-nullptr unless fPng_ptr is. |
if (fPng_ptr) { |
png_infopp info_pp = fInfo_ptr ? &fInfo_ptr : nullptr; |
- png_destroy_read_struct(&fPng_ptr, info_pp, png_infopp_NULL); |
+ png_destroy_read_struct(&fPng_ptr, info_pp, nullptr); |
} |
} |
@@ -126,91 +102,75 @@ private: |
}; |
#define AutoCleanPng(...) SK_REQUIRE_LOCAL_VAR(AutoCleanPng) |
-//checks if there is transparency info in the tRNS chunk |
-//image types which could have data in the tRNS chunk include: Index8, Gray8, RGB |
-static bool has_transparency_in_tRNS(png_structp png_ptr, |
- png_infop info_ptr) { |
- if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { |
- return false; |
- } |
- |
- png_bytep trans; |
- int num_trans; |
- png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, nullptr); |
- return num_trans > 0; |
-} |
- |
// Method for coverting to either an SkPMColor or a similarly packed |
// unpremultiplied color. |
typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b); |
// Note: SkColorTable claims to store SkPMColors, which is not necessarily |
// the case here. |
+// TODO: If we add support for non-native swizzles, we'll need to handle that here. |
bool SkPngCodec::decodePalette(bool premultiply, int* ctableCount) { |
- int numPalette; |
- png_colorp palette; |
- png_bytep trans; |
- if (!png_get_PLTE(fPng_ptr, fInfo_ptr, &palette, &numPalette)) { |
+ int numColors; |
+ png_color* palette; |
+ if (!png_get_PLTE(fPng_ptr, fInfo_ptr, &palette, &numColors)) { |
return false; |
} |
- // Note: These are not necessarily SkPMColors |
- SkPMColor colorStorage[256]; // worst-case storage |
- SkPMColor* colorPtr = colorStorage; |
+ // Note: These are not necessarily SkPMColors. |
+ SkPMColor colorPtr[256]; |
- int numTrans; |
- if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) { |
- png_get_tRNS(fPng_ptr, fInfo_ptr, &trans, &numTrans, nullptr); |
- } else { |
- numTrans = 0; |
- } |
+ png_bytep alphas; |
+ int numColorsWithAlpha = 0; |
+ if (png_get_tRNS(fPng_ptr, fInfo_ptr, &alphas, &numColorsWithAlpha, nullptr)) { |
+ // Choose which function to use to create the color table. If the final destination's |
+ // colortype is unpremultiplied, the color table will store unpremultiplied colors. |
+ PackColorProc proc; |
+ if (premultiply) { |
+ proc = &SkPremultiplyARGBInline; |
+ } else { |
+ proc = &SkPackARGB32NoCheck; |
+ } |
- // check for bad images that might make us crash |
- if (numTrans > numPalette) { |
- numTrans = numPalette; |
+ for (int i = 0; i < numColorsWithAlpha; i++) { |
+ // We don't have a function in SkOpts that combines a set of alphas with a set |
+ // of RGBs. We could write one, but it's hardly worth it, given that this |
+ // is such a small fraction of the total decode time. |
+ colorPtr[i] = proc(alphas[i], palette->red, palette->green, palette->blue); |
+ palette++; |
+ } |
} |
- int index = 0; |
- |
- // Choose which function to use to create the color table. If the final destination's |
- // colortype is unpremultiplied, the color table will store unpremultiplied colors. |
- PackColorProc proc; |
- if (premultiply) { |
- proc = &SkPreMultiplyARGB; |
- } else { |
- proc = &SkPackARGB32NoCheck; |
- } |
- for (; index < numTrans; index++) { |
- *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue); |
- palette++; |
- } |
+ if (numColorsWithAlpha < numColors) { |
+ // The optimized code depends on a 3-byte png_color struct with the colors |
+ // in RGB order. These checks make sure it is safe to use. |
+ static_assert(3 == sizeof(png_color), "png_color struct has changed. Opts are broken."); |
+#ifdef SK_DEBUG |
+ SkASSERT(&palette->red < &palette->green); |
+ SkASSERT(&palette->green < &palette->blue); |
+#endif |
- for (; index < numPalette; index++) { |
- *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue); |
- palette++; |
+#ifdef SK_PMCOLOR_IS_RGBA |
+ SkOpts::RGB_to_RGB1(colorPtr + numColorsWithAlpha, palette, numColors - numColorsWithAlpha); |
+#else |
+ SkOpts::RGB_to_BGR1(colorPtr + numColorsWithAlpha, palette, numColors - numColorsWithAlpha); |
+#endif |
} |
- /* BUGGY IMAGE WORKAROUND |
- Invalid images could contain pixel values that are greater than the number of palette |
- entries. Since we use pixel values as indices into the palette this could result in reading |
- beyond the end of the palette which could leak the contents of uninitialized memory. To |
- ensure this doesn't happen, we grow the colortable to the maximum size that can be |
- addressed by the bitdepth of the image and fill it with the last palette color or black if |
- the palette is empty (really broken image). |
- */ |
- int colorCount = SkTMax(numPalette, 1 << SkTMin(fBitDepth, 8)); |
- SkPMColor lastColor = index > 0 ? colorPtr[-1] : SkPackARGB32(0xFF, 0, 0, 0); |
- for (; index < colorCount; index++) { |
- *colorPtr++ = lastColor; |
+ // Pad the color table with the last color in the table (or black) in the case that |
+ // invalid pixel indices exceed the number of colors in the table. |
+ const int maxColors = 1 << fBitDepth; |
+ if (numColors < maxColors) { |
+ SkPMColor lastColor = numColors > 0 ? colorPtr[numColors - 1] : SK_ColorBLACK; |
+ sk_memset32(colorPtr + numColors, lastColor, maxColors - numColors); |
} |
- // Set the new color count |
+ // Set the new color count. |
if (ctableCount != nullptr) { |
- *ctableCount = colorCount; |
+ *ctableCount = maxColors; |
} |
- fColorTable.reset(new SkColorTable(colorStorage, colorCount)); |
+ fColorTable.reset(new SkColorTable(colorPtr, maxColors)); |
return true; |
} |
@@ -283,84 +243,81 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
// PNG file before the first IDAT (image data chunk). |
png_read_info(png_ptr, info_ptr); |
png_uint_32 origWidth, origHeight; |
- int bitDepth, colorType; |
+ int bitDepth, encodedColorType; |
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth, |
- &colorType, int_p_NULL, int_p_NULL, int_p_NULL); |
+ &encodedColorType, nullptr, nullptr, nullptr); |
if (bitDepthPtr) { |
*bitDepthPtr = bitDepth; |
} |
- // sanity check for size |
- { |
- int64_t size = sk_64_mul(origWidth, origHeight); |
- // now check that if we are 4-bytes per pixel, we also don't overflow |
- if (size < 0 || size > (0x7FFFFFFF >> 2)) { |
- return false; |
- } |
- } |
- |
- // Tell libpng to strip 16 bit/color files down to 8 bits/color |
+ // Tell libpng to strip 16 bit/color files down to 8 bits/color. |
+ // TODO: Should we handle this in SkSwizzler? Could this also benefit |
+ // RAW decodes? |
if (bitDepth == 16) { |
+ SkASSERT(PNG_COLOR_TYPE_PALETTE != encodedColorType); |
png_set_strip_16(png_ptr); |
} |
-#ifdef PNG_READ_PACK_SUPPORTED |
- // Extract multiple pixels with bit depths of 1, 2, and 4 from a single |
- // byte into separate bytes (useful for paletted and grayscale images). |
- if (bitDepth < 8) { |
- png_set_packing(png_ptr); |
- } |
-#endif |
- // Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel. |
- if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { |
- png_set_expand_gray_1_2_4_to_8(png_ptr); |
- } |
- // Now determine the default SkColorType and SkAlphaType and set required transforms |
- SkColorType skColorType = kUnknown_SkColorType; |
- SkAlphaType skAlphaType = kUnknown_SkAlphaType; |
- switch (colorType) { |
+ // Now determine the default colorType and alphaType and set the required transforms. |
+ // Often, we depend on SkSwizzler to perform any transforms that we need. However, we |
+ // still depend on libpng for many of the rare and PNG-specific cases. |
+ SkColorType colorType = kUnknown_SkColorType; |
+ SkAlphaType alphaType = kUnknown_SkAlphaType; |
+ switch (encodedColorType) { |
case PNG_COLOR_TYPE_PALETTE: |
- skColorType = kIndex_8_SkColorType; |
- skAlphaType = has_transparency_in_tRNS(png_ptr, info_ptr) ? |
+ // Extract multiple pixels with bit depths of 1, 2, and 4 from a single |
+ // byte into separate bytes (useful for paletted and grayscale images). |
+ if (bitDepth < 8) { |
+ // TODO: Should we use SkSwizzler here? |
+ png_set_packing(png_ptr); |
+ } |
+ |
+ colorType = kIndex_8_SkColorType; |
+ // Set the alpha type depending on if a transparency chunk exists. |
+ alphaType = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ? |
kUnpremul_SkAlphaType : kOpaque_SkAlphaType; |
break; |
case PNG_COLOR_TYPE_RGB: |
- if (has_transparency_in_tRNS(png_ptr, info_ptr)) { |
- //convert to RGBA with tranparency information in tRNS chunk if it exists |
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { |
+ // Convert to RGBA if transparency chunk exists. |
png_set_tRNS_to_alpha(png_ptr); |
- skAlphaType = kUnpremul_SkAlphaType; |
+ alphaType = kUnpremul_SkAlphaType; |
} else { |
- skAlphaType = kOpaque_SkAlphaType; |
+ alphaType = kOpaque_SkAlphaType; |
} |
- skColorType = kN32_SkColorType; |
+ colorType = kN32_SkColorType; |
break; |
case PNG_COLOR_TYPE_GRAY: |
- if (has_transparency_in_tRNS(png_ptr, info_ptr)) { |
- //FIXME: support gray with alpha as a color type |
- //convert to RGBA if there is transparentcy info in the tRNS chunk |
+ // Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel. |
+ if (bitDepth < 8) { |
+ // TODO: Should we use SkSwizzler here? |
+ png_set_expand_gray_1_2_4_to_8(png_ptr); |
+ } |
+ |
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { |
+ // Convert to RGBA if there is a transparency chunk. |
png_set_tRNS_to_alpha(png_ptr); |
png_set_gray_to_rgb(png_ptr); |
- skColorType = kN32_SkColorType; |
- skAlphaType = kUnpremul_SkAlphaType; |
+ colorType = kN32_SkColorType; |
+ alphaType = kUnpremul_SkAlphaType; |
} else { |
- skColorType = kGray_8_SkColorType; |
- skAlphaType = kOpaque_SkAlphaType; |
+ colorType = kGray_8_SkColorType; |
+ alphaType = kOpaque_SkAlphaType; |
} |
break; |
case PNG_COLOR_TYPE_GRAY_ALPHA: |
- //FIXME: support gray with alpha as a color type |
- //convert to RGBA |
+ // Convert to RGBA if the image has alpha. |
png_set_gray_to_rgb(png_ptr); |
- skColorType = kN32_SkColorType; |
- skAlphaType = kUnpremul_SkAlphaType; |
+ colorType = kN32_SkColorType; |
+ alphaType = kUnpremul_SkAlphaType; |
break; |
case PNG_COLOR_TYPE_RGBA: |
- skColorType = kN32_SkColorType; |
- skAlphaType = kUnpremul_SkAlphaType; |
+ colorType = kN32_SkColorType; |
+ alphaType = kUnpremul_SkAlphaType; |
break; |
default: |
- //all the color types have been covered above |
+ // All the color types have been covered above. |
SkASSERT(false); |
} |
@@ -372,7 +329,7 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
// FIXME: Also need to check for sRGB ( https://bug.skia.org/3471 ). |
if (imageInfo) { |
- *imageInfo = SkImageInfo::Make(origWidth, origHeight, skColorType, skAlphaType); |
+ *imageInfo = SkImageInfo::Make(origWidth, origHeight, colorType, alphaType); |
} |
autoClean.detach(); |
if (png_ptrp) { |
@@ -404,7 +361,7 @@ void SkPngCodec::destroyReadStruct() { |
if (fPng_ptr) { |
// We will never have a nullptr fInfo_ptr with a non-nullptr fPng_ptr |
SkASSERT(fInfo_ptr); |
- png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL); |
+ png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, nullptr); |
fPng_ptr = nullptr; |
fInfo_ptr = nullptr; |
} |
@@ -426,7 +383,7 @@ SkCodec::Result SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo, |
} |
png_read_update_info(fPng_ptr, fInfo_ptr); |
- //srcColorType was determined in read_header() which determined png color type |
+ // srcColorType was determined in read_header() which determined png color type |
const SkColorType srcColorType = this->getInfo().colorType(); |
switch (srcColorType) { |
@@ -449,7 +406,7 @@ SkCodec::Result SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo, |
} |
break; |
default: |
- //would have exited before now if the colorType was supported by png |
+ // We will always recommend one of the above colorTypes. |
SkASSERT(false); |
} |
@@ -459,10 +416,8 @@ SkCodec::Result SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo, |
// Create the swizzler. SkPngCodec retains ownership of the color table. |
const SkPMColor* colors = get_color_ptr(fColorTable.get()); |
fSwizzler.reset(SkSwizzler::CreateSwizzler(fSrcConfig, colors, requestedInfo, options)); |
- if (!fSwizzler) { |
- // FIXME: CreateSwizzler could fail for another reason. |
- return kUnimplemented; |
- } |
+ SkASSERT(fSwizzler); |
+ |
return kSuccess; |
} |
@@ -545,7 +500,7 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* |
uint8_t* srcRow = base; |
for (int y = 0; y < height; y++) { |
uint8_t* bmRow = srcRow; |
- png_read_rows(fPng_ptr, &bmRow, png_bytepp_NULL, 1); |
+ png_read_rows(fPng_ptr, &bmRow, nullptr, 1); |
srcRow += srcRowBytes; |
} |
} |
@@ -561,17 +516,13 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* |
storage.reset(requestedInfo.width() * SkSwizzler::BytesPerPixel(fSrcConfig)); |
uint8_t* srcRow = storage.get(); |
for (; row < requestedInfo.height(); row++) { |
- png_read_rows(fPng_ptr, &srcRow, png_bytepp_NULL, 1); |
+ png_read_rows(fPng_ptr, &srcRow, nullptr, 1); |
// FIXME: Only call IsOpaque once, outside the loop. Same for onGetScanlines. |
fSwizzler->swizzle(dstRow, srcRow); |
dstRow = SkTAddOffset<void>(dstRow, dstRowBytes); |
} |
} |
- // FIXME: do we need substituteTranspColor? Note that we cannot do it for |
- // scanline decoding, but we could do it here. Alternatively, we could do |
- // it as we go, instead of in post-processing like SkPNGImageDecoder. |
- |
if (setjmp(png_jmpbuf(fPng_ptr))) { |
// We've already read all the scanlines. This is a success. |
return kSuccess; |
@@ -628,7 +579,7 @@ public: |
void* dstRow = dst; |
for (; row < count; row++) { |
- png_read_rows(this->png_ptr(), &fSrcRow, png_bytepp_NULL, 1); |
+ png_read_rows(this->png_ptr(), &fSrcRow, nullptr, 1); |
this->swizzler()->swizzle(dstRow, fSrcRow); |
dstRow = SkTAddOffset<void>(dstRow, rowBytes); |
} |
@@ -646,7 +597,7 @@ public: |
//calling png_read_rows in a loop is insignificantly slower than calling it once with count |
//as png_read_rows has it's own loop which calls png_read_row count times. |
for (int row = 0; row < count; row++) { |
- png_read_rows(this->png_ptr(), &fSrcRow, png_bytepp_NULL, 1); |
+ png_read_rows(this->png_ptr(), &fSrcRow, nullptr, 1); |
} |
return true; |
} |
@@ -730,17 +681,17 @@ public: |
for (int i = 0; i < this->numberPasses(); i++) { |
// read rows we planned to skip into garbage row |
for (int y = 0; y < startRow; y++){ |
- png_read_rows(this->png_ptr(), &fGarbageRowPtr, png_bytepp_NULL, 1); |
+ png_read_rows(this->png_ptr(), &fGarbageRowPtr, nullptr, 1); |
} |
// read rows we care about into buffer |
srcRow = storagePtr; |
for (int y = 0; y < count; y++) { |
- png_read_rows(this->png_ptr(), &srcRow, png_bytepp_NULL, 1); |
+ png_read_rows(this->png_ptr(), &srcRow, nullptr, 1); |
srcRow += fSrcRowBytes; |
} |
// read rows we don't want into garbage buffer |
for (int y = 0; y < fHeight - startRow - count; y++) { |
- png_read_rows(this->png_ptr(), &fGarbageRowPtr, png_bytepp_NULL, 1); |
+ png_read_rows(this->png_ptr(), &fGarbageRowPtr, nullptr, 1); |
} |
} |
//swizzle the rows we care about |