Home | History | Annotate | Download | only in dec
      1 // Copyright 2011 Google Inc.
      2 //
      3 // This code is licensed under the same terms as WebM:
      4 //  Software License Agreement:  http://www.webmproject.org/license/software/
      5 //  Additional IP Rights Grant:  http://www.webmproject.org/license/additional/
      6 // -----------------------------------------------------------------------------
      7 //
      8 // Incremental decoding
      9 //
     10 // Author: somnath (at) google.com (Somnath Banerjee)
     11 
     12 #include <assert.h>
     13 #include <string.h>
     14 #include <stdlib.h>
     15 
     16 #include "webpi.h"
     17 #include "vp8i.h"
     18 
     19 #if defined(__cplusplus) || defined(c_plusplus)
     20 extern "C" {
     21 #endif
     22 
     23 #define RIFF_HEADER_SIZE 20
     24 #define VP8_HEADER_SIZE 10
     25 #define WEBP_HEADER_SIZE (RIFF_HEADER_SIZE + VP8_HEADER_SIZE)
     26 #define CHUNK_SIZE 4096
     27 #define MAX_MB_SIZE 4096
     28 
     29 //------------------------------------------------------------------------------
     30 // Data structures for memory and states
     31 
     32 // Decoding states. State normally flows like HEADER->PARTS0->DATA->DONE.
     33 // If there is any error the decoder goes into state ERROR.
     34 typedef enum { STATE_HEADER = 0, STATE_PARTS0 = 1,
     35                STATE_DATA = 2, STATE_DONE = 3,
     36                STATE_ERROR = 4
     37 } DecState;
     38 
     39 // Operating state for the MemBuffer
     40 typedef enum { MEM_MODE_NONE = 0,
     41                MEM_MODE_APPEND, MEM_MODE_MAP
     42 } MemBufferMode;
     43 
     44 // storage for partition #0 and partial data (in a rolling fashion)
     45 typedef struct {
     46   MemBufferMode mode_;  // Operation mode
     47   uint32_t start_;      // start location of the data to be decoded
     48   uint32_t end_;        // end location
     49   size_t buf_size_;     // size of the allocated buffer
     50   uint8_t* buf_;        // We don't own this buffer in case WebPIUpdate()
     51 
     52   size_t part0_size_;         // size of partition #0
     53   const uint8_t* part0_buf_;  // buffer to store partition #0
     54 } MemBuffer;
     55 
     56 struct WebPIDecoder {
     57   DecState state_;         // current decoding state
     58   WebPDecParams params_;   // Params to store output info
     59   VP8Decoder* dec_;
     60   VP8Io io_;
     61 
     62   MemBuffer mem_;          // input memory buffer.
     63   WebPDecBuffer output_;   // output buffer (when no external one is supplied)
     64 };
     65 
     66 // MB context to restore in case VP8DecodeMB() fails
     67 typedef struct {
     68   VP8MB left_;
     69   VP8MB info_;
     70   uint8_t intra_t_[4];
     71   uint8_t intra_l_[4];
     72   VP8BitReader br_;
     73   VP8BitReader token_br_;
     74 } MBContext;
     75 
     76 //------------------------------------------------------------------------------
     77 // MemBuffer: incoming data handling
     78 
     79 #define REMAP(PTR, OLD_BASE, NEW_BASE) (PTR) = (NEW_BASE) + ((PTR) - OLD_BASE)
     80 
     81 static inline size_t MemDataSize(const MemBuffer* mem) {
     82   return (mem->end_ - mem->start_);
     83 }
     84 
     85 // Appends data to the end of MemBuffer->buf_. It expands the allocated memory
     86 // size if required and also updates VP8BitReader's if new memory is allocated.
     87 static int AppendToMemBuffer(WebPIDecoder* const idec,
     88                              const uint8_t* const data, size_t data_size) {
     89   MemBuffer* const mem = &idec->mem_;
     90   VP8Decoder* const dec = idec->dec_;
     91   const int last_part = dec->num_parts_ - 1;
     92   assert(mem->mode_ == MEM_MODE_APPEND);
     93 
     94   if (mem->end_ + data_size > mem->buf_size_) {  // Need some free memory
     95     int p;
     96     uint8_t* new_buf = NULL;
     97     const int num_chunks = (MemDataSize(mem) + data_size + CHUNK_SIZE - 1)
     98         / CHUNK_SIZE;
     99     const size_t new_size = num_chunks * CHUNK_SIZE;
    100     const uint8_t* const base = mem->buf_ + mem->start_;
    101 
    102     new_buf = (uint8_t*)malloc(new_size);
    103     if (!new_buf) return 0;
    104     memcpy(new_buf, base, MemDataSize(mem));
    105 
    106     // adjust VP8BitReader pointers
    107     for (p = 0; p <= last_part; ++p) {
    108       if (dec->parts_[p].buf_) {
    109         REMAP(dec->parts_[p].buf_, base, new_buf);
    110         REMAP(dec->parts_[p].buf_end_, base, new_buf);
    111       }
    112     }
    113 
    114     // adjust memory pointers
    115     free(mem->buf_);
    116     mem->buf_ = new_buf;
    117     mem->buf_size_ = new_size;
    118 
    119     mem->end_ = MemDataSize(mem);
    120     mem->start_ = 0;
    121   }
    122 
    123   memcpy(mem->buf_ + mem->end_, data, data_size);
    124   mem->end_ += data_size;
    125   assert(mem->end_ <= mem->buf_size_);
    126   dec->parts_[last_part].buf_end_ = mem->buf_ + mem->end_;
    127 
    128   // note: setting up idec->io_ is only really needed at the beginning
    129   // of the decoding, till partition #0 is complete.
    130   idec->io_.data = mem->buf_ + mem->start_;
    131   idec->io_.data_size = MemDataSize(mem);
    132   return 1;
    133 }
    134 
    135 static int RemapMemBuffer(WebPIDecoder* const idec,
    136                           const uint8_t* const data, size_t data_size) {
    137   int p;
    138   MemBuffer* const mem = &idec->mem_;
    139   VP8Decoder* const dec = idec->dec_;
    140   const int last_part = dec->num_parts_ - 1;
    141   const uint8_t* base = mem->buf_;
    142 
    143   assert(mem->mode_ == MEM_MODE_MAP);
    144   if (data_size < mem->buf_size_) {
    145     return 0;  // we cannot remap to a shorter buffer!
    146   }
    147 
    148   for (p = 0; p <= last_part; ++p) {
    149     if (dec->parts_[p].buf_) {
    150       REMAP(dec->parts_[p].buf_, base, data);
    151       REMAP(dec->parts_[p].buf_end_, base, data);
    152     }
    153   }
    154   dec->parts_[last_part].buf_end_ = data + data_size;
    155 
    156   // Remap partition #0 data pointer to new offset.
    157   if (dec->br_.buf_) {
    158     REMAP(dec->br_.buf_, base, data);
    159     REMAP(dec->br_.buf_end_, base, data);
    160   }
    161 
    162   mem->buf_ = (uint8_t*)data;
    163   mem->end_ = mem->buf_size_ = data_size;
    164 
    165   idec->io_.data = data;
    166   idec->io_.data_size = data_size;
    167   return 1;
    168 }
    169 
    170 static void InitMemBuffer(MemBuffer* const mem) {
    171   mem->mode_       = MEM_MODE_NONE;
    172   mem->buf_        = 0;
    173   mem->buf_size_   = 0;
    174   mem->part0_buf_  = 0;
    175   mem->part0_size_ = 0;
    176 }
    177 
    178 static void ClearMemBuffer(MemBuffer* const mem) {
    179   assert(mem);
    180   if (mem->mode_ == MEM_MODE_APPEND) {
    181     free(mem->buf_);
    182     free((void*)mem->part0_buf_);
    183   }
    184 }
    185 
    186 static int CheckMemBufferMode(MemBuffer* const mem, MemBufferMode expected) {
    187   if (mem->mode_ == MEM_MODE_NONE) {
    188     mem->mode_ = expected;    // switch to the expected mode
    189   } else if (mem->mode_ != expected) {
    190     return 0;         // we mixed the modes => error
    191   }
    192   assert(mem->mode_ == expected);   // mode is ok
    193   return 1;
    194 }
    195 
    196 #undef REMAP
    197 
    198 //------------------------------------------------------------------------------
    199 // Macroblock-decoding contexts
    200 
    201 static void SaveContext(const VP8Decoder* dec, const VP8BitReader* token_br,
    202                         MBContext* const context) {
    203   const VP8BitReader* const br = &dec->br_;
    204   const VP8MB* const left = dec->mb_info_ - 1;
    205   const VP8MB* const info = dec->mb_info_ + dec->mb_x_;
    206 
    207   context->left_ = *left;
    208   context->info_ = *info;
    209   context->br_ = *br;
    210   context->token_br_ = *token_br;
    211   memcpy(context->intra_t_, dec->intra_t_ + 4 * dec->mb_x_, 4);
    212   memcpy(context->intra_l_, dec->intra_l_, 4);
    213 }
    214 
    215 static void RestoreContext(const MBContext* context, VP8Decoder* const dec,
    216                            VP8BitReader* const token_br) {
    217   VP8BitReader* const br = &dec->br_;
    218   VP8MB* const left = dec->mb_info_ - 1;
    219   VP8MB* const info = dec->mb_info_ + dec->mb_x_;
    220 
    221   *left = context->left_;
    222   *info = context->info_;
    223   *br = context->br_;
    224   *token_br = context->token_br_;
    225   memcpy(dec->intra_t_ + 4 * dec->mb_x_, context->intra_t_, 4);
    226   memcpy(dec->intra_l_, context->intra_l_, 4);
    227 }
    228 
    229 //------------------------------------------------------------------------------
    230 
    231 static VP8StatusCode IDecError(WebPIDecoder* idec, VP8StatusCode error) {
    232   if (idec->state_ == STATE_DATA) {
    233     VP8Io* const io = &idec->io_;
    234     if (io->teardown) {
    235       io->teardown(io);
    236     }
    237   }
    238   idec->state_ = STATE_ERROR;
    239   return error;
    240 }
    241 
    242 // Header
    243 static VP8StatusCode DecodeHeader(WebPIDecoder* const idec) {
    244   uint32_t riff_header_size, bits;
    245   const uint8_t* data = idec->mem_.buf_ + idec->mem_.start_;
    246   uint32_t curr_size = MemDataSize(&idec->mem_);
    247   uint32_t chunk_size;
    248 
    249   if (curr_size < WEBP_HEADER_SIZE) {
    250     return VP8_STATUS_SUSPENDED;
    251   }
    252 
    253   // Validate and Skip over RIFF header
    254   chunk_size = WebPCheckRIFFHeader(&data, &curr_size);
    255   if (chunk_size == 0 ||
    256       curr_size < VP8_HEADER_SIZE ||
    257       !VP8GetInfo(data, curr_size, chunk_size, NULL, NULL, NULL)) {
    258     return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
    259   }
    260 
    261   riff_header_size = idec->mem_.end_ - curr_size;
    262   bits = data[0] | (data[1] << 8) | (data[2] << 16);
    263 
    264   idec->mem_.part0_size_ = (bits >> 5) + VP8_HEADER_SIZE;
    265   idec->mem_.start_ += riff_header_size;
    266   assert(idec->mem_.start_ <= idec->mem_.end_);
    267 
    268   idec->io_.data_size -= riff_header_size;
    269   idec->io_.data = data;
    270   idec->state_ = STATE_PARTS0;
    271   return VP8_STATUS_OK;
    272 }
    273 
    274 // Partition #0
    275 static int CopyParts0Data(WebPIDecoder* idec) {
    276   VP8BitReader* const br = &idec->dec_->br_;
    277   const size_t psize = br->buf_end_ - br->buf_;
    278   MemBuffer* const mem = &idec->mem_;
    279   assert(!mem->part0_buf_);
    280   assert(psize > 0);
    281   assert(psize <= mem->part0_size_);
    282   if (mem->mode_ == MEM_MODE_APPEND) {
    283     // We copy and grab ownership of the partition #0 data.
    284     uint8_t* const part0_buf = (uint8_t*)malloc(psize);
    285     if (!part0_buf) {
    286       return 0;
    287     }
    288     memcpy(part0_buf, br->buf_, psize);
    289     mem->part0_buf_ = part0_buf;
    290     mem->start_ += psize;
    291     br->buf_ = part0_buf;
    292     br->buf_end_ = part0_buf + psize;
    293   } else {
    294     // Else: just keep pointers to the partition #0's data in dec_->br_.
    295   }
    296   return 1;
    297 }
    298 
    299 static VP8StatusCode DecodePartition0(WebPIDecoder* const idec) {
    300   VP8Decoder* const dec = idec->dec_;
    301   VP8Io* const io = &idec->io_;
    302   const WebPDecParams* const params = &idec->params_;
    303   WebPDecBuffer* const output = params->output;
    304 
    305   // Wait till we have enough data for the whole partition #0
    306   if (MemDataSize(&idec->mem_) < idec->mem_.part0_size_) {
    307     return VP8_STATUS_SUSPENDED;
    308   }
    309 
    310   if (!VP8GetHeaders(dec, io)) {
    311     const VP8StatusCode status = dec->status_;
    312     if (status == VP8_STATUS_SUSPENDED ||
    313         status == VP8_STATUS_NOT_ENOUGH_DATA) {
    314       // treating NOT_ENOUGH_DATA as SUSPENDED state
    315       return VP8_STATUS_SUSPENDED;
    316     }
    317     return IDecError(idec, status);
    318   }
    319 
    320   // Allocate/Verify output buffer now
    321   dec->status_ = WebPAllocateDecBuffer(io->width, io->height, params->options,
    322                                        output);
    323   if (dec->status_ != VP8_STATUS_OK) {
    324     return IDecError(idec, dec->status_);
    325   }
    326 
    327   // Allocate memory and prepare everything.
    328   if (!VP8InitFrame(dec, io)) {
    329     return IDecError(idec, dec->status_);
    330   }
    331 
    332   if (!CopyParts0Data(idec)) {
    333     return IDecError(idec, VP8_STATUS_OUT_OF_MEMORY);
    334   }
    335 
    336   // Finish setting up the decoding parameters.
    337   if (VP8FinishFrameSetup(dec, io) != VP8_STATUS_OK) {
    338     return IDecError(idec, dec->status_);
    339   }
    340   // Note: past this point, teardown() must always be called
    341   // in case of error.
    342   idec->state_ = STATE_DATA;
    343   return VP8_STATUS_OK;
    344 }
    345 
    346 // Remaining partitions
    347 static VP8StatusCode DecodeRemaining(WebPIDecoder* const idec) {
    348   VP8BitReader*  br;
    349   VP8Decoder* const dec = idec->dec_;
    350   VP8Io* const io = &idec->io_;
    351 
    352   assert(dec->ready_);
    353 
    354   br = &dec->br_;
    355   for (; dec->mb_y_ < dec->mb_h_; ++dec->mb_y_) {
    356     VP8BitReader* token_br = &dec->parts_[dec->mb_y_ & (dec->num_parts_ - 1)];
    357     if (dec->mb_x_ == 0) {
    358       VP8MB* const left = dec->mb_info_ - 1;
    359       left->nz_ = 0;
    360       left->dc_nz_ = 0;
    361       memset(dec->intra_l_, B_DC_PRED, sizeof(dec->intra_l_));
    362     }
    363 
    364     for (; dec->mb_x_ < dec->mb_w_;  dec->mb_x_++) {
    365       MBContext context;
    366       SaveContext(dec, token_br, &context);
    367 
    368       if (!VP8DecodeMB(dec, token_br)) {
    369         RestoreContext(&context, dec, token_br);
    370         // We shouldn't fail when MAX_MB data was available
    371         if (dec->num_parts_ == 1 && MemDataSize(&idec->mem_) > MAX_MB_SIZE) {
    372           return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
    373         }
    374         return VP8_STATUS_SUSPENDED;
    375       }
    376       VP8ReconstructBlock(dec);
    377       // Store data and save block's filtering params
    378       VP8StoreBlock(dec);
    379 
    380       // Release buffer only if there is only one partition
    381       if (dec->num_parts_ == 1) {
    382         idec->mem_.start_ = token_br->buf_ - idec->mem_.buf_;
    383         assert(idec->mem_.start_ <= idec->mem_.end_);
    384       }
    385     }
    386     if (dec->filter_type_ > 0) {
    387       VP8FilterRow(dec);
    388     }
    389     if (!VP8FinishRow(dec, io)) {
    390       return IDecError(idec, VP8_STATUS_USER_ABORT);
    391     }
    392     dec->mb_x_ = 0;
    393   }
    394 
    395   if (io->teardown) {
    396     io->teardown(io);
    397   }
    398   dec->ready_ = 0;
    399   idec->state_ = STATE_DONE;
    400 
    401   return VP8_STATUS_OK;
    402 }
    403 
    404   // Main decoding loop
    405 static VP8StatusCode IDecode(WebPIDecoder* idec) {
    406   VP8StatusCode status = VP8_STATUS_SUSPENDED;
    407   assert(idec->dec_);
    408 
    409   if (idec->state_ == STATE_HEADER) {
    410     status = DecodeHeader(idec);
    411   }
    412   if (idec->state_ == STATE_PARTS0) {
    413     status = DecodePartition0(idec);
    414   }
    415   if (idec->state_ == STATE_DATA) {
    416     status = DecodeRemaining(idec);
    417   }
    418   return status;
    419 }
    420 
    421 //------------------------------------------------------------------------------
    422 // Public functions
    423 
    424 WebPIDecoder* WebPINewDecoder(WebPDecBuffer* const output_buffer) {
    425   WebPIDecoder* idec = (WebPIDecoder*)calloc(1, sizeof(WebPIDecoder));
    426   if (idec == NULL) {
    427     return NULL;
    428   }
    429 
    430   idec->dec_ = VP8New();
    431   if (idec->dec_ == NULL) {
    432     free(idec);
    433     return NULL;
    434   }
    435 
    436   idec->state_ = STATE_HEADER;
    437 
    438   InitMemBuffer(&idec->mem_);
    439   WebPInitDecBuffer(&idec->output_);
    440   VP8InitIo(&idec->io_);
    441 
    442   WebPResetDecParams(&idec->params_);
    443   idec->params_.output = output_buffer ? output_buffer : &idec->output_;
    444   WebPInitCustomIo(&idec->params_, &idec->io_);  // Plug the I/O functions.
    445 
    446   return idec;
    447 }
    448 
    449 WebPIDecoder* WebPIDecode(const uint8_t* data, uint32_t data_size,
    450                           WebPDecoderConfig* const config) {
    451   WebPIDecoder* idec;
    452 
    453   // Parse the bitstream's features, if requested:
    454   if (data != NULL && data_size > 0 && config != NULL) {
    455     if (WebPGetFeatures(data, data_size, &config->input) != VP8_STATUS_OK) {
    456       return NULL;
    457     }
    458   }
    459   // Create an instance of the incremental decoder
    460   idec = WebPINewDecoder(config ? &config->output : NULL);
    461   if (!idec) {
    462     return NULL;
    463   }
    464   // Finish initialization
    465   if (config != NULL) {
    466     idec->params_.options = &config->options;
    467   }
    468   return idec;
    469 }
    470 
    471 void WebPIDelete(WebPIDecoder* const idec) {
    472   if (!idec) return;
    473   VP8Delete(idec->dec_);
    474   ClearMemBuffer(&idec->mem_);
    475   WebPFreeDecBuffer(&idec->output_);
    476   free(idec);
    477 }
    478 
    479 //------------------------------------------------------------------------------
    480 // Wrapper toward WebPINewDecoder
    481 
    482 WebPIDecoder* WebPINew(WEBP_CSP_MODE mode) {
    483   WebPIDecoder* const idec = WebPINewDecoder(NULL);
    484   if (!idec) return NULL;
    485   idec->output_.colorspace = mode;
    486   return idec;
    487 }
    488 
    489 WebPIDecoder* WebPINewRGB(WEBP_CSP_MODE mode, uint8_t* output_buffer,
    490                           int output_buffer_size, int output_stride) {
    491   WebPIDecoder* idec;
    492   if (mode >= MODE_YUV) return NULL;
    493   idec = WebPINewDecoder(NULL);
    494   if (!idec) return NULL;
    495   idec->output_.colorspace = mode;
    496   idec->output_.is_external_memory = 1;
    497   idec->output_.u.RGBA.rgba = output_buffer;
    498   idec->output_.u.RGBA.stride = output_stride;
    499   idec->output_.u.RGBA.size = output_buffer_size;
    500   return idec;
    501 }
    502 
    503 WebPIDecoder* WebPINewYUV(uint8_t* luma, int luma_size, int luma_stride,
    504                           uint8_t* u, int u_size, int u_stride,
    505                           uint8_t* v, int v_size, int v_stride) {
    506   WebPIDecoder* const idec = WebPINewDecoder(NULL);
    507   if (!idec) return NULL;
    508   idec->output_.colorspace = MODE_YUV;
    509   idec->output_.is_external_memory = 1;
    510   idec->output_.u.YUVA.y = luma;
    511   idec->output_.u.YUVA.y_stride = luma_stride;
    512   idec->output_.u.YUVA.y_size = luma_size;
    513   idec->output_.u.YUVA.u = u;
    514   idec->output_.u.YUVA.u_stride = u_stride;
    515   idec->output_.u.YUVA.u_size = u_size;
    516   idec->output_.u.YUVA.v = v;
    517   idec->output_.u.YUVA.v_stride = v_stride;
    518   idec->output_.u.YUVA.v_size = v_size;
    519   return idec;
    520 }
    521 
    522 //------------------------------------------------------------------------------
    523 
    524 static VP8StatusCode IDecCheckStatus(const WebPIDecoder* const idec) {
    525   assert(idec);
    526   if (idec->dec_ == NULL) {
    527     return VP8_STATUS_USER_ABORT;
    528   }
    529   if (idec->state_ == STATE_ERROR) {
    530     return VP8_STATUS_BITSTREAM_ERROR;
    531   }
    532   if (idec->state_ == STATE_DONE) {
    533     return VP8_STATUS_OK;
    534   }
    535   return VP8_STATUS_SUSPENDED;
    536 }
    537 
    538 VP8StatusCode WebPIAppend(WebPIDecoder* const idec, const uint8_t* data,
    539                           uint32_t data_size) {
    540   VP8StatusCode status;
    541   if (idec == NULL || data == NULL) {
    542     return VP8_STATUS_INVALID_PARAM;
    543   }
    544   status = IDecCheckStatus(idec);
    545   if (status != VP8_STATUS_SUSPENDED) {
    546     return status;
    547   }
    548   // Check mixed calls between RemapMemBuffer and AppendToMemBuffer.
    549   if (!CheckMemBufferMode(&idec->mem_, MEM_MODE_APPEND)) {
    550     return VP8_STATUS_INVALID_PARAM;
    551   }
    552   // Append data to memory buffer
    553   if (!AppendToMemBuffer(idec, data, data_size)) {
    554     return VP8_STATUS_OUT_OF_MEMORY;
    555   }
    556   return IDecode(idec);
    557 }
    558 
    559 VP8StatusCode WebPIUpdate(WebPIDecoder* const idec, const uint8_t* data,
    560                           uint32_t data_size) {
    561   VP8StatusCode status;
    562   if (idec == NULL || data == NULL) {
    563     return VP8_STATUS_INVALID_PARAM;
    564   }
    565   status = IDecCheckStatus(idec);
    566   if (status != VP8_STATUS_SUSPENDED) {
    567     return status;
    568   }
    569   // Check mixed calls between RemapMemBuffer and AppendToMemBuffer.
    570   if (!CheckMemBufferMode(&idec->mem_, MEM_MODE_MAP)) {
    571     return VP8_STATUS_INVALID_PARAM;
    572   }
    573   // Make the memory buffer point to the new buffer
    574   if (!RemapMemBuffer(idec, data, data_size)) {
    575     return VP8_STATUS_INVALID_PARAM;
    576   }
    577   return IDecode(idec);
    578 }
    579 
    580 //------------------------------------------------------------------------------
    581 
    582 static const WebPDecBuffer* GetOutputBuffer(const WebPIDecoder* const idec) {
    583   if (!idec || !idec->dec_ || idec->state_ <= STATE_PARTS0) {
    584     return NULL;
    585   }
    586   return idec->params_.output;
    587 }
    588 
    589 const WebPDecBuffer* WebPIDecodedArea(const WebPIDecoder* const idec,
    590                                       int* const left, int* const top,
    591                                       int* const width, int* const height) {
    592   const WebPDecBuffer* const src = GetOutputBuffer(idec);
    593   if (left) *left = 0;
    594   if (top) *top = 0;
    595   // TODO(skal): later include handling of rotations.
    596   if (src) {
    597     if (width) *width = src->width;
    598     if (height) *height = idec->params_.last_y;
    599   } else {
    600     if (width) *width = 0;
    601     if (height) *height = 0;
    602   }
    603   return src;
    604 }
    605 
    606 uint8_t* WebPIDecGetRGB(const WebPIDecoder* const idec, int* last_y,
    607                         int* width, int* height, int* stride) {
    608   const WebPDecBuffer* const src = GetOutputBuffer(idec);
    609   if (!src) return NULL;
    610   if (src->colorspace >= MODE_YUV) {
    611     return NULL;
    612   }
    613 
    614   if (last_y) *last_y = idec->params_.last_y;
    615   if (width) *width = src->width;
    616   if (height) *height = src->height;
    617   if (stride) *stride = src->u.RGBA.stride;
    618 
    619   return src->u.RGBA.rgba;
    620 }
    621 
    622 uint8_t* WebPIDecGetYUV(const WebPIDecoder* const idec, int* last_y,
    623                         uint8_t** u, uint8_t** v,
    624                         int* width, int* height, int *stride, int* uv_stride) {
    625   const WebPDecBuffer* const src = GetOutputBuffer(idec);
    626   if (!src) return NULL;
    627   if (src->colorspace < MODE_YUV) {
    628     return NULL;
    629   }
    630 
    631   if (last_y) *last_y = idec->params_.last_y;
    632   if (u) *u = src->u.YUVA.u;
    633   if (v) *v = src->u.YUVA.v;
    634   if (width) *width = src->width;
    635   if (height) *height = src->height;
    636   if (stride) *stride = src->u.YUVA.y_stride;
    637   if (uv_stride) *uv_stride = src->u.YUVA.u_stride;
    638 
    639   return src->u.YUVA.y;
    640 }
    641 
    642 int WebPISetIOHooks(WebPIDecoder* const idec,
    643                     VP8IoPutHook put,
    644                     VP8IoSetupHook setup,
    645                     VP8IoTeardownHook teardown,
    646                     void* user_data) {
    647   if (!idec || !idec->dec_ || idec->state_ > STATE_HEADER) {
    648     return 0;
    649   }
    650 
    651   idec->io_.put = put;
    652   idec->io_.setup = setup;
    653   idec->io_.teardown = teardown;
    654   idec->io_.opaque = user_data;
    655 
    656   return 1;
    657 }
    658 
    659 #if defined(__cplusplus) || defined(c_plusplus)
    660 }    // extern "C"
    661 #endif
    662