Path: blob/master/3rdparty/libwebp/src/mux/muxedit.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// 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) \68if (idx == (INDEX)) { \69err = ChunkAssignData(&chunk, data, copy_data, tag); \70if (err == WEBP_MUX_OK) { \71err = ChunkSetNth(&chunk, (LIST), nth); \72} \73return err; \74}7576static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, uint32_t nth,77const WebPData* const data, int copy_data) {78WebPChunk chunk;79WebPMuxError err = WEBP_MUX_NOT_FOUND;80const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag);81assert(mux != NULL);82assert(!IsWPI(kChunks[idx].id));8384ChunkInit(&chunk);85SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_);86SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_);87SWITCH_ID_LIST(IDX_ANIM, &mux->anim_);88SWITCH_ID_LIST(IDX_EXIF, &mux->exif_);89SWITCH_ID_LIST(IDX_XMP, &mux->xmp_);90SWITCH_ID_LIST(IDX_UNKNOWN, &mux->unknown_);91return err;92}93#undef SWITCH_ID_LIST9495// Create data for frame given image data, offsets and duration.96static WebPMuxError CreateFrameData(97int width, int height, const WebPMuxFrameInfo* const info,98WebPData* const frame) {99uint8_t* frame_bytes;100const size_t frame_size = kChunks[IDX_ANMF].size;101102assert(width > 0 && height > 0 && info->duration >= 0);103assert(info->dispose_method == (info->dispose_method & 1));104// Note: assertion on upper bounds is done in PutLE24().105106frame_bytes = (uint8_t*)WebPSafeMalloc(1ULL, frame_size);107if (frame_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;108109PutLE24(frame_bytes + 0, info->x_offset / 2);110PutLE24(frame_bytes + 3, info->y_offset / 2);111112PutLE24(frame_bytes + 6, width - 1);113PutLE24(frame_bytes + 9, height - 1);114PutLE24(frame_bytes + 12, info->duration);115frame_bytes[15] =116(info->blend_method == WEBP_MUX_NO_BLEND ? 2 : 0) |117(info->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? 1 : 0);118119frame->bytes = frame_bytes;120frame->size = frame_size;121return WEBP_MUX_OK;122}123124// Outputs image data given a bitstream. The bitstream can either be a125// single-image WebP file or raw VP8/VP8L data.126// Also outputs 'is_lossless' to be true if the given bitstream is lossless.127static WebPMuxError GetImageData(const WebPData* const bitstream,128WebPData* const image, WebPData* const alpha,129int* const is_lossless) {130WebPDataInit(alpha); // Default: no alpha.131if (bitstream->size < TAG_SIZE ||132memcmp(bitstream->bytes, "RIFF", TAG_SIZE)) {133// It is NOT webp file data. Return input data as is.134*image = *bitstream;135} else {136// It is webp file data. Extract image data from it.137const WebPMuxImage* wpi;138WebPMux* const mux = WebPMuxCreate(bitstream, 0);139if (mux == NULL) return WEBP_MUX_BAD_DATA;140wpi = mux->images_;141assert(wpi != NULL && wpi->img_ != NULL);142*image = wpi->img_->data_;143if (wpi->alpha_ != NULL) {144*alpha = wpi->alpha_->data_;145}146WebPMuxDelete(mux);147}148*is_lossless = VP8LCheckSignature(image->bytes, image->size);149return WEBP_MUX_OK;150}151152static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) {153WebPMuxError err = WEBP_MUX_NOT_FOUND;154assert(chunk_list);155while (*chunk_list) {156WebPChunk* const chunk = *chunk_list;157if (chunk->tag_ == tag) {158*chunk_list = ChunkDelete(chunk);159err = WEBP_MUX_OK;160} else {161chunk_list = &chunk->next_;162}163}164return err;165}166167static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) {168const WebPChunkId id = ChunkGetIdFromTag(tag);169assert(mux != NULL);170if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT;171return DeleteChunks(MuxGetChunkListFromId(mux, id), tag);172}173174//------------------------------------------------------------------------------175// Set API(s).176177WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4],178const WebPData* chunk_data, int copy_data) {179uint32_t tag;180WebPMuxError err;181if (mux == NULL || fourcc == NULL || chunk_data == NULL ||182chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) {183return WEBP_MUX_INVALID_ARGUMENT;184}185tag = ChunkGetTagFromFourCC(fourcc);186187// Delete existing chunk(s) with the same 'fourcc'.188err = MuxDeleteAllNamedData(mux, tag);189if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;190191// Add the given chunk.192return MuxSet(mux, tag, 1, chunk_data, copy_data);193}194195// Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'.196static WebPMuxError AddDataToChunkList(197const WebPData* const data, int copy_data, uint32_t tag,198WebPChunk** chunk_list) {199WebPChunk chunk;200WebPMuxError err;201ChunkInit(&chunk);202err = ChunkAssignData(&chunk, data, copy_data, tag);203if (err != WEBP_MUX_OK) goto Err;204err = ChunkSetNth(&chunk, chunk_list, 1);205if (err != WEBP_MUX_OK) goto Err;206return WEBP_MUX_OK;207Err:208ChunkRelease(&chunk);209return err;210}211212// Extracts image & alpha data from the given bitstream and then sets wpi.alpha_213// and wpi.img_ appropriately.214static WebPMuxError SetAlphaAndImageChunks(215const WebPData* const bitstream, int copy_data, WebPMuxImage* const wpi) {216int is_lossless = 0;217WebPData image, alpha;218WebPMuxError err = GetImageData(bitstream, &image, &alpha, &is_lossless);219const int image_tag =220is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag;221if (err != WEBP_MUX_OK) return err;222if (alpha.bytes != NULL) {223err = AddDataToChunkList(&alpha, copy_data, kChunks[IDX_ALPHA].tag,224&wpi->alpha_);225if (err != WEBP_MUX_OK) return err;226}227err = AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_);228if (err != WEBP_MUX_OK) return err;229return MuxImageFinalize(wpi) ? WEBP_MUX_OK : WEBP_MUX_INVALID_ARGUMENT;230}231232WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream,233int copy_data) {234WebPMuxImage wpi;235WebPMuxError err;236237// Sanity checks.238if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL ||239bitstream->size > MAX_CHUNK_PAYLOAD) {240return WEBP_MUX_INVALID_ARGUMENT;241}242243if (mux->images_ != NULL) {244// Only one 'simple image' can be added in mux. So, remove present images.245DeleteAllImages(&mux->images_);246}247248MuxImageInit(&wpi);249err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);250if (err != WEBP_MUX_OK) goto Err;251252// Add this WebPMuxImage to mux.253err = MuxImagePush(&wpi, &mux->images_);254if (err != WEBP_MUX_OK) goto Err;255256// All is well.257return WEBP_MUX_OK;258259Err: // Something bad happened.260MuxImageRelease(&wpi);261return err;262}263264WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* info,265int copy_data) {266WebPMuxImage wpi;267WebPMuxError err;268const WebPData* const bitstream = &info->bitstream;269270// Sanity checks.271if (mux == NULL || info == NULL) return WEBP_MUX_INVALID_ARGUMENT;272273if (info->id != WEBP_CHUNK_ANMF) return WEBP_MUX_INVALID_ARGUMENT;274275if (bitstream->bytes == NULL || bitstream->size > MAX_CHUNK_PAYLOAD) {276return WEBP_MUX_INVALID_ARGUMENT;277}278279if (mux->images_ != NULL) {280const WebPMuxImage* const image = mux->images_;281const uint32_t image_id = (image->header_ != NULL) ?282ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE;283if (image_id != info->id) {284return WEBP_MUX_INVALID_ARGUMENT; // Conflicting frame types.285}286}287288MuxImageInit(&wpi);289err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);290if (err != WEBP_MUX_OK) goto Err;291assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful.292293{294WebPData frame;295const uint32_t tag = kChunks[IDX_ANMF].tag;296WebPMuxFrameInfo tmp = *info;297tmp.x_offset &= ~1; // Snap offsets to even.298tmp.y_offset &= ~1;299if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET ||300tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET ||301(tmp.duration < 0 || tmp.duration >= MAX_DURATION) ||302tmp.dispose_method != (tmp.dispose_method & 1)) {303err = WEBP_MUX_INVALID_ARGUMENT;304goto Err;305}306err = CreateFrameData(wpi.width_, wpi.height_, &tmp, &frame);307if (err != WEBP_MUX_OK) goto Err;308// Add frame chunk (with copy_data = 1).309err = AddDataToChunkList(&frame, 1, tag, &wpi.header_);310WebPDataClear(&frame); // frame owned by wpi.header_ now.311if (err != WEBP_MUX_OK) goto Err;312}313314// Add this WebPMuxImage to mux.315err = MuxImagePush(&wpi, &mux->images_);316if (err != WEBP_MUX_OK) goto Err;317318// All is well.319return WEBP_MUX_OK;320321Err: // Something bad happened.322MuxImageRelease(&wpi);323return err;324}325326WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux,327const WebPMuxAnimParams* params) {328WebPMuxError err;329uint8_t data[ANIM_CHUNK_SIZE];330const WebPData anim = { data, ANIM_CHUNK_SIZE };331332if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;333if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) {334return WEBP_MUX_INVALID_ARGUMENT;335}336337// Delete any existing ANIM chunk(s).338err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);339if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;340341// Set the animation parameters.342PutLE32(data, params->bgcolor);343PutLE16(data + 4, params->loop_count);344return MuxSet(mux, kChunks[IDX_ANIM].tag, 1, &anim, 1);345}346347WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux,348int width, int height) {349WebPMuxError err;350if (mux == NULL) {351return WEBP_MUX_INVALID_ARGUMENT;352}353if (width < 0 || height < 0 ||354width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {355return WEBP_MUX_INVALID_ARGUMENT;356}357if (width * (uint64_t)height >= MAX_IMAGE_AREA) {358return WEBP_MUX_INVALID_ARGUMENT;359}360if ((width * height) == 0 && (width | height) != 0) {361// one of width / height is zero, but not both -> invalid!362return WEBP_MUX_INVALID_ARGUMENT;363}364// If we already assembled a VP8X chunk, invalidate it.365err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);366if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;367368mux->canvas_width_ = width;369mux->canvas_height_ = height;370return WEBP_MUX_OK;371}372373//------------------------------------------------------------------------------374// Delete API(s).375376WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]) {377if (mux == NULL || fourcc == NULL) return WEBP_MUX_INVALID_ARGUMENT;378return MuxDeleteAllNamedData(mux, ChunkGetTagFromFourCC(fourcc));379}380381WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) {382if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;383return MuxImageDeleteNth(&mux->images_, nth);384}385386//------------------------------------------------------------------------------387// Assembly of the WebP RIFF file.388389static WebPMuxError GetFrameInfo(390const WebPChunk* const frame_chunk,391int* const x_offset, int* const y_offset, int* const duration) {392const WebPData* const data = &frame_chunk->data_;393const size_t expected_data_size = ANMF_CHUNK_SIZE;394assert(frame_chunk->tag_ == kChunks[IDX_ANMF].tag);395assert(frame_chunk != NULL);396if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT;397398*x_offset = 2 * GetLE24(data->bytes + 0);399*y_offset = 2 * GetLE24(data->bytes + 3);400*duration = GetLE24(data->bytes + 12);401return WEBP_MUX_OK;402}403404static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,405int* const x_offset, int* const y_offset,406int* const duration,407int* const width, int* const height) {408const WebPChunk* const frame_chunk = wpi->header_;409WebPMuxError err;410assert(wpi != NULL);411assert(frame_chunk != NULL);412413// Get offsets and duration from ANMF chunk.414err = GetFrameInfo(frame_chunk, x_offset, y_offset, duration);415if (err != WEBP_MUX_OK) return err;416417// Get width and height from VP8/VP8L chunk.418if (width != NULL) *width = wpi->width_;419if (height != NULL) *height = wpi->height_;420return WEBP_MUX_OK;421}422423// Returns the tightest dimension for the canvas considering the image list.424static WebPMuxError GetAdjustedCanvasSize(const WebPMux* const mux,425int* const width, int* const height) {426WebPMuxImage* wpi = NULL;427assert(mux != NULL);428assert(width != NULL && height != NULL);429430wpi = mux->images_;431assert(wpi != NULL);432assert(wpi->img_ != NULL);433434if (wpi->next_ != NULL) {435int max_x = 0, max_y = 0;436// if we have a chain of wpi's, header_ is necessarily set437assert(wpi->header_ != NULL);438// Aggregate the bounding box for animation frames.439for (; wpi != NULL; wpi = wpi->next_) {440int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0;441const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset,442&duration, &w, &h);443const int max_x_pos = x_offset + w;444const int max_y_pos = y_offset + h;445if (err != WEBP_MUX_OK) return err;446assert(x_offset < MAX_POSITION_OFFSET);447assert(y_offset < MAX_POSITION_OFFSET);448449if (max_x_pos > max_x) max_x = max_x_pos;450if (max_y_pos > max_y) max_y = max_y_pos;451}452*width = max_x;453*height = max_y;454} else {455// For a single image, canvas dimensions are same as image dimensions.456*width = wpi->width_;457*height = wpi->height_;458}459return WEBP_MUX_OK;460}461462// VP8X format:463// Total Size : 10,464// Flags : 4 bytes,465// Width : 3 bytes,466// Height : 3 bytes.467static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {468WebPMuxError err = WEBP_MUX_OK;469uint32_t flags = 0;470int width = 0;471int height = 0;472uint8_t data[VP8X_CHUNK_SIZE];473const WebPData vp8x = { data, VP8X_CHUNK_SIZE };474const WebPMuxImage* images = NULL;475476assert(mux != NULL);477images = mux->images_; // First image.478if (images == NULL || images->img_ == NULL ||479images->img_->data_.bytes == NULL) {480return WEBP_MUX_INVALID_ARGUMENT;481}482483// If VP8X chunk(s) is(are) already present, remove them (and later add new484// VP8X chunk with updated flags).485err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);486if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;487488// Set flags.489if (mux->iccp_ != NULL && mux->iccp_->data_.bytes != NULL) {490flags |= ICCP_FLAG;491}492if (mux->exif_ != NULL && mux->exif_->data_.bytes != NULL) {493flags |= EXIF_FLAG;494}495if (mux->xmp_ != NULL && mux->xmp_->data_.bytes != NULL) {496flags |= XMP_FLAG;497}498if (images->header_ != NULL) {499if (images->header_->tag_ == kChunks[IDX_ANMF].tag) {500// This is an image with animation.501flags |= ANIMATION_FLAG;502}503}504if (MuxImageCount(images, WEBP_CHUNK_ALPHA) > 0) {505flags |= ALPHA_FLAG; // Some images have an alpha channel.506}507508err = GetAdjustedCanvasSize(mux, &width, &height);509if (err != WEBP_MUX_OK) return err;510511if (width <= 0 || height <= 0) {512return WEBP_MUX_INVALID_ARGUMENT;513}514if (width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {515return WEBP_MUX_INVALID_ARGUMENT;516}517518if (mux->canvas_width_ != 0 || mux->canvas_height_ != 0) {519if (width > mux->canvas_width_ || height > mux->canvas_height_) {520return WEBP_MUX_INVALID_ARGUMENT;521}522width = mux->canvas_width_;523height = mux->canvas_height_;524}525526if (flags == 0 && mux->unknown_ == NULL) {527// For simple file format, VP8X chunk should not be added.528return WEBP_MUX_OK;529}530531if (MuxHasAlpha(images)) {532// This means some frames explicitly/implicitly contain alpha.533// Note: This 'flags' update must NOT be done for a lossless image534// without a VP8X chunk!535flags |= ALPHA_FLAG;536}537538PutLE32(data + 0, flags); // VP8X chunk flags.539PutLE24(data + 4, width - 1); // canvas width.540PutLE24(data + 7, height - 1); // canvas height.541542return MuxSet(mux, kChunks[IDX_VP8X].tag, 1, &vp8x, 1);543}544545// Cleans up 'mux' by removing any unnecessary chunks.546static WebPMuxError MuxCleanup(WebPMux* const mux) {547int num_frames;548int num_anim_chunks;549550// If we have an image with a single frame, and its rectangle551// covers the whole canvas, convert it to a non-animated image552// (to avoid writing ANMF chunk unnecessarily).553WebPMuxError err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames);554if (err != WEBP_MUX_OK) return err;555if (num_frames == 1) {556WebPMuxImage* frame = NULL;557err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame);558assert(err == WEBP_MUX_OK); // We know that one frame does exist.559assert(frame != NULL);560if (frame->header_ != NULL &&561((mux->canvas_width_ == 0 && mux->canvas_height_ == 0) ||562(frame->width_ == mux->canvas_width_ &&563frame->height_ == mux->canvas_height_))) {564assert(frame->header_->tag_ == kChunks[IDX_ANMF].tag);565ChunkDelete(frame->header_); // Removes ANMF chunk.566frame->header_ = NULL;567num_frames = 0;568}569}570// Remove ANIM chunk if this is a non-animated image.571err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks);572if (err != WEBP_MUX_OK) return err;573if (num_anim_chunks >= 1 && num_frames == 0) {574err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);575if (err != WEBP_MUX_OK) return err;576}577return WEBP_MUX_OK;578}579580// Total size of a list of images.581static size_t ImageListDiskSize(const WebPMuxImage* wpi_list) {582size_t size = 0;583while (wpi_list != NULL) {584size += MuxImageDiskSize(wpi_list);585wpi_list = wpi_list->next_;586}587return size;588}589590// Write out the given list of images into 'dst'.591static uint8_t* ImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) {592while (wpi_list != NULL) {593dst = MuxImageEmit(wpi_list, dst);594wpi_list = wpi_list->next_;595}596return dst;597}598599WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {600size_t size = 0;601uint8_t* data = NULL;602uint8_t* dst = NULL;603WebPMuxError err;604605if (assembled_data == NULL) {606return WEBP_MUX_INVALID_ARGUMENT;607}608// Clean up returned data, in case something goes wrong.609memset(assembled_data, 0, sizeof(*assembled_data));610611if (mux == NULL) {612return WEBP_MUX_INVALID_ARGUMENT;613}614615// Finalize mux.616err = MuxCleanup(mux);617if (err != WEBP_MUX_OK) return err;618err = CreateVP8XChunk(mux);619if (err != WEBP_MUX_OK) return err;620621// Allocate data.622size = ChunkListDiskSize(mux->vp8x_) + ChunkListDiskSize(mux->iccp_)623+ ChunkListDiskSize(mux->anim_) + ImageListDiskSize(mux->images_)624+ ChunkListDiskSize(mux->exif_) + ChunkListDiskSize(mux->xmp_)625+ ChunkListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE;626627data = (uint8_t*)WebPSafeMalloc(1ULL, size);628if (data == NULL) return WEBP_MUX_MEMORY_ERROR;629630// Emit header & chunks.631dst = MuxEmitRiffHeader(data, size);632dst = ChunkListEmit(mux->vp8x_, dst);633dst = ChunkListEmit(mux->iccp_, dst);634dst = ChunkListEmit(mux->anim_, dst);635dst = ImageListEmit(mux->images_, dst);636dst = ChunkListEmit(mux->exif_, dst);637dst = ChunkListEmit(mux->xmp_, dst);638dst = ChunkListEmit(mux->unknown_, dst);639assert(dst == data + size);640641// Validate mux.642err = MuxValidate(mux);643if (err != WEBP_MUX_OK) {644WebPSafeFree(data);645data = NULL;646size = 0;647}648649// Finalize data.650assembled_data->bytes = data;651assembled_data->size = size;652653return err;654}655656//------------------------------------------------------------------------------657658659