diff --git a/makefile.unix b/makefile.unix index 925f9ec5..258003c9 100644 --- a/makefile.unix +++ b/makefile.unix @@ -60,8 +60,8 @@ COL = /usr/bin/col LDFLAGS = $(EXTRA_LIBS) -lm DEC_OBJS = src/dec/frame.o src/dec/webp.o src/dec/quant.o src/dec/tree.o \ - src/dec/vp8.o src/dec/idec.o src/dec/alpha.o src/dec/layer.o \ - src/dec/io.o src/dec/buffer.o + src/dec/vp8.o src/dec/vp8l.o src/dec/idec.o src/dec/alpha.o \ + src/dec/layer.o src/dec/io.o src/dec/buffer.o ENC_OBJS = src/enc/webpenc.o src/enc/syntax.o \ src/enc/alpha.o src/enc/layer.o \ src/enc/tree.o src/enc/config.o src/enc/frame.o \ @@ -69,21 +69,23 @@ ENC_OBJS = src/enc/webpenc.o src/enc/syntax.o \ src/enc/cost.o src/enc/picture.o src/enc/filter.o DSP_OBJS = src/dsp/cpu.o src/dsp/enc.o \ src/dsp/enc_sse2.o src/dsp/dec.o src/dsp/dec_sse2.o \ - src/dsp/dec_neon.o src/dsp/upsampling.o src/dsp/upsampling_sse2.o \ - src/dsp/yuv.o + src/dsp/dec_neon.o src/dsp/lossless.o src/dsp/upsampling.o \ + src/dsp/upsampling_sse2.o src/dsp/yuv.o UTILS_OBJS = src/utils/alpha.o src/utils/bit_reader.o src/utils/bit_writer.o \ - src/utils/filters.o src/utils/quant_levels.o src/utils/rescaler.o \ - src/utils/thread.o src/utils/tcoder.o + src/utils/color_cache.o src/utils/filters.o src/utils/huffman.o \ + src/utils/quant_levels.o src/utils/rescaler.o src/utils/thread.o \ + src/utils/tcoder.o OBJS = $(DEC_OBJS) $(ENC_OBJS) $(DSP_OBJS) $(UTILS_OBJS) MUX_OBJS = src/mux/muxedit.o src/mux/muxinternal.o src/mux/muxread.o HDRS = src/webp/encode.h src/enc/vp8enci.h src/enc/cost.h src/webp/mux.h \ - src/dec/vp8i.h \ - src/dsp/yuv.h src/dsp/dsp.h \ - src/utils/alpha.h src/utils/bit_writer.h src/utils/bit_reader.h \ - src/utils/filters.h src/utils/thread.h src/utils/tcoder.h + src/dec/vp8i.h src/dec/vp8li.h src/dsp/yuv.h src/dsp/dsp.h \ + src/dsp/lossless.h src/utils/alpha.h src/utils/bit_writer.h \ + src/utils/bit_reader.h src/utils/color_cache.h src/utils/filters.h \ + src/utils/huffman.h src/utils/thread.h \ + src/utils/tcoder.h OUT_LIBS = src/libwebp.a src/mux/libwebpmux.a OUT_EXAMPLES = examples/cwebp examples/dwebp examples/webpmux diff --git a/src/dec/Makefile.am b/src/dec/Makefile.am index afcadd1a..3a963b2d 100644 --- a/src/dec/Makefile.am +++ b/src/dec/Makefile.am @@ -1,7 +1,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src libwebpdecode_la_SOURCES = vp8i.h webpi.h \ - frame.c quant.c tree.c vp8.c webp.c \ + frame.c quant.c tree.c vp8.c vp8l.c webp.c \ idec.c alpha.c layer.c io.c buffer.c libwebpdecode_la_CPPFLAGS = $(USE_EXPERIMENTAL_CODE) libwebpdecodeinclude_HEADERS = ../webp/decode.h ../webp/decode_vp8.h \ diff --git a/src/dec/idec.c b/src/dec/idec.c index 4ce07cad..06f9238c 100644 --- a/src/dec/idec.c +++ b/src/dec/idec.c @@ -35,6 +35,8 @@ typedef enum { STATE_VP8_FRAME_HEADER, // For VP8 Frame header (within VP8 chunk). STATE_VP8_PARTS0, STATE_VP8_DATA, + STATE_VP8L_HEADER, + STATE_VP8L_DATA, STATE_DONE, STATE_ERROR } DecState; @@ -88,6 +90,17 @@ static WEBP_INLINE size_t MemDataSize(const MemBuffer* mem) { return (mem->end_ - mem->start_); } +static void ResizeLosslessBitReader(VP8Decoder* const dec, + const uint8_t* const new_buf, + size_t new_size) { + VP8LDecoder* const vp8l_decoder = &dec->vp8l_decoder_; + if (vp8l_decoder->br_.buf_ != NULL) { + VP8LBitReaderResize(&vp8l_decoder->br_, + new_buf + vp8l_decoder->br_offset_, + new_size - vp8l_decoder->br_offset_); + } +} + // Appends data to the end of MemBuffer->buf_. It expands the allocated memory // size if required and also updates VP8BitReader's if new memory is allocated. static int AppendToMemBuffer(WebPIDecoder* const idec, @@ -130,6 +143,14 @@ static int AppendToMemBuffer(WebPIDecoder* const idec, assert(last_part >= 0); dec->parts_[last_part].buf_end_ = mem->buf_ + mem->end_; + // Lossless Bit Reader needs to be resized for every invocation of + // WebPIAppend as buf_end_ is changing with every invocation. + if (dec->is_lossless_) { + VP8LDecoder* const vp8l_decoder = &dec->vp8l_decoder_; + vp8l_decoder->br_offset_ = mem->start_; + ResizeLosslessBitReader(dec, mem->buf_, mem->end_); + } + // note: setting up idec->io_ is only really needed at the beginning // of the decoding, till partition #0 is complete. idec->io_.data = mem->buf_ + mem->start_; @@ -150,19 +171,23 @@ static int RemapMemBuffer(WebPIDecoder* const idec, return 0; // we cannot remap to a shorter buffer! } - for (p = 0; p <= last_part; ++p) { - if (dec->parts_[p].buf_) { - REMAP(dec->parts_[p].buf_, base, data); - REMAP(dec->parts_[p].buf_end_, base, data); + if (!dec->is_lossless_) { + for (p = 0; p <= last_part; ++p) { + if (dec->parts_[p].buf_) { + REMAP(dec->parts_[p].buf_, base, data); + REMAP(dec->parts_[p].buf_end_, base, data); + } } - } - assert(last_part >= 0); - dec->parts_[last_part].buf_end_ = data + data_size; + assert(last_part >= 0); + dec->parts_[last_part].buf_end_ = data + data_size; - // Remap partition #0 data pointer to new offset. - if (dec->br_.buf_) { - REMAP(dec->br_.buf_, base, data); - REMAP(dec->br_.buf_end_, base, data); + // Remap partition #0 data pointer to new offset. + if (dec->br_.buf_) { + REMAP(dec->br_.buf_, base, data); + REMAP(dec->br_.buf_end_, base, data); + } + } else { + ResizeLosslessBitReader(dec, data, data_size); } mem->buf_ = (uint8_t*)data; @@ -255,20 +280,27 @@ static void ChangeState(WebPIDecoder* const idec, DecState new_state, // Headers static VP8StatusCode DecodeWebPHeaders(WebPIDecoder* const idec) { const uint8_t* data = idec->mem_.buf_ + idec->mem_.start_; + VP8Decoder* const dec = idec->dec_; uint32_t curr_size = MemDataSize(&idec->mem_); - uint32_t vp8_size; - uint32_t bytes_skipped; VP8StatusCode status; + WebPHeaderStructure headers; - status = WebPParseHeaders(&data, &curr_size, &vp8_size, &bytes_skipped, - &idec->dec_->alpha_data_, - &idec->dec_->alpha_data_size_); + headers.data = data; + headers.data_size = curr_size; + status = WebPParseHeaders(&headers); if (status == VP8_STATUS_NOT_ENOUGH_DATA) { return VP8_STATUS_SUSPENDED; // We haven't found a VP8 chunk yet. } else if (status == VP8_STATUS_OK) { - idec->vp8_size_ = vp8_size; - ChangeState(idec, STATE_VP8_FRAME_HEADER, bytes_skipped); - return VP8_STATUS_OK; // We have skipped all pre-VP8 chunks. + idec->vp8_size_ = headers.vp8_size; + dec->alpha_data_ = headers.alpha_data; + dec->alpha_data_size_ = headers.alpha_data_size; + dec->is_lossless_ = headers.is_lossless; + if (!dec->is_lossless_) { + ChangeState(idec, STATE_VP8_FRAME_HEADER, headers.offset); + } else { + ChangeState(idec, STATE_VP8L_HEADER, headers.offset); + } + return VP8_STATUS_OK; } else { return IDecError(idec, status); } @@ -417,6 +449,69 @@ static VP8StatusCode DecodeRemaining(WebPIDecoder* const idec) { return VP8_STATUS_OK; } +static int ErrorStatusLossless(WebPIDecoder* const idec, VP8StatusCode status) { + if (status == VP8_STATUS_SUSPENDED || status == VP8_STATUS_NOT_ENOUGH_DATA) { + return VP8_STATUS_SUSPENDED; + } + return IDecError(idec, status); +} + +static VP8StatusCode DecodeVP8LHeader(WebPIDecoder* const idec) { + VP8Io* const io = &idec->io_; + VP8Decoder* const dec = idec->dec_; + VP8LDecoder* const vp8l_decoder = &dec->vp8l_decoder_; + const WebPDecParams* const params = &idec->params_; + WebPDecBuffer* const output = params->output; + uint32_t curr_size = MemDataSize(&idec->mem_); + + // Wait until there's enough data for decoding header. + if (curr_size < (idec->vp8_size_ >> 3)) { + return VP8_STATUS_SUSPENDED; + } + + VP8LInitDecoder(vp8l_decoder); + // start_ corresponds to bytes consumed in the last state (parsing RIFF + // header). For Update mode, io->data is set to mem_.buf_ and hence require an + // offset corresponding to start_ in BitReader. In Append mode, io->data + // is already start_ offset over mem_.buf_, so doesn't require additional + // offset in BitReader. + vp8l_decoder->br_offset_ = + (io->data == idec->mem_.buf_) ? idec->mem_.start_ : 0; + if (!VP8LDecodeHeader(vp8l_decoder, io)) { + VP8LClear(vp8l_decoder); + return ErrorStatusLossless(idec, vp8l_decoder->status_); + } + // Allocate/verify output buffer now. + dec->status_ = WebPAllocateDecBuffer(io->width, io->height, params->options, + output); + if (dec->status_ != VP8_STATUS_OK) { + return IDecError(idec, dec->status_); + } + + idec->state_ = STATE_VP8L_DATA; + return VP8_STATUS_OK; +} + +static VP8StatusCode DecodeVP8LData(WebPIDecoder* const idec) { + VP8Decoder* const dec = idec->dec_; + VP8LDecoder* const vp8l_decoder = &dec->vp8l_decoder_; + const uint32_t curr_size = MemDataSize(&idec->mem_); + + // At present Lossless decoder can't decode image incrementally. So wait till + // all the image data is aggregated before image can be decoded. + if (curr_size < idec->vp8_size_) { + return VP8_STATUS_SUSPENDED; + } + + if (!VP8LDecodeImage(vp8l_decoder)) { + return ErrorStatusLossless(idec, vp8l_decoder->status_); + } + + idec->state_ = STATE_DONE; + + return VP8_STATUS_OK; +} + // Main decoding loop static VP8StatusCode IDecode(WebPIDecoder* idec) { VP8StatusCode status = VP8_STATUS_SUSPENDED; @@ -434,6 +529,12 @@ static VP8StatusCode IDecode(WebPIDecoder* idec) { if (idec->state_ == STATE_VP8_DATA) { status = DecodeRemaining(idec); } + if (idec->state_ == STATE_VP8L_HEADER) { + status = DecodeVP8LHeader(idec); + } + if (idec->state_ == STATE_VP8L_DATA) { + status = DecodeVP8LData(idec); + } return status; } diff --git a/src/dec/vp8.c b/src/dec/vp8.c index cceebf0b..79add78e 100644 --- a/src/dec/vp8.c +++ b/src/dec/vp8.c @@ -12,7 +12,9 @@ #include #include "./vp8i.h" +#include "./vp8li.h" #include "./webpi.h" +#include "../utils/bit_reader.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { @@ -243,14 +245,11 @@ static int ParseFilterHeader(VP8BitReader* br, VP8Decoder* const dec) { int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { const uint8_t* buf; uint32_t buf_size; - const uint8_t* alpha_data_tmp; - uint32_t alpha_size_tmp; - uint32_t vp8_chunk_size; - uint32_t bytes_skipped; VP8FrameHeader* frm_hdr; VP8PictureHeader* pic_hdr; VP8BitReader* br; VP8StatusCode status; + WebPHeaderStructure headers; if (dec == NULL) { return 0; @@ -261,26 +260,44 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { "null VP8Io passed to VP8GetHeaders()"); } - buf = io->data; - buf_size = io->data_size; + headers.data = io->data; + headers.data_size = io->data_size; // Process Pre-VP8 chunks. - status = WebPParseHeaders(&buf, &buf_size, &vp8_chunk_size, &bytes_skipped, - &alpha_data_tmp, &alpha_size_tmp); + status = WebPParseHeaders(&headers); if (status != VP8_STATUS_OK) { return VP8SetError(dec, status, "Incorrect/incomplete header."); } + if (headers.is_lossless) { + int ok; + VP8LDecoder* const vp8l_decoder = &dec->vp8l_decoder_; + VP8LInitDecoder(vp8l_decoder); + vp8l_decoder->br_offset_ = headers.offset; + ok = VP8LDecodeHeader(vp8l_decoder, io); + if (!ok) { + VP8LClear(vp8l_decoder); + return VP8SetError(dec, vp8l_decoder->status_, + "Incorrect/incomplete header."); + } else { + dec->is_lossless_ = 1; + } + return ok; + } + if (dec->alpha_data_ == NULL) { assert(dec->alpha_data_size_ == 0); // We have NOT set alpha data yet. Set it now. // (This is to ensure that dec->alpha_data_ is NOT reset to NULL if // WebPParseHeaders() is called more than once, as in incremental decoding // case.) - dec->alpha_data_ = alpha_data_tmp; - dec->alpha_data_size_ = alpha_size_tmp; + dec->alpha_data_ = headers.alpha_data; + dec->alpha_data_size_ = headers.alpha_data_size; } // Process the VP8 frame header. + buf = headers.data + headers.offset; + buf_size = headers.data_size - headers.offset; + assert(headers.data_size >= headers.offset); // WebPParseHeaders' guarantee if (buf_size < 4) { return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, "Truncated header."); @@ -724,6 +741,9 @@ int VP8Decode(VP8Decoder* const dec, VP8Io* const io) { } assert(dec->ready_); + // TODO: Implement lossless decoding. Error till then. + if (dec->is_lossless_) goto Error; + // Finish setting up the decoding parameter. Will call io->setup(). ok = (VP8EnterCritical(dec, io) == VP8_STATUS_OK); if (ok) { // good to go. @@ -737,13 +757,14 @@ int VP8Decode(VP8Decoder* const dec, VP8Io* const io) { ok &= VP8ExitCritical(dec, io); } + Error: if (!ok) { VP8Clear(dec); return 0; } dec->ready_ = 0; - return 1; + return ok; } void VP8Clear(VP8Decoder* const dec) { diff --git a/src/dec/webp.c b/src/dec/webp.c index b995eb3f..4f0c20ba 100644 --- a/src/dec/webp.c +++ b/src/dec/webp.c @@ -12,6 +12,7 @@ #include #include "./vp8i.h" +#include "./vp8li.h" #include "./webpi.h" #include "../mux/muxi.h" // For MAX_CHUNK_PAYLOAD. #include "../webp/mux.h" // For 'ALPHA_FLAG'. @@ -31,6 +32,10 @@ extern "C" { // 16..19 size of the raw VP8 image data, starting at offset 20 // 20.... the VP8 bytes // Or, +// 12..15 "VP8L": 4-bytes tags, signaling the use of VP8L lossless format +// 16..19 size of the raw VP8L image data, starting at offset 20 +// 20.... the VP8L bytes +// Or, // 12..15 "VP8X": 4-bytes tags, describing the extended-VP8 chunk. // 16..19 size of the VP8X chunk starting at offset 20. // 20..23 VP8X flags bit-map corresponding to the chunk-types present. @@ -45,41 +50,54 @@ static WEBP_INLINE uint32_t get_le32(const uint8_t* const data) { return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); } -VP8StatusCode WebPParseRIFF(const uint8_t** data, uint32_t* data_size, - uint32_t* riff_size) { +// Validates the RIFF container (if detected) and skips over it. +// If a RIFF container is detected, +// Returns VP8_STATUS_BITSTREAM_ERROR for invalid header, and +// VP8_STATUS_OK otherwise. +// In case there are not enough bytes (partial RIFF container), return 0 for +// riff_size. Else return the riff_size extracted from the header. +static VP8StatusCode ParseRIFF(const uint8_t** data, uint32_t* data_size, + uint32_t* riff_size) { assert(data); assert(data_size); assert(riff_size); + *riff_size = 0; // Default: no RIFF present. if (*data_size >= RIFF_HEADER_SIZE && !memcmp(*data, "RIFF", TAG_SIZE)) { if (memcmp(*data + 8, "WEBP", TAG_SIZE)) { return VP8_STATUS_BITSTREAM_ERROR; // Wrong image file signature. } else { - *riff_size = get_le32(*data + TAG_SIZE); + const uint32_t size = get_le32(*data + TAG_SIZE); // Check that we have at least one chunk (i.e "WEBP" + "VP8?nnnn"). - if (*riff_size < TAG_SIZE + CHUNK_HEADER_SIZE) { + if (size < TAG_SIZE + CHUNK_HEADER_SIZE) { return VP8_STATUS_BITSTREAM_ERROR; } // We have a RIFF container. Skip it. + *riff_size = size; *data += RIFF_HEADER_SIZE; *data_size -= RIFF_HEADER_SIZE; } - } else { - *riff_size = 0; // Did not get full RIFF header. } return VP8_STATUS_OK; } -VP8StatusCode WebPParseVP8X(const uint8_t** data, uint32_t* data_size, - uint32_t* bytes_skipped, - int* width, int* height, uint32_t* flags) { +// Validates the VP8X header and skips over it. +// Returns VP8_STATUS_BITSTREAM_ERROR for invalid VP8X header, +// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and +// VP8_STATUS_OK otherwise. +// If a VP8X chunk is found, found_vp8x is set to true and *width, *height and +// *flags are set to the corresponding values extracted from the VP8X chunk. +static VP8StatusCode ParseVP8X(const uint8_t** data, uint32_t* data_size, + int* found_vp8x, + int* width, int* height, uint32_t* flags) { + const uint32_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; assert(data); assert(data_size); - assert(bytes_skipped); + assert(found_vp8x); - *bytes_skipped = 0; + *found_vp8x = 0; - if (*data_size < CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE) { + if (*data_size < vp8x_size) { return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. } @@ -98,39 +116,43 @@ VP8StatusCode WebPParseVP8X(const uint8_t** data, uint32_t* data_size, *height = get_le32(*data + 16); } // Skip over VP8X header bytes. - *bytes_skipped = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; - *data += *bytes_skipped; - *data_size -= *bytes_skipped; + *data += vp8x_size; + *data_size -= vp8x_size; + *found_vp8x = 1; } return VP8_STATUS_OK; } -VP8StatusCode WebPParseOptionalChunks(const uint8_t** data, uint32_t* data_size, - uint32_t riff_size, - uint32_t* bytes_skipped, - const uint8_t** alpha_data, - uint32_t* alpha_size) { +// Skips to the next VP8 chunk header in the data given the size of the RIFF +// chunk 'riff_size'. +// Returns VP8_STATUS_BITSTREAM_ERROR if any invalid chunk size is encountered, +// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and +// VP8_STATUS_OK otherwise. +// If an alpha chunk is found, alpha_data and alpha_size are set appropriately. +static VP8StatusCode ParseOptionalChunks(const uint8_t** data, + uint32_t* data_size, + uint32_t riff_size, + const uint8_t** alpha_data, + uint32_t* alpha_size) { const uint8_t* buf; uint32_t buf_size; - + uint32_t total_size = TAG_SIZE + // "WEBP". + CHUNK_HEADER_SIZE + // "VP8Xnnnn". + VP8X_CHUNK_SIZE; // data. assert(data); assert(data_size); - assert(bytes_skipped); - assert(alpha_data); - assert(alpha_size); - buf = *data; buf_size = *data_size; - *bytes_skipped = 0; + + assert(alpha_data); + assert(alpha_size); *alpha_data = NULL; *alpha_size = 0; while (1) { uint32_t chunk_size; - uint32_t cur_skip_size; - const uint32_t bytes_skipped_header = TAG_SIZE + // "WEBP". - CHUNK_HEADER_SIZE + // "VP8Xnnnn". - VP8X_CHUNK_SIZE; // data. + uint32_t disk_chunk_size; // chunk_size with padding + *data = buf; *data_size = buf_size; @@ -140,20 +162,19 @@ VP8StatusCode WebPParseOptionalChunks(const uint8_t** data, uint32_t* data_size, chunk_size = get_le32(buf + TAG_SIZE); // For odd-sized chunk-payload, there's one byte padding at the end. - cur_skip_size = (CHUNK_HEADER_SIZE + chunk_size + 1) & ~1; + disk_chunk_size = (CHUNK_HEADER_SIZE + chunk_size + 1) & ~1; + total_size += disk_chunk_size; - // Check that total bytes skipped along with current chunk size - // does not exceed riff_size. - if (riff_size > 0 && - (bytes_skipped_header + *bytes_skipped + cur_skip_size > riff_size)) { - return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size. + // Check that total bytes skipped so far does not exceed riff_size. + if (riff_size > 0 && (total_size > riff_size)) { + return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size. } - if (buf_size < cur_skip_size) { // Insufficient data. + if (buf_size < disk_chunk_size) { // Insufficient data. return VP8_STATUS_NOT_ENOUGH_DATA; } - if (!memcmp(buf, "ALPH", TAG_SIZE)) { // A valid ALPH header. + 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)) { // A valid VP8 header. @@ -161,108 +182,104 @@ VP8StatusCode WebPParseOptionalChunks(const uint8_t** data, uint32_t* data_size, } // We have a full and valid chunk; skip it. - buf += cur_skip_size; - buf_size -= cur_skip_size; - *bytes_skipped += cur_skip_size; + buf += disk_chunk_size; + buf_size -= disk_chunk_size; } } -VP8StatusCode WebPParseVP8Header(const uint8_t** data, uint32_t* data_size, - uint32_t riff_size, uint32_t* bytes_skipped, - uint32_t* vp8_chunk_size) { +// Validates the VP8 Header ("VP8 nnnn" or "VP8L nnnn") and skips over it. +// Returns VP8_STATUS_BITSTREAM_ERROR for invalid (chunk larger than +// riff_size) VP8 header, +// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and +// VP8_STATUS_OK otherwise. +// If a VP8/VP8L chunk is found, chunk_size is set to the total number of bytes +// extracted from the VP8/VP8L chunk header. +// The flag 'is_lossless' is set to 1 in case of VP8L chunk. +static VP8StatusCode ParseVP8Header(const uint8_t** data, uint32_t* data_size, + uint32_t riff_size, + uint32_t* chunk_size, int* is_lossless) { + const int is_vp8 = !memcmp(*data, "VP8 ", TAG_SIZE); + const int is_vp8l = !memcmp(*data, "VP8L", TAG_SIZE); + const uint32_t minimal_size = + TAG_SIZE + CHUNK_HEADER_SIZE; // "WEBP" + "VP8 nnnn" assert(data); assert(data_size); - assert(bytes_skipped); - assert(vp8_chunk_size); + assert(chunk_size); + assert(is_lossless); - *bytes_skipped = 0; - *vp8_chunk_size = 0; + *chunk_size = *data_size; // default: raw data if (*data_size < CHUNK_HEADER_SIZE) { return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. } - if (!memcmp(*data, "VP8 ", TAG_SIZE)) { - *vp8_chunk_size = get_le32(*data + TAG_SIZE); - if ((riff_size >= TAG_SIZE + CHUNK_HEADER_SIZE) && // "WEBP" + "VP8 nnnn". - (*vp8_chunk_size > riff_size - (TAG_SIZE + CHUNK_HEADER_SIZE))) { + if (is_vp8 || is_vp8l) { + const uint32_t size = get_le32(*data + TAG_SIZE); + if ((riff_size >= minimal_size) && (size > riff_size - minimal_size)) { return VP8_STATUS_BITSTREAM_ERROR; // Inconsistent size information. } // Skip over CHUNK_HEADER_SIZE bytes from VP8 Header. - *bytes_skipped = CHUNK_HEADER_SIZE; - *data += *bytes_skipped; - *data_size -= *bytes_skipped; + *chunk_size = size; + *data += CHUNK_HEADER_SIZE; + *data_size -= CHUNK_HEADER_SIZE; + *is_lossless = is_vp8l; } return VP8_STATUS_OK; } -VP8StatusCode WebPParseHeaders(const uint8_t** data, uint32_t* data_size, - uint32_t* vp8_size, uint32_t* bytes_skipped, - const uint8_t** alpha_data, - uint32_t* alpha_size) { +//------------------------------------------------------------------------------ + +VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers) { const uint8_t* buf; uint32_t buf_size; - uint32_t riff_size; - uint32_t vp8_size_tmp; - uint32_t optional_data_size; - uint32_t vp8x_skip_size; - uint32_t vp8_skip_size; + int found_vp8x; VP8StatusCode status; - assert(data); - assert(data_size); - assert(vp8_size); - assert(bytes_skipped); - assert(alpha_data); - assert(alpha_size); + assert(headers); - buf = *data; - buf_size = *data_size; - - *vp8_size = 0; - *bytes_skipped = 0; - *alpha_data = NULL; - *alpha_size = 0; + buf = headers->data; + buf_size = headers->data_size; + headers->alpha_data = NULL; + headers->alpha_data_size = 0; + headers->vp8_size = 0; + headers->is_lossless = 0; + headers->offset = 0; + headers->riff_size = 0; if (buf == NULL || buf_size < RIFF_HEADER_SIZE) { return VP8_STATUS_NOT_ENOUGH_DATA; } // Skip over RIFF header. - if (WebPParseRIFF(&buf, &buf_size, &riff_size) != VP8_STATUS_OK) { + if (ParseRIFF(&buf, &buf_size, &headers->riff_size) != VP8_STATUS_OK) { return VP8_STATUS_BITSTREAM_ERROR; // Wrong RIFF header. } // Skip over VP8X header. - status = WebPParseVP8X(&buf, &buf_size, &vp8x_skip_size, NULL, NULL, NULL); + status = ParseVP8X(&buf, &buf_size, &found_vp8x, NULL, NULL, NULL); if (status != VP8_STATUS_OK) { return status; // Wrong VP8X chunk / insufficient data. } - if (vp8x_skip_size > 0) { + if (found_vp8x) { // Skip over optional chunks. - status = WebPParseOptionalChunks(&buf, &buf_size, riff_size, - &optional_data_size, - alpha_data, alpha_size); + status = ParseOptionalChunks(&buf, &buf_size, headers->riff_size, + &headers->alpha_data, + &headers->alpha_data_size); if (status != VP8_STATUS_OK) { return status; // Found an invalid chunk size / insufficient data. } } // Skip over VP8 chunk header. - status = WebPParseVP8Header(&buf, &buf_size, riff_size, &vp8_skip_size, - &vp8_size_tmp); + status = ParseVP8Header(&buf, &buf_size, headers->riff_size, + &headers->vp8_size, &headers->is_lossless); if (status != VP8_STATUS_OK) { return status; // Invalid VP8 header / insufficient data. } - if (vp8_skip_size > 0) { - *vp8_size = vp8_size_tmp; - } - *bytes_skipped = (uint32_t)(buf - *data); - assert(buf - *data < MAX_CHUNK_PAYLOAD); - assert(*bytes_skipped == *data_size - buf_size); - *data = buf; - *data_size = buf_size; + headers->offset = (uint32_t)(buf - headers->data); + assert((uint64_t)(buf - headers->data) < MAX_CHUNK_PAYLOAD); + assert(headers->offset == headers->data_size - buf_size); return VP8_STATUS_OK; } @@ -310,9 +327,18 @@ static VP8StatusCode DecodeInto(const uint8_t* data, uint32_t data_size, params->output); if (status == VP8_STATUS_OK) { // Decode - if (!VP8Decode(dec, &io)) { - status = dec->status_; + if (!dec->is_lossless_) { + if (!VP8Decode(dec, &io)) { + status = dec->status_; + } + } else { + VP8LDecoder* const vp8l_decoder = &dec->vp8l_decoder_; + if (!VP8LDecodeImage(vp8l_decoder)) { + status = VP8_STATUS_BITSTREAM_ERROR; + } } + } else if (dec->is_lossless_) { // Clear lossless decoder on error. + VP8LClear(&dec->vp8l_decoder_); } } VP8Delete(dec); @@ -479,13 +505,12 @@ static void DefaultFeatures(WebPBitstreamFeatures* const features) { static VP8StatusCode GetFeatures(const uint8_t* data, uint32_t data_size, WebPBitstreamFeatures* const features) { - uint32_t vp8_chunk_size = 0; + uint32_t chunk_size = 0; uint32_t riff_size = 0; - uint32_t flags = 0; - uint32_t vp8x_skip_size = 0; - uint32_t vp8_skip_size = 0; int* const width = &features->width; int* const height = &features->height; + int found_vp8x; + int is_lossless = 0; VP8StatusCode status; if (features == NULL || data == NULL) { @@ -494,35 +519,42 @@ static VP8StatusCode GetFeatures(const uint8_t* data, uint32_t data_size, DefaultFeatures(features); // Skip over RIFF header. - status = WebPParseRIFF(&data, &data_size, &riff_size); + status = ParseRIFF(&data, &data_size, &riff_size); if (status != VP8_STATUS_OK) { return status; // Wrong RIFF header / insufficient data. } // Skip over VP8X. - status = WebPParseVP8X(&data, &data_size, &vp8x_skip_size, - width, height, &flags); - if (status != VP8_STATUS_OK) { - return status; // Wrong VP8X / insufficient data. - } - features->has_alpha = !!(flags & ALPHA_FLAG); - if (vp8x_skip_size > 0) { - return VP8_STATUS_OK; // Return features from VP8X header. + { + uint32_t flags = 0; + status = ParseVP8X(&data, &data_size, &found_vp8x, width, height, &flags); + if (status != VP8_STATUS_OK) { + return status; // Wrong VP8X / insufficient data. + } + features->has_alpha = !!(flags & ALPHA_FLAG); + if (found_vp8x) { + return VP8_STATUS_OK; // Return features from VP8X header. + } } // Skip over VP8 header. - status = WebPParseVP8Header(&data, &data_size, riff_size, &vp8_skip_size, - &vp8_chunk_size); + status = ParseVP8Header(&data, &data_size, riff_size, + &chunk_size, &is_lossless); if (status != VP8_STATUS_OK) { return status; // Wrong VP8 chunk-header / insufficient data. } - if (vp8_skip_size == 0) { - vp8_chunk_size = data_size; // No VP8 chunk wrapper over raw VP8 data. - } - // Validates raw VP8 data. - if (!VP8GetInfo(data, data_size, vp8_chunk_size, width, height)) { - return VP8_STATUS_BITSTREAM_ERROR; + if (!is_lossless) { + // Validates raw VP8 data. + if (!VP8GetInfo(data, data_size, chunk_size, width, height)) { + return VP8_STATUS_BITSTREAM_ERROR; + } + } else { + // Validates raw VP8L data. + if (!VP8LGetInfo(data, data_size, width, height)) { + return VP8_STATUS_BITSTREAM_ERROR; + } + features->has_alpha = 1; } return VP8_STATUS_OK; // Return features from VP8 header. diff --git a/src/dec/webpi.h b/src/dec/webpi.h index 1c36abf3..221cb44c 100644 --- a/src/dec/webpi.h +++ b/src/dec/webpi.h @@ -54,65 +54,25 @@ void WebPResetDecParams(WebPDecParams* const params); #define VP8X_CHUNK_SIZE 12 #define VP8_FRAME_HEADER_SIZE 10 // Size of the frame header within VP8 data. -// Validates the RIFF container (if detected) and skips over it. -// If a RIFF container is detected, -// Returns VP8_STATUS_BITSTREAM_ERROR for invalid header, and -// VP8_STATUS_OK otherwise. -// In case there are not enough bytes (partial RIFF container), return 0 for -// riff_size. Else return the riff_size extracted from the header. -VP8StatusCode WebPParseRIFF(const uint8_t** data, uint32_t* data_size, - uint32_t* riff_size); +// Structure storing a description of the RIFF headers. +typedef struct { + const uint8_t* data; // input buffer + uint32_t data_size; // input buffer size + uint32_t offset; // offset to main data chunk (VP8 or VP8L) + const uint8_t* alpha_data; // points to alpha chunk (if present) + uint32_t alpha_data_size; // alpha chunk size + uint32_t vp8_size; // vp8 compressed data size + uint32_t riff_size; // size of the riff payload (or 0 if absent) + int is_lossless; // true if a VP8L chunk is present +} WebPHeaderStructure; -// Validates the VP8X Header and skips over it. -// Returns VP8_STATUS_BITSTREAM_ERROR for invalid VP8X header, -// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and -// VP8_STATUS_OK otherwise. -// If a VP8 chunk is found, bytes_skipped is set to the total number of bytes -// that are skipped; also Width, Height & Flags are set to the corresponding -// fields extracted from the VP8X chunk. -VP8StatusCode WebPParseVP8X(const uint8_t** data, uint32_t* data_size, - uint32_t* bytes_skipped, - int* width, int* height, uint32_t* flags); - -// Skips to the next VP8 chunk header in the data given the size of the RIFF -// chunk 'riff_size'. -// Returns VP8_STATUS_BITSTREAM_ERROR if any invalid chunk size is encountered, -// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and -// VP8_STATUS_OK otherwise. -// If a VP8 chunk is found, bytes_skipped is set to the total number of bytes -// that are skipped. Also, if an alpha chunk is found, alpha_data and alpha_size -// are set appropriately. -VP8StatusCode WebPParseOptionalChunks(const uint8_t** data, uint32_t* data_size, - uint32_t riff_size, - uint32_t* bytes_skipped, - const uint8_t** alpha_data, - uint32_t* alpha_size); - -// Validates the VP8 Header ("VP8 nnnn") and skips over it. -// Returns VP8_STATUS_BITSTREAM_ERROR for invalid (vp8_chunk_size greater than -// riff_size) VP8 header, -// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and -// VP8_STATUS_OK otherwise. -// If a VP8 chunk is found, bytes_skipped is set to the total number of bytes -// that are skipped and vp8_chunk_size is set to the corresponding size -// extracted from the VP8 chunk header. -// For a partial VP8 chunk, vp8_chunk_size is set to 0. -VP8StatusCode WebPParseVP8Header(const uint8_t** data, uint32_t* data_size, - uint32_t riff_size, uint32_t* bytes_skipped, - uint32_t* vp8_chunk_size); - -// Skips over all valid chunks prior to the first VP8 frame header. +// Skips over all valid chunks prior to the first VP8/VP8L frame header. // Returns VP8_STATUS_OK on success, // VP8_STATUS_BITSTREAM_ERROR if an invalid header/chunk is found, and // VP8_STATUS_NOT_ENOUGH_DATA if case of insufficient data. -// Also, data, data_size, vp8_size, bytes_skipped, alpha_data & alpha_size are -// updated appropriately on success, where -// vp8_size is the size of VP8 chunk data (extracted from VP8 chunk header) and -// bytes_skipped is set to the total number of bytes that are skipped. -VP8StatusCode WebPParseHeaders(const uint8_t** data, uint32_t* data_size, - uint32_t* vp8_size, uint32_t* bytes_skipped, - const uint8_t** alpha_data, - uint32_t* alpha_size); +// In 'headers', vp8_size, offset, alpha_data, alpha_size and lossless fields +// are updated appropriately upon success. +VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers); //------------------------------------------------------------------------------ // Misc utils diff --git a/src/dsp/Makefile.am b/src/dsp/Makefile.am index dd410e65..0014c4e8 100644 --- a/src/dsp/Makefile.am +++ b/src/dsp/Makefile.am @@ -3,6 +3,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src libwebpdsp_la_SOURCES = dsp.h cpu.c \ enc.c enc_sse2.c \ dec.c dec_sse2.c dec_neon.c \ + lossless.h lossless.c \ upsampling.c upsampling_sse2.c \ yuv.h yuv.c libwebpdsp_la_LDFLAGS = -lm diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 40b64338..368d90ad 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -3,7 +3,9 @@ AM_CPPFLAGS = -I$(top_srcdir)/src libwebputils_la_SOURCES = alpha.h alpha.c \ bit_reader.h bit_reader.c \ bit_writer.h bit_writer.c \ + color_cache.h color_cache.c \ filters.h filters.c \ + huffman.h huffman.c \ quant_levels.c rescaler.c \ tcoder.h tcoderi.h tcoder.c \ thread.h thread.c