muxedit.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. // Copyright 2011 Google Inc. All Rights Reserved.
  2. //
  3. // Use of this source code is governed by a BSD-style license
  4. // that can be found in the COPYING file in the root of the source
  5. // tree. An additional intellectual property rights grant can be found
  6. // in the file PATENTS. All contributing project authors may
  7. // be found in the AUTHORS file in the root of the source tree.
  8. // -----------------------------------------------------------------------------
  9. //
  10. // Set and delete APIs for mux.
  11. //
  12. // Authors: Urvang (urvang@google.com)
  13. // Vikas (vikasa@google.com)
  14. #include <assert.h>
  15. #include "./muxi.h"
  16. #include "../utils/utils.h"
  17. //------------------------------------------------------------------------------
  18. // Life of a mux object.
  19. static void MuxInit(WebPMux* const mux) {
  20. assert(mux != NULL);
  21. memset(mux, 0, sizeof(*mux));
  22. mux->canvas_width_ = 0; // just to be explicit
  23. mux->canvas_height_ = 0;
  24. }
  25. WebPMux* WebPNewInternal(int version) {
  26. if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
  27. return NULL;
  28. } else {
  29. WebPMux* const mux = (WebPMux*)WebPSafeMalloc(1ULL, sizeof(WebPMux));
  30. if (mux != NULL) MuxInit(mux);
  31. return mux;
  32. }
  33. }
  34. // Delete all images in 'wpi_list'.
  35. static void DeleteAllImages(WebPMuxImage** const wpi_list) {
  36. while (*wpi_list != NULL) {
  37. *wpi_list = MuxImageDelete(*wpi_list);
  38. }
  39. }
  40. static void MuxRelease(WebPMux* const mux) {
  41. assert(mux != NULL);
  42. DeleteAllImages(&mux->images_);
  43. ChunkListDelete(&mux->vp8x_);
  44. ChunkListDelete(&mux->iccp_);
  45. ChunkListDelete(&mux->anim_);
  46. ChunkListDelete(&mux->exif_);
  47. ChunkListDelete(&mux->xmp_);
  48. ChunkListDelete(&mux->unknown_);
  49. }
  50. void WebPMuxDelete(WebPMux* mux) {
  51. if (mux != NULL) {
  52. MuxRelease(mux);
  53. WebPSafeFree(mux);
  54. }
  55. }
  56. //------------------------------------------------------------------------------
  57. // Helper method(s).
  58. // Handy MACRO, makes MuxSet() very symmetric to MuxGet().
  59. #define SWITCH_ID_LIST(INDEX, LIST) \
  60. if (idx == (INDEX)) { \
  61. err = ChunkAssignData(&chunk, data, copy_data, tag); \
  62. if (err == WEBP_MUX_OK) { \
  63. err = ChunkSetHead(&chunk, (LIST)); \
  64. } \
  65. return err; \
  66. }
  67. static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag,
  68. const WebPData* const data, int copy_data) {
  69. WebPChunk chunk;
  70. WebPMuxError err = WEBP_MUX_NOT_FOUND;
  71. const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag);
  72. assert(mux != NULL);
  73. assert(!IsWPI(kChunks[idx].id));
  74. ChunkInit(&chunk);
  75. SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_);
  76. SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_);
  77. SWITCH_ID_LIST(IDX_ANIM, &mux->anim_);
  78. SWITCH_ID_LIST(IDX_EXIF, &mux->exif_);
  79. SWITCH_ID_LIST(IDX_XMP, &mux->xmp_);
  80. SWITCH_ID_LIST(IDX_UNKNOWN, &mux->unknown_);
  81. return err;
  82. }
  83. #undef SWITCH_ID_LIST
  84. // Create data for frame given image data, offsets and duration.
  85. static WebPMuxError CreateFrameData(
  86. int width, int height, const WebPMuxFrameInfo* const info,
  87. WebPData* const frame) {
  88. uint8_t* frame_bytes;
  89. const size_t frame_size = kChunks[IDX_ANMF].size;
  90. assert(width > 0 && height > 0 && info->duration >= 0);
  91. assert(info->dispose_method == (info->dispose_method & 1));
  92. // Note: assertion on upper bounds is done in PutLE24().
  93. frame_bytes = (uint8_t*)WebPSafeMalloc(1ULL, frame_size);
  94. if (frame_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;
  95. PutLE24(frame_bytes + 0, info->x_offset / 2);
  96. PutLE24(frame_bytes + 3, info->y_offset / 2);
  97. PutLE24(frame_bytes + 6, width - 1);
  98. PutLE24(frame_bytes + 9, height - 1);
  99. PutLE24(frame_bytes + 12, info->duration);
  100. frame_bytes[15] =
  101. (info->blend_method == WEBP_MUX_NO_BLEND ? 2 : 0) |
  102. (info->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? 1 : 0);
  103. frame->bytes = frame_bytes;
  104. frame->size = frame_size;
  105. return WEBP_MUX_OK;
  106. }
  107. // Outputs image data given a bitstream. The bitstream can either be a
  108. // single-image WebP file or raw VP8/VP8L data.
  109. // Also outputs 'is_lossless' to be true if the given bitstream is lossless.
  110. static WebPMuxError GetImageData(const WebPData* const bitstream,
  111. WebPData* const image, WebPData* const alpha,
  112. int* const is_lossless) {
  113. WebPDataInit(alpha); // Default: no alpha.
  114. if (bitstream->size < TAG_SIZE ||
  115. memcmp(bitstream->bytes, "RIFF", TAG_SIZE)) {
  116. // It is NOT webp file data. Return input data as is.
  117. *image = *bitstream;
  118. } else {
  119. // It is webp file data. Extract image data from it.
  120. const WebPMuxImage* wpi;
  121. WebPMux* const mux = WebPMuxCreate(bitstream, 0);
  122. if (mux == NULL) return WEBP_MUX_BAD_DATA;
  123. wpi = mux->images_;
  124. assert(wpi != NULL && wpi->img_ != NULL);
  125. *image = wpi->img_->data_;
  126. if (wpi->alpha_ != NULL) {
  127. *alpha = wpi->alpha_->data_;
  128. }
  129. WebPMuxDelete(mux);
  130. }
  131. *is_lossless = VP8LCheckSignature(image->bytes, image->size);
  132. return WEBP_MUX_OK;
  133. }
  134. static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) {
  135. WebPMuxError err = WEBP_MUX_NOT_FOUND;
  136. assert(chunk_list);
  137. while (*chunk_list) {
  138. WebPChunk* const chunk = *chunk_list;
  139. if (chunk->tag_ == tag) {
  140. *chunk_list = ChunkDelete(chunk);
  141. err = WEBP_MUX_OK;
  142. } else {
  143. chunk_list = &chunk->next_;
  144. }
  145. }
  146. return err;
  147. }
  148. static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) {
  149. const WebPChunkId id = ChunkGetIdFromTag(tag);
  150. assert(mux != NULL);
  151. if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT;
  152. return DeleteChunks(MuxGetChunkListFromId(mux, id), tag);
  153. }
  154. //------------------------------------------------------------------------------
  155. // Set API(s).
  156. WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4],
  157. const WebPData* chunk_data, int copy_data) {
  158. uint32_t tag;
  159. WebPMuxError err;
  160. if (mux == NULL || fourcc == NULL || chunk_data == NULL ||
  161. chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) {
  162. return WEBP_MUX_INVALID_ARGUMENT;
  163. }
  164. tag = ChunkGetTagFromFourCC(fourcc);
  165. // Delete existing chunk(s) with the same 'fourcc'.
  166. err = MuxDeleteAllNamedData(mux, tag);
  167. if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
  168. // Add the given chunk.
  169. return MuxSet(mux, tag, chunk_data, copy_data);
  170. }
  171. // Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'.
  172. static WebPMuxError AddDataToChunkList(
  173. const WebPData* const data, int copy_data, uint32_t tag,
  174. WebPChunk** chunk_list) {
  175. WebPChunk chunk;
  176. WebPMuxError err;
  177. ChunkInit(&chunk);
  178. err = ChunkAssignData(&chunk, data, copy_data, tag);
  179. if (err != WEBP_MUX_OK) goto Err;
  180. err = ChunkSetHead(&chunk, chunk_list);
  181. if (err != WEBP_MUX_OK) goto Err;
  182. return WEBP_MUX_OK;
  183. Err:
  184. ChunkRelease(&chunk);
  185. return err;
  186. }
  187. // Extracts image & alpha data from the given bitstream and then sets wpi.alpha_
  188. // and wpi.img_ appropriately.
  189. static WebPMuxError SetAlphaAndImageChunks(
  190. const WebPData* const bitstream, int copy_data, WebPMuxImage* const wpi) {
  191. int is_lossless = 0;
  192. WebPData image, alpha;
  193. WebPMuxError err = GetImageData(bitstream, &image, &alpha, &is_lossless);
  194. const int image_tag =
  195. is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag;
  196. if (err != WEBP_MUX_OK) return err;
  197. if (alpha.bytes != NULL) {
  198. err = AddDataToChunkList(&alpha, copy_data, kChunks[IDX_ALPHA].tag,
  199. &wpi->alpha_);
  200. if (err != WEBP_MUX_OK) return err;
  201. }
  202. err = AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_);
  203. if (err != WEBP_MUX_OK) return err;
  204. return MuxImageFinalize(wpi) ? WEBP_MUX_OK : WEBP_MUX_INVALID_ARGUMENT;
  205. }
  206. WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream,
  207. int copy_data) {
  208. WebPMuxImage wpi;
  209. WebPMuxError err;
  210. if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL ||
  211. bitstream->size > MAX_CHUNK_PAYLOAD) {
  212. return WEBP_MUX_INVALID_ARGUMENT;
  213. }
  214. if (mux->images_ != NULL) {
  215. // Only one 'simple image' can be added in mux. So, remove present images.
  216. DeleteAllImages(&mux->images_);
  217. }
  218. MuxImageInit(&wpi);
  219. err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);
  220. if (err != WEBP_MUX_OK) goto Err;
  221. // Add this WebPMuxImage to mux.
  222. err = MuxImagePush(&wpi, &mux->images_);
  223. if (err != WEBP_MUX_OK) goto Err;
  224. // All is well.
  225. return WEBP_MUX_OK;
  226. Err: // Something bad happened.
  227. MuxImageRelease(&wpi);
  228. return err;
  229. }
  230. WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* info,
  231. int copy_data) {
  232. WebPMuxImage wpi;
  233. WebPMuxError err;
  234. if (mux == NULL || info == NULL) return WEBP_MUX_INVALID_ARGUMENT;
  235. if (info->id != WEBP_CHUNK_ANMF) return WEBP_MUX_INVALID_ARGUMENT;
  236. if (info->bitstream.bytes == NULL ||
  237. info->bitstream.size > MAX_CHUNK_PAYLOAD) {
  238. return WEBP_MUX_INVALID_ARGUMENT;
  239. }
  240. if (mux->images_ != NULL) {
  241. const WebPMuxImage* const image = mux->images_;
  242. const uint32_t image_id = (image->header_ != NULL) ?
  243. ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE;
  244. if (image_id != info->id) {
  245. return WEBP_MUX_INVALID_ARGUMENT; // Conflicting frame types.
  246. }
  247. }
  248. MuxImageInit(&wpi);
  249. err = SetAlphaAndImageChunks(&info->bitstream, copy_data, &wpi);
  250. if (err != WEBP_MUX_OK) goto Err;
  251. assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful.
  252. {
  253. WebPData frame;
  254. const uint32_t tag = kChunks[IDX_ANMF].tag;
  255. WebPMuxFrameInfo tmp = *info;
  256. tmp.x_offset &= ~1; // Snap offsets to even.
  257. tmp.y_offset &= ~1;
  258. if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET ||
  259. tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET ||
  260. (tmp.duration < 0 || tmp.duration >= MAX_DURATION) ||
  261. tmp.dispose_method != (tmp.dispose_method & 1)) {
  262. err = WEBP_MUX_INVALID_ARGUMENT;
  263. goto Err;
  264. }
  265. err = CreateFrameData(wpi.width_, wpi.height_, &tmp, &frame);
  266. if (err != WEBP_MUX_OK) goto Err;
  267. // Add frame chunk (with copy_data = 1).
  268. err = AddDataToChunkList(&frame, 1, tag, &wpi.header_);
  269. WebPDataClear(&frame); // frame owned by wpi.header_ now.
  270. if (err != WEBP_MUX_OK) goto Err;
  271. }
  272. // Add this WebPMuxImage to mux.
  273. err = MuxImagePush(&wpi, &mux->images_);
  274. if (err != WEBP_MUX_OK) goto Err;
  275. // All is well.
  276. return WEBP_MUX_OK;
  277. Err: // Something bad happened.
  278. MuxImageRelease(&wpi);
  279. return err;
  280. }
  281. WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux,
  282. const WebPMuxAnimParams* params) {
  283. WebPMuxError err;
  284. uint8_t data[ANIM_CHUNK_SIZE];
  285. const WebPData anim = { data, ANIM_CHUNK_SIZE };
  286. if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
  287. if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) {
  288. return WEBP_MUX_INVALID_ARGUMENT;
  289. }
  290. // Delete any existing ANIM chunk(s).
  291. err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
  292. if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
  293. // Set the animation parameters.
  294. PutLE32(data, params->bgcolor);
  295. PutLE16(data + 4, params->loop_count);
  296. return MuxSet(mux, kChunks[IDX_ANIM].tag, &anim, 1);
  297. }
  298. WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux,
  299. int width, int height) {
  300. WebPMuxError err;
  301. if (mux == NULL) {
  302. return WEBP_MUX_INVALID_ARGUMENT;
  303. }
  304. if (width < 0 || height < 0 ||
  305. width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {
  306. return WEBP_MUX_INVALID_ARGUMENT;
  307. }
  308. if (width * (uint64_t)height >= MAX_IMAGE_AREA) {
  309. return WEBP_MUX_INVALID_ARGUMENT;
  310. }
  311. if ((width * height) == 0 && (width | height) != 0) {
  312. // one of width / height is zero, but not both -> invalid!
  313. return WEBP_MUX_INVALID_ARGUMENT;
  314. }
  315. // If we already assembled a VP8X chunk, invalidate it.
  316. err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);
  317. if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
  318. mux->canvas_width_ = width;
  319. mux->canvas_height_ = height;
  320. return WEBP_MUX_OK;
  321. }
  322. //------------------------------------------------------------------------------
  323. // Delete API(s).
  324. WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]) {
  325. if (mux == NULL || fourcc == NULL) return WEBP_MUX_INVALID_ARGUMENT;
  326. return MuxDeleteAllNamedData(mux, ChunkGetTagFromFourCC(fourcc));
  327. }
  328. WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) {
  329. if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
  330. return MuxImageDeleteNth(&mux->images_, nth);
  331. }
  332. //------------------------------------------------------------------------------
  333. // Assembly of the WebP RIFF file.
  334. static WebPMuxError GetFrameInfo(
  335. const WebPChunk* const frame_chunk,
  336. int* const x_offset, int* const y_offset, int* const duration) {
  337. const WebPData* const data = &frame_chunk->data_;
  338. const size_t expected_data_size = ANMF_CHUNK_SIZE;
  339. assert(frame_chunk->tag_ == kChunks[IDX_ANMF].tag);
  340. assert(frame_chunk != NULL);
  341. if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT;
  342. *x_offset = 2 * GetLE24(data->bytes + 0);
  343. *y_offset = 2 * GetLE24(data->bytes + 3);
  344. *duration = GetLE24(data->bytes + 12);
  345. return WEBP_MUX_OK;
  346. }
  347. static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,
  348. int* const x_offset, int* const y_offset,
  349. int* const duration,
  350. int* const width, int* const height) {
  351. const WebPChunk* const frame_chunk = wpi->header_;
  352. WebPMuxError err;
  353. assert(wpi != NULL);
  354. assert(frame_chunk != NULL);
  355. // Get offsets and duration from ANMF chunk.
  356. err = GetFrameInfo(frame_chunk, x_offset, y_offset, duration);
  357. if (err != WEBP_MUX_OK) return err;
  358. // Get width and height from VP8/VP8L chunk.
  359. if (width != NULL) *width = wpi->width_;
  360. if (height != NULL) *height = wpi->height_;
  361. return WEBP_MUX_OK;
  362. }
  363. // Returns the tightest dimension for the canvas considering the image list.
  364. static WebPMuxError GetAdjustedCanvasSize(const WebPMux* const mux,
  365. int* const width, int* const height) {
  366. WebPMuxImage* wpi = NULL;
  367. assert(mux != NULL);
  368. assert(width != NULL && height != NULL);
  369. wpi = mux->images_;
  370. assert(wpi != NULL);
  371. assert(wpi->img_ != NULL);
  372. if (wpi->next_ != NULL) {
  373. int max_x = 0, max_y = 0;
  374. // if we have a chain of wpi's, header_ is necessarily set
  375. assert(wpi->header_ != NULL);
  376. // Aggregate the bounding box for animation frames.
  377. for (; wpi != NULL; wpi = wpi->next_) {
  378. int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0;
  379. const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset,
  380. &duration, &w, &h);
  381. const int max_x_pos = x_offset + w;
  382. const int max_y_pos = y_offset + h;
  383. if (err != WEBP_MUX_OK) return err;
  384. assert(x_offset < MAX_POSITION_OFFSET);
  385. assert(y_offset < MAX_POSITION_OFFSET);
  386. if (max_x_pos > max_x) max_x = max_x_pos;
  387. if (max_y_pos > max_y) max_y = max_y_pos;
  388. }
  389. *width = max_x;
  390. *height = max_y;
  391. } else {
  392. // For a single image, canvas dimensions are same as image dimensions.
  393. *width = wpi->width_;
  394. *height = wpi->height_;
  395. }
  396. return WEBP_MUX_OK;
  397. }
  398. // VP8X format:
  399. // Total Size : 10,
  400. // Flags : 4 bytes,
  401. // Width : 3 bytes,
  402. // Height : 3 bytes.
  403. static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {
  404. WebPMuxError err = WEBP_MUX_OK;
  405. uint32_t flags = 0;
  406. int width = 0;
  407. int height = 0;
  408. uint8_t data[VP8X_CHUNK_SIZE];
  409. const WebPData vp8x = { data, VP8X_CHUNK_SIZE };
  410. const WebPMuxImage* images = NULL;
  411. assert(mux != NULL);
  412. images = mux->images_; // First image.
  413. if (images == NULL || images->img_ == NULL ||
  414. images->img_->data_.bytes == NULL) {
  415. return WEBP_MUX_INVALID_ARGUMENT;
  416. }
  417. // If VP8X chunk(s) is(are) already present, remove them (and later add new
  418. // VP8X chunk with updated flags).
  419. err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);
  420. if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
  421. // Set flags.
  422. if (mux->iccp_ != NULL && mux->iccp_->data_.bytes != NULL) {
  423. flags |= ICCP_FLAG;
  424. }
  425. if (mux->exif_ != NULL && mux->exif_->data_.bytes != NULL) {
  426. flags |= EXIF_FLAG;
  427. }
  428. if (mux->xmp_ != NULL && mux->xmp_->data_.bytes != NULL) {
  429. flags |= XMP_FLAG;
  430. }
  431. if (images->header_ != NULL) {
  432. if (images->header_->tag_ == kChunks[IDX_ANMF].tag) {
  433. // This is an image with animation.
  434. flags |= ANIMATION_FLAG;
  435. }
  436. }
  437. if (MuxImageCount(images, WEBP_CHUNK_ALPHA) > 0) {
  438. flags |= ALPHA_FLAG; // Some images have an alpha channel.
  439. }
  440. err = GetAdjustedCanvasSize(mux, &width, &height);
  441. if (err != WEBP_MUX_OK) return err;
  442. if (width <= 0 || height <= 0) {
  443. return WEBP_MUX_INVALID_ARGUMENT;
  444. }
  445. if (width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {
  446. return WEBP_MUX_INVALID_ARGUMENT;
  447. }
  448. if (mux->canvas_width_ != 0 || mux->canvas_height_ != 0) {
  449. if (width > mux->canvas_width_ || height > mux->canvas_height_) {
  450. return WEBP_MUX_INVALID_ARGUMENT;
  451. }
  452. width = mux->canvas_width_;
  453. height = mux->canvas_height_;
  454. }
  455. if (flags == 0 && mux->unknown_ == NULL) {
  456. // For simple file format, VP8X chunk should not be added.
  457. return WEBP_MUX_OK;
  458. }
  459. if (MuxHasAlpha(images)) {
  460. // This means some frames explicitly/implicitly contain alpha.
  461. // Note: This 'flags' update must NOT be done for a lossless image
  462. // without a VP8X chunk!
  463. flags |= ALPHA_FLAG;
  464. }
  465. PutLE32(data + 0, flags); // VP8X chunk flags.
  466. PutLE24(data + 4, width - 1); // canvas width.
  467. PutLE24(data + 7, height - 1); // canvas height.
  468. return MuxSet(mux, kChunks[IDX_VP8X].tag, &vp8x, 1);
  469. }
  470. // Cleans up 'mux' by removing any unnecessary chunks.
  471. static WebPMuxError MuxCleanup(WebPMux* const mux) {
  472. int num_frames;
  473. int num_anim_chunks;
  474. // If we have an image with a single frame, and its rectangle
  475. // covers the whole canvas, convert it to a non-animated image
  476. // (to avoid writing ANMF chunk unnecessarily).
  477. WebPMuxError err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames);
  478. if (err != WEBP_MUX_OK) return err;
  479. if (num_frames == 1) {
  480. WebPMuxImage* frame = NULL;
  481. err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame);
  482. assert(err == WEBP_MUX_OK); // We know that one frame does exist.
  483. assert(frame != NULL);
  484. if (frame->header_ != NULL &&
  485. ((mux->canvas_width_ == 0 && mux->canvas_height_ == 0) ||
  486. (frame->width_ == mux->canvas_width_ &&
  487. frame->height_ == mux->canvas_height_))) {
  488. assert(frame->header_->tag_ == kChunks[IDX_ANMF].tag);
  489. ChunkDelete(frame->header_); // Removes ANMF chunk.
  490. frame->header_ = NULL;
  491. num_frames = 0;
  492. }
  493. }
  494. // Remove ANIM chunk if this is a non-animated image.
  495. err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks);
  496. if (err != WEBP_MUX_OK) return err;
  497. if (num_anim_chunks >= 1 && num_frames == 0) {
  498. err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
  499. if (err != WEBP_MUX_OK) return err;
  500. }
  501. return WEBP_MUX_OK;
  502. }
  503. // Total size of a list of images.
  504. static size_t ImageListDiskSize(const WebPMuxImage* wpi_list) {
  505. size_t size = 0;
  506. while (wpi_list != NULL) {
  507. size += MuxImageDiskSize(wpi_list);
  508. wpi_list = wpi_list->next_;
  509. }
  510. return size;
  511. }
  512. // Write out the given list of images into 'dst'.
  513. static uint8_t* ImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) {
  514. while (wpi_list != NULL) {
  515. dst = MuxImageEmit(wpi_list, dst);
  516. wpi_list = wpi_list->next_;
  517. }
  518. return dst;
  519. }
  520. WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
  521. size_t size = 0;
  522. uint8_t* data = NULL;
  523. uint8_t* dst = NULL;
  524. WebPMuxError err;
  525. if (assembled_data == NULL) {
  526. return WEBP_MUX_INVALID_ARGUMENT;
  527. }
  528. // Clean up returned data, in case something goes wrong.
  529. memset(assembled_data, 0, sizeof(*assembled_data));
  530. if (mux == NULL) {
  531. return WEBP_MUX_INVALID_ARGUMENT;
  532. }
  533. // Finalize mux.
  534. err = MuxCleanup(mux);
  535. if (err != WEBP_MUX_OK) return err;
  536. err = CreateVP8XChunk(mux);
  537. if (err != WEBP_MUX_OK) return err;
  538. // Allocate data.
  539. size = ChunkListDiskSize(mux->vp8x_) + ChunkListDiskSize(mux->iccp_)
  540. + ChunkListDiskSize(mux->anim_) + ImageListDiskSize(mux->images_)
  541. + ChunkListDiskSize(mux->exif_) + ChunkListDiskSize(mux->xmp_)
  542. + ChunkListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE;
  543. data = (uint8_t*)WebPSafeMalloc(1ULL, size);
  544. if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
  545. // Emit header & chunks.
  546. dst = MuxEmitRiffHeader(data, size);
  547. dst = ChunkListEmit(mux->vp8x_, dst);
  548. dst = ChunkListEmit(mux->iccp_, dst);
  549. dst = ChunkListEmit(mux->anim_, dst);
  550. dst = ImageListEmit(mux->images_, dst);
  551. dst = ChunkListEmit(mux->exif_, dst);
  552. dst = ChunkListEmit(mux->xmp_, dst);
  553. dst = ChunkListEmit(mux->unknown_, dst);
  554. assert(dst == data + size);
  555. // Validate mux.
  556. err = MuxValidate(mux);
  557. if (err != WEBP_MUX_OK) {
  558. WebPSafeFree(data);
  559. data = NULL;
  560. size = 0;
  561. }
  562. // Finalize data.
  563. assembled_data->bytes = data;
  564. assembled_data->size = size;
  565. return err;
  566. }
  567. //------------------------------------------------------------------------------