Path: blob/master/3rdparty/libwebp/src/mux/muxinternal.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// 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// Outputs a pointer to 'prev_chunk->next_',114// where 'prev_chunk' is the pointer to the chunk at position (nth - 1).115// Returns true if nth chunk was found.116static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth,117WebPChunk*** const location) {118uint32_t count = 0;119assert(chunk_list != NULL);120*location = chunk_list;121122while (*chunk_list != NULL) {123WebPChunk* const cur_chunk = *chunk_list;124++count;125if (count == nth) return 1; // Found.126chunk_list = &cur_chunk->next_;127*location = chunk_list;128}129130// *chunk_list is ok to be NULL if adding at last location.131return (nth == 0 || (count == nth - 1)) ? 1 : 0;132}133134//------------------------------------------------------------------------------135// Chunk writer methods.136137WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,138int copy_data, uint32_t tag) {139// For internally allocated chunks, always copy data & make it owner of data.140if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) {141copy_data = 1;142}143144ChunkRelease(chunk);145146if (data != NULL) {147if (copy_data) { // Copy data.148if (!WebPDataCopy(data, &chunk->data_)) return WEBP_MUX_MEMORY_ERROR;149chunk->owner_ = 1; // Chunk is owner of data.150} else { // Don't copy data.151chunk->data_ = *data;152}153}154chunk->tag_ = tag;155return WEBP_MUX_OK;156}157158WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list,159uint32_t nth) {160WebPChunk* new_chunk;161162if (!ChunkSearchListToSet(chunk_list, nth, &chunk_list)) {163return WEBP_MUX_NOT_FOUND;164}165166new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, sizeof(*new_chunk));167if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR;168*new_chunk = *chunk;169chunk->owner_ = 0;170new_chunk->next_ = *chunk_list;171*chunk_list = new_chunk;172return WEBP_MUX_OK;173}174175//------------------------------------------------------------------------------176// Chunk deletion method(s).177178WebPChunk* ChunkDelete(WebPChunk* const chunk) {179WebPChunk* const next = ChunkRelease(chunk);180WebPSafeFree(chunk);181return next;182}183184void ChunkListDelete(WebPChunk** const chunk_list) {185while (*chunk_list != NULL) {186*chunk_list = ChunkDelete(*chunk_list);187}188}189190//------------------------------------------------------------------------------191// Chunk serialization methods.192193static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) {194const size_t chunk_size = chunk->data_.size;195assert(chunk);196assert(chunk->tag_ != NIL_TAG);197PutLE32(dst + 0, chunk->tag_);198PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size);199assert(chunk_size == (uint32_t)chunk_size);200memcpy(dst + CHUNK_HEADER_SIZE, chunk->data_.bytes, chunk_size);201if (chunk_size & 1)202dst[CHUNK_HEADER_SIZE + chunk_size] = 0; // Add padding.203return dst + ChunkDiskSize(chunk);204}205206uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) {207while (chunk_list != NULL) {208dst = ChunkEmit(chunk_list, dst);209chunk_list = chunk_list->next_;210}211return dst;212}213214size_t ChunkListDiskSize(const WebPChunk* chunk_list) {215size_t size = 0;216while (chunk_list != NULL) {217size += ChunkDiskSize(chunk_list);218chunk_list = chunk_list->next_;219}220return size;221}222223//------------------------------------------------------------------------------224// Life of a MuxImage object.225226void MuxImageInit(WebPMuxImage* const wpi) {227assert(wpi);228memset(wpi, 0, sizeof(*wpi));229}230231WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) {232WebPMuxImage* next;233if (wpi == NULL) return NULL;234ChunkDelete(wpi->header_);235ChunkDelete(wpi->alpha_);236ChunkDelete(wpi->img_);237ChunkListDelete(&wpi->unknown_);238239next = wpi->next_;240MuxImageInit(wpi);241return next;242}243244//------------------------------------------------------------------------------245// MuxImage search methods.246247// Get a reference to appropriate chunk list within an image given chunk tag.248static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi,249WebPChunkId id) {250assert(wpi != NULL);251switch (id) {252case WEBP_CHUNK_ANMF: return (WebPChunk**)&wpi->header_;253case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_;254case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_;255default: return NULL;256}257}258259int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) {260int count = 0;261const WebPMuxImage* current;262for (current = wpi_list; current != NULL; current = current->next_) {263if (id == WEBP_CHUNK_NIL) {264++count; // Special case: count all images.265} else {266const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id);267if (wpi_chunk != NULL) {268const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_);269if (wpi_chunk_id == id) ++count; // Count images with a matching 'id'.270}271}272}273return count;274}275276// Outputs a pointer to 'prev_wpi->next_',277// where 'prev_wpi' is the pointer to the image at position (nth - 1).278// Returns true if nth image was found.279static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth,280WebPMuxImage*** const location) {281uint32_t count = 0;282assert(wpi_list);283*location = wpi_list;284285if (nth == 0) {286nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL);287if (nth == 0) return 0; // Not found.288}289290while (*wpi_list != NULL) {291WebPMuxImage* const cur_wpi = *wpi_list;292++count;293if (count == nth) return 1; // Found.294wpi_list = &cur_wpi->next_;295*location = wpi_list;296}297return 0; // Not found.298}299300//------------------------------------------------------------------------------301// MuxImage writer methods.302303WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) {304WebPMuxImage* new_wpi;305306while (*wpi_list != NULL) {307WebPMuxImage* const cur_wpi = *wpi_list;308if (cur_wpi->next_ == NULL) break;309wpi_list = &cur_wpi->next_;310}311312new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi));313if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR;314*new_wpi = *wpi;315new_wpi->next_ = NULL;316317if (*wpi_list != NULL) {318(*wpi_list)->next_ = new_wpi;319} else {320*wpi_list = new_wpi;321}322return WEBP_MUX_OK;323}324325//------------------------------------------------------------------------------326// MuxImage deletion methods.327328WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) {329// Delete the components of wpi. If wpi is NULL this is a noop.330WebPMuxImage* const next = MuxImageRelease(wpi);331WebPSafeFree(wpi);332return next;333}334335WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) {336assert(wpi_list);337if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) {338return WEBP_MUX_NOT_FOUND;339}340*wpi_list = MuxImageDelete(*wpi_list);341return WEBP_MUX_OK;342}343344//------------------------------------------------------------------------------345// MuxImage reader methods.346347WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,348WebPMuxImage** wpi) {349assert(wpi_list);350assert(wpi);351if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth,352(WebPMuxImage***)&wpi_list)) {353return WEBP_MUX_NOT_FOUND;354}355*wpi = (WebPMuxImage*)*wpi_list;356return WEBP_MUX_OK;357}358359//------------------------------------------------------------------------------360// MuxImage serialization methods.361362// Size of an image.363size_t MuxImageDiskSize(const WebPMuxImage* const wpi) {364size_t size = 0;365if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_);366if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_);367if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_);368if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_);369return size;370}371372// Special case as ANMF chunk encapsulates other image chunks.373static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,374size_t total_size, uint8_t* dst) {375const size_t header_size = header->data_.size;376const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;377assert(header->tag_ == kChunks[IDX_ANMF].tag);378PutLE32(dst + 0, header->tag_);379PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);380assert(header_size == (uint32_t)header_size);381memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);382if (header_size & 1) {383dst[CHUNK_HEADER_SIZE + header_size] = 0; // Add padding.384}385return dst + ChunkDiskSize(header);386}387388uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {389// Ordering of chunks to be emitted is strictly as follows:390// 1. ANMF chunk (if present).391// 2. ALPH chunk (if present).392// 3. VP8/VP8L chunk.393assert(wpi);394if (wpi->header_ != NULL) {395dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst);396}397if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);398if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);399if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst);400return dst;401}402403//------------------------------------------------------------------------------404// Helper methods for mux.405406int MuxHasAlpha(const WebPMuxImage* images) {407while (images != NULL) {408if (images->has_alpha_) return 1;409images = images->next_;410}411return 0;412}413414uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {415PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F'));416PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE);417assert(size == (uint32_t)size);418PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P'));419return data + RIFF_HEADER_SIZE;420}421422WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {423assert(mux != NULL);424switch (id) {425case WEBP_CHUNK_VP8X: return (WebPChunk**)&mux->vp8x_;426case WEBP_CHUNK_ICCP: return (WebPChunk**)&mux->iccp_;427case WEBP_CHUNK_ANIM: return (WebPChunk**)&mux->anim_;428case WEBP_CHUNK_EXIF: return (WebPChunk**)&mux->exif_;429case WEBP_CHUNK_XMP: return (WebPChunk**)&mux->xmp_;430default: return (WebPChunk**)&mux->unknown_;431}432}433434static int IsNotCompatible(int feature, int num_items) {435return (feature != 0) != (num_items > 0);436}437438#define NO_FLAG ((WebPFeatureFlags)0)439440// Test basic constraints:441// retrieval, maximum number of chunks by index (use -1 to skip)442// and feature incompatibility (use NO_FLAG to skip).443// On success returns WEBP_MUX_OK and stores the chunk count in *num.444static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx,445WebPFeatureFlags feature,446uint32_t vp8x_flags,447int max, int* num) {448const WebPMuxError err =449WebPMuxNumChunks(mux, kChunks[idx].id, num);450if (err != WEBP_MUX_OK) return err;451if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT;452if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) {453return WEBP_MUX_INVALID_ARGUMENT;454}455return WEBP_MUX_OK;456}457458WebPMuxError MuxValidate(const WebPMux* const mux) {459int num_iccp;460int num_exif;461int num_xmp;462int num_anim;463int num_frames;464int num_vp8x;465int num_images;466int num_alpha;467uint32_t flags;468WebPMuxError err;469470// Verify mux is not NULL.471if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;472473// Verify mux has at least one image.474if (mux->images_ == NULL) return WEBP_MUX_INVALID_ARGUMENT;475476err = WebPMuxGetFeatures(mux, &flags);477if (err != WEBP_MUX_OK) return err;478479// At most one color profile chunk.480err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp);481if (err != WEBP_MUX_OK) return err;482483// At most one EXIF metadata.484err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif);485if (err != WEBP_MUX_OK) return err;486487// At most one XMP metadata.488err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);489if (err != WEBP_MUX_OK) return err;490491// Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.492// At most one ANIM chunk.493err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);494if (err != WEBP_MUX_OK) return err;495err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames);496if (err != WEBP_MUX_OK) return err;497498{499const int has_animation = !!(flags & ANIMATION_FLAG);500if (has_animation && (num_anim == 0 || num_frames == 0)) {501return WEBP_MUX_INVALID_ARGUMENT;502}503if (!has_animation && (num_anim == 1 || num_frames > 0)) {504return WEBP_MUX_INVALID_ARGUMENT;505}506if (!has_animation) {507const WebPMuxImage* images = mux->images_;508// There can be only one image.509if (images == NULL || images->next_ != NULL) {510return WEBP_MUX_INVALID_ARGUMENT;511}512// Size must match.513if (mux->canvas_width_ > 0) {514if (images->width_ != mux->canvas_width_ ||515images->height_ != mux->canvas_height_) {516return WEBP_MUX_INVALID_ARGUMENT;517}518}519}520}521522// Verify either VP8X chunk is present OR there is only one elem in523// mux->images_.524err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x);525if (err != WEBP_MUX_OK) return err;526err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images);527if (err != WEBP_MUX_OK) return err;528if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT;529530// ALPHA_FLAG & alpha chunk(s) are consistent.531// Note: ALPHA_FLAG can be set when there is actually no Alpha data present.532if (MuxHasAlpha(mux->images_)) {533if (num_vp8x > 0) {534// VP8X chunk is present, so it should contain ALPHA_FLAG.535if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT;536} else {537// VP8X chunk is not present, so ALPH chunks should NOT be present either.538err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha);539if (err != WEBP_MUX_OK) return err;540if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT;541}542}543544return WEBP_MUX_OK;545}546547#undef NO_FLAG548549//------------------------------------------------------------------------------550551552553