Path: blob/master/3rdparty/libwebp/src/mux/muxread.c
16349 views
// Copyright 2011 Google Inc. All Rights Reserved.1//2// Use of this source code is governed by a BSD-style license3// that can be found in the COPYING file in the root of the source4// tree. An additional intellectual property rights grant can be found5// in the file PATENTS. All contributing project authors may6// be found in the AUTHORS file in the root of the source tree.7// -----------------------------------------------------------------------------8//9// Read APIs for mux.10//11// Authors: Urvang ([email protected])12// Vikas ([email protected])1314#include <assert.h>15#include "src/mux/muxi.h"16#include "src/utils/utils.h"1718//------------------------------------------------------------------------------19// Helper method(s).2021// Handy MACRO.22#define SWITCH_ID_LIST(INDEX, LIST) \23if (idx == (INDEX)) { \24const WebPChunk* const chunk = ChunkSearchList((LIST), nth, \25kChunks[(INDEX)].tag); \26if (chunk) { \27*data = chunk->data_; \28return WEBP_MUX_OK; \29} else { \30return WEBP_MUX_NOT_FOUND; \31} \32}3334static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,35uint32_t nth, WebPData* const data) {36assert(mux != NULL);37assert(!IsWPI(kChunks[idx].id));38WebPDataInit(data);3940SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_);41SWITCH_ID_LIST(IDX_ICCP, mux->iccp_);42SWITCH_ID_LIST(IDX_ANIM, mux->anim_);43SWITCH_ID_LIST(IDX_EXIF, mux->exif_);44SWITCH_ID_LIST(IDX_XMP, mux->xmp_);45assert(idx != IDX_UNKNOWN);46return WEBP_MUX_NOT_FOUND;47}48#undef SWITCH_ID_LIST4950// Fill the chunk with the given data (includes chunk header bytes), after some51// verifications.52static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,53const uint8_t* data, size_t data_size,54size_t riff_size, int copy_data) {55uint32_t chunk_size;56WebPData chunk_data;5758// Sanity checks.59if (data_size < CHUNK_HEADER_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA;60chunk_size = GetLE32(data + TAG_SIZE);6162{63const size_t chunk_disk_size = SizeWithPadding(chunk_size);64if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA;65if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA;66}6768// Data assignment.69chunk_data.bytes = data + CHUNK_HEADER_SIZE;70chunk_data.size = chunk_size;71return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));72}7374int MuxImageFinalize(WebPMuxImage* const wpi) {75const WebPChunk* const img = wpi->img_;76const WebPData* const image = &img->data_;77const int is_lossless = (img->tag_ == kChunks[IDX_VP8L].tag);78int w, h;79int vp8l_has_alpha = 0;80const int ok = is_lossless ?81VP8LGetInfo(image->bytes, image->size, &w, &h, &vp8l_has_alpha) :82VP8GetInfo(image->bytes, image->size, image->size, &w, &h);83assert(img != NULL);84if (ok) {85// Ignore ALPH chunk accompanying VP8L.86if (is_lossless && (wpi->alpha_ != NULL)) {87ChunkDelete(wpi->alpha_);88wpi->alpha_ = NULL;89}90wpi->width_ = w;91wpi->height_ = h;92wpi->has_alpha_ = vp8l_has_alpha || (wpi->alpha_ != NULL);93}94return ok;95}9697static int MuxImageParse(const WebPChunk* const chunk, int copy_data,98WebPMuxImage* const wpi) {99const uint8_t* bytes = chunk->data_.bytes;100size_t size = chunk->data_.size;101const uint8_t* const last = bytes + size;102WebPChunk subchunk;103size_t subchunk_size;104ChunkInit(&subchunk);105106assert(chunk->tag_ == kChunks[IDX_ANMF].tag);107assert(!wpi->is_partial_);108109// ANMF.110{111const size_t hdr_size = ANMF_CHUNK_SIZE;112const WebPData temp = { bytes, hdr_size };113// Each of ANMF chunk contain a header at the beginning. So, its size should114// be at least 'hdr_size'.115if (size < hdr_size) goto Fail;116ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);117}118ChunkSetNth(&subchunk, &wpi->header_, 1);119wpi->is_partial_ = 1; // Waiting for ALPH and/or VP8/VP8L chunks.120121// Rest of the chunks.122subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;123bytes += subchunk_size;124size -= subchunk_size;125126while (bytes != last) {127ChunkInit(&subchunk);128if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,129copy_data) != WEBP_MUX_OK) {130goto Fail;131}132switch (ChunkGetIdFromTag(subchunk.tag_)) {133case WEBP_CHUNK_ALPHA:134if (wpi->alpha_ != NULL) goto Fail; // Consecutive ALPH chunks.135if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail;136wpi->is_partial_ = 1; // Waiting for a VP8 chunk.137break;138case WEBP_CHUNK_IMAGE:139if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail;140if (!MuxImageFinalize(wpi)) goto Fail;141wpi->is_partial_ = 0; // wpi is completely filled.142break;143case WEBP_CHUNK_UNKNOWN:144if (wpi->is_partial_) goto Fail; // Encountered an unknown chunk145// before some image chunks.146if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail;147break;148default:149goto Fail;150break;151}152subchunk_size = ChunkDiskSize(&subchunk);153bytes += subchunk_size;154size -= subchunk_size;155}156if (wpi->is_partial_) goto Fail;157return 1;158159Fail:160ChunkRelease(&subchunk);161return 0;162}163164//------------------------------------------------------------------------------165// Create a mux object from WebP-RIFF data.166167WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,168int version) {169size_t riff_size;170uint32_t tag;171const uint8_t* end;172WebPMux* mux = NULL;173WebPMuxImage* wpi = NULL;174const uint8_t* data;175size_t size;176WebPChunk chunk;177ChunkInit(&chunk);178179// Sanity checks.180if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {181return NULL; // version mismatch182}183if (bitstream == NULL) return NULL;184185data = bitstream->bytes;186size = bitstream->size;187188if (data == NULL) return NULL;189if (size < RIFF_HEADER_SIZE) return NULL;190if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') ||191GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) {192return NULL;193}194195mux = WebPMuxNew();196if (mux == NULL) return NULL;197198if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err;199200tag = GetLE32(data + RIFF_HEADER_SIZE);201if (tag != kChunks[IDX_VP8].tag &&202tag != kChunks[IDX_VP8L].tag &&203tag != kChunks[IDX_VP8X].tag) {204goto Err; // First chunk should be VP8, VP8L or VP8X.205}206207riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE));208if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) {209goto Err;210} else {211if (riff_size < size) { // Redundant data after last chunk.212size = riff_size; // To make sure we don't read any data beyond mux_size.213}214}215216end = data + size;217data += RIFF_HEADER_SIZE;218size -= RIFF_HEADER_SIZE;219220wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*wpi));221if (wpi == NULL) goto Err;222MuxImageInit(wpi);223224// Loop over chunks.225while (data != end) {226size_t data_size;227WebPChunkId id;228WebPChunk** chunk_list;229if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,230copy_data) != WEBP_MUX_OK) {231goto Err;232}233data_size = ChunkDiskSize(&chunk);234id = ChunkGetIdFromTag(chunk.tag_);235switch (id) {236case WEBP_CHUNK_ALPHA:237if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks.238if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;239wpi->is_partial_ = 1; // Waiting for a VP8 chunk.240break;241case WEBP_CHUNK_IMAGE:242if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;243if (!MuxImageFinalize(wpi)) goto Err;244wpi->is_partial_ = 0; // wpi is completely filled.245PushImage:246// Add this to mux->images_ list.247if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;248MuxImageInit(wpi); // Reset for reading next image.249break;250case WEBP_CHUNK_ANMF:251if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete.252if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;253ChunkRelease(&chunk);254goto PushImage;255break;256default: // A non-image chunk.257if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before258// getting all chunks of an image.259chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk.260if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;261if (id == WEBP_CHUNK_VP8X) { // grab global specs262mux->canvas_width_ = GetLE24(data + 12) + 1;263mux->canvas_height_ = GetLE24(data + 15) + 1;264}265break;266}267data += data_size;268size -= data_size;269ChunkInit(&chunk);270}271272// Incomplete image.273if (wpi->is_partial_) goto Err;274275// Validate mux if complete.276if (MuxValidate(mux) != WEBP_MUX_OK) goto Err;277278MuxImageDelete(wpi);279return mux; // All OK;280281Err: // Something bad happened.282ChunkRelease(&chunk);283MuxImageDelete(wpi);284WebPMuxDelete(mux);285return NULL;286}287288//------------------------------------------------------------------------------289// Get API(s).290291// Validates that the given mux has a single image.292static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) {293const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE);294const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF);295296if (num_images == 0) {297// No images in mux.298return WEBP_MUX_NOT_FOUND;299} else if (num_images == 1 && num_frames == 0) {300// Valid case (single image).301return WEBP_MUX_OK;302} else {303// Frame case OR an invalid mux.304return WEBP_MUX_INVALID_ARGUMENT;305}306}307308// Get the canvas width, height and flags after validating that VP8X/VP8/VP8L309// chunk and canvas size are valid.310static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux,311int* width, int* height, uint32_t* flags) {312int w, h;313uint32_t f = 0;314WebPData data;315assert(mux != NULL);316317// Check if VP8X chunk is present.318if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) {319if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA;320f = GetLE32(data.bytes + 0);321w = GetLE24(data.bytes + 4) + 1;322h = GetLE24(data.bytes + 7) + 1;323} else {324const WebPMuxImage* const wpi = mux->images_;325// Grab user-forced canvas size as default.326w = mux->canvas_width_;327h = mux->canvas_height_;328if (w == 0 && h == 0 && ValidateForSingleImage(mux) == WEBP_MUX_OK) {329// single image and not forced canvas size => use dimension of first frame330assert(wpi != NULL);331w = wpi->width_;332h = wpi->height_;333}334if (wpi != NULL) {335if (wpi->has_alpha_) f |= ALPHA_FLAG;336}337}338if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA;339340if (width != NULL) *width = w;341if (height != NULL) *height = h;342if (flags != NULL) *flags = f;343return WEBP_MUX_OK;344}345346WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width, int* height) {347if (mux == NULL || width == NULL || height == NULL) {348return WEBP_MUX_INVALID_ARGUMENT;349}350return MuxGetCanvasInfo(mux, width, height, NULL);351}352353WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) {354if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT;355return MuxGetCanvasInfo(mux, NULL, NULL, flags);356}357358static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width,359int height, uint32_t flags) {360const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE;361assert(width >= 1 && height >= 1);362assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE);363assert(width * (uint64_t)height < MAX_IMAGE_AREA);364PutLE32(dst, MKFOURCC('V', 'P', '8', 'X'));365PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE);366PutLE32(dst + CHUNK_HEADER_SIZE, flags);367PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1);368PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1);369return dst + vp8x_size;370}371372// Assemble a single image WebP bitstream from 'wpi'.373static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,374WebPData* const bitstream) {375uint8_t* dst;376377// Allocate data.378const int need_vp8x = (wpi->alpha_ != NULL);379const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0;380const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0;381// Note: No need to output ANMF chunk for a single image.382const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size +383ChunkDiskSize(wpi->img_);384uint8_t* const data = (uint8_t*)WebPSafeMalloc(1ULL, size);385if (data == NULL) return WEBP_MUX_MEMORY_ERROR;386387// Main RIFF header.388dst = MuxEmitRiffHeader(data, size);389390if (need_vp8x) {391dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG); // VP8X.392dst = ChunkListEmit(wpi->alpha_, dst); // ALPH.393}394395// Bitstream.396dst = ChunkListEmit(wpi->img_, dst);397assert(dst == data + size);398399// Output.400bitstream->bytes = data;401bitstream->size = size;402return WEBP_MUX_OK;403}404405WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4],406WebPData* chunk_data) {407CHUNK_INDEX idx;408if (mux == NULL || fourcc == NULL || chunk_data == NULL) {409return WEBP_MUX_INVALID_ARGUMENT;410}411idx = ChunkGetIndexFromFourCC(fourcc);412if (IsWPI(kChunks[idx].id)) { // An image chunk.413return WEBP_MUX_INVALID_ARGUMENT;414} else if (idx != IDX_UNKNOWN) { // A known chunk type.415return MuxGet(mux, idx, 1, chunk_data);416} else { // An unknown chunk type.417const WebPChunk* const chunk =418ChunkSearchList(mux->unknown_, 1, ChunkGetTagFromFourCC(fourcc));419if (chunk == NULL) return WEBP_MUX_NOT_FOUND;420*chunk_data = chunk->data_;421return WEBP_MUX_OK;422}423}424425static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi,426WebPMuxFrameInfo* const info) {427// Set some defaults for unrelated fields.428info->x_offset = 0;429info->y_offset = 0;430info->duration = 1;431info->dispose_method = WEBP_MUX_DISPOSE_NONE;432info->blend_method = WEBP_MUX_BLEND;433// Extract data for related fields.434info->id = ChunkGetIdFromTag(wpi->img_->tag_);435return SynthesizeBitstream(wpi, &info->bitstream);436}437438static WebPMuxError MuxGetFrameInternal(const WebPMuxImage* const wpi,439WebPMuxFrameInfo* const frame) {440const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag);441const WebPData* frame_data;442if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT;443assert(wpi->header_ != NULL); // Already checked by WebPMuxGetFrame().444// Get frame chunk.445frame_data = &wpi->header_->data_;446if (frame_data->size < kChunks[IDX_ANMF].size) return WEBP_MUX_BAD_DATA;447// Extract info.448frame->x_offset = 2 * GetLE24(frame_data->bytes + 0);449frame->y_offset = 2 * GetLE24(frame_data->bytes + 3);450{451const uint8_t bits = frame_data->bytes[15];452frame->duration = GetLE24(frame_data->bytes + 12);453frame->dispose_method =454(bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE;455frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND;456}457frame->id = ChunkGetIdFromTag(wpi->header_->tag_);458return SynthesizeBitstream(wpi, &frame->bitstream);459}460461WebPMuxError WebPMuxGetFrame(462const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) {463WebPMuxError err;464WebPMuxImage* wpi;465466// Sanity checks.467if (mux == NULL || frame == NULL) {468return WEBP_MUX_INVALID_ARGUMENT;469}470471// Get the nth WebPMuxImage.472err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &wpi);473if (err != WEBP_MUX_OK) return err;474475// Get frame info.476if (wpi->header_ == NULL) {477return MuxGetImageInternal(wpi, frame);478} else {479return MuxGetFrameInternal(wpi, frame);480}481}482483WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,484WebPMuxAnimParams* params) {485WebPData anim;486WebPMuxError err;487488if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;489490err = MuxGet(mux, IDX_ANIM, 1, &anim);491if (err != WEBP_MUX_OK) return err;492if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA;493params->bgcolor = GetLE32(anim.bytes);494params->loop_count = GetLE16(anim.bytes + 4);495496return WEBP_MUX_OK;497}498499// Get chunk index from chunk id. Returns IDX_NIL if not found.500static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) {501int i;502for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) {503if (id == kChunks[i].id) return (CHUNK_INDEX)i;504}505return IDX_NIL;506}507508// Count number of chunks matching 'tag' in the 'chunk_list'.509// If tag == NIL_TAG, any tag will be matched.510static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) {511int count = 0;512const WebPChunk* current;513for (current = chunk_list; current != NULL; current = current->next_) {514if (tag == NIL_TAG || current->tag_ == tag) {515count++; // Count chunks whose tags match.516}517}518return count;519}520521WebPMuxError WebPMuxNumChunks(const WebPMux* mux,522WebPChunkId id, int* num_elements) {523if (mux == NULL || num_elements == NULL) {524return WEBP_MUX_INVALID_ARGUMENT;525}526527if (IsWPI(id)) {528*num_elements = MuxImageCount(mux->images_, id);529} else {530WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id);531const CHUNK_INDEX idx = ChunkGetIndexFromId(id);532*num_elements = CountChunks(*chunk_list, kChunks[idx].tag);533}534535return WEBP_MUX_OK;536}537538//------------------------------------------------------------------------------539540541