Path: blob/master/thirdparty/libwebp/src/mux/muxread.c
21409 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 <stddef.h>1617#include "src/dec/vp8_dec.h"18#include "src/mux/muxi.h"19#include "src/utils/utils.h"20#include "src/webp/format_constants.h"21#include "src/webp/mux.h"22#include "src/webp/mux_types.h"23#include "src/webp/types.h"2425//------------------------------------------------------------------------------26// Helper method(s).2728// Handy MACRO.29#define SWITCH_ID_LIST(INDEX, LIST) \30do { \31if (idx == (INDEX)) { \32const WebPChunk* const chunk = ChunkSearchList((LIST), nth, \33kChunks[(INDEX)].tag); \34if (chunk) { \35*data = chunk->data; \36return WEBP_MUX_OK; \37} else { \38return WEBP_MUX_NOT_FOUND; \39} \40} \41} while (0)4243static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,44uint32_t nth, WebPData* const data) {45assert(mux != NULL);46assert(idx != IDX_LAST_CHUNK);47assert(!IsWPI(kChunks[idx].id));48WebPDataInit(data);4950SWITCH_ID_LIST(IDX_VP8X, mux->vp8x);51SWITCH_ID_LIST(IDX_ICCP, mux->iccp);52SWITCH_ID_LIST(IDX_ANIM, mux->anim);53SWITCH_ID_LIST(IDX_EXIF, mux->exif);54SWITCH_ID_LIST(IDX_XMP, mux->xmp);55assert(idx != IDX_UNKNOWN);56return WEBP_MUX_NOT_FOUND;57}58#undef SWITCH_ID_LIST5960// Fill the chunk with the given data (includes chunk header bytes), after some61// verifications.62static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,63const uint8_t* data, size_t data_size,64size_t riff_size, int copy_data) {65uint32_t chunk_size;66WebPData chunk_data;6768// Correctness checks.69if (data_size < CHUNK_HEADER_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA;70chunk_size = GetLE32(data + TAG_SIZE);71if (chunk_size > MAX_CHUNK_PAYLOAD) return WEBP_MUX_BAD_DATA;7273{74const size_t chunk_disk_size = SizeWithPadding(chunk_size);75if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA;76if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA;77}7879// Data assignment.80chunk_data.bytes = data + CHUNK_HEADER_SIZE;81chunk_data.size = chunk_size;82return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));83}8485int MuxImageFinalize(WebPMuxImage* const wpi) {86const WebPChunk* const img = wpi->img;87const WebPData* const image = &img->data;88const int is_lossless = (img->tag == kChunks[IDX_VP8L].tag);89int w, h;90int vp8l_has_alpha = 0;91const int ok = is_lossless ?92VP8LGetInfo(image->bytes, image->size, &w, &h, &vp8l_has_alpha) :93VP8GetInfo(image->bytes, image->size, image->size, &w, &h);94assert(img != NULL);95if (ok) {96// Ignore ALPH chunk accompanying VP8L.97if (is_lossless && (wpi->alpha != NULL)) {98ChunkDelete(wpi->alpha);99wpi->alpha = NULL;100}101wpi->width = w;102wpi->height = h;103wpi->has_alpha = vp8l_has_alpha || (wpi->alpha != NULL);104}105return ok;106}107108static int MuxImageParse(const WebPChunk* const chunk, int copy_data,109WebPMuxImage* const wpi) {110const uint8_t* bytes = chunk->data.bytes;111size_t size = chunk->data.size;112const uint8_t* const last = (bytes == NULL) ? NULL : bytes + size;113WebPChunk subchunk;114size_t subchunk_size;115WebPChunk** unknown_chunk_list = &wpi->unknown;116ChunkInit(&subchunk);117118assert(chunk->tag == kChunks[IDX_ANMF].tag);119assert(!wpi->is_partial);120121// ANMF.122{123const size_t hdr_size = ANMF_CHUNK_SIZE;124const WebPData temp = { bytes, hdr_size };125// Each of ANMF chunk contain a header at the beginning. So, its size should126// be at least 'hdr_size'.127if (size < hdr_size) goto Fail;128if (ChunkAssignData(&subchunk, &temp, copy_data,129chunk->tag) != WEBP_MUX_OK) {130goto Fail;131}132}133if (ChunkSetHead(&subchunk, &wpi->header) != WEBP_MUX_OK) goto Fail;134wpi->is_partial = 1; // Waiting for ALPH and/or VP8/VP8L chunks.135136// Rest of the chunks.137subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;138bytes += subchunk_size;139size -= subchunk_size;140141while (bytes != last) {142ChunkInit(&subchunk);143if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,144copy_data) != WEBP_MUX_OK) {145goto Fail;146}147switch (ChunkGetIdFromTag(subchunk.tag)) {148case WEBP_CHUNK_ALPHA:149if (wpi->alpha != NULL) goto Fail; // Consecutive ALPH chunks.150if (ChunkSetHead(&subchunk, &wpi->alpha) != WEBP_MUX_OK) goto Fail;151wpi->is_partial = 1; // Waiting for a VP8 chunk.152break;153case WEBP_CHUNK_IMAGE:154if (wpi->img != NULL) goto Fail; // Only 1 image chunk allowed.155if (ChunkSetHead(&subchunk, &wpi->img) != WEBP_MUX_OK) goto Fail;156if (!MuxImageFinalize(wpi)) goto Fail;157wpi->is_partial = 0; // wpi is completely filled.158break;159case WEBP_CHUNK_UNKNOWN:160if (wpi->is_partial) {161goto Fail; // Encountered an unknown chunk162// before some image chunks.163}164if (ChunkAppend(&subchunk, &unknown_chunk_list) != WEBP_MUX_OK) {165goto Fail;166}167break;168default:169goto Fail;170}171subchunk_size = ChunkDiskSize(&subchunk);172bytes += subchunk_size;173size -= subchunk_size;174}175if (wpi->is_partial) goto Fail;176return 1;177178Fail:179ChunkRelease(&subchunk);180return 0;181}182183//------------------------------------------------------------------------------184// Create a mux object from WebP-RIFF data.185186WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,187int version) {188size_t riff_size;189uint32_t tag;190const uint8_t* end;191WebPMux* mux = NULL;192WebPMuxImage* wpi = NULL;193const uint8_t* data;194size_t size;195WebPChunk chunk;196// Stores the end of the chunk lists so that it is faster to append data to197// their ends.198WebPChunk** chunk_list_ends[WEBP_CHUNK_NIL + 1] = { NULL };199ChunkInit(&chunk);200201if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {202return NULL; // version mismatch203}204if (bitstream == NULL) return NULL;205206data = bitstream->bytes;207size = bitstream->size;208209if (data == NULL) return NULL;210if (size < RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE) return NULL;211if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') ||212GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) {213return NULL;214}215216mux = WebPMuxNew();217if (mux == NULL) return NULL;218219tag = GetLE32(data + RIFF_HEADER_SIZE);220if (tag != kChunks[IDX_VP8].tag &&221tag != kChunks[IDX_VP8L].tag &&222tag != kChunks[IDX_VP8X].tag) {223goto Err; // First chunk should be VP8, VP8L or VP8X.224}225226riff_size = GetLE32(data + TAG_SIZE);227if (riff_size > MAX_CHUNK_PAYLOAD) goto Err;228229// Note this padding is historical and differs from demux.c which does not230// pad the file size.231riff_size = SizeWithPadding(riff_size);232// Make sure the whole RIFF header is available.233if (riff_size < RIFF_HEADER_SIZE) goto Err;234if (riff_size > size) goto Err;235// There's no point in reading past the end of the RIFF chunk. Note riff_size236// includes CHUNK_HEADER_SIZE after SizeWithPadding().237if (size > riff_size) {238size = riff_size;239}240241end = data + size;242data += RIFF_HEADER_SIZE;243size -= RIFF_HEADER_SIZE;244245wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*wpi));246if (wpi == NULL) goto Err;247MuxImageInit(wpi);248249// Loop over chunks.250while (data != end) {251size_t data_size;252WebPChunkId id;253if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,254copy_data) != WEBP_MUX_OK) {255goto Err;256}257data_size = ChunkDiskSize(&chunk);258id = ChunkGetIdFromTag(chunk.tag);259switch (id) {260case WEBP_CHUNK_ALPHA:261if (wpi->alpha != NULL) goto Err; // Consecutive ALPH chunks.262if (ChunkSetHead(&chunk, &wpi->alpha) != WEBP_MUX_OK) goto Err;263wpi->is_partial = 1; // Waiting for a VP8 chunk.264break;265case WEBP_CHUNK_IMAGE:266if (ChunkSetHead(&chunk, &wpi->img) != WEBP_MUX_OK) goto Err;267if (!MuxImageFinalize(wpi)) goto Err;268wpi->is_partial = 0; // wpi is completely filled.269PushImage:270// Add this to mux->images list.271if (MuxImagePush(wpi, &mux->images) != WEBP_MUX_OK) goto Err;272MuxImageInit(wpi); // Reset for reading next image.273break;274case WEBP_CHUNK_ANMF:275if (wpi->is_partial) goto Err; // Previous wpi is still incomplete.276if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;277ChunkRelease(&chunk);278goto PushImage;279default: // A non-image chunk.280if (wpi->is_partial) goto Err; // Encountered a non-image chunk before281// getting all chunks of an image.282if (chunk_list_ends[id] == NULL) {283chunk_list_ends[id] =284MuxGetChunkListFromId(mux, id); // List to add this chunk.285}286if (ChunkAppend(&chunk, &chunk_list_ends[id]) != WEBP_MUX_OK) goto Err;287if (id == WEBP_CHUNK_VP8X) { // grab global specs288if (data_size < CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE) goto Err;289mux->canvas_width = GetLE24(data + 12) + 1;290mux->canvas_height = GetLE24(data + 15) + 1;291}292break;293}294data += data_size;295size -= data_size;296ChunkInit(&chunk);297}298299// Incomplete image.300if (wpi->is_partial) goto Err;301302// Validate mux if complete.303if (MuxValidate(mux) != WEBP_MUX_OK) goto Err;304305MuxImageDelete(wpi);306return mux; // All OK;307308Err: // Something bad happened.309ChunkRelease(&chunk);310MuxImageDelete(wpi);311WebPMuxDelete(mux);312return NULL;313}314315//------------------------------------------------------------------------------316// Get API(s).317318// Validates that the given mux has a single image.319static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) {320const int num_images = MuxImageCount(mux->images, WEBP_CHUNK_IMAGE);321const int num_frames = MuxImageCount(mux->images, WEBP_CHUNK_ANMF);322323if (num_images == 0) {324// No images in mux.325return WEBP_MUX_NOT_FOUND;326} else if (num_images == 1 && num_frames == 0) {327// Valid case (single image).328return WEBP_MUX_OK;329} else {330// Frame case OR an invalid mux.331return WEBP_MUX_INVALID_ARGUMENT;332}333}334335// Get the canvas width, height and flags after validating that VP8X/VP8/VP8L336// chunk and canvas size are valid.337static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux,338int* width, int* height, uint32_t* flags) {339int w, h;340uint32_t f = 0;341WebPData data;342assert(mux != NULL);343344// Check if VP8X chunk is present.345if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) {346if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA;347f = GetLE32(data.bytes + 0);348w = GetLE24(data.bytes + 4) + 1;349h = GetLE24(data.bytes + 7) + 1;350} else {351const WebPMuxImage* const wpi = mux->images;352// Grab user-forced canvas size as default.353w = mux->canvas_width;354h = mux->canvas_height;355if (w == 0 && h == 0 && ValidateForSingleImage(mux) == WEBP_MUX_OK) {356// single image and not forced canvas size => use dimension of first frame357assert(wpi != NULL);358w = wpi->width;359h = wpi->height;360}361if (wpi != NULL) {362if (wpi->has_alpha) f |= ALPHA_FLAG;363}364}365if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA;366367if (width != NULL) *width = w;368if (height != NULL) *height = h;369if (flags != NULL) *flags = f;370return WEBP_MUX_OK;371}372373WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width, int* height) {374if (mux == NULL || width == NULL || height == NULL) {375return WEBP_MUX_INVALID_ARGUMENT;376}377return MuxGetCanvasInfo(mux, width, height, NULL);378}379380WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) {381if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT;382return MuxGetCanvasInfo(mux, NULL, NULL, flags);383}384385static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width,386int height, uint32_t flags) {387const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE;388assert(width >= 1 && height >= 1);389assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE);390assert(width * (uint64_t)height < MAX_IMAGE_AREA);391PutLE32(dst, MKFOURCC('V', 'P', '8', 'X'));392PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE);393PutLE32(dst + CHUNK_HEADER_SIZE, flags);394PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1);395PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1);396return dst + vp8x_size;397}398399// Assemble a single image WebP bitstream from 'wpi'.400static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,401WebPData* const bitstream) {402uint8_t* dst;403404// Allocate data.405const int need_vp8x = (wpi->alpha != NULL);406const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0;407const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha) : 0;408// Note: No need to output ANMF chunk for a single image.409const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size +410ChunkDiskSize(wpi->img);411uint8_t* const data = (uint8_t*)WebPSafeMalloc(1ULL, size);412if (data == NULL) return WEBP_MUX_MEMORY_ERROR;413414// There should be at most one alpha chunk and exactly one img chunk.415assert(wpi->alpha == NULL || wpi->alpha->next == NULL);416assert(wpi->img != NULL && wpi->img->next == NULL);417418// Main RIFF header.419dst = MuxEmitRiffHeader(data, size);420421if (need_vp8x) {422dst = EmitVP8XChunk(dst, wpi->width, wpi->height, ALPHA_FLAG); // VP8X.423dst = ChunkListEmit(wpi->alpha, dst); // ALPH.424}425426// Bitstream.427dst = ChunkListEmit(wpi->img, dst);428assert(dst == data + size);429430// Output.431bitstream->bytes = data;432bitstream->size = size;433return WEBP_MUX_OK;434}435436WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4],437WebPData* chunk_data) {438CHUNK_INDEX idx;439if (mux == NULL || fourcc == NULL || chunk_data == NULL) {440return WEBP_MUX_INVALID_ARGUMENT;441}442idx = ChunkGetIndexFromFourCC(fourcc);443assert(idx != IDX_LAST_CHUNK);444if (IsWPI(kChunks[idx].id)) { // An image chunk.445return WEBP_MUX_INVALID_ARGUMENT;446} else if (idx != IDX_UNKNOWN) { // A known chunk type.447return MuxGet(mux, idx, 1, chunk_data);448} else { // An unknown chunk type.449const WebPChunk* const chunk =450ChunkSearchList(mux->unknown, 1, ChunkGetTagFromFourCC(fourcc));451if (chunk == NULL) return WEBP_MUX_NOT_FOUND;452*chunk_data = chunk->data;453return WEBP_MUX_OK;454}455}456457static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi,458WebPMuxFrameInfo* const info) {459// Set some defaults for unrelated fields.460info->x_offset = 0;461info->y_offset = 0;462info->duration = 1;463info->dispose_method = WEBP_MUX_DISPOSE_NONE;464info->blend_method = WEBP_MUX_BLEND;465// Extract data for related fields.466info->id = ChunkGetIdFromTag(wpi->img->tag);467return SynthesizeBitstream(wpi, &info->bitstream);468}469470static WebPMuxError MuxGetFrameInternal(const WebPMuxImage* const wpi,471WebPMuxFrameInfo* const frame) {472const int is_frame = (wpi->header->tag == kChunks[IDX_ANMF].tag);473const WebPData* frame_data;474if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT;475assert(wpi->header != NULL); // Already checked by WebPMuxGetFrame().476// Get frame chunk.477frame_data = &wpi->header->data;478if (frame_data->size < kChunks[IDX_ANMF].size) return WEBP_MUX_BAD_DATA;479// Extract info.480frame->x_offset = 2 * GetLE24(frame_data->bytes + 0);481frame->y_offset = 2 * GetLE24(frame_data->bytes + 3);482{483const uint8_t bits = frame_data->bytes[15];484frame->duration = GetLE24(frame_data->bytes + 12);485frame->dispose_method =486(bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE;487frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND;488}489frame->id = ChunkGetIdFromTag(wpi->header->tag);490return SynthesizeBitstream(wpi, &frame->bitstream);491}492493WebPMuxError WebPMuxGetFrame(494const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) {495WebPMuxError err;496WebPMuxImage* wpi;497498if (mux == NULL || frame == NULL) {499return WEBP_MUX_INVALID_ARGUMENT;500}501502// Get the nth WebPMuxImage.503err = MuxImageGetNth((const WebPMuxImage**)&mux->images, nth, &wpi);504if (err != WEBP_MUX_OK) return err;505506// Get frame info.507if (wpi->header == NULL) {508return MuxGetImageInternal(wpi, frame);509} else {510return MuxGetFrameInternal(wpi, frame);511}512}513514WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,515WebPMuxAnimParams* params) {516WebPData anim;517WebPMuxError err;518519if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;520521err = MuxGet(mux, IDX_ANIM, 1, &anim);522if (err != WEBP_MUX_OK) return err;523if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA;524params->bgcolor = GetLE32(anim.bytes);525params->loop_count = GetLE16(anim.bytes + 4);526527return WEBP_MUX_OK;528}529530// Get chunk index from chunk id. Returns IDX_NIL if not found.531static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) {532int i;533for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) {534if (id == kChunks[i].id) return (CHUNK_INDEX)i;535}536return IDX_NIL;537}538539// Count number of chunks matching 'tag' in the 'chunk_list'.540// If tag == NIL_TAG, any tag will be matched.541static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) {542int count = 0;543const WebPChunk* current;544for (current = chunk_list; current != NULL; current = current->next) {545if (tag == NIL_TAG || current->tag == tag) {546count++; // Count chunks whose tags match.547}548}549return count;550}551552WebPMuxError WebPMuxNumChunks(const WebPMux* mux,553WebPChunkId id, int* num_elements) {554if (mux == NULL || num_elements == NULL) {555return WEBP_MUX_INVALID_ARGUMENT;556}557558if (IsWPI(id)) {559*num_elements = MuxImageCount(mux->images, id);560} else {561WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id);562const CHUNK_INDEX idx = ChunkGetIndexFromId(id);563*num_elements = CountChunks(*chunk_list, kChunks[idx].tag);564}565566return WEBP_MUX_OK;567}568569//------------------------------------------------------------------------------570571572