Make SkCodec support peek() and read()
- Update SkCodec's dox to point out that some of the data must be read twice in order to decode - Add an accessor that reports how much is needed for reading twice - Make SkCodec default to use peek() If an input stream supports peek()ing, peek() instead of reading. This way the stream need not implement rewind() - Make SkCodec use read() + rewind() as a backup So that streams without peek() implemented can still function properly (assuming they can rewind). - read everything we may need to determine the format once In SkCodec::NewFromStream, peek()/read() 14 bytes, which is enough to read all of the types we support. Pass the buffer to each subtype, which will have enough info to determine whether it is the right type. This simplifies the code and results in less reading and rewinding. - NOTE: SkWbmpCodec needs the following number of bytes for the header + 1 (type) + 1 (reserved) + 3 (width - bytes needed to support up to 0xFFFF) + 3 (height - bytes needed to support up to 0xFFFF) = 8 - in SkWebpCodec, support using read + rewind as a backup if peek does not work. A change in Android will add peek() to JavaInputStreamAdapter. BUG=skia:3257 Review URL: https://codereview.chromium.org/1472123002
This commit is contained in:
parent
0671b967eb
commit
db30be2f94
@ -25,10 +25,35 @@ class SkSampler;
|
|||||||
*/
|
*/
|
||||||
class SkCodec : SkNoncopyable {
|
class SkCodec : SkNoncopyable {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* Minimum number of bytes that must be buffered in SkStream input.
|
||||||
|
*
|
||||||
|
* An SkStream passed to NewFromStream must be able to use this many
|
||||||
|
* bytes to determine the image type. Then the same SkStream must be
|
||||||
|
* passed to the correct decoder to read from the beginning.
|
||||||
|
*
|
||||||
|
* This can be accomplished by implementing peek() to support peeking
|
||||||
|
* this many bytes, or by implementing rewind() to be able to rewind()
|
||||||
|
* after reading this many bytes.
|
||||||
|
*/
|
||||||
|
static size_t MinBufferedBytesNeeded();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this stream represents an encoded image that we know how to decode,
|
* If this stream represents an encoded image that we know how to decode,
|
||||||
* return an SkCodec that can decode it. Otherwise return NULL.
|
* return an SkCodec that can decode it. Otherwise return NULL.
|
||||||
*
|
*
|
||||||
|
* As stated above, this call must be able to peek or read
|
||||||
|
* MinBufferedBytesNeeded to determine the correct format, and then start
|
||||||
|
* reading from the beginning. First it will attempt to peek, and it
|
||||||
|
* assumes that if less than MinBufferedBytesNeeded bytes (but more than
|
||||||
|
* zero) are returned, this is because the stream is shorter than this,
|
||||||
|
* so falling back to reading would not provide more data. If peek()
|
||||||
|
* returns zero bytes, this call will instead attempt to read(). This
|
||||||
|
* will require that the stream can be rewind()ed.
|
||||||
|
*
|
||||||
|
* If SkPngChunkReader is not NULL, take a ref and pass it to libpng if
|
||||||
|
* the image is a png.
|
||||||
|
*
|
||||||
* If the SkPngChunkReader is not NULL then:
|
* If the SkPngChunkReader is not NULL then:
|
||||||
* If the image is not a PNG, the SkPngChunkReader will be ignored.
|
* If the image is not a PNG, the SkPngChunkReader will be ignored.
|
||||||
* If the image is a PNG, the SkPngChunkReader will be reffed.
|
* If the image is a PNG, the SkPngChunkReader will be reffed.
|
||||||
|
@ -56,12 +56,10 @@ enum BmpInputFormat {
|
|||||||
/*
|
/*
|
||||||
* Checks the start of the stream to see if the image is a bitmap
|
* Checks the start of the stream to see if the image is a bitmap
|
||||||
*/
|
*/
|
||||||
bool SkBmpCodec::IsBmp(SkStream* stream) {
|
bool SkBmpCodec::IsBmp(const void* buffer, size_t bytesRead) {
|
||||||
// TODO: Support "IC", "PT", "CI", "CP", "BA"
|
// TODO: Support "IC", "PT", "CI", "CP", "BA"
|
||||||
const char bmpSig[] = { 'B', 'M' };
|
const char bmpSig[] = { 'B', 'M' };
|
||||||
char buffer[sizeof(bmpSig)];
|
return bytesRead >= sizeof(bmpSig) && !memcmp(buffer, bmpSig, sizeof(bmpSig));
|
||||||
return stream->read(buffer, sizeof(bmpSig)) == sizeof(bmpSig) &&
|
|
||||||
!memcmp(buffer, bmpSig, sizeof(bmpSig));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -20,11 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
class SkBmpCodec : public SkCodec {
|
class SkBmpCodec : public SkCodec {
|
||||||
public:
|
public:
|
||||||
|
static bool IsBmp(const void*, size_t);
|
||||||
/*
|
|
||||||
* Checks the start of the stream to see if the image is a bmp
|
|
||||||
*/
|
|
||||||
static bool IsBmp(SkStream*);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assumes IsBmp was called and returned true
|
* Assumes IsBmp was called and returned true
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
#include "SkWebpCodec.h"
|
#include "SkWebpCodec.h"
|
||||||
|
|
||||||
struct DecoderProc {
|
struct DecoderProc {
|
||||||
bool (*IsFormat)(SkStream*);
|
bool (*IsFormat)(const void*, size_t);
|
||||||
SkCodec* (*NewFromStream)(SkStream*);
|
SkCodec* (*NewFromStream)(SkStream*);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,6 +35,10 @@ static const DecoderProc gDecoderProcs[] = {
|
|||||||
{ SkWbmpCodec::IsWbmp, SkWbmpCodec::NewFromStream }
|
{ SkWbmpCodec::IsWbmp, SkWbmpCodec::NewFromStream }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
size_t SkCodec::MinBufferedBytesNeeded() {
|
||||||
|
return WEBP_VP8_HEADER_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
SkCodec* SkCodec::NewFromStream(SkStream* stream,
|
SkCodec* SkCodec::NewFromStream(SkStream* stream,
|
||||||
SkPngChunkReader* chunkReader) {
|
SkPngChunkReader* chunkReader) {
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
@ -42,23 +46,41 @@ SkCodec* SkCodec::NewFromStream(SkStream* stream,
|
|||||||
}
|
}
|
||||||
|
|
||||||
SkAutoTDelete<SkStream> streamDeleter(stream);
|
SkAutoTDelete<SkStream> streamDeleter(stream);
|
||||||
|
|
||||||
|
// 14 is enough to read all of the supported types.
|
||||||
|
const size_t bytesToRead = 14;
|
||||||
|
SkASSERT(bytesToRead <= MinBufferedBytesNeeded());
|
||||||
|
|
||||||
|
char buffer[bytesToRead];
|
||||||
|
size_t bytesRead = stream->peek(buffer, bytesToRead);
|
||||||
|
|
||||||
|
// It is also possible to have a complete image less than bytesToRead bytes
|
||||||
|
// (e.g. a 1 x 1 wbmp), meaning peek() would return less than bytesToRead.
|
||||||
|
// Assume that if bytesRead < bytesToRead, but > 0, the stream is shorter
|
||||||
|
// than bytesToRead, so pass that directly to the decoder.
|
||||||
|
// It also is possible the stream uses too small a buffer for peeking, but
|
||||||
|
// we trust the caller to use a large enough buffer.
|
||||||
|
|
||||||
|
if (0 == bytesRead) {
|
||||||
|
SkCodecPrintf("Could not peek!\n");
|
||||||
|
// It is possible the stream does not support peeking, but does support
|
||||||
|
// rewinding.
|
||||||
|
// Attempt to read() and pass the actual amount read to the decoder.
|
||||||
|
bytesRead = stream->read(buffer, bytesToRead);
|
||||||
|
if (!stream->rewind()) {
|
||||||
|
SkCodecPrintf("Could not rewind!\n");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SkAutoTDelete<SkCodec> codec(nullptr);
|
SkAutoTDelete<SkCodec> codec(nullptr);
|
||||||
// PNG is special, since we want to be able to supply an SkPngChunkReader.
|
// PNG is special, since we want to be able to supply an SkPngChunkReader.
|
||||||
// But this code follows the same pattern as the loop.
|
// But this code follows the same pattern as the loop.
|
||||||
const bool isPng = SkPngCodec::IsPng(stream);
|
if (SkPngCodec::IsPng(buffer, bytesRead)) {
|
||||||
if (!stream->rewind()) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (isPng) {
|
|
||||||
codec.reset(SkPngCodec::NewFromStream(streamDeleter.detach(), chunkReader));
|
codec.reset(SkPngCodec::NewFromStream(streamDeleter.detach(), chunkReader));
|
||||||
} else {
|
} else {
|
||||||
for (DecoderProc proc : gDecoderProcs) {
|
for (DecoderProc proc : gDecoderProcs) {
|
||||||
const bool correctFormat = proc.IsFormat(stream);
|
if (proc.IsFormat(buffer, bytesRead)) {
|
||||||
if (!stream->rewind()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (correctFormat) {
|
|
||||||
codec.reset(proc.NewFromStream(streamDeleter.detach()));
|
codec.reset(proc.NewFromStream(streamDeleter.detach()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,8 @@
|
|||||||
/*
|
/*
|
||||||
* Checks the start of the stream to see if the image is a gif
|
* Checks the start of the stream to see if the image is a gif
|
||||||
*/
|
*/
|
||||||
bool SkGifCodec::IsGif(SkStream* stream) {
|
bool SkGifCodec::IsGif(const void* buf, size_t bytesRead) {
|
||||||
char buf[GIF_STAMP_LEN];
|
if (bytesRead >= GIF_STAMP_LEN) {
|
||||||
if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
|
|
||||||
if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
|
if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
|
||||||
memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
|
memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
|
||||||
memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0)
|
memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0)
|
||||||
|
@ -19,11 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
class SkGifCodec : public SkCodec {
|
class SkGifCodec : public SkCodec {
|
||||||
public:
|
public:
|
||||||
|
static bool IsGif(const void*, size_t);
|
||||||
/*
|
|
||||||
* Checks the start of the stream to see if the image is a gif
|
|
||||||
*/
|
|
||||||
static bool IsGif(SkStream*);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assumes IsGif was called and returned true
|
* Assumes IsGif was called and returned true
|
||||||
|
@ -55,11 +55,10 @@ static SkImageInfo fix_embedded_alpha(const SkImageInfo& dstInfo, SkAlphaType em
|
|||||||
/*
|
/*
|
||||||
* Checks the start of the stream to see if the image is an Ico or Cur
|
* Checks the start of the stream to see if the image is an Ico or Cur
|
||||||
*/
|
*/
|
||||||
bool SkIcoCodec::IsIco(SkStream* stream) {
|
bool SkIcoCodec::IsIco(const void* buffer, size_t bytesRead) {
|
||||||
const char icoSig[] = { '\x00', '\x00', '\x01', '\x00' };
|
const char icoSig[] = { '\x00', '\x00', '\x01', '\x00' };
|
||||||
const char curSig[] = { '\x00', '\x00', '\x02', '\x00' };
|
const char curSig[] = { '\x00', '\x00', '\x02', '\x00' };
|
||||||
char buffer[sizeof(icoSig)];
|
return bytesRead >= sizeof(icoSig) &&
|
||||||
return stream->read(buffer, sizeof(icoSig)) == sizeof(icoSig) &&
|
|
||||||
(!memcmp(buffer, icoSig, sizeof(icoSig)) ||
|
(!memcmp(buffer, icoSig, sizeof(icoSig)) ||
|
||||||
!memcmp(buffer, curSig, sizeof(curSig)));
|
!memcmp(buffer, curSig, sizeof(curSig)));
|
||||||
}
|
}
|
||||||
@ -176,10 +175,8 @@ SkCodec* SkIcoCodec::NewFromStream(SkStream* stream) {
|
|||||||
bytesRead += size;
|
bytesRead += size;
|
||||||
|
|
||||||
// Check if the embedded codec is bmp or png and create the codec
|
// Check if the embedded codec is bmp or png and create the codec
|
||||||
const bool isPng = SkPngCodec::IsPng(embeddedStream);
|
|
||||||
SkAssertResult(embeddedStream->rewind());
|
|
||||||
SkCodec* codec = nullptr;
|
SkCodec* codec = nullptr;
|
||||||
if (isPng) {
|
if (SkPngCodec::IsPng((const char*) data->bytes(), data->size())) {
|
||||||
codec = SkPngCodec::NewFromStream(embeddedStream.detach());
|
codec = SkPngCodec::NewFromStream(embeddedStream.detach());
|
||||||
} else {
|
} else {
|
||||||
codec = SkBmpCodec::NewFromIco(embeddedStream.detach());
|
codec = SkBmpCodec::NewFromIco(embeddedStream.detach());
|
||||||
|
@ -15,11 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
class SkIcoCodec : public SkCodec {
|
class SkIcoCodec : public SkCodec {
|
||||||
public:
|
public:
|
||||||
|
static bool IsIco(const void*, size_t);
|
||||||
/*
|
|
||||||
* Checks the start of the stream to see if the image is a Ico or Cur
|
|
||||||
*/
|
|
||||||
static bool IsIco(SkStream*);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assumes IsIco was called and returned true
|
* Assumes IsIco was called and returned true
|
||||||
|
@ -206,17 +206,8 @@ bool SkPngCodec::decodePalette(bool premultiply, int* ctableCount) {
|
|||||||
// Creation
|
// Creation
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#define PNG_BYTES_TO_CHECK 4
|
bool SkPngCodec::IsPng(const char* buf, size_t bytesRead) {
|
||||||
|
return !png_sig_cmp((png_bytep) buf, (png_size_t)0, bytesRead);
|
||||||
bool SkPngCodec::IsPng(SkStream* stream) {
|
|
||||||
char buf[PNG_BYTES_TO_CHECK];
|
|
||||||
if (stream->read(buf, PNG_BYTES_TO_CHECK) != PNG_BYTES_TO_CHECK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads the header and initializes the output fields, if not NULL.
|
// Reads the header and initializes the output fields, if not NULL.
|
||||||
|
@ -19,7 +19,7 @@ class SkStream;
|
|||||||
|
|
||||||
class SkPngCodec : public SkCodec {
|
class SkPngCodec : public SkCodec {
|
||||||
public:
|
public:
|
||||||
static bool IsPng(SkStream*);
|
static bool IsPng(const char*, size_t);
|
||||||
|
|
||||||
// Assume IsPng was called and returned true.
|
// Assume IsPng was called and returned true.
|
||||||
static SkCodec* NewFromStream(SkStream*, SkPngChunkReader* = NULL);
|
static SkCodec* NewFromStream(SkStream*, SkPngChunkReader* = NULL);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "SkCodecPriv.h"
|
#include "SkCodecPriv.h"
|
||||||
#include "SkColorPriv.h"
|
#include "SkColorPriv.h"
|
||||||
#include "SkColorTable.h"
|
#include "SkColorTable.h"
|
||||||
|
#include "SkData.h"
|
||||||
#include "SkStream.h"
|
#include "SkStream.h"
|
||||||
#include "SkCodec_wbmp.h"
|
#include "SkCodec_wbmp.h"
|
||||||
|
|
||||||
@ -151,8 +152,10 @@ SkCodec::Result SkWbmpCodec::onGetPixels(const SkImageInfo& info,
|
|||||||
return kSuccess;
|
return kSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SkWbmpCodec::IsWbmp(SkStream* stream) {
|
bool SkWbmpCodec::IsWbmp(const void* buffer, size_t bytesRead) {
|
||||||
return read_header(stream, nullptr);
|
SkAutoTUnref<SkData> data(SkData::NewWithoutCopy(buffer, bytesRead));
|
||||||
|
SkMemoryStream stream(data);
|
||||||
|
return read_header(&stream, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
SkCodec* SkWbmpCodec::NewFromStream(SkStream* stream) {
|
SkCodec* SkWbmpCodec::NewFromStream(SkStream* stream) {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
class SkWbmpCodec final : public SkCodec {
|
class SkWbmpCodec final : public SkCodec {
|
||||||
public:
|
public:
|
||||||
static bool IsWbmp(SkStream*);
|
static bool IsWbmp(const void*, size_t);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assumes IsWbmp was called and returned true
|
* Assumes IsWbmp was called and returned true
|
||||||
|
@ -23,11 +23,9 @@ extern "C" {
|
|||||||
#include "jpeglib.h"
|
#include "jpeglib.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SkJpegCodec::IsJpeg(SkStream* stream) {
|
bool SkJpegCodec::IsJpeg(const void* buffer, size_t bytesRead) {
|
||||||
static const uint8_t jpegSig[] = { 0xFF, 0xD8, 0xFF };
|
static const uint8_t jpegSig[] = { 0xFF, 0xD8, 0xFF };
|
||||||
char buffer[sizeof(jpegSig)];
|
return bytesRead >= 3 && !memcmp(buffer, jpegSig, sizeof(jpegSig));
|
||||||
return stream->read(buffer, sizeof(jpegSig)) == sizeof(jpegSig) &&
|
|
||||||
!memcmp(buffer, jpegSig, sizeof(jpegSig));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut,
|
bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut,
|
||||||
|
@ -25,12 +25,7 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
class SkJpegCodec : public SkCodec {
|
class SkJpegCodec : public SkCodec {
|
||||||
public:
|
public:
|
||||||
|
static bool IsJpeg(const void*, size_t);
|
||||||
/*
|
|
||||||
* Checks the start of the stream to see if the image is a jpeg
|
|
||||||
* Does not take ownership of the stream
|
|
||||||
*/
|
|
||||||
static bool IsJpeg(SkStream*);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assumes IsJpeg was called and returned true
|
* Assumes IsJpeg was called and returned true
|
||||||
|
@ -20,26 +20,26 @@
|
|||||||
#include "webp/decode.h"
|
#include "webp/decode.h"
|
||||||
#include "webp/encode.h"
|
#include "webp/encode.h"
|
||||||
|
|
||||||
bool SkWebpCodec::IsWebp(SkStream* stream) {
|
bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) {
|
||||||
// WEBP starts with the following:
|
// WEBP starts with the following:
|
||||||
// RIFFXXXXWEBPVP
|
// RIFFXXXXWEBPVP
|
||||||
// Where XXXX is unspecified.
|
// Where XXXX is unspecified.
|
||||||
const char LENGTH = 14;
|
const char* bytes = static_cast<const char*>(buf);
|
||||||
char bytes[LENGTH];
|
return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6);
|
||||||
if (stream->read(&bytes, LENGTH) != LENGTH) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const size_t WEBP_VP8_HEADER_SIZE = 30;
|
|
||||||
|
|
||||||
// Parse headers of RIFF container, and check for valid Webp (VP8) content.
|
// Parse headers of RIFF container, and check for valid Webp (VP8) content.
|
||||||
// NOTE: This calls peek instead of read, since onGetPixels will need these
|
// NOTE: This calls peek instead of read, since onGetPixels will need these
|
||||||
// bytes again.
|
// bytes again.
|
||||||
static bool webp_parse_header(SkStream* stream, SkImageInfo* info) {
|
static bool webp_parse_header(SkStream* stream, SkImageInfo* info) {
|
||||||
unsigned char buffer[WEBP_VP8_HEADER_SIZE];
|
unsigned char buffer[WEBP_VP8_HEADER_SIZE];
|
||||||
if (!stream->peek(buffer, WEBP_VP8_HEADER_SIZE)) {
|
SkASSERT(WEBP_VP8_HEADER_SIZE <= SkCodec::MinBufferedBytesNeeded());
|
||||||
|
|
||||||
|
const size_t bytesPeeked = stream->peek(buffer, WEBP_VP8_HEADER_SIZE);
|
||||||
|
if (bytesPeeked != WEBP_VP8_HEADER_SIZE) {
|
||||||
|
// Use read + rewind as a backup
|
||||||
|
if (stream->read(buffer, WEBP_VP8_HEADER_SIZE) != WEBP_VP8_HEADER_SIZE
|
||||||
|
|| !stream->rewind())
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,11 +15,13 @@
|
|||||||
|
|
||||||
class SkStream;
|
class SkStream;
|
||||||
|
|
||||||
|
static const size_t WEBP_VP8_HEADER_SIZE = 30;
|
||||||
|
|
||||||
class SkWebpCodec final : public SkCodec {
|
class SkWebpCodec final : public SkCodec {
|
||||||
public:
|
public:
|
||||||
// Assumes IsWebp was called and returned true.
|
// Assumes IsWebp was called and returned true.
|
||||||
static SkCodec* NewFromStream(SkStream*);
|
static SkCodec* NewFromStream(SkStream*);
|
||||||
static bool IsWebp(SkStream*);
|
static bool IsWebp(const void*, size_t);
|
||||||
protected:
|
protected:
|
||||||
Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*)
|
Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*)
|
||||||
override;
|
override;
|
||||||
|
@ -848,6 +848,54 @@ DEF_TEST(Codec_pngChunkReader, r) {
|
|||||||
}
|
}
|
||||||
#endif // PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
|
#endif // PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
|
||||||
|
|
||||||
|
// Stream that can only peek up to a limit
|
||||||
|
class LimitedPeekingMemStream : public SkStream {
|
||||||
|
public:
|
||||||
|
LimitedPeekingMemStream(SkData* data, size_t limit)
|
||||||
|
: fStream(data)
|
||||||
|
, fLimit(limit) {}
|
||||||
|
|
||||||
|
size_t peek(void* buf, size_t bytes) const override {
|
||||||
|
return fStream.peek(buf, SkTMin(bytes, fLimit));
|
||||||
|
}
|
||||||
|
size_t read(void* buf, size_t bytes) override {
|
||||||
|
return fStream.read(buf, bytes);
|
||||||
|
}
|
||||||
|
bool rewind() override {
|
||||||
|
return fStream.rewind();
|
||||||
|
}
|
||||||
|
bool isAtEnd() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
SkMemoryStream fStream;
|
||||||
|
const size_t fLimit;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test that even if webp_parse_header fails to peek enough, it will fall back to read()
|
||||||
|
// + rewind() and succeed.
|
||||||
|
DEF_TEST(Codec_webp_peek, r) {
|
||||||
|
const char* path = "baby_tux.webp";
|
||||||
|
SkString fullPath(GetResourcePath(path));
|
||||||
|
SkAutoTUnref<SkData> data(SkData::NewFromFileName(fullPath.c_str()));
|
||||||
|
if (!data) {
|
||||||
|
SkDebugf("Missing resource '%s'\n", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The limit is less than webp needs to peek or read.
|
||||||
|
SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(new LimitedPeekingMemStream(data, 25)));
|
||||||
|
REPORTER_ASSERT(r, codec);
|
||||||
|
|
||||||
|
test_info(r, codec, codec->getInfo(), SkCodec::kSuccess, nullptr);
|
||||||
|
|
||||||
|
// Similarly, a stream which does not peek should still succeed.
|
||||||
|
codec.reset(SkCodec::NewFromStream(new LimitedPeekingMemStream(data, 0)));
|
||||||
|
REPORTER_ASSERT(r, codec);
|
||||||
|
|
||||||
|
test_info(r, codec, codec->getInfo(), SkCodec::kSuccess, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
// SkCodec's wbmp decoder was initially more restrictive than SkImageDecoder.
|
// SkCodec's wbmp decoder was initially more restrictive than SkImageDecoder.
|
||||||
// It required the second byte to be zero. But SkImageDecoder allowed a couple
|
// It required the second byte to be zero. But SkImageDecoder allowed a couple
|
||||||
// of bits to be 1 (so long as they do not overlap with 0x9F). Test that
|
// of bits to be 1 (so long as they do not overlap with 0x9F). Test that
|
||||||
@ -877,3 +925,32 @@ DEF_TEST(Codec_wbmp, r) {
|
|||||||
}
|
}
|
||||||
test_info(r, codec, codec->getInfo(), SkCodec::kSuccess, nullptr);
|
test_info(r, codec, codec->getInfo(), SkCodec::kSuccess, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wbmp images have a header that can be arbitrarily large, depending on the
|
||||||
|
// size of the image. We cap the size at 65535, meaning we only need to look at
|
||||||
|
// 8 bytes to determine whether we can read the image. This is important
|
||||||
|
// because SkCodec only passes 14 bytes to SkWbmpCodec to determine whether the
|
||||||
|
// image is a wbmp.
|
||||||
|
DEF_TEST(Codec_wbmp_max_size, r) {
|
||||||
|
const unsigned char maxSizeWbmp[] = { 0x00, 0x00, // Header
|
||||||
|
0x83, 0xFF, 0x7F, // W: 65535
|
||||||
|
0x83, 0xFF, 0x7F }; // H: 65535
|
||||||
|
SkAutoTDelete<SkStream> stream(new SkMemoryStream(maxSizeWbmp, sizeof(maxSizeWbmp), false));
|
||||||
|
SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach()));
|
||||||
|
|
||||||
|
REPORTER_ASSERT(r, codec);
|
||||||
|
if (!codec) return;
|
||||||
|
|
||||||
|
REPORTER_ASSERT(r, codec->getInfo().width() == 65535);
|
||||||
|
REPORTER_ASSERT(r, codec->getInfo().height() == 65535);
|
||||||
|
|
||||||
|
// Now test an image which is too big. Any image with a larger header (i.e.
|
||||||
|
// has bigger width/height) is also too big.
|
||||||
|
const unsigned char tooBigWbmp[] = { 0x00, 0x00, // Header
|
||||||
|
0x84, 0x80, 0x00, // W: 65536
|
||||||
|
0x84, 0x80, 0x00 }; // H: 65536
|
||||||
|
stream.reset(new SkMemoryStream(tooBigWbmp, sizeof(tooBigWbmp), false));
|
||||||
|
codec.reset(SkCodec::NewFromStream(stream.detach()));
|
||||||
|
|
||||||
|
REPORTER_ASSERT(r, !codec);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user