Path: blob/master/thirdparty/libwebp/src/mux/muxinternal.c
9913 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// Internal objects and utils 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#define UNDEFINED_CHUNK_SIZE ((uint32_t)(-1))1920const ChunkInfo kChunks[] = {21{ MKFOURCC('V', 'P', '8', 'X'), WEBP_CHUNK_VP8X, VP8X_CHUNK_SIZE },22{ MKFOURCC('I', 'C', 'C', 'P'), WEBP_CHUNK_ICCP, UNDEFINED_CHUNK_SIZE },23{ MKFOURCC('A', 'N', 'I', 'M'), WEBP_CHUNK_ANIM, ANIM_CHUNK_SIZE },24{ MKFOURCC('A', 'N', 'M', 'F'), WEBP_CHUNK_ANMF, ANMF_CHUNK_SIZE },25{ MKFOURCC('A', 'L', 'P', 'H'), WEBP_CHUNK_ALPHA, UNDEFINED_CHUNK_SIZE },26{ MKFOURCC('V', 'P', '8', ' '), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE },27{ MKFOURCC('V', 'P', '8', 'L'), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE },28{ MKFOURCC('E', 'X', 'I', 'F'), WEBP_CHUNK_EXIF, UNDEFINED_CHUNK_SIZE },29{ MKFOURCC('X', 'M', 'P', ' '), WEBP_CHUNK_XMP, UNDEFINED_CHUNK_SIZE },30{ NIL_TAG, WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE },3132{ NIL_TAG, WEBP_CHUNK_NIL, UNDEFINED_CHUNK_SIZE }33};3435//------------------------------------------------------------------------------3637int WebPGetMuxVersion(void) {38return (MUX_MAJ_VERSION << 16) | (MUX_MIN_VERSION << 8) | MUX_REV_VERSION;39}4041//------------------------------------------------------------------------------42// Life of a chunk object.4344void ChunkInit(WebPChunk* const chunk) {45assert(chunk);46memset(chunk, 0, sizeof(*chunk));47chunk->tag_ = NIL_TAG;48}4950WebPChunk* ChunkRelease(WebPChunk* const chunk) {51WebPChunk* next;52if (chunk == NULL) return NULL;53if (chunk->owner_) {54WebPDataClear(&chunk->data_);55}56next = chunk->next_;57ChunkInit(chunk);58return next;59}6061//------------------------------------------------------------------------------62// Chunk misc methods.6364CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) {65int i;66for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {67if (tag == kChunks[i].tag) return (CHUNK_INDEX)i;68}69return IDX_UNKNOWN;70}7172WebPChunkId ChunkGetIdFromTag(uint32_t tag) {73int i;74for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {75if (tag == kChunks[i].tag) return kChunks[i].id;76}77return WEBP_CHUNK_UNKNOWN;78}7980uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) {81return MKFOURCC(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);82}8384CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) {85const uint32_t tag = ChunkGetTagFromFourCC(fourcc);86return ChunkGetIndexFromTag(tag);87}8889//------------------------------------------------------------------------------90// Chunk search methods.9192// Returns next chunk in the chunk list with the given tag.93static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) {94while (chunk != NULL && chunk->tag_ != tag) {95chunk = chunk->next_;96}97return chunk;98}99100WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) {101uint32_t iter = nth;102first = ChunkSearchNextInList(first, tag);103if (first == NULL) return NULL;104105while (--iter != 0) {106WebPChunk* next_chunk = ChunkSearchNextInList(first->next_, tag);107if (next_chunk == NULL) break;108first = next_chunk;109}110return ((nth > 0) && (iter > 0)) ? NULL : first;111}112113//------------------------------------------------------------------------------114// Chunk writer methods.115116WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,117int copy_data, uint32_t tag) {118// For internally allocated chunks, always copy data & make it owner of data.119if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) {120copy_data = 1;121}122123ChunkRelease(chunk);124125if (data != NULL) {126if (copy_data) { // Copy data.127if (!WebPDataCopy(data, &chunk->data_)) return WEBP_MUX_MEMORY_ERROR;128chunk->owner_ = 1; // Chunk is owner of data.129} else { // Don't copy data.130chunk->data_ = *data;131}132}133chunk->tag_ = tag;134return WEBP_MUX_OK;135}136137WebPMuxError ChunkSetHead(WebPChunk* const chunk,138WebPChunk** const chunk_list) {139WebPChunk* new_chunk;140141assert(chunk_list != NULL);142if (*chunk_list != NULL) {143return WEBP_MUX_NOT_FOUND;144}145146new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, sizeof(*new_chunk));147if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR;148*new_chunk = *chunk;149chunk->owner_ = 0;150new_chunk->next_ = NULL;151*chunk_list = new_chunk;152return WEBP_MUX_OK;153}154155WebPMuxError ChunkAppend(WebPChunk* const chunk,156WebPChunk*** const chunk_list) {157WebPMuxError err;158assert(chunk_list != NULL && *chunk_list != NULL);159160if (**chunk_list == NULL) {161err = ChunkSetHead(chunk, *chunk_list);162} else {163WebPChunk* last_chunk = **chunk_list;164while (last_chunk->next_ != NULL) last_chunk = last_chunk->next_;165err = ChunkSetHead(chunk, &last_chunk->next_);166if (err == WEBP_MUX_OK) *chunk_list = &last_chunk->next_;167}168return err;169}170171//------------------------------------------------------------------------------172// Chunk deletion method(s).173174WebPChunk* ChunkDelete(WebPChunk* const chunk) {175WebPChunk* const next = ChunkRelease(chunk);176WebPSafeFree(chunk);177return next;178}179180void ChunkListDelete(WebPChunk** const chunk_list) {181while (*chunk_list != NULL) {182*chunk_list = ChunkDelete(*chunk_list);183}184}185186//------------------------------------------------------------------------------187// Chunk serialization methods.188189static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) {190const size_t chunk_size = chunk->data_.size;191assert(chunk);192assert(chunk->tag_ != NIL_TAG);193PutLE32(dst + 0, chunk->tag_);194PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size);195assert(chunk_size == (uint32_t)chunk_size);196memcpy(dst + CHUNK_HEADER_SIZE, chunk->data_.bytes, chunk_size);197if (chunk_size & 1)198dst[CHUNK_HEADER_SIZE + chunk_size] = 0; // Add padding.199return dst + ChunkDiskSize(chunk);200}201202uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) {203while (chunk_list != NULL) {204dst = ChunkEmit(chunk_list, dst);205chunk_list = chunk_list->next_;206}207return dst;208}209210size_t ChunkListDiskSize(const WebPChunk* chunk_list) {211size_t size = 0;212while (chunk_list != NULL) {213size += ChunkDiskSize(chunk_list);214chunk_list = chunk_list->next_;215}216return size;217}218219//------------------------------------------------------------------------------220// Life of a MuxImage object.221222void MuxImageInit(WebPMuxImage* const wpi) {223assert(wpi);224memset(wpi, 0, sizeof(*wpi));225}226227WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) {228WebPMuxImage* next;229if (wpi == NULL) return NULL;230// There should be at most one chunk of header_, alpha_, img_ but we call231// ChunkListDelete to be safe232ChunkListDelete(&wpi->header_);233ChunkListDelete(&wpi->alpha_);234ChunkListDelete(&wpi->img_);235ChunkListDelete(&wpi->unknown_);236237next = wpi->next_;238MuxImageInit(wpi);239return next;240}241242//------------------------------------------------------------------------------243// MuxImage search methods.244245// Get a reference to appropriate chunk list within an image given chunk tag.246static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi,247WebPChunkId id) {248assert(wpi != NULL);249switch (id) {250case WEBP_CHUNK_ANMF: return (WebPChunk**)&wpi->header_;251case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_;252case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_;253default: return NULL;254}255}256257int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) {258int count = 0;259const WebPMuxImage* current;260for (current = wpi_list; current != NULL; current = current->next_) {261if (id == WEBP_CHUNK_NIL) {262++count; // Special case: count all images.263} else {264const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id);265if (wpi_chunk != NULL) {266const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_);267if (wpi_chunk_id == id) ++count; // Count images with a matching 'id'.268}269}270}271return count;272}273274// Outputs a pointer to 'prev_wpi->next_',275// where 'prev_wpi' is the pointer to the image at position (nth - 1).276// Returns true if nth image was found.277static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth,278WebPMuxImage*** const location) {279uint32_t count = 0;280assert(wpi_list);281*location = wpi_list;282283if (nth == 0) {284nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL);285if (nth == 0) return 0; // Not found.286}287288while (*wpi_list != NULL) {289WebPMuxImage* const cur_wpi = *wpi_list;290++count;291if (count == nth) return 1; // Found.292wpi_list = &cur_wpi->next_;293*location = wpi_list;294}295return 0; // Not found.296}297298//------------------------------------------------------------------------------299// MuxImage writer methods.300301WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) {302WebPMuxImage* new_wpi;303304while (*wpi_list != NULL) {305WebPMuxImage* const cur_wpi = *wpi_list;306if (cur_wpi->next_ == NULL) break;307wpi_list = &cur_wpi->next_;308}309310new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi));311if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR;312*new_wpi = *wpi;313new_wpi->next_ = NULL;314315if (*wpi_list != NULL) {316(*wpi_list)->next_ = new_wpi;317} else {318*wpi_list = new_wpi;319}320return WEBP_MUX_OK;321}322323//------------------------------------------------------------------------------324// MuxImage deletion methods.325326WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) {327// Delete the components of wpi. If wpi is NULL this is a noop.328WebPMuxImage* const next = MuxImageRelease(wpi);329WebPSafeFree(wpi);330return next;331}332333WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) {334assert(wpi_list);335if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) {336return WEBP_MUX_NOT_FOUND;337}338*wpi_list = MuxImageDelete(*wpi_list);339return WEBP_MUX_OK;340}341342//------------------------------------------------------------------------------343// MuxImage reader methods.344345WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,346WebPMuxImage** wpi) {347assert(wpi_list);348assert(wpi);349if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth,350(WebPMuxImage***)&wpi_list)) {351return WEBP_MUX_NOT_FOUND;352}353*wpi = (WebPMuxImage*)*wpi_list;354return WEBP_MUX_OK;355}356357//------------------------------------------------------------------------------358// MuxImage serialization methods.359360// Size of an image.361size_t MuxImageDiskSize(const WebPMuxImage* const wpi) {362size_t size = 0;363if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_);364if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_);365if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_);366if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_);367return size;368}369370// Special case as ANMF chunk encapsulates other image chunks.371static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,372size_t total_size, uint8_t* dst) {373const size_t header_size = header->data_.size;374const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;375assert(header->tag_ == kChunks[IDX_ANMF].tag);376PutLE32(dst + 0, header->tag_);377PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);378assert(header_size == (uint32_t)header_size);379memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);380if (header_size & 1) {381dst[CHUNK_HEADER_SIZE + header_size] = 0; // Add padding.382}383return dst + ChunkDiskSize(header);384}385386uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {387// Ordering of chunks to be emitted is strictly as follows:388// 1. ANMF chunk (if present).389// 2. ALPH chunk (if present).390// 3. VP8/VP8L chunk.391assert(wpi);392if (wpi->header_ != NULL) {393dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst);394}395if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);396if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);397if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst);398return dst;399}400401//------------------------------------------------------------------------------402// Helper methods for mux.403404int MuxHasAlpha(const WebPMuxImage* images) {405while (images != NULL) {406if (images->has_alpha_) return 1;407images = images->next_;408}409return 0;410}411412uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {413PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F'));414PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE);415assert(size == (uint32_t)size);416PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P'));417return data + RIFF_HEADER_SIZE;418}419420WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {421assert(mux != NULL);422switch (id) {423case WEBP_CHUNK_VP8X: return (WebPChunk**)&mux->vp8x_;424case WEBP_CHUNK_ICCP: return (WebPChunk**)&mux->iccp_;425case WEBP_CHUNK_ANIM: return (WebPChunk**)&mux->anim_;426case WEBP_CHUNK_EXIF: return (WebPChunk**)&mux->exif_;427case WEBP_CHUNK_XMP: return (WebPChunk**)&mux->xmp_;428default: return (WebPChunk**)&mux->unknown_;429}430}431432static int IsNotCompatible(int feature, int num_items) {433return (feature != 0) != (num_items > 0);434}435436#define NO_FLAG ((WebPFeatureFlags)0)437438// Test basic constraints:439// retrieval, maximum number of chunks by index (use -1 to skip)440// and feature incompatibility (use NO_FLAG to skip).441// On success returns WEBP_MUX_OK and stores the chunk count in *num.442static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx,443WebPFeatureFlags feature,444uint32_t vp8x_flags,445int max, int* num) {446const WebPMuxError err =447WebPMuxNumChunks(mux, kChunks[idx].id, num);448if (err != WEBP_MUX_OK) return err;449if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT;450if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) {451return WEBP_MUX_INVALID_ARGUMENT;452}453return WEBP_MUX_OK;454}455456WebPMuxError MuxValidate(const WebPMux* const mux) {457int num_iccp;458int num_exif;459int num_xmp;460int num_anim;461int num_frames;462int num_vp8x;463int num_images;464int num_alpha;465uint32_t flags;466WebPMuxError err;467468// Verify mux is not NULL.469if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;470471// Verify mux has at least one image.472if (mux->images_ == NULL) return WEBP_MUX_INVALID_ARGUMENT;473474err = WebPMuxGetFeatures(mux, &flags);475if (err != WEBP_MUX_OK) return err;476477// At most one color profile chunk.478err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp);479if (err != WEBP_MUX_OK) return err;480481// At most one EXIF metadata.482err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif);483if (err != WEBP_MUX_OK) return err;484485// At most one XMP metadata.486err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);487if (err != WEBP_MUX_OK) return err;488489// Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.490// At most one ANIM chunk.491err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);492if (err != WEBP_MUX_OK) return err;493err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames);494if (err != WEBP_MUX_OK) return err;495496{497const int has_animation = !!(flags & ANIMATION_FLAG);498if (has_animation && (num_anim == 0 || num_frames == 0)) {499return WEBP_MUX_INVALID_ARGUMENT;500}501if (!has_animation && (num_anim == 1 || num_frames > 0)) {502return WEBP_MUX_INVALID_ARGUMENT;503}504if (!has_animation) {505const WebPMuxImage* images = mux->images_;506// There can be only one image.507if (images == NULL || images->next_ != NULL) {508return WEBP_MUX_INVALID_ARGUMENT;509}510// Size must match.511if (mux->canvas_width_ > 0) {512if (images->width_ != mux->canvas_width_ ||513images->height_ != mux->canvas_height_) {514return WEBP_MUX_INVALID_ARGUMENT;515}516}517}518}519520// Verify either VP8X chunk is present OR there is only one elem in521// mux->images_.522err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x);523if (err != WEBP_MUX_OK) return err;524err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images);525if (err != WEBP_MUX_OK) return err;526if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT;527528// ALPHA_FLAG & alpha chunk(s) are consistent.529// Note: ALPHA_FLAG can be set when there is actually no Alpha data present.530if (MuxHasAlpha(mux->images_)) {531if (num_vp8x > 0) {532// VP8X chunk is present, so it should contain ALPHA_FLAG.533if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT;534} else {535// VP8X chunk is not present, so ALPH chunks should NOT be present either.536err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha);537if (err != WEBP_MUX_OK) return err;538if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT;539}540}541542return WEBP_MUX_OK;543}544545#undef NO_FLAG546547//------------------------------------------------------------------------------548549550551