Path: blob/master/thirdparty/libwebp/src/mux/muxedit.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// Set and delete 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// Life of a mux object.2021static void MuxInit(WebPMux* const mux) {22assert(mux != NULL);23memset(mux, 0, sizeof(*mux));24mux->canvas_width_ = 0; // just to be explicit25mux->canvas_height_ = 0;26}2728WebPMux* WebPNewInternal(int version) {29if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {30return NULL;31} else {32WebPMux* const mux = (WebPMux*)WebPSafeMalloc(1ULL, sizeof(WebPMux));33if (mux != NULL) MuxInit(mux);34return mux;35}36}3738// Delete all images in 'wpi_list'.39static void DeleteAllImages(WebPMuxImage** const wpi_list) {40while (*wpi_list != NULL) {41*wpi_list = MuxImageDelete(*wpi_list);42}43}4445static void MuxRelease(WebPMux* const mux) {46assert(mux != NULL);47DeleteAllImages(&mux->images_);48ChunkListDelete(&mux->vp8x_);49ChunkListDelete(&mux->iccp_);50ChunkListDelete(&mux->anim_);51ChunkListDelete(&mux->exif_);52ChunkListDelete(&mux->xmp_);53ChunkListDelete(&mux->unknown_);54}5556void WebPMuxDelete(WebPMux* mux) {57if (mux != NULL) {58MuxRelease(mux);59WebPSafeFree(mux);60}61}6263//------------------------------------------------------------------------------64// Helper method(s).6566// Handy MACRO, makes MuxSet() very symmetric to MuxGet().67#define SWITCH_ID_LIST(INDEX, LIST) \68do { \69if (idx == (INDEX)) { \70err = ChunkAssignData(&chunk, data, copy_data, tag); \71if (err == WEBP_MUX_OK) { \72err = ChunkSetHead(&chunk, (LIST)); \73if (err != WEBP_MUX_OK) ChunkRelease(&chunk); \74} \75return err; \76} \77} while (0)7879static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag,80const WebPData* const data, int copy_data) {81WebPChunk chunk;82WebPMuxError err = WEBP_MUX_NOT_FOUND;83const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag);84assert(mux != NULL);85assert(!IsWPI(kChunks[idx].id));8687ChunkInit(&chunk);88SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_);89SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_);90SWITCH_ID_LIST(IDX_ANIM, &mux->anim_);91SWITCH_ID_LIST(IDX_EXIF, &mux->exif_);92SWITCH_ID_LIST(IDX_XMP, &mux->xmp_);93SWITCH_ID_LIST(IDX_UNKNOWN, &mux->unknown_);94return err;95}96#undef SWITCH_ID_LIST9798// Create data for frame given image data, offsets and duration.99static WebPMuxError CreateFrameData(100int width, int height, const WebPMuxFrameInfo* const info,101WebPData* const frame) {102uint8_t* frame_bytes;103const size_t frame_size = kChunks[IDX_ANMF].size;104105assert(width > 0 && height > 0 && info->duration >= 0);106assert(info->dispose_method == (info->dispose_method & 1));107// Note: assertion on upper bounds is done in PutLE24().108109frame_bytes = (uint8_t*)WebPSafeMalloc(1ULL, frame_size);110if (frame_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;111112PutLE24(frame_bytes + 0, info->x_offset / 2);113PutLE24(frame_bytes + 3, info->y_offset / 2);114115PutLE24(frame_bytes + 6, width - 1);116PutLE24(frame_bytes + 9, height - 1);117PutLE24(frame_bytes + 12, info->duration);118frame_bytes[15] =119(info->blend_method == WEBP_MUX_NO_BLEND ? 2 : 0) |120(info->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? 1 : 0);121122frame->bytes = frame_bytes;123frame->size = frame_size;124return WEBP_MUX_OK;125}126127// Outputs image data given a bitstream. The bitstream can either be a128// single-image WebP file or raw VP8/VP8L data.129// Also outputs 'is_lossless' to be true if the given bitstream is lossless.130static WebPMuxError GetImageData(const WebPData* const bitstream,131WebPData* const image, WebPData* const alpha,132int* const is_lossless) {133WebPDataInit(alpha); // Default: no alpha.134if (bitstream->size < TAG_SIZE ||135memcmp(bitstream->bytes, "RIFF", TAG_SIZE)) {136// It is NOT webp file data. Return input data as is.137*image = *bitstream;138} else {139// It is webp file data. Extract image data from it.140const WebPMuxImage* wpi;141WebPMux* const mux = WebPMuxCreate(bitstream, 0);142if (mux == NULL) return WEBP_MUX_BAD_DATA;143wpi = mux->images_;144assert(wpi != NULL && wpi->img_ != NULL);145*image = wpi->img_->data_;146if (wpi->alpha_ != NULL) {147*alpha = wpi->alpha_->data_;148}149WebPMuxDelete(mux);150}151*is_lossless = VP8LCheckSignature(image->bytes, image->size);152return WEBP_MUX_OK;153}154155static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) {156WebPMuxError err = WEBP_MUX_NOT_FOUND;157assert(chunk_list);158while (*chunk_list) {159WebPChunk* const chunk = *chunk_list;160if (chunk->tag_ == tag) {161*chunk_list = ChunkDelete(chunk);162err = WEBP_MUX_OK;163} else {164chunk_list = &chunk->next_;165}166}167return err;168}169170static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) {171const WebPChunkId id = ChunkGetIdFromTag(tag);172assert(mux != NULL);173if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT;174return DeleteChunks(MuxGetChunkListFromId(mux, id), tag);175}176177//------------------------------------------------------------------------------178// Set API(s).179180WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4],181const WebPData* chunk_data, int copy_data) {182uint32_t tag;183WebPMuxError err;184if (mux == NULL || fourcc == NULL || chunk_data == NULL ||185chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) {186return WEBP_MUX_INVALID_ARGUMENT;187}188tag = ChunkGetTagFromFourCC(fourcc);189190// Delete existing chunk(s) with the same 'fourcc'.191err = MuxDeleteAllNamedData(mux, tag);192if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;193194// Add the given chunk.195return MuxSet(mux, tag, chunk_data, copy_data);196}197198// Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'.199static WebPMuxError AddDataToChunkList(200const WebPData* const data, int copy_data, uint32_t tag,201WebPChunk** chunk_list) {202WebPChunk chunk;203WebPMuxError err;204ChunkInit(&chunk);205err = ChunkAssignData(&chunk, data, copy_data, tag);206if (err != WEBP_MUX_OK) goto Err;207err = ChunkSetHead(&chunk, chunk_list);208if (err != WEBP_MUX_OK) goto Err;209return WEBP_MUX_OK;210Err:211ChunkRelease(&chunk);212return err;213}214215// Extracts image & alpha data from the given bitstream and then sets wpi.alpha_216// and wpi.img_ appropriately.217static WebPMuxError SetAlphaAndImageChunks(218const WebPData* const bitstream, int copy_data, WebPMuxImage* const wpi) {219int is_lossless = 0;220WebPData image, alpha;221WebPMuxError err = GetImageData(bitstream, &image, &alpha, &is_lossless);222const int image_tag =223is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag;224if (err != WEBP_MUX_OK) return err;225if (alpha.bytes != NULL) {226err = AddDataToChunkList(&alpha, copy_data, kChunks[IDX_ALPHA].tag,227&wpi->alpha_);228if (err != WEBP_MUX_OK) return err;229}230err = AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_);231if (err != WEBP_MUX_OK) return err;232return MuxImageFinalize(wpi) ? WEBP_MUX_OK : WEBP_MUX_INVALID_ARGUMENT;233}234235WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream,236int copy_data) {237WebPMuxImage wpi;238WebPMuxError err;239240if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL ||241bitstream->size > MAX_CHUNK_PAYLOAD) {242return WEBP_MUX_INVALID_ARGUMENT;243}244245if (mux->images_ != NULL) {246// Only one 'simple image' can be added in mux. So, remove present images.247DeleteAllImages(&mux->images_);248}249250MuxImageInit(&wpi);251err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);252if (err != WEBP_MUX_OK) goto Err;253254// Add this WebPMuxImage to mux.255err = MuxImagePush(&wpi, &mux->images_);256if (err != WEBP_MUX_OK) goto Err;257258// All is well.259return WEBP_MUX_OK;260261Err: // Something bad happened.262MuxImageRelease(&wpi);263return err;264}265266WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* info,267int copy_data) {268WebPMuxImage wpi;269WebPMuxError err;270271if (mux == NULL || info == NULL) return WEBP_MUX_INVALID_ARGUMENT;272273if (info->id != WEBP_CHUNK_ANMF) return WEBP_MUX_INVALID_ARGUMENT;274275if (info->bitstream.bytes == NULL ||276info->bitstream.size > MAX_CHUNK_PAYLOAD) {277return WEBP_MUX_INVALID_ARGUMENT;278}279280if (mux->images_ != NULL) {281const WebPMuxImage* const image = mux->images_;282const uint32_t image_id = (image->header_ != NULL) ?283ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE;284if (image_id != info->id) {285return WEBP_MUX_INVALID_ARGUMENT; // Conflicting frame types.286}287}288289MuxImageInit(&wpi);290err = SetAlphaAndImageChunks(&info->bitstream, copy_data, &wpi);291if (err != WEBP_MUX_OK) goto Err;292assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful.293294{295WebPData frame;296const uint32_t tag = kChunks[IDX_ANMF].tag;297WebPMuxFrameInfo tmp = *info;298tmp.x_offset &= ~1; // Snap offsets to even.299tmp.y_offset &= ~1;300if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET ||301tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET ||302(tmp.duration < 0 || tmp.duration >= MAX_DURATION) ||303tmp.dispose_method != (tmp.dispose_method & 1)) {304err = WEBP_MUX_INVALID_ARGUMENT;305goto Err;306}307err = CreateFrameData(wpi.width_, wpi.height_, &tmp, &frame);308if (err != WEBP_MUX_OK) goto Err;309// Add frame chunk (with copy_data = 1).310err = AddDataToChunkList(&frame, 1, tag, &wpi.header_);311WebPDataClear(&frame); // frame owned by wpi.header_ now.312if (err != WEBP_MUX_OK) goto Err;313}314315// Add this WebPMuxImage to mux.316err = MuxImagePush(&wpi, &mux->images_);317if (err != WEBP_MUX_OK) goto Err;318319// All is well.320return WEBP_MUX_OK;321322Err: // Something bad happened.323MuxImageRelease(&wpi);324return err;325}326327WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux,328const WebPMuxAnimParams* params) {329WebPMuxError err;330uint8_t data[ANIM_CHUNK_SIZE];331const WebPData anim = { data, ANIM_CHUNK_SIZE };332333if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;334if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) {335return WEBP_MUX_INVALID_ARGUMENT;336}337338// Delete any existing ANIM chunk(s).339err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);340if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;341342// Set the animation parameters.343PutLE32(data, params->bgcolor);344PutLE16(data + 4, params->loop_count);345return MuxSet(mux, kChunks[IDX_ANIM].tag, &anim, 1);346}347348WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux,349int width, int height) {350WebPMuxError err;351if (mux == NULL) {352return WEBP_MUX_INVALID_ARGUMENT;353}354if (width < 0 || height < 0 ||355width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {356return WEBP_MUX_INVALID_ARGUMENT;357}358if (width * (uint64_t)height >= MAX_IMAGE_AREA) {359return WEBP_MUX_INVALID_ARGUMENT;360}361if ((width * height) == 0 && (width | height) != 0) {362// one of width / height is zero, but not both -> invalid!363return WEBP_MUX_INVALID_ARGUMENT;364}365// If we already assembled a VP8X chunk, invalidate it.366err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);367if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;368369mux->canvas_width_ = width;370mux->canvas_height_ = height;371return WEBP_MUX_OK;372}373374//------------------------------------------------------------------------------375// Delete API(s).376377WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]) {378if (mux == NULL || fourcc == NULL) return WEBP_MUX_INVALID_ARGUMENT;379return MuxDeleteAllNamedData(mux, ChunkGetTagFromFourCC(fourcc));380}381382WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) {383if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;384return MuxImageDeleteNth(&mux->images_, nth);385}386387//------------------------------------------------------------------------------388// Assembly of the WebP RIFF file.389390static WebPMuxError GetFrameInfo(391const WebPChunk* const frame_chunk,392int* const x_offset, int* const y_offset, int* const duration) {393const WebPData* const data = &frame_chunk->data_;394const size_t expected_data_size = ANMF_CHUNK_SIZE;395assert(frame_chunk->tag_ == kChunks[IDX_ANMF].tag);396assert(frame_chunk != NULL);397if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT;398399*x_offset = 2 * GetLE24(data->bytes + 0);400*y_offset = 2 * GetLE24(data->bytes + 3);401*duration = GetLE24(data->bytes + 12);402return WEBP_MUX_OK;403}404405static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,406int* const x_offset, int* const y_offset,407int* const duration,408int* const width, int* const height) {409const WebPChunk* const frame_chunk = wpi->header_;410WebPMuxError err;411assert(wpi != NULL);412assert(frame_chunk != NULL);413414// Get offsets and duration from ANMF chunk.415err = GetFrameInfo(frame_chunk, x_offset, y_offset, duration);416if (err != WEBP_MUX_OK) return err;417418// Get width and height from VP8/VP8L chunk.419if (width != NULL) *width = wpi->width_;420if (height != NULL) *height = wpi->height_;421return WEBP_MUX_OK;422}423424// Returns the tightest dimension for the canvas considering the image list.425static WebPMuxError GetAdjustedCanvasSize(const WebPMux* const mux,426int* const width, int* const height) {427WebPMuxImage* wpi = NULL;428assert(mux != NULL);429assert(width != NULL && height != NULL);430431wpi = mux->images_;432assert(wpi != NULL);433assert(wpi->img_ != NULL);434435if (wpi->next_ != NULL) {436int max_x = 0, max_y = 0;437// if we have a chain of wpi's, header_ is necessarily set438assert(wpi->header_ != NULL);439// Aggregate the bounding box for animation frames.440for (; wpi != NULL; wpi = wpi->next_) {441int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0;442const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset,443&duration, &w, &h);444const int max_x_pos = x_offset + w;445const int max_y_pos = y_offset + h;446if (err != WEBP_MUX_OK) return err;447assert(x_offset < MAX_POSITION_OFFSET);448assert(y_offset < MAX_POSITION_OFFSET);449450if (max_x_pos > max_x) max_x = max_x_pos;451if (max_y_pos > max_y) max_y = max_y_pos;452}453*width = max_x;454*height = max_y;455} else {456// For a single image, canvas dimensions are same as image dimensions.457*width = wpi->width_;458*height = wpi->height_;459}460return WEBP_MUX_OK;461}462463// VP8X format:464// Total Size : 10,465// Flags : 4 bytes,466// Width : 3 bytes,467// Height : 3 bytes.468static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {469WebPMuxError err = WEBP_MUX_OK;470uint32_t flags = 0;471int width = 0;472int height = 0;473uint8_t data[VP8X_CHUNK_SIZE];474const WebPData vp8x = { data, VP8X_CHUNK_SIZE };475const WebPMuxImage* images = NULL;476477assert(mux != NULL);478images = mux->images_; // First image.479if (images == NULL || images->img_ == NULL ||480images->img_->data_.bytes == NULL) {481return WEBP_MUX_INVALID_ARGUMENT;482}483484// If VP8X chunk(s) is(are) already present, remove them (and later add new485// VP8X chunk with updated flags).486err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);487if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;488489// Set flags.490if (mux->iccp_ != NULL && mux->iccp_->data_.bytes != NULL) {491flags |= ICCP_FLAG;492}493if (mux->exif_ != NULL && mux->exif_->data_.bytes != NULL) {494flags |= EXIF_FLAG;495}496if (mux->xmp_ != NULL && mux->xmp_->data_.bytes != NULL) {497flags |= XMP_FLAG;498}499if (images->header_ != NULL) {500if (images->header_->tag_ == kChunks[IDX_ANMF].tag) {501// This is an image with animation.502flags |= ANIMATION_FLAG;503}504}505if (MuxImageCount(images, WEBP_CHUNK_ALPHA) > 0) {506flags |= ALPHA_FLAG; // Some images have an alpha channel.507}508509err = GetAdjustedCanvasSize(mux, &width, &height);510if (err != WEBP_MUX_OK) return err;511512if (width <= 0 || height <= 0) {513return WEBP_MUX_INVALID_ARGUMENT;514}515if (width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {516return WEBP_MUX_INVALID_ARGUMENT;517}518519if (mux->canvas_width_ != 0 || mux->canvas_height_ != 0) {520if (width > mux->canvas_width_ || height > mux->canvas_height_) {521return WEBP_MUX_INVALID_ARGUMENT;522}523width = mux->canvas_width_;524height = mux->canvas_height_;525}526527if (flags == 0 && mux->unknown_ == NULL) {528// For simple file format, VP8X chunk should not be added.529return WEBP_MUX_OK;530}531532if (MuxHasAlpha(images)) {533// This means some frames explicitly/implicitly contain alpha.534// Note: This 'flags' update must NOT be done for a lossless image535// without a VP8X chunk!536flags |= ALPHA_FLAG;537}538539PutLE32(data + 0, flags); // VP8X chunk flags.540PutLE24(data + 4, width - 1); // canvas width.541PutLE24(data + 7, height - 1); // canvas height.542543return MuxSet(mux, kChunks[IDX_VP8X].tag, &vp8x, 1);544}545546// Cleans up 'mux' by removing any unnecessary chunks.547static WebPMuxError MuxCleanup(WebPMux* const mux) {548int num_frames;549int num_anim_chunks;550551// If we have an image with a single frame, and its rectangle552// covers the whole canvas, convert it to a non-animated image553// (to avoid writing ANMF chunk unnecessarily).554WebPMuxError err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames);555if (err != WEBP_MUX_OK) return err;556if (num_frames == 1) {557WebPMuxImage* frame = NULL;558err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame);559if (err != WEBP_MUX_OK) return err;560// We know that one frame does exist.561assert(frame != NULL);562if (frame->header_ != NULL &&563((mux->canvas_width_ == 0 && mux->canvas_height_ == 0) ||564(frame->width_ == mux->canvas_width_ &&565frame->height_ == mux->canvas_height_))) {566assert(frame->header_->tag_ == kChunks[IDX_ANMF].tag);567ChunkDelete(frame->header_); // Removes ANMF chunk.568frame->header_ = NULL;569num_frames = 0;570}571}572// Remove ANIM chunk if this is a non-animated image.573err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks);574if (err != WEBP_MUX_OK) return err;575if (num_anim_chunks >= 1 && num_frames == 0) {576err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);577if (err != WEBP_MUX_OK) return err;578}579return WEBP_MUX_OK;580}581582// Total size of a list of images.583static size_t ImageListDiskSize(const WebPMuxImage* wpi_list) {584size_t size = 0;585while (wpi_list != NULL) {586size += MuxImageDiskSize(wpi_list);587wpi_list = wpi_list->next_;588}589return size;590}591592// Write out the given list of images into 'dst'.593static uint8_t* ImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) {594while (wpi_list != NULL) {595dst = MuxImageEmit(wpi_list, dst);596wpi_list = wpi_list->next_;597}598return dst;599}600601WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {602size_t size = 0;603uint8_t* data = NULL;604uint8_t* dst = NULL;605WebPMuxError err;606607if (assembled_data == NULL) {608return WEBP_MUX_INVALID_ARGUMENT;609}610// Clean up returned data, in case something goes wrong.611memset(assembled_data, 0, sizeof(*assembled_data));612613if (mux == NULL) {614return WEBP_MUX_INVALID_ARGUMENT;615}616617// Finalize mux.618err = MuxCleanup(mux);619if (err != WEBP_MUX_OK) return err;620err = CreateVP8XChunk(mux);621if (err != WEBP_MUX_OK) return err;622623// Allocate data.624size = ChunkListDiskSize(mux->vp8x_) + ChunkListDiskSize(mux->iccp_)625+ ChunkListDiskSize(mux->anim_) + ImageListDiskSize(mux->images_)626+ ChunkListDiskSize(mux->exif_) + ChunkListDiskSize(mux->xmp_)627+ ChunkListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE;628629data = (uint8_t*)WebPSafeMalloc(1ULL, size);630if (data == NULL) return WEBP_MUX_MEMORY_ERROR;631632// Emit header & chunks.633dst = MuxEmitRiffHeader(data, size);634dst = ChunkListEmit(mux->vp8x_, dst);635dst = ChunkListEmit(mux->iccp_, dst);636dst = ChunkListEmit(mux->anim_, dst);637dst = ImageListEmit(mux->images_, dst);638dst = ChunkListEmit(mux->exif_, dst);639dst = ChunkListEmit(mux->xmp_, dst);640dst = ChunkListEmit(mux->unknown_, dst);641assert(dst == data + size);642643// Validate mux.644err = MuxValidate(mux);645if (err != WEBP_MUX_OK) {646WebPSafeFree(data);647data = NULL;648size = 0;649}650651// Finalize data.652assembled_data->bytes = data;653assembled_data->size = size;654655return err;656}657658//------------------------------------------------------------------------------659660661