Path: blob/master/thirdparty/libwebp/src/mux/muxinternal.c
21518 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 <stddef.h>16#include <string.h>1718#include "src/mux/muxi.h"19#include "src/webp/types.h"20#include "src/utils/utils.h"21#include "src/webp/format_constants.h"22#include "src/webp/mux.h"23#include "src/webp/mux_types.h"2425#define UNDEFINED_CHUNK_SIZE ((uint32_t)(-1))2627const ChunkInfo kChunks[] = {28{ MKFOURCC('V', 'P', '8', 'X'), WEBP_CHUNK_VP8X, VP8X_CHUNK_SIZE },29{ MKFOURCC('I', 'C', 'C', 'P'), WEBP_CHUNK_ICCP, UNDEFINED_CHUNK_SIZE },30{ MKFOURCC('A', 'N', 'I', 'M'), WEBP_CHUNK_ANIM, ANIM_CHUNK_SIZE },31{ MKFOURCC('A', 'N', 'M', 'F'), WEBP_CHUNK_ANMF, ANMF_CHUNK_SIZE },32{ MKFOURCC('A', 'L', 'P', 'H'), WEBP_CHUNK_ALPHA, UNDEFINED_CHUNK_SIZE },33{ MKFOURCC('V', 'P', '8', ' '), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE },34{ MKFOURCC('V', 'P', '8', 'L'), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE },35{ MKFOURCC('E', 'X', 'I', 'F'), WEBP_CHUNK_EXIF, UNDEFINED_CHUNK_SIZE },36{ MKFOURCC('X', 'M', 'P', ' '), WEBP_CHUNK_XMP, UNDEFINED_CHUNK_SIZE },37{ NIL_TAG, WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE },3839{ NIL_TAG, WEBP_CHUNK_NIL, UNDEFINED_CHUNK_SIZE }40};4142//------------------------------------------------------------------------------4344int WebPGetMuxVersion(void) {45return (MUX_MAJ_VERSION << 16) | (MUX_MIN_VERSION << 8) | MUX_REV_VERSION;46}4748//------------------------------------------------------------------------------49// Life of a chunk object.5051void ChunkInit(WebPChunk* const chunk) {52assert(chunk);53memset(chunk, 0, sizeof(*chunk));54chunk->tag = NIL_TAG;55}5657WebPChunk* ChunkRelease(WebPChunk* const chunk) {58WebPChunk* next;59if (chunk == NULL) return NULL;60if (chunk->owner) {61WebPDataClear(&chunk->data);62}63next = chunk->next;64ChunkInit(chunk);65return next;66}6768//------------------------------------------------------------------------------69// Chunk misc methods.7071CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) {72int i;73for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {74if (tag == kChunks[i].tag) return (CHUNK_INDEX)i;75}76return IDX_UNKNOWN;77}7879WebPChunkId ChunkGetIdFromTag(uint32_t tag) {80int i;81for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {82if (tag == kChunks[i].tag) return kChunks[i].id;83}84return WEBP_CHUNK_UNKNOWN;85}8687uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) {88return MKFOURCC(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);89}9091CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) {92const uint32_t tag = ChunkGetTagFromFourCC(fourcc);93return ChunkGetIndexFromTag(tag);94}9596//------------------------------------------------------------------------------97// Chunk search methods.9899// Returns next chunk in the chunk list with the given tag.100static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) {101while (chunk != NULL && chunk->tag != tag) {102chunk = chunk->next;103}104return chunk;105}106107WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) {108uint32_t iter = nth;109first = ChunkSearchNextInList(first, tag);110if (first == NULL) return NULL;111112while (--iter != 0) {113WebPChunk* next_chunk = ChunkSearchNextInList(first->next, tag);114if (next_chunk == NULL) break;115first = next_chunk;116}117return ((nth > 0) && (iter > 0)) ? NULL : first;118}119120//------------------------------------------------------------------------------121// Chunk writer methods.122123WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,124int copy_data, uint32_t tag) {125// For internally allocated chunks, always copy data & make it owner of data.126if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) {127copy_data = 1;128}129130ChunkRelease(chunk);131132if (data != NULL) {133if (copy_data) { // Copy data.134if (!WebPDataCopy(data, &chunk->data)) return WEBP_MUX_MEMORY_ERROR;135chunk->owner = 1; // Chunk is owner of data.136} else { // Don't copy data.137chunk->data = *data;138}139}140chunk->tag = tag;141return WEBP_MUX_OK;142}143144WebPMuxError ChunkSetHead(WebPChunk* const chunk,145WebPChunk** const chunk_list) {146WebPChunk* new_chunk;147148assert(chunk_list != NULL);149if (*chunk_list != NULL) {150return WEBP_MUX_NOT_FOUND;151}152153new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, sizeof(*new_chunk));154if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR;155*new_chunk = *chunk;156chunk->owner = 0;157new_chunk->next = NULL;158*chunk_list = new_chunk;159return WEBP_MUX_OK;160}161162WebPMuxError ChunkAppend(WebPChunk* const chunk,163WebPChunk*** const chunk_list) {164WebPMuxError err;165assert(chunk_list != NULL && *chunk_list != NULL);166167if (**chunk_list == NULL) {168err = ChunkSetHead(chunk, *chunk_list);169} else {170WebPChunk* last_chunk = **chunk_list;171while (last_chunk->next != NULL) last_chunk = last_chunk->next;172err = ChunkSetHead(chunk, &last_chunk->next);173if (err == WEBP_MUX_OK) *chunk_list = &last_chunk->next;174}175return err;176}177178//------------------------------------------------------------------------------179// Chunk deletion method(s).180181WebPChunk* ChunkDelete(WebPChunk* const chunk) {182WebPChunk* const next = ChunkRelease(chunk);183WebPSafeFree(chunk);184return next;185}186187void ChunkListDelete(WebPChunk** const chunk_list) {188while (*chunk_list != NULL) {189*chunk_list = ChunkDelete(*chunk_list);190}191}192193//------------------------------------------------------------------------------194// Chunk serialization methods.195196static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) {197const size_t chunk_size = chunk->data.size;198assert(chunk);199assert(chunk->tag != NIL_TAG);200PutLE32(dst + 0, chunk->tag);201PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size);202assert(chunk_size == (uint32_t)chunk_size);203memcpy(dst + CHUNK_HEADER_SIZE, chunk->data.bytes, chunk_size);204if (chunk_size & 1)205dst[CHUNK_HEADER_SIZE + chunk_size] = 0; // Add padding.206return dst + ChunkDiskSize(chunk);207}208209uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) {210while (chunk_list != NULL) {211dst = ChunkEmit(chunk_list, dst);212chunk_list = chunk_list->next;213}214return dst;215}216217size_t ChunkListDiskSize(const WebPChunk* chunk_list) {218size_t size = 0;219while (chunk_list != NULL) {220size += ChunkDiskSize(chunk_list);221chunk_list = chunk_list->next;222}223return size;224}225226//------------------------------------------------------------------------------227// Life of a MuxImage object.228229void MuxImageInit(WebPMuxImage* const wpi) {230assert(wpi);231memset(wpi, 0, sizeof(*wpi));232}233234WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) {235WebPMuxImage* next;236if (wpi == NULL) return NULL;237// There should be at most one chunk of 'header', 'alpha', 'img' but we call238// ChunkListDelete to be safe239ChunkListDelete(&wpi->header);240ChunkListDelete(&wpi->alpha);241ChunkListDelete(&wpi->img);242ChunkListDelete(&wpi->unknown);243244next = wpi->next;245MuxImageInit(wpi);246return next;247}248249//------------------------------------------------------------------------------250// MuxImage search methods.251252// Get a reference to appropriate chunk list within an image given chunk tag.253static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi,254WebPChunkId id) {255assert(wpi != NULL);256switch (id) {257case WEBP_CHUNK_ANMF: return (WebPChunk**)&wpi->header;258case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha;259case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img;260default: return NULL;261}262}263264int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) {265int count = 0;266const WebPMuxImage* current;267for (current = wpi_list; current != NULL; current = current->next) {268if (id == WEBP_CHUNK_NIL) {269++count; // Special case: count all images.270} else {271const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id);272if (wpi_chunk != NULL) {273const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag);274if (wpi_chunk_id == id) ++count; // Count images with a matching 'id'.275}276}277}278return count;279}280281// Outputs a pointer to 'prev_wpi->next',282// where 'prev_wpi' is the pointer to the image at position (nth - 1).283// Returns true if nth image was found.284static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth,285WebPMuxImage*** const location) {286uint32_t count = 0;287assert(wpi_list);288*location = wpi_list;289290if (nth == 0) {291nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL);292if (nth == 0) return 0; // Not found.293}294295while (*wpi_list != NULL) {296WebPMuxImage* const cur_wpi = *wpi_list;297++count;298if (count == nth) return 1; // Found.299wpi_list = &cur_wpi->next;300*location = wpi_list;301}302return 0; // Not found.303}304305//------------------------------------------------------------------------------306// MuxImage writer methods.307308WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) {309WebPMuxImage* new_wpi;310311while (*wpi_list != NULL) {312WebPMuxImage* const cur_wpi = *wpi_list;313if (cur_wpi->next == NULL) break;314wpi_list = &cur_wpi->next;315}316317new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi));318if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR;319*new_wpi = *wpi;320new_wpi->next = NULL;321322if (*wpi_list != NULL) {323(*wpi_list)->next = new_wpi;324} else {325*wpi_list = new_wpi;326}327return WEBP_MUX_OK;328}329330//------------------------------------------------------------------------------331// MuxImage deletion methods.332333WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) {334// Delete the components of wpi. If wpi is NULL this is a noop.335WebPMuxImage* const next = MuxImageRelease(wpi);336WebPSafeFree(wpi);337return next;338}339340WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) {341assert(wpi_list);342if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) {343return WEBP_MUX_NOT_FOUND;344}345*wpi_list = MuxImageDelete(*wpi_list);346return WEBP_MUX_OK;347}348349//------------------------------------------------------------------------------350// MuxImage reader methods.351352WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,353WebPMuxImage** wpi) {354assert(wpi_list);355assert(wpi);356if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth,357(WebPMuxImage***)&wpi_list)) {358return WEBP_MUX_NOT_FOUND;359}360*wpi = (WebPMuxImage*)*wpi_list;361return WEBP_MUX_OK;362}363364//------------------------------------------------------------------------------365// MuxImage serialization methods.366367// Size of an image.368size_t MuxImageDiskSize(const WebPMuxImage* const wpi) {369size_t size = 0;370if (wpi->header != NULL) size += ChunkDiskSize(wpi->header);371if (wpi->alpha != NULL) size += ChunkDiskSize(wpi->alpha);372if (wpi->img != NULL) size += ChunkDiskSize(wpi->img);373if (wpi->unknown != NULL) size += ChunkListDiskSize(wpi->unknown);374return size;375}376377// Special case as ANMF chunk encapsulates other image chunks.378static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,379size_t total_size, uint8_t* dst) {380const size_t header_size = header->data.size;381const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;382assert(header->tag == kChunks[IDX_ANMF].tag);383PutLE32(dst + 0, header->tag);384PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);385assert(header_size == (uint32_t)header_size);386memcpy(dst + CHUNK_HEADER_SIZE, header->data.bytes, header_size);387if (header_size & 1) {388dst[CHUNK_HEADER_SIZE + header_size] = 0; // Add padding.389}390return dst + ChunkDiskSize(header);391}392393uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {394// Ordering of chunks to be emitted is strictly as follows:395// 1. ANMF chunk (if present).396// 2. ALPH chunk (if present).397// 3. VP8/VP8L chunk.398assert(wpi);399if (wpi->header != NULL) {400dst = ChunkEmitSpecial(wpi->header, MuxImageDiskSize(wpi), dst);401}402if (wpi->alpha != NULL) dst = ChunkEmit(wpi->alpha, dst);403if (wpi->img != NULL) dst = ChunkEmit(wpi->img, dst);404if (wpi->unknown != NULL) dst = ChunkListEmit(wpi->unknown, dst);405return dst;406}407408//------------------------------------------------------------------------------409// Helper methods for mux.410411int MuxHasAlpha(const WebPMuxImage* images) {412while (images != NULL) {413if (images->has_alpha) return 1;414images = images->next;415}416return 0;417}418419uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {420PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F'));421PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE);422assert(size == (uint32_t)size);423PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P'));424return data + RIFF_HEADER_SIZE;425}426427WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {428assert(mux != NULL);429switch (id) {430case WEBP_CHUNK_VP8X: return (WebPChunk**)&mux->vp8x;431case WEBP_CHUNK_ICCP: return (WebPChunk**)&mux->iccp;432case WEBP_CHUNK_ANIM: return (WebPChunk**)&mux->anim;433case WEBP_CHUNK_EXIF: return (WebPChunk**)&mux->exif;434case WEBP_CHUNK_XMP: return (WebPChunk**)&mux->xmp;435default: return (WebPChunk**)&mux->unknown;436}437}438439static int IsNotCompatible(int feature, int num_items) {440return (feature != 0) != (num_items > 0);441}442443#define NO_FLAG ((WebPFeatureFlags)0)444445// Test basic constraints:446// retrieval, maximum number of chunks by index (use -1 to skip)447// and feature incompatibility (use NO_FLAG to skip).448// On success returns WEBP_MUX_OK and stores the chunk count in *num.449static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx,450WebPFeatureFlags feature,451uint32_t vp8x_flags,452int max, int* num) {453const WebPMuxError err =454WebPMuxNumChunks(mux, kChunks[idx].id, num);455if (err != WEBP_MUX_OK) return err;456if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT;457if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) {458return WEBP_MUX_INVALID_ARGUMENT;459}460return WEBP_MUX_OK;461}462463WebPMuxError MuxValidate(const WebPMux* const mux) {464int num_iccp;465int num_exif;466int num_xmp;467int num_anim;468int num_frames;469int num_vp8x;470int num_images;471int num_alpha;472uint32_t flags;473WebPMuxError err;474475// Verify mux is not NULL.476if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;477478// Verify mux has at least one image.479if (mux->images == NULL) return WEBP_MUX_INVALID_ARGUMENT;480481err = WebPMuxGetFeatures(mux, &flags);482if (err != WEBP_MUX_OK) return err;483484// At most one color profile chunk.485err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp);486if (err != WEBP_MUX_OK) return err;487488// At most one EXIF metadata.489err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif);490if (err != WEBP_MUX_OK) return err;491492// At most one XMP metadata.493err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);494if (err != WEBP_MUX_OK) return err;495496// Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.497// At most one ANIM chunk.498err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);499if (err != WEBP_MUX_OK) return err;500err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames);501if (err != WEBP_MUX_OK) return err;502503{504const int has_animation = !!(flags & ANIMATION_FLAG);505if (has_animation && (num_anim == 0 || num_frames == 0)) {506return WEBP_MUX_INVALID_ARGUMENT;507}508if (!has_animation && (num_anim == 1 || num_frames > 0)) {509return WEBP_MUX_INVALID_ARGUMENT;510}511if (!has_animation) {512const WebPMuxImage* images = mux->images;513// There can be only one image.514if (images == NULL || images->next != NULL) {515return WEBP_MUX_INVALID_ARGUMENT;516}517// Size must match.518if (mux->canvas_width > 0) {519if (images->width != mux->canvas_width ||520images->height != mux->canvas_height) {521return WEBP_MUX_INVALID_ARGUMENT;522}523}524}525}526527// Verify either VP8X chunk is present OR there is only one elem in528// mux->images.529err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x);530if (err != WEBP_MUX_OK) return err;531err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images);532if (err != WEBP_MUX_OK) return err;533if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT;534535// ALPHA_FLAG & alpha chunk(s) are consistent.536// Note: ALPHA_FLAG can be set when there is actually no Alpha data present.537if (MuxHasAlpha(mux->images)) {538if (num_vp8x > 0) {539// VP8X chunk is present, so it should contain ALPHA_FLAG.540if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT;541} else {542// VP8X chunk is not present, so ALPH chunks should NOT be present either.543err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha);544if (err != WEBP_MUX_OK) return err;545if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT;546}547}548549return WEBP_MUX_OK;550}551552#undef NO_FLAG553554//------------------------------------------------------------------------------555556557