diff --git a/NEWS b/NEWS index f61fad26..3f956e00 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +- Next version: + * Add incremental decoding support for images containing ALPH and ICCP chunks. + - 3/20/13: version 0.3.0 This is a binary compatible release. * WebPINewRGB/WebPINewYUVA accept being passed a NULL output buffer diff --git a/src/dec/alpha.c b/src/dec/alpha.c index 5c9cdd6a..630e367f 100644 --- a/src/dec/alpha.c +++ b/src/dec/alpha.c @@ -113,11 +113,13 @@ const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, if (row == 0) { // Decode everything during the first call. + assert(!dec->is_alpha_decoded_); if (!DecodeAlpha(dec->alpha_data_, (size_t)dec->alpha_data_size_, dec->pic_hdr_.width_, dec->pic_hdr_.height_, stride, dec->alpha_plane_)) { return NULL; // Error. } + dec->is_alpha_decoded_ = 1; } // Return a pointer to the current decoded row. diff --git a/src/dec/idec.c b/src/dec/idec.c index 17810c83..cbf9638e 100644 --- a/src/dec/idec.c +++ b/src/dec/idec.c @@ -97,6 +97,23 @@ static WEBP_INLINE size_t MemDataSize(const MemBuffer* mem) { return (mem->end_ - mem->start_); } +// Check if we need to preserve the compressed alpha data, as it may not have +// been decoded yet. +static int NeedCompressedAlpha(const WebPIDecoder* const idec) { + if (idec->state_ == STATE_PRE_VP8) { + // We haven't parsed the headers yet, so we don't know whether the image is + // lossy or lossless. This also means that we haven't parsed the ALPH chunk. + return 0; + } + if (idec->is_lossless_) { + return 0; // ALPH chunk is not present for lossless images. + } else { + const VP8Decoder* const dec = (VP8Decoder*)idec->dec_; + assert(dec != NULL); // Must be true as idec->state_ != STATE_PRE_VP8. + return (dec->alpha_data_ != NULL) && !dec->is_alpha_decoded_; + } +} + static void DoRemap(WebPIDecoder* const idec, ptrdiff_t offset) { MemBuffer* const mem = &idec->mem_; const uint8_t* const new_base = mem->buf_ + mem->start_; @@ -122,6 +139,7 @@ static void DoRemap(WebPIDecoder* const idec, ptrdiff_t offset) { } assert(last_part >= 0); dec->parts_[last_part].buf_end_ = mem->buf_ + mem->end_; + if (NeedCompressedAlpha(idec)) dec->alpha_data_ += offset; } else { // Resize lossless bitreader VP8LDecoder* const dec = (VP8LDecoder*)idec->dec_; VP8LBitReaderSetBuffer(&dec->br_, new_base, MemDataSize(mem)); @@ -133,8 +151,12 @@ static void DoRemap(WebPIDecoder* const idec, ptrdiff_t offset) { // size if required and also updates VP8BitReader's if new memory is allocated. static int AppendToMemBuffer(WebPIDecoder* const idec, const uint8_t* const data, size_t data_size) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec_; MemBuffer* const mem = &idec->mem_; - const uint8_t* const old_base = mem->buf_ + mem->start_; + const int need_compressed_alpha = NeedCompressedAlpha(idec); + const uint8_t* const old_start = mem->buf_ + mem->start_; + const uint8_t* const old_base = + need_compressed_alpha ? dec->alpha_data_ : old_start; assert(mem->mode_ == MEM_MODE_APPEND); if (data_size > MAX_CHUNK_PAYLOAD) { // security safeguard: trying to allocate more than what the format @@ -143,7 +165,8 @@ static int AppendToMemBuffer(WebPIDecoder* const idec, } if (mem->end_ + data_size > mem->buf_size_) { // Need some free memory - const size_t current_size = MemDataSize(mem); + const size_t new_mem_start = old_start - old_base; + const size_t current_size = MemDataSize(mem) + new_mem_start; const uint64_t new_size = (uint64_t)current_size + data_size; const uint64_t extra_size = (new_size + CHUNK_SIZE - 1) & ~(CHUNK_SIZE - 1); uint8_t* const new_buf = @@ -153,7 +176,7 @@ static int AppendToMemBuffer(WebPIDecoder* const idec, free(mem->buf_); mem->buf_ = new_buf; mem->buf_size_ = (size_t)extra_size; - mem->start_ = 0; + mem->start_ = new_mem_start; mem->end_ = current_size; } @@ -161,14 +184,15 @@ static int AppendToMemBuffer(WebPIDecoder* const idec, mem->end_ += data_size; assert(mem->end_ <= mem->buf_size_); - DoRemap(idec, mem->buf_ + mem->start_ - old_base); + DoRemap(idec, mem->buf_ + mem->start_ - old_start); return 1; } static int RemapMemBuffer(WebPIDecoder* const idec, const uint8_t* const data, size_t data_size) { MemBuffer* const mem = &idec->mem_; - const uint8_t* const old_base = mem->buf_ + mem->start_; + const uint8_t* const old_buf = mem->buf_; + const uint8_t* const old_start = old_buf + mem->start_; assert(mem->mode_ == MEM_MODE_MAP); if (data_size < mem->buf_size_) return 0; // can't remap to a shorter buffer! @@ -176,7 +200,7 @@ static int RemapMemBuffer(WebPIDecoder* const idec, mem->buf_ = (uint8_t*)data; mem->end_ = mem->buf_size_ = data_size; - DoRemap(idec, mem->buf_ + mem->start_ - old_base); + DoRemap(idec, mem->buf_ + mem->start_ - old_start); return 1; } diff --git a/src/dec/vp8i.h b/src/dec/vp8i.h index 1aa92385..4f1a91c8 100644 --- a/src/dec/vp8i.h +++ b/src/dec/vp8i.h @@ -276,6 +276,7 @@ struct VP8Decoder { // extensions const uint8_t* alpha_data_; // compressed alpha data (if present) size_t alpha_data_size_; + int is_alpha_decoded_; // true if alpha_data_ is decoded in alpha_plane_ uint8_t* alpha_plane_; // output. Persistent, contains the whole data. int layer_colorspace_; diff --git a/src/dec/webp.c b/src/dec/webp.c index 39d90188..aa3a703f 100644 --- a/src/dec/webp.c +++ b/src/dec/webp.c @@ -192,6 +192,15 @@ static VP8StatusCode ParseOptionalChunks(const uint8_t** const data, return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size. } + // Start of a (possibly incomplete) VP8/VP8L chunk implies that we have + // parsed all the optional chunks. + // Note: This check must occur before the check 'buf_size < disk_chunk_size' + // below to allow incomplete VP8/VP8L chunks. + if (!memcmp(buf, "VP8 ", TAG_SIZE) || + !memcmp(buf, "VP8L", TAG_SIZE)) { + return VP8_STATUS_OK; + } + if (buf_size < disk_chunk_size) { // Insufficient data. return VP8_STATUS_NOT_ENOUGH_DATA; } @@ -199,9 +208,6 @@ static VP8StatusCode ParseOptionalChunks(const uint8_t** const data, if (!memcmp(buf, "ALPH", TAG_SIZE)) { // A valid ALPH header. *alpha_data = buf + CHUNK_HEADER_SIZE; *alpha_size = chunk_size; - } else if (!memcmp(buf, "VP8 ", TAG_SIZE) || - !memcmp(buf, "VP8L", TAG_SIZE)) { // A valid VP8/VP8L header. - return VP8_STATUS_OK; // Found. } // We have a full and valid chunk; skip it.