Reland "Make SkPngCodec only read as much of the stream as necessary"
(Originally uploaded as 13900.) Previously, SkPngCodec assumed that the stream only contained one image, which ended at the end of the stream. It read the stream in arbitrarily-sized chunks, and then passed that data to libpng for processing. If a stream contains more than one image, this may result in reading beyond the end of the image, making future reads read the wrong data. Now, SkPngCodec starts by reading 8 bytes at a time. After the signature, 8 bytes is enough to know which chunk is next and how many bytes are in the chunk. When decoding the size, we stop when we reach IDAT, and when decoding the image, we stop when we reach IEND. This manual parsing is necessary to support APNG, which is planned in the future. It also allows us to remove the SK_GOOGLE3_PNG_HACK, which was a workaround for reading more than necessary at the beginning of the image. Add a test that simulates the issue, by decoding a special stream that reports an error if the codec attempts to read beyond the end. Temporarily disable the partial decoding tests for png. A larger change will be necessary to get those working again, and no clients are currently relying on incrementally decoding PNGs (i.e. decode part of an image, then decode further with more data). Include a workaround for older versions of libpng (e.g. 1.2 in Google3). In older versions, if the row callback is null when the IDAT header is processed, reading the image will fail. When we see the IDAT, we save the length and process a recreated IDAT header later, after the row callback has been set. Bug: skia:5368 Bug:b/34073812 Test: Existing tests, plus a new test in dm. Change-Id: I293a4ddc013b82669a8b735062228b26d0bce933 Reviewed-on: https://skia-review.googlesource.com/13984 Commit-Queue: Leon Scroggins <scroggo@google.com> Reviewed-by: Matt Sarett <msarett@google.com>
This commit is contained in:
parent
6a1a5f7403
commit
83239658f2
@ -34,6 +34,7 @@ tests_sources = [
|
||||
"$_tests/ClipperTest.cpp",
|
||||
"$_tests/ClipStackTest.cpp",
|
||||
"$_tests/CodecAnimTest.cpp",
|
||||
"$_tests/CodecExactReadTest.cpp",
|
||||
"$_tests/CodecPartialTest.cpp",
|
||||
"$_tests/CodecTest.cpp",
|
||||
"$_tests/ColorFilterTest.cpp",
|
||||
|
@ -22,17 +22,13 @@
|
||||
#include "SkUtils.h"
|
||||
|
||||
#include "png.h"
|
||||
#include <algorithm>
|
||||
|
||||
// This warning triggers false postives way too often in here.
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic ignored "-Wclobbered"
|
||||
#endif
|
||||
|
||||
#if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
|
||||
// This is not needed with version 1.5
|
||||
#undef SK_GOOGLE3_PNG_HACK
|
||||
#endif
|
||||
|
||||
// FIXME (scroggo): We can use png_jumpbuf directly once Google3 is on 1.6
|
||||
#define PNG_JMPBUF(x) png_jmpbuf((png_structp) x)
|
||||
|
||||
@ -80,8 +76,6 @@ public:
|
||||
SkCodec** codecPtr)
|
||||
: fPng_ptr(png_ptr)
|
||||
, fInfo_ptr(nullptr)
|
||||
, fDecodedBounds(false)
|
||||
, fReadHeader(false)
|
||||
, fStream(stream)
|
||||
, fChunkReader(reader)
|
||||
, fOutCodec(codecPtr)
|
||||
@ -117,28 +111,12 @@ public:
|
||||
private:
|
||||
png_structp fPng_ptr;
|
||||
png_infop fInfo_ptr;
|
||||
bool fDecodedBounds;
|
||||
bool fReadHeader;
|
||||
SkStream* fStream;
|
||||
SkPngChunkReader* fChunkReader;
|
||||
SkCodec** fOutCodec;
|
||||
|
||||
/**
|
||||
* Supplied to libpng to call when it has read enough data to determine
|
||||
* bounds.
|
||||
*/
|
||||
static void InfoCallback(png_structp png_ptr, png_infop) {
|
||||
// png_get_progressive_ptr returns the pointer we set on the png_ptr with
|
||||
// png_set_progressive_read_fn
|
||||
static_cast<AutoCleanPng*>(png_get_progressive_ptr(png_ptr))->infoCallback();
|
||||
}
|
||||
void infoCallback(size_t idatLength);
|
||||
|
||||
void infoCallback();
|
||||
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
// public so it can be called by SkPngCodec::rereadHeaderIfNecessary().
|
||||
public:
|
||||
#endif
|
||||
void releasePngPtrs() {
|
||||
fPng_ptr = nullptr;
|
||||
fInfo_ptr = nullptr;
|
||||
@ -146,12 +124,29 @@ public:
|
||||
};
|
||||
#define AutoCleanPng(...) SK_REQUIRE_LOCAL_VAR(AutoCleanPng)
|
||||
|
||||
static inline bool is_chunk(const png_byte* chunk, const char* tag) {
|
||||
return memcmp(chunk + 4, tag, 4) == 0;
|
||||
}
|
||||
|
||||
static inline bool process_data(png_structp png_ptr, png_infop info_ptr,
|
||||
SkStream* stream, void* buffer, size_t bufferSize, size_t length) {
|
||||
while (length > 0) {
|
||||
const size_t bytesToProcess = std::min(bufferSize, length);
|
||||
if (stream->read(buffer, bytesToProcess) < bytesToProcess) {
|
||||
return false;
|
||||
}
|
||||
png_process_data(png_ptr, info_ptr, (png_bytep) buffer, bytesToProcess);
|
||||
length -= bytesToProcess;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AutoCleanPng::decodeBounds() {
|
||||
if (setjmp(PNG_JMPBUF(fPng_ptr))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
png_set_progressive_read_fn(fPng_ptr, this, InfoCallback, nullptr, nullptr);
|
||||
png_set_progressive_read_fn(fPng_ptr, nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
// Arbitrary buffer size, though note that it matches (below)
|
||||
// SkPngCodec::processData(). FIXME: Can we better suit this to the size of
|
||||
@ -159,22 +154,38 @@ bool AutoCleanPng::decodeBounds() {
|
||||
constexpr size_t kBufferSize = 4096;
|
||||
char buffer[kBufferSize];
|
||||
|
||||
{
|
||||
// Parse the signature.
|
||||
if (fStream->read(buffer, 8) < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, 8);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const size_t bytesRead = fStream->read(buffer, kBufferSize);
|
||||
if (!bytesRead) {
|
||||
// Parse chunk length and type.
|
||||
if (fStream->read(buffer, 8) < 8) {
|
||||
// We have read to the end of the input without decoding bounds.
|
||||
break;
|
||||
}
|
||||
|
||||
png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, bytesRead);
|
||||
if (fReadHeader) {
|
||||
break;
|
||||
png_byte* chunk = reinterpret_cast<png_byte*>(buffer);
|
||||
const size_t length = png_get_uint_32(chunk);
|
||||
|
||||
if (is_chunk(chunk, "IDAT")) {
|
||||
this->infoCallback(length);
|
||||
return true;
|
||||
}
|
||||
|
||||
png_process_data(fPng_ptr, fInfo_ptr, chunk, 8);
|
||||
// Process the full chunk + CRC.
|
||||
if (!process_data(fPng_ptr, fInfo_ptr, fStream, buffer, kBufferSize, length + 4)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For safety, clear the pointer to this object.
|
||||
png_set_progressive_read_fn(fPng_ptr, nullptr, nullptr, nullptr, nullptr);
|
||||
return fDecodedBounds;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SkPngCodec::processData() {
|
||||
@ -198,16 +209,33 @@ void SkPngCodec::processData() {
|
||||
constexpr size_t kBufferSize = 4096;
|
||||
char buffer[kBufferSize];
|
||||
|
||||
bool iend = false;
|
||||
while (true) {
|
||||
const size_t bytesRead = this->stream()->read(buffer, kBufferSize);
|
||||
png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, bytesRead);
|
||||
size_t length;
|
||||
if (fDecodedIdat) {
|
||||
// Parse chunk length and type.
|
||||
if (this->stream()->read(buffer, 8) < 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bytesRead) {
|
||||
// We have read to the end of the input. Note that we quit *after*
|
||||
// calling png_process_data, because decodeBounds may have told
|
||||
// libpng to save the remainder of the buffer, in which case
|
||||
// png_process_data will process the saved buffer, though the
|
||||
// stream has no more to read.
|
||||
png_byte* chunk = reinterpret_cast<png_byte*>(buffer);
|
||||
png_process_data(fPng_ptr, fInfo_ptr, chunk, 8);
|
||||
if (is_chunk(chunk, "IEND")) {
|
||||
iend = true;
|
||||
}
|
||||
|
||||
length = png_get_uint_32(chunk);
|
||||
} else {
|
||||
length = fIdatLength;
|
||||
png_byte idat[] = {0, 0, 0, 0, 'I', 'D', 'A', 'T'};
|
||||
png_save_uint_32(idat, length);
|
||||
png_process_data(fPng_ptr, fInfo_ptr, idat, 8);
|
||||
fDecodedIdat = true;
|
||||
}
|
||||
|
||||
// Process the full chunk + CRC.
|
||||
if (!process_data(fPng_ptr, fInfo_ptr, this->stream(), buffer, kBufferSize, length + 4)
|
||||
|| iend) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -485,12 +513,6 @@ public:
|
||||
GetDecoder(png_ptr)->rowCallback(row, rowNum);
|
||||
}
|
||||
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
static void RereadInfoCallback(png_structp png_ptr, png_infop) {
|
||||
GetDecoder(png_ptr)->rereadInfoCallback();
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
int fRowsWrittenToOutput;
|
||||
void* fDst;
|
||||
@ -509,11 +531,7 @@ private:
|
||||
|
||||
Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override {
|
||||
const int height = this->getInfo().height();
|
||||
png_progressive_info_ptr callback = nullptr;
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
callback = RereadInfoCallback;
|
||||
#endif
|
||||
png_set_progressive_read_fn(this->png_ptr(), this, callback, AllRowsCallback, nullptr);
|
||||
png_set_progressive_read_fn(this->png_ptr(), this, nullptr, AllRowsCallback, nullptr);
|
||||
fDst = dst;
|
||||
fRowBytes = rowBytes;
|
||||
|
||||
@ -542,11 +560,7 @@ private:
|
||||
}
|
||||
|
||||
void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override {
|
||||
png_progressive_info_ptr callback = nullptr;
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
callback = RereadInfoCallback;
|
||||
#endif
|
||||
png_set_progressive_read_fn(this->png_ptr(), this, callback, RowCallback, nullptr);
|
||||
png_set_progressive_read_fn(this->png_ptr(), this, nullptr, RowCallback, nullptr);
|
||||
fFirstRow = firstRow;
|
||||
fLastRow = lastRow;
|
||||
fDst = dst;
|
||||
@ -615,12 +629,6 @@ public:
|
||||
decoder->interlacedRowCallback(row, rowNum, pass);
|
||||
}
|
||||
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
static void RereadInfoInterlacedCallback(png_structp png_ptr, png_infop) {
|
||||
static_cast<SkPngInterlacedDecoder*>(png_get_progressive_ptr(png_ptr))->rereadInfoInterlaced();
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
const int fNumberPasses;
|
||||
int fFirstRow;
|
||||
@ -634,14 +642,6 @@ private:
|
||||
|
||||
typedef SkPngCodec INHERITED;
|
||||
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
void rereadInfoInterlaced() {
|
||||
this->rereadInfoCallback();
|
||||
// Note: This allocates more memory than necessary, if we are sampling/subset.
|
||||
this->setUpInterlaceBuffer(this->getInfo().height());
|
||||
}
|
||||
#endif
|
||||
|
||||
// FIXME: Currently sharing interlaced callback for all rows and subset. It's not
|
||||
// as expensive as the subset version of non-interlaced, but it still does extra
|
||||
// work.
|
||||
@ -675,11 +675,7 @@ private:
|
||||
SkCodec::Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override {
|
||||
const int height = this->getInfo().height();
|
||||
this->setUpInterlaceBuffer(height);
|
||||
png_progressive_info_ptr callback = nullptr;
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
callback = RereadInfoInterlacedCallback;
|
||||
#endif
|
||||
png_set_progressive_read_fn(this->png_ptr(), this, callback, InterlacedRowCallback,
|
||||
png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback,
|
||||
nullptr);
|
||||
|
||||
fFirstRow = 0;
|
||||
@ -709,11 +705,7 @@ private:
|
||||
void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override {
|
||||
// FIXME: We could skip rows in the interlace buffer that we won't put in the output.
|
||||
this->setUpInterlaceBuffer(lastRow - firstRow + 1);
|
||||
png_progressive_info_ptr callback = nullptr;
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
callback = RereadInfoInterlacedCallback;
|
||||
#endif
|
||||
png_set_progressive_read_fn(this->png_ptr(), this, callback, InterlacedRowCallback, nullptr);
|
||||
png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, nullptr);
|
||||
fFirstRow = firstRow;
|
||||
fLastRow = lastRow;
|
||||
fDst = dst;
|
||||
@ -767,54 +759,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
bool SkPngCodec::rereadHeaderIfNecessary() {
|
||||
if (!fNeedsToRereadHeader) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// On the first call, we'll need to rewind ourselves. Future calls will
|
||||
// have already rewound in rewindIfNecessary.
|
||||
if (this->stream()->getPosition() > 0) {
|
||||
this->stream()->rewind();
|
||||
}
|
||||
|
||||
this->destroyReadStruct();
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr,
|
||||
sk_error_fn, sk_warning_fn);
|
||||
if (!png_ptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only use the AutoCleanPng to delete png_ptr as necessary.
|
||||
// (i.e. not for reading bounds etc.)
|
||||
AutoCleanPng autoClean(png_ptr, nullptr, nullptr, nullptr);
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (info_ptr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
autoClean.setInfoPtr(info_ptr);
|
||||
|
||||
#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
|
||||
// Hookup our chunkReader so we can see any user-chunks the caller may be interested in.
|
||||
// This needs to be installed before we read the png header. Android may store ninepatch
|
||||
// chunks in the header.
|
||||
if (fPngChunkReader.get()) {
|
||||
png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
|
||||
png_set_read_user_chunk_fn(png_ptr, (png_voidp) fPngChunkReader.get(), sk_read_user_chunk);
|
||||
}
|
||||
#endif
|
||||
|
||||
fPng_ptr = png_ptr;
|
||||
fInfo_ptr = info_ptr;
|
||||
autoClean.releasePngPtrs();
|
||||
fNeedsToRereadHeader = false;
|
||||
return true;
|
||||
}
|
||||
#endif // SK_GOOGLE3_PNG_HACK
|
||||
|
||||
// Reads the header and initializes the output fields, if not NULL.
|
||||
//
|
||||
// @param stream Input data. Will be read to get enough information to properly
|
||||
@ -886,21 +830,17 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME (scroggo): Once SK_GOOGLE3_PNG_HACK is no more, this method can be inline in
|
||||
// AutoCleanPng::infoCallback
|
||||
static void general_info_callback(png_structp png_ptr, png_infop info_ptr,
|
||||
SkEncodedInfo::Color* outColor, SkEncodedInfo::Alpha* outAlpha,
|
||||
int* outBitDepth) {
|
||||
void AutoCleanPng::infoCallback(size_t idatLength) {
|
||||
png_uint_32 origWidth, origHeight;
|
||||
int bitDepth, encodedColorType;
|
||||
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
|
||||
png_get_IHDR(fPng_ptr, fInfo_ptr, &origWidth, &origHeight, &bitDepth,
|
||||
&encodedColorType, nullptr, nullptr, nullptr);
|
||||
|
||||
// TODO: Should we support 16-bits of precision for gray images?
|
||||
if (bitDepth == 16 && (PNG_COLOR_TYPE_GRAY == encodedColorType ||
|
||||
PNG_COLOR_TYPE_GRAY_ALPHA == encodedColorType)) {
|
||||
bitDepth = 8;
|
||||
png_set_strip_16(png_ptr);
|
||||
png_set_strip_16(fPng_ptr);
|
||||
}
|
||||
|
||||
// Now determine the default colorType and alphaType and set the required transforms.
|
||||
@ -915,18 +855,18 @@ static void general_info_callback(png_structp png_ptr, png_infop info_ptr,
|
||||
if (bitDepth < 8) {
|
||||
// TODO: Should we use SkSwizzler here?
|
||||
bitDepth = 8;
|
||||
png_set_packing(png_ptr);
|
||||
png_set_packing(fPng_ptr);
|
||||
}
|
||||
|
||||
color = SkEncodedInfo::kPalette_Color;
|
||||
// Set the alpha depending on if a transparency chunk exists.
|
||||
alpha = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ?
|
||||
alpha = png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS) ?
|
||||
SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha;
|
||||
break;
|
||||
case PNG_COLOR_TYPE_RGB:
|
||||
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
|
||||
if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) {
|
||||
// Convert to RGBA if transparency chunk exists.
|
||||
png_set_tRNS_to_alpha(png_ptr);
|
||||
png_set_tRNS_to_alpha(fPng_ptr);
|
||||
color = SkEncodedInfo::kRGBA_Color;
|
||||
alpha = SkEncodedInfo::kBinary_Alpha;
|
||||
} else {
|
||||
@ -939,11 +879,11 @@ static void general_info_callback(png_structp png_ptr, png_infop info_ptr,
|
||||
if (bitDepth < 8) {
|
||||
// TODO: Should we use SkSwizzler here?
|
||||
bitDepth = 8;
|
||||
png_set_expand_gray_1_2_4_to_8(png_ptr);
|
||||
png_set_expand_gray_1_2_4_to_8(fPng_ptr);
|
||||
}
|
||||
|
||||
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
|
||||
png_set_tRNS_to_alpha(png_ptr);
|
||||
if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) {
|
||||
png_set_tRNS_to_alpha(fPng_ptr);
|
||||
color = SkEncodedInfo::kGrayAlpha_Color;
|
||||
alpha = SkEncodedInfo::kBinary_Alpha;
|
||||
} else {
|
||||
@ -965,43 +905,9 @@ static void general_info_callback(png_structp png_ptr, png_infop info_ptr,
|
||||
color = SkEncodedInfo::kRGBA_Color;
|
||||
alpha = SkEncodedInfo::kUnpremul_Alpha;
|
||||
}
|
||||
if (outColor) {
|
||||
*outColor = color;
|
||||
}
|
||||
if (outAlpha) {
|
||||
*outAlpha = alpha;
|
||||
}
|
||||
if (outBitDepth) {
|
||||
*outBitDepth = bitDepth;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
void SkPngCodec::rereadInfoCallback() {
|
||||
general_info_callback(fPng_ptr, fInfo_ptr, nullptr, nullptr, nullptr);
|
||||
png_set_interlace_handling(fPng_ptr);
|
||||
png_read_update_info(fPng_ptr, fInfo_ptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
void AutoCleanPng::infoCallback() {
|
||||
SkEncodedInfo::Color color;
|
||||
SkEncodedInfo::Alpha alpha;
|
||||
int bitDepth;
|
||||
general_info_callback(fPng_ptr, fInfo_ptr, &color, &alpha, &bitDepth);
|
||||
|
||||
const int numberPasses = png_set_interlace_handling(fPng_ptr);
|
||||
|
||||
fReadHeader = true;
|
||||
fDecodedBounds = true;
|
||||
#ifndef SK_GOOGLE3_PNG_HACK
|
||||
// 1 tells libpng to save any extra data. We may be able to be more efficient by saving
|
||||
// it ourselves.
|
||||
png_process_data_pause(fPng_ptr, 1);
|
||||
#else
|
||||
// Hack to make png_process_data stop.
|
||||
fPng_ptr->buffer_size = 0;
|
||||
#endif
|
||||
if (fOutCodec) {
|
||||
SkASSERT(nullptr == *fOutCodec);
|
||||
SkColorSpace_Base::ICCTypeFlag iccType = SkColorSpace_Base::kRGB_ICCTypeFlag;
|
||||
@ -1016,11 +922,6 @@ void AutoCleanPng::infoCallback() {
|
||||
}
|
||||
|
||||
SkEncodedInfo encodedInfo = SkEncodedInfo::Make(color, alpha, bitDepth);
|
||||
// FIXME (scroggo): Once we get rid of SK_GOOGLE3_PNG_HACK, general_info_callback can
|
||||
// be inlined, so these values will already be set.
|
||||
png_uint_32 origWidth = png_get_image_width(fPng_ptr, fInfo_ptr);
|
||||
png_uint_32 origHeight = png_get_image_height(fPng_ptr, fInfo_ptr);
|
||||
png_byte bitDepth = png_get_bit_depth(fPng_ptr, fInfo_ptr);
|
||||
SkImageInfo imageInfo = encodedInfo.makeImageInfo(origWidth, origHeight, colorSpace);
|
||||
|
||||
if (SkEncodedInfo::kOpaque_Alpha == alpha) {
|
||||
@ -1041,9 +942,9 @@ void AutoCleanPng::infoCallback() {
|
||||
fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, numberPasses);
|
||||
}
|
||||
(*fOutCodec)->setUnsupportedICC(unsupportedICC);
|
||||
static_cast<SkPngCodec*>(*fOutCodec)->setIdatLength(idatLength);
|
||||
}
|
||||
|
||||
|
||||
// Release the pointers, which are now owned by the codec or the caller is expected to
|
||||
// take ownership.
|
||||
this->releasePngPtrs();
|
||||
@ -1058,9 +959,8 @@ SkPngCodec::SkPngCodec(const SkEncodedInfo& encodedInfo, const SkImageInfo& imag
|
||||
, fInfo_ptr(info_ptr)
|
||||
, fColorXformSrcRow(nullptr)
|
||||
, fBitDepth(bitDepth)
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
, fNeedsToRereadHeader(true)
|
||||
#endif
|
||||
, fIdatLength(0)
|
||||
, fDecodedIdat(false)
|
||||
{}
|
||||
|
||||
SkPngCodec::~SkPngCodec() {
|
||||
@ -1187,10 +1087,6 @@ SkSampler* SkPngCodec::getSampler(bool createIfNecessary) {
|
||||
}
|
||||
|
||||
bool SkPngCodec::onRewind() {
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
fNeedsToRereadHeader = true;
|
||||
return true;
|
||||
#else
|
||||
// This sets fPng_ptr and fInfo_ptr to nullptr. If read_header
|
||||
// succeeds, they will be repopulated, and if it fails, they will
|
||||
// remain nullptr. Any future accesses to fPng_ptr and fInfo_ptr will
|
||||
@ -1206,8 +1102,8 @@ bool SkPngCodec::onRewind() {
|
||||
|
||||
fPng_ptr = png_ptr;
|
||||
fInfo_ptr = info_ptr;
|
||||
fDecodedIdat = false;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
|
||||
@ -1219,13 +1115,6 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
|
||||
{
|
||||
return kInvalidConversion;
|
||||
}
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
// Note that this is done after initializeXforms. Otherwise that method
|
||||
// would not have png_ptr to use.
|
||||
if (!this->rereadHeaderIfNecessary()) {
|
||||
return kCouldNotRewind;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (options.fSubset) {
|
||||
return kUnimplemented;
|
||||
@ -1244,12 +1133,6 @@ SkCodec::Result SkPngCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
|
||||
{
|
||||
return kInvalidConversion;
|
||||
}
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
// See note in onGetPixels.
|
||||
if (!this->rereadHeaderIfNecessary()) {
|
||||
return kCouldNotRewind;
|
||||
}
|
||||
#endif
|
||||
|
||||
this->allocateStorage(dstInfo);
|
||||
|
||||
|
@ -16,13 +16,6 @@
|
||||
#include "SkRefCnt.h"
|
||||
#include "SkSwizzler.h"
|
||||
|
||||
// FIXME (scroggo): GOOGLE3 is currently using an outdated version of libpng,
|
||||
// so we need to work around the lack of the method png_process_data_pause.
|
||||
// This code will be unnecessary once we update GOOGLE3. It would make more
|
||||
// sense to condition this on the version of libpng being used, but we do not
|
||||
// know that here because png.h is only included by the cpp file.
|
||||
#define SK_GOOGLE3_PNG_HACK
|
||||
|
||||
class SkStream;
|
||||
|
||||
class SkPngCodec : public SkCodec {
|
||||
@ -32,6 +25,9 @@ public:
|
||||
// Assume IsPng was called and returned true.
|
||||
static SkCodec* NewFromStream(SkStream*, SkPngChunkReader* = NULL);
|
||||
|
||||
// FIXME (scroggo): Temporarily needed by AutoCleanPng.
|
||||
void setIdatLength(size_t len) { fIdatLength = len; }
|
||||
|
||||
~SkPngCodec() override;
|
||||
|
||||
protected:
|
||||
@ -76,18 +72,6 @@ protected:
|
||||
*/
|
||||
void processData();
|
||||
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
// In libpng 1.2.56, png_process_data_pause does not exist, so when we wanted to
|
||||
// read the header, we may have read too far. In that case, we need to delete the
|
||||
// png_ptr and info_ptr and recreate them. This method does that (and attaches the
|
||||
// chunk reader.
|
||||
bool rereadHeaderIfNecessary();
|
||||
|
||||
// This method sets up the new png_ptr/info_ptr (created in rereadHeaderIfNecessary)
|
||||
// the way we set up the old one the first time in AutoCleanPng.decodeBounds's callback.
|
||||
void rereadInfoCallback();
|
||||
#endif
|
||||
|
||||
Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes,
|
||||
const SkCodec::Options&,
|
||||
SkPMColor* ctable, int* ctableCount) override;
|
||||
@ -134,9 +118,8 @@ private:
|
||||
SkAlphaType fXformAlphaType;
|
||||
int fXformWidth;
|
||||
|
||||
#ifdef SK_GOOGLE3_PNG_HACK
|
||||
bool fNeedsToRereadHeader;
|
||||
#endif
|
||||
size_t fIdatLength;
|
||||
bool fDecodedIdat;
|
||||
|
||||
typedef SkCodec INHERITED;
|
||||
};
|
||||
|
102
tests/CodecExactReadTest.cpp
Normal file
102
tests/CodecExactReadTest.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "Resources.h"
|
||||
#include "Test.h"
|
||||
|
||||
#include "SkBitmap.h"
|
||||
#include "SkCodec.h"
|
||||
#include "SkData.h"
|
||||
#include "SkStream.h"
|
||||
|
||||
namespace {
|
||||
// This class emits a skiatest failure if a client attempts to read beyond its
|
||||
// end. Since it is used with complete, valid images, and contains nothing
|
||||
// after the encoded image data, it will emit a failure if the client attempts
|
||||
// to read beyond the logical end of the data.
|
||||
class MyStream : public SkStream {
|
||||
public:
|
||||
static MyStream* Make(const char* path, skiatest::Reporter* r) {
|
||||
SkASSERT(path);
|
||||
sk_sp<SkData> data(GetResourceAsData(path));
|
||||
if (!data) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new MyStream(path, std::move(data), r);
|
||||
}
|
||||
|
||||
size_t read(void* buf, size_t bytes) override {
|
||||
const size_t remaining = fStream.getLength() - fStream.getPosition();
|
||||
if (bytes > remaining) {
|
||||
ERRORF(fReporter, "Tried to read %lu bytes (only %lu remaining) from %s",
|
||||
bytes, remaining, fPath);
|
||||
}
|
||||
return fStream.read(buf, bytes);
|
||||
}
|
||||
|
||||
bool rewind() override {
|
||||
return fStream.rewind();
|
||||
}
|
||||
|
||||
bool isAtEnd() const override {
|
||||
return fStream.isAtEnd();
|
||||
}
|
||||
private:
|
||||
const char* fPath;
|
||||
SkMemoryStream fStream;
|
||||
skiatest::Reporter* fReporter; // Unowned
|
||||
|
||||
MyStream(const char* path, sk_sp<SkData> data, skiatest::Reporter* r)
|
||||
: fPath(path)
|
||||
, fStream(std::move(data))
|
||||
, fReporter(r)
|
||||
{}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Test that SkPngCodec does not attempt to read its input beyond the logical
|
||||
// end of its data. Some other SkCodecs do, but some Android apps rely on not
|
||||
// doing so for PNGs.
|
||||
DEF_TEST(Codec_end, r) {
|
||||
for (const char* path : { "plane.png",
|
||||
"yellow_rose.png",
|
||||
"plane_interlaced.png" }) {
|
||||
std::unique_ptr<MyStream> stream(MyStream::Make(path, r));
|
||||
if (!stream) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unique_ptr<SkCodec> codec(SkCodec::NewFromStream(stream.release()));
|
||||
if (!codec) {
|
||||
ERRORF(r, "Failed to create a codec from %s\n", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto info = codec->getInfo().makeColorType(kN32_SkColorType);
|
||||
SkBitmap bm;
|
||||
bm.allocPixels(info);
|
||||
|
||||
auto result = codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes());
|
||||
if (result != SkCodec::kSuccess) {
|
||||
ERRORF(r, "Failed to getPixels from %s. error %i", path, result);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rewind and do an incremental decode.
|
||||
result = codec->startIncrementalDecode(bm.info(), bm.getPixels(), bm.rowBytes());
|
||||
if (result != SkCodec::kSuccess) {
|
||||
ERRORF(r, "Failed to startIncrementalDecode from %s. error %i", path, result);
|
||||
continue;
|
||||
}
|
||||
|
||||
result = codec->incrementalDecode();
|
||||
if (result != SkCodec::kSuccess) {
|
||||
ERRORF(r, "Failed to incrementalDecode from %s. error %i", path, result);
|
||||
}
|
||||
}
|
||||
}
|
@ -118,6 +118,9 @@ static void test_partial(skiatest::Reporter* r, const char* name, size_t minByte
|
||||
}
|
||||
|
||||
DEF_TEST(Codec_partial, r) {
|
||||
#if 0
|
||||
// FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
|
||||
// support incremental decoding.
|
||||
test_partial(r, "plane.png");
|
||||
test_partial(r, "plane_interlaced.png");
|
||||
test_partial(r, "yellow_rose.png");
|
||||
@ -128,7 +131,7 @@ DEF_TEST(Codec_partial, r) {
|
||||
test_partial(r, "arrow.png");
|
||||
test_partial(r, "randPixels.png");
|
||||
test_partial(r, "baby_tux.png");
|
||||
|
||||
#endif
|
||||
test_partial(r, "box.gif");
|
||||
test_partial(r, "randPixels.gif", 215);
|
||||
test_partial(r, "color_wheel.gif");
|
||||
|
Loading…
Reference in New Issue
Block a user