blob: 16d8d301e6ed14dd80250a66e5e05ed2d6c6d61e [file] [log] [blame]
cristyb1860752011-03-14 00:27:46 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% W W EEEEE BBBB PPPP %
7% W W E B B P P %
8% W W W EEE BBBB PPPP %
9% WW WW E B B P %
10% W W EEEEE BBBB P %
11% %
12% %
13% Read/Write WebP Image Format %
14% %
15% Software Design %
Cristycb635602017-08-02 06:59:15 -040016% Cristy %
cristyb1860752011-03-14 00:27:46 +000017% March 2011 %
18% %
19% %
Cristyd8420112021-01-01 14:52:00 -050020% Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
cristyb1860752011-03-14 00:27:46 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
Cristy83d74de2018-10-13 10:17:25 -040026% https://imagemagick.org/script/license.php %
cristyb1860752011-03-14 00:27:46 +000027% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37*/
38
39/*
40 Include declarations.
41*/
cristy4c08aed2011-07-01 19:47:50 +000042#include "MagickCore/studio.h"
cristy76ce6e12013-04-05 14:33:38 +000043#include "MagickCore/artifact.h"
cristy4c08aed2011-07-01 19:47:50 +000044#include "MagickCore/blob.h"
45#include "MagickCore/blob-private.h"
46#include "MagickCore/client.h"
cristye65bb192013-08-01 11:22:06 +000047#include "MagickCore/colorspace-private.h"
cristy4c08aed2011-07-01 19:47:50 +000048#include "MagickCore/display.h"
49#include "MagickCore/exception.h"
50#include "MagickCore/exception-private.h"
51#include "MagickCore/image.h"
52#include "MagickCore/image-private.h"
Cristy75f47302020-08-02 10:08:22 -040053#include "MagickCore/layer.h"
cristy4c08aed2011-07-01 19:47:50 +000054#include "MagickCore/list.h"
55#include "MagickCore/magick.h"
56#include "MagickCore/monitor.h"
57#include "MagickCore/monitor-private.h"
58#include "MagickCore/memory_.h"
59#include "MagickCore/option.h"
60#include "MagickCore/pixel-accessor.h"
Cristy692b6782017-11-19 12:35:18 -050061#include "MagickCore/profile.h"
Cristy46d4fb52019-09-29 20:32:20 -040062#include "MagickCore/property.h"
cristy4c08aed2011-07-01 19:47:50 +000063#include "MagickCore/quantum-private.h"
64#include "MagickCore/static.h"
65#include "MagickCore/string_.h"
cristye71e0892013-02-18 13:13:53 +000066#include "MagickCore/string-private.h"
cristy4c08aed2011-07-01 19:47:50 +000067#include "MagickCore/module.h"
68#include "MagickCore/utility.h"
69#include "MagickCore/xwindow.h"
70#include "MagickCore/xwindow-private.h"
cristy644040e2011-03-14 17:31:59 +000071#if defined(MAGICKCORE_WEBP_DELEGATE)
72#include <webp/decode.h>
73#include <webp/encode.h>
Cristy692b6782017-11-19 12:35:18 -050074#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
75#include <webp/mux.h>
Cristy46d4fb52019-09-29 20:32:20 -040076#include <webp/demux.h>
Cristy692b6782017-11-19 12:35:18 -050077#endif
cristy644040e2011-03-14 17:31:59 +000078#endif
cristyb1860752011-03-14 00:27:46 +000079
80/*
81 Forward declarations.
82*/
83#if defined(MAGICKCORE_WEBP_DELEGATE)
84static MagickBooleanType
cristy018f07f2011-09-04 21:15:19 +000085 WriteWEBPImage(const ImageInfo *,Image *,ExceptionInfo *);
cristyb1860752011-03-14 00:27:46 +000086#endif
87
cristydffa79e2013-02-20 00:57:26 +000088/*
89%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90% %
91% %
92% %
93% I s W E B P %
94% %
95% %
96% %
97%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98%
99% IsWEBP() returns MagickTrue if the image format type, identified by the
100% magick string, is WebP.
101%
102% The format of the IsWEBP method is:
103%
104% MagickBooleanType IsWEBP(const unsigned char *magick,const size_t length)
105%
106% A description of each parameter follows:
107%
108% o magick: compare image format pattern against these bytes.
109%
110% o length: Specifies the length of the magick string.
111%
112*/
113static MagickBooleanType IsWEBP(const unsigned char *magick,const size_t length)
114{
115 if (length < 12)
116 return(MagickFalse);
117 if (LocaleNCompare((const char *) magick+8,"WEBP",4) == 0)
118 return(MagickTrue);
119 return(MagickFalse);
120}
121
cristyb1860752011-03-14 00:27:46 +0000122#if defined(MAGICKCORE_WEBP_DELEGATE)
123/*
124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
125% %
126% %
127% %
128% R e a d W E B P I m a g e %
129% %
130% %
131% %
132%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
133%
134% ReadWEBPImage() reads an image in the WebP image format.
135%
136% The format of the ReadWEBPImage method is:
137%
138% Image *ReadWEBPImage(const ImageInfo *image_info,
139% ExceptionInfo *exception)
140%
141% A description of each parameter follows:
142%
143% o image_info: the image info.
144%
145% o exception: return any errors or warnings in this structure.
146%
147*/
cristy8f25aa82013-02-23 02:14:37 +0000148
dirk05d2ff72015-11-18 23:13:43 +0100149static inline uint32_t ReadWebPLSBWord(
150 const unsigned char *magick_restrict data)
cristy8f25aa82013-02-23 02:14:37 +0000151{
Cristyf2dc1dd2020-12-28 13:59:26 -0500152 const unsigned char
cristy0b7a0332013-02-23 02:28:08 +0000153 *p;
cristy8f25aa82013-02-23 02:14:37 +0000154
Cristyf2dc1dd2020-12-28 13:59:26 -0500155 uint32_t
cristy0b7a0332013-02-23 02:28:08 +0000156 value;
157
158 p=data;
159 value=(uint32_t) (*p++);
160 value|=((uint32_t) (*p++)) << 8;
161 value|=((uint32_t) (*p++)) << 16;
162 value|=((uint32_t) (*p++)) << 24;
163 return(value);
cristy8f25aa82013-02-23 02:14:37 +0000164}
165
166static MagickBooleanType IsWEBPImageLossless(const unsigned char *stream,
167 const size_t length)
168{
cristy0b7a0332013-02-23 02:28:08 +0000169#define VP8_CHUNK_INDEX 15
cristy8f25aa82013-02-23 02:14:37 +0000170#define LOSSLESS_FLAG 'L'
171#define EXTENDED_HEADER 'X'
172#define VP8_CHUNK_HEADER "VP8"
173#define VP8_CHUNK_HEADER_SIZE 3
cristy0b7a0332013-02-23 02:28:08 +0000174#define RIFF_HEADER_SIZE 12
cristy8f25aa82013-02-23 02:14:37 +0000175#define VP8X_CHUNK_SIZE 10
176#define TAG_SIZE 4
177#define CHUNK_SIZE_BYTES 4
178#define CHUNK_HEADER_SIZE 8
cristya230a612014-01-05 20:51:47 +0000179#define MAX_CHUNK_PAYLOAD (~0U-CHUNK_HEADER_SIZE-1)
cristy8f25aa82013-02-23 02:14:37 +0000180
Cristy27e15b72019-11-17 16:30:19 -0500181 size_t
cristy8f25aa82013-02-23 02:14:37 +0000182 offset;
183
184 /*
185 Read simple header.
186 */
Cristy361ed682018-03-27 20:39:27 -0400187 if (length <= VP8_CHUNK_INDEX)
188 return(MagickFalse);
cristy8f25aa82013-02-23 02:14:37 +0000189 if (stream[VP8_CHUNK_INDEX] != EXTENDED_HEADER)
cristyef632a52013-12-03 11:06:33 +0000190 return(stream[VP8_CHUNK_INDEX] == LOSSLESS_FLAG ? MagickTrue : MagickFalse);
cristy8f25aa82013-02-23 02:14:37 +0000191 /*
192 Read extended header.
193 */
194 offset=RIFF_HEADER_SIZE+TAG_SIZE+CHUNK_SIZE_BYTES+VP8X_CHUNK_SIZE;
Alex Gaynore0ed80f2019-11-17 10:29:04 -0500195 while (offset <= (length-TAG_SIZE-TAG_SIZE-4))
cristy8f25aa82013-02-23 02:14:37 +0000196 {
197 uint32_t
198 chunk_size,
199 chunk_size_pad;
200
cristyef978292013-02-23 02:32:06 +0000201 chunk_size=ReadWebPLSBWord(stream+offset+TAG_SIZE);
cristy8f25aa82013-02-23 02:14:37 +0000202 if (chunk_size > MAX_CHUNK_PAYLOAD)
203 break;
204 chunk_size_pad=(CHUNK_HEADER_SIZE+chunk_size+1) & ~1;
cristy5def2032013-06-30 17:44:08 +0000205 if (memcmp(stream+offset,VP8_CHUNK_HEADER,VP8_CHUNK_HEADER_SIZE) == 0)
cristyef978292013-02-23 02:32:06 +0000206 return(*(stream+offset+VP8_CHUNK_HEADER_SIZE) == LOSSLESS_FLAG ?
cristy8f25aa82013-02-23 02:14:37 +0000207 MagickTrue : MagickFalse);
208 offset+=chunk_size_pad;
209 }
210 return(MagickFalse);
211}
212
Cristy9dd05872019-10-12 10:31:46 -0400213static int FillBasicWEBPInfo(Image *image,const uint8_t *stream,size_t length,
214 WebPDecoderConfig *configure)
215{
Cristy46d4fb52019-09-29 20:32:20 -0400216 WebPBitstreamFeatures
217 *magick_restrict features = &configure->input;
cristyffa663d2011-03-14 00:58:51 +0000218
cristyef632a52013-12-03 11:06:33 +0000219 int
220 webp_status;
221
Cristy46d4fb52019-09-29 20:32:20 -0400222 webp_status=WebPGetFeatures(stream,length,features);
223
224 if (webp_status != VP8_STATUS_OK)
Dirk Lemstraabb91792019-10-26 13:23:52 +0200225 return(webp_status);
Cristy46d4fb52019-09-29 20:32:20 -0400226
227 image->columns=(size_t) features->width;
228 image->rows=(size_t) features->height;
229 image->depth=8;
230 image->alpha_trait=features->has_alpha != 0 ? BlendPixelTrait :
231 UndefinedPixelTrait;
232
Dirk Lemstraabb91792019-10-26 13:23:52 +0200233 return(webp_status);
Cristy46d4fb52019-09-29 20:32:20 -0400234}
235
Cristy9dd05872019-10-12 10:31:46 -0400236static int ReadSingleWEBPImage(Image *image,const uint8_t *stream,
jace.k (김준성)7b743df2019-12-17 14:57:46 +0900237 size_t length,WebPDecoderConfig *configure,ExceptionInfo *exception,
238 MagickBooleanType is_first)
Cristy9dd05872019-10-12 10:31:46 -0400239{
Cristy46d4fb52019-09-29 20:32:20 -0400240 int
241 webp_status;
cristyffa663d2011-03-14 00:58:51 +0000242
Cristyf2dc1dd2020-12-28 13:59:26 -0500243 unsigned char
cristy644040e2011-03-14 17:31:59 +0000244 *p;
245
jace.k (김준성)7b743df2019-12-17 14:57:46 +0900246 size_t
247 canvas_width,
248 canvas_height,
249 image_width,
250 image_height;
251
cristy644040e2011-03-14 17:31:59 +0000252 ssize_t
jace.k (김준성)7b743df2019-12-17 14:57:46 +0900253 x_offset,
254 y_offset,
cristy644040e2011-03-14 17:31:59 +0000255 y;
256
cristyaf29b622013-02-18 15:06:48 +0000257 WebPDecBuffer
Dirk Lemstra89d15bb2020-12-28 09:56:41 +0100258 *magick_restrict webp_image;
cristyaf29b622013-02-18 15:06:48 +0000259
Cristy46d4fb52019-09-29 20:32:20 -0400260 MagickBooleanType
261 status;
cristy644040e2011-03-14 17:31:59 +0000262
Dirk Lemstra89d15bb2020-12-28 09:56:41 +0100263 webp_image=&configure->output;
jace.k (김준성)c835fda2019-12-18 08:38:52 +0900264 if (is_first)
265 {
266 canvas_width=image->columns;
267 canvas_height=image->rows;
268 x_offset=image->page.x;
269 y_offset=image->page.y;
270 image->page.x=0;
271 image->page.y=0;
272 }
273 else
274 {
275 x_offset=0;
276 y_offset=0;
277 }
jace.k (김준성)7b743df2019-12-17 14:57:46 +0900278 webp_status=FillBasicWEBPInfo(image,stream,length,configure);
279 image_width=image->columns;
280 image_height=image->rows;
281 if (is_first)
282 {
283 image->columns=canvas_width;
284 image->rows=canvas_height;
285 }
286
287 if (webp_status != VP8_STATUS_OK)
Dirk Lemstraabb91792019-10-26 13:23:52 +0200288 return(webp_status);
Cristy46d4fb52019-09-29 20:32:20 -0400289
290 if (IsWEBPImageLossless(stream,length) != MagickFalse)
291 image->quality=100;
292
jace.k (김준성)7b743df2019-12-17 14:57:46 +0900293 webp_status=WebPDecode(stream,length,configure);
294 if (webp_status != VP8_STATUS_OK)
Dirk Lemstraabb91792019-10-26 13:23:52 +0200295 return(webp_status);
Cristy46d4fb52019-09-29 20:32:20 -0400296
cristyb22ab412014-04-06 15:09:41 +0000297 p=(unsigned char *) webp_image->u.RGBA.rgba;
cristy644040e2011-03-14 17:31:59 +0000298 for (y=0; y < (ssize_t) image->rows; y++)
299 {
Cristyf2dc1dd2020-12-28 13:59:26 -0500300 Quantum
cristyaf29b622013-02-18 15:06:48 +0000301 *q;
302
Cristyf2dc1dd2020-12-28 13:59:26 -0500303 ssize_t
cristyaf29b622013-02-18 15:06:48 +0000304 x;
305
cristy644040e2011-03-14 17:31:59 +0000306 q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000307 if (q == (Quantum *) NULL)
cristy644040e2011-03-14 17:31:59 +0000308 break;
309 for (x=0; x < (ssize_t) image->columns; x++)
310 {
Cristyfcaccf42020-12-22 16:20:58 -0500311 if ((x >= x_offset && x < (ssize_t) (x_offset+image_width)) &&
312 (y >= y_offset && y < (ssize_t) (y_offset+image_height)))
jace.k (김준성)c835fda2019-12-18 08:38:52 +0900313 {
314 SetPixelRed(image,ScaleCharToQuantum(*p++),q);
315 SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
316 SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
317 SetPixelAlpha(image,ScaleCharToQuantum(*p++),q);
318 }
319 else
320 {
321 SetPixelRed(image,0,q);
322 SetPixelGreen(image,0,q);
323 SetPixelBlue(image,0,q);
324 SetPixelAlpha(image,0,q);
325 }
cristyed231572011-07-14 02:18:59 +0000326 q+=GetPixelChannels(image);
cristy644040e2011-03-14 17:31:59 +0000327 }
328 if (SyncAuthenticPixels(image,exception) == MagickFalse)
329 break;
330 status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
331 image->rows);
332 if (status == MagickFalse)
333 break;
334 }
cristyaf29b622013-02-18 15:06:48 +0000335 WebPFreeDecBuffer(webp_image);
Cristy692b6782017-11-19 12:35:18 -0500336#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
337 {
338 StringInfo
339 *profile;
340
341 uint32_t
342 webp_flags = 0;
343
344 WebPData
Cristy5e87ab22018-06-24 16:26:06 -0400345 chunk,
Dirk Lemstraabb91792019-10-26 13:23:52 +0200346 content;
Cristy692b6782017-11-19 12:35:18 -0500347
348 WebPMux
349 *mux;
350
351 /*
Cristy5fa0d442020-09-22 14:50:00 -0400352 Extract any profiles:
353 https://developers.google.com/speed/webp/docs/container-api.
Cristy692b6782017-11-19 12:35:18 -0500354 */
Dirk Lemstraabb91792019-10-26 13:23:52 +0200355 content.bytes=stream;
356 content.size=length;
Cristy692b6782017-11-19 12:35:18 -0500357 mux=WebPMuxCreate(&content,0);
Cristy5e87ab22018-06-24 16:26:06 -0400358 (void) memset(&chunk,0,sizeof(chunk));
Cristy692b6782017-11-19 12:35:18 -0500359 WebPMuxGetFeatures(mux,&webp_flags);
360 if (webp_flags & ICCP_FLAG)
361 {
362 WebPMuxGetChunk(mux,"ICCP",&chunk);
363 profile=BlobToStringInfo(chunk.bytes,chunk.size);
364 if (profile != (StringInfo *) NULL)
365 {
366 SetImageProfile(image,"ICC",profile,exception);
367 profile=DestroyStringInfo(profile);
368 }
369 }
370 if (webp_flags & EXIF_FLAG)
371 {
372 WebPMuxGetChunk(mux,"EXIF",&chunk);
373 profile=BlobToStringInfo(chunk.bytes,chunk.size);
374 if (profile != (StringInfo *) NULL)
375 {
376 SetImageProfile(image,"EXIF",profile,exception);
377 profile=DestroyStringInfo(profile);
378 }
379 }
380 if (webp_flags & XMP_FLAG)
381 {
382 WebPMuxGetChunk(mux,"XMP",&chunk);
383 profile=BlobToStringInfo(chunk.bytes,chunk.size);
384 if (profile != (StringInfo *) NULL)
385 {
386 SetImageProfile(image,"XMP",profile,exception);
387 profile=DestroyStringInfo(profile);
388 }
389 }
390 WebPMuxDelete(mux);
391 }
392#endif
Dirk Lemstraabb91792019-10-26 13:23:52 +0200393 return(webp_status);
Cristy46d4fb52019-09-29 20:32:20 -0400394}
395
396#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
Cristy7caee072019-11-17 10:54:58 -0500397static int ReadAnimatedWEBPImage(const ImageInfo *image_info,Image *image,
398 uint8_t *stream,size_t length,WebPDecoderConfig *configure,
Dirk Lemstraabb91792019-10-26 13:23:52 +0200399 ExceptionInfo *exception)
400{
401 Image
402 *original_image;
Cristy46d4fb52019-09-29 20:32:20 -0400403
404 int
Dirk Lemstraabb91792019-10-26 13:23:52 +0200405 image_count,
406 webp_status;
Cristy46d4fb52019-09-29 20:32:20 -0400407
jace.k (김준성)7b743df2019-12-17 14:57:46 +0900408 size_t
409 canvas_width,
410 canvas_height;
411
Dirk Lemstraabb91792019-10-26 13:23:52 +0200412 WebPData
413 data;
Cristy46d4fb52019-09-29 20:32:20 -0400414
Dirk Lemstraabb91792019-10-26 13:23:52 +0200415 WebPDemuxer
416 *demux;
Cristy46d4fb52019-09-29 20:32:20 -0400417
Dirk Lemstraabb91792019-10-26 13:23:52 +0200418 WebPIterator
419 iter;
420
421 image_count=0;
422 webp_status=0;
423 original_image=image;
jace.k (김준성)7b743df2019-12-17 14:57:46 +0900424 webp_status=FillBasicWEBPInfo(image,stream,length,configure);
425 canvas_width=image->columns;
426 canvas_height=image->rows;
Dirk Lemstraabb91792019-10-26 13:23:52 +0200427 data.bytes=stream;
428 data.size=length;
Cristy0e421ed2020-12-01 00:58:17 +0000429 {
430 WebPMux
431 *mux;
432
433 WebPMuxAnimParams
434 params;
435
436 WebPMuxError
437 status;
438
439 mux=WebPMuxCreate(&data,0);
440 status=WebPMuxGetAnimationParams(mux,&params);
441 if (status >= 0)
442 image->iterations=params.loop_count;
443 WebPMuxDelete(mux);
444 }
Dirk Lemstraabb91792019-10-26 13:23:52 +0200445 demux=WebPDemux(&data);
Cristyfab704b2020-08-03 18:46:38 -0400446 if (WebPDemuxGetFrame(demux,1,&iter))
447 {
448 do
449 {
450 if (image_count != 0)
451 {
452 AcquireNextImage(image_info,image,exception);
453 if (GetNextImageInList(image) == (Image *) NULL)
454 break;
455 image=SyncNextImageInList(image);
456 CloneImageProperties(image,original_image);
457 image->page.x=iter.x_offset;
458 image->page.y=iter.y_offset;
459 webp_status=ReadSingleWEBPImage(image,iter.fragment.bytes,
460 iter.fragment.size,configure,exception,MagickFalse);
461 }
462 else
463 {
464 image->page.x=iter.x_offset;
465 image->page.y=iter.y_offset;
466 webp_status=ReadSingleWEBPImage(image,iter.fragment.bytes,
467 iter.fragment.size,configure,exception,MagickTrue);
468 }
469 if (webp_status != VP8_STATUS_OK)
470 break;
471 image->page.width=canvas_width;
472 image->page.height=canvas_height;
473 image->ticks_per_second=100;
474 image->delay=iter.duration/10;
475 image->dispose=NoneDispose;
476 if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND)
477 image->dispose=BackgroundDispose;
478 (void) SetImageProperty(image,"webp:mux-blend",
479 "AtopPreviousAlphaBlend",exception);
480 if (iter.blend_method == WEBP_MUX_BLEND)
481 (void) SetImageProperty(image,"webp:mux-blend",
482 "AtopBackgroundAlphaBlend",exception);
483 image_count++;
484 } while (WebPDemuxNextFrame(&iter));
485 WebPDemuxReleaseIterator(&iter);
486 }
Cristy46d4fb52019-09-29 20:32:20 -0400487 WebPDemuxDelete(demux);
Dirk Lemstraabb91792019-10-26 13:23:52 +0200488 return(webp_status);
Cristy46d4fb52019-09-29 20:32:20 -0400489}
490#endif
491
492static Image *ReadWEBPImage(const ImageInfo *image_info,
493 ExceptionInfo *exception)
494{
495#define ThrowWEBPException(severity,tag) \
496{ \
497 if (stream != (unsigned char *) NULL) \
498 stream=(unsigned char*) RelinquishMagickMemory(stream); \
499 if (webp_image != (WebPDecBuffer *) NULL) \
500 WebPFreeDecBuffer(webp_image); \
501 ThrowReaderException(severity,tag); \
502}
503
504 Image
505 *image;
506
507 int
508 webp_status;
509
510 MagickBooleanType
511 status;
512
513 size_t
514 length;
515
516 ssize_t
517 count;
518
519 unsigned char
520 header[12],
521 *stream;
522
523 WebPDecoderConfig
524 configure;
525
526 WebPDecBuffer
527 *magick_restrict webp_image = &configure.output;
528
529 /*
530 Open image file.
531 */
532 assert(image_info != (const ImageInfo *) NULL);
533 assert(image_info->signature == MagickCoreSignature);
534 if (image_info->debug != MagickFalse)
535 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
536 image_info->filename);
537 assert(exception != (ExceptionInfo *) NULL);
538 assert(exception->signature == MagickCoreSignature);
539 image=AcquireImage(image_info,exception);
540 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
541 if (status == MagickFalse)
542 {
543 image=DestroyImageList(image);
544 return((Image *) NULL);
545 }
546 stream=(unsigned char *) NULL;
547 if (WebPInitDecoderConfig(&configure) == 0)
548 ThrowReaderException(ResourceLimitError,"UnableToDecodeImageFile");
549 webp_image->colorspace=MODE_RGBA;
550 count=ReadBlob(image,12,header);
551 if (count != 12)
552 ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
553 status=IsWEBP(header,count);
554 if (status == MagickFalse)
555 ThrowWEBPException(CorruptImageError,"CorruptImage");
556 length=(size_t) (ReadWebPLSBWord(header+4)+8);
557 if (length < 12)
558 ThrowWEBPException(CorruptImageError,"CorruptImage");
559 if (length > GetBlobSize(image))
560 ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
561 stream=(unsigned char *) AcquireQuantumMemory(length,sizeof(*stream));
562 if (stream == (unsigned char *) NULL)
563 ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
564 (void) memcpy(stream,header,12);
565 count=ReadBlob(image,length-12,stream+12);
566 if (count != (ssize_t) (length-12))
567 ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
568
569 webp_status=FillBasicWEBPInfo(image,stream,length,&configure);
570 if (webp_status == VP8_STATUS_OK) {
Cristy46d4fb52019-09-29 20:32:20 -0400571 if (configure.input.has_animation) {
572#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
Cristy7caee072019-11-17 10:54:58 -0500573 webp_status=ReadAnimatedWEBPImage(image_info,image,stream,length,
574 &configure,exception);
Cristy46d4fb52019-09-29 20:32:20 -0400575#else
Alex Gaynord58edd32019-09-30 07:41:01 -0400576 webp_status=VP8_STATUS_UNSUPPORTED_FEATURE;
Cristy46d4fb52019-09-29 20:32:20 -0400577#endif
578 } else {
jace.k (김준성)7b743df2019-12-17 14:57:46 +0900579 webp_status=ReadSingleWEBPImage(image,stream,length,&configure,exception,MagickFalse);
Cristy46d4fb52019-09-29 20:32:20 -0400580 }
581 }
582
583 if (webp_status != VP8_STATUS_OK)
584 switch (webp_status)
585 {
586 case VP8_STATUS_OUT_OF_MEMORY:
587 {
588 ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
589 break;
590 }
591 case VP8_STATUS_INVALID_PARAM:
592 {
593 ThrowWEBPException(CorruptImageError,"invalid parameter");
594 break;
595 }
596 case VP8_STATUS_BITSTREAM_ERROR:
597 {
598 ThrowWEBPException(CorruptImageError,"CorruptImage");
599 break;
600 }
601 case VP8_STATUS_UNSUPPORTED_FEATURE:
602 {
603 ThrowWEBPException(CoderError,"DataEncodingSchemeIsNotSupported");
604 break;
605 }
606 case VP8_STATUS_SUSPENDED:
607 {
608 ThrowWEBPException(CorruptImageError,"decoder suspended");
609 break;
610 }
611 case VP8_STATUS_USER_ABORT:
612 {
613 ThrowWEBPException(CorruptImageError,"user abort");
614 break;
615 }
616 case VP8_STATUS_NOT_ENOUGH_DATA:
617 {
618 ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
619 break;
620 }
621 default:
622 ThrowWEBPException(CorruptImageError,"CorruptImage");
623 }
624
cristye71e0892013-02-18 13:13:53 +0000625 stream=(unsigned char*) RelinquishMagickMemory(stream);
zhangjife757a22017-02-17 18:34:45 +0800626 (void) CloseBlob(image);
cristyffa663d2011-03-14 00:58:51 +0000627 return(image);
cristyb1860752011-03-14 00:27:46 +0000628}
629#endif
630
631/*
632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
633% %
634% %
635% %
636% R e g i s t e r W E B P I m a g e %
637% %
638% %
639% %
640%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
641%
642% RegisterWEBPImage() adds attributes for the WebP image format to
643% the list of supported formats. The attributes include the image format
644% tag, a method to read and/or write the format, whether the format
645% supports the saving of more than one frame to the same file or blob,
646% whether the format supports native in-memory I/O, and a brief
647% description of the format.
648%
649% The format of the RegisterWEBPImage method is:
650%
651% size_t RegisterWEBPImage(void)
652%
653*/
654ModuleExport size_t RegisterWEBPImage(void)
655{
cristyaf29b622013-02-18 15:06:48 +0000656 char
cristy151b66d2015-04-15 10:50:31 +0000657 version[MagickPathExtent];
cristyaf29b622013-02-18 15:06:48 +0000658
cristyb1860752011-03-14 00:27:46 +0000659 MagickInfo
660 *entry;
661
cristyaf29b622013-02-18 15:06:48 +0000662 *version='\0';
dirk06b627a2015-04-06 18:59:17 +0000663 entry=AcquireMagickInfo("WEBP","WEBP","WebP Image Format");
cristyb1860752011-03-14 00:27:46 +0000664#if defined(MAGICKCORE_WEBP_DELEGATE)
665 entry->decoder=(DecodeImageHandler *) ReadWEBPImage;
666 entry->encoder=(EncodeImageHandler *) WriteWEBPImage;
cristy151b66d2015-04-15 10:50:31 +0000667 (void) FormatLocaleString(version,MagickPathExtent,"libwebp %d.%d.%d [%04X]",
よや5ce163f2019-02-10 22:42:15 +0900668 (WebPGetEncoderVersion() >> 16) & 0xff,
669 (WebPGetEncoderVersion() >> 8) & 0xff,
670 (WebPGetEncoderVersion() >> 0) & 0xff,WEBP_ENCODER_ABI_VERSION);
cristyb1860752011-03-14 00:27:46 +0000671#endif
dirk8648daf2015-09-14 22:26:43 +0200672 entry->mime_type=ConstantString("image/webp");
Cristycb635602017-08-02 06:59:15 -0400673 entry->flags|=CoderDecoderSeekableStreamFlag;
Dirk Lemstra47f757a2020-01-10 14:58:40 +0100674#if !defined(MAGICKCORE_WEBPMUX_DELEGATE)
675 entry->flags^=CoderAdjoinFlag;
Dirk Lemstra2424fb22020-01-10 14:55:54 +0100676#endif
cristydffa79e2013-02-20 00:57:26 +0000677 entry->magick=(IsImageFormatHandler *) IsWEBP;
cristyaf29b622013-02-18 15:06:48 +0000678 if (*version != '\0')
679 entry->version=ConstantString(version);
cristyb1860752011-03-14 00:27:46 +0000680 (void) RegisterMagickInfo(entry);
681 return(MagickImageCoderSignature);
682}
683
684/*
685%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
686% %
687% %
688% %
689% U n r e g i s t e r W E B P I m a g e %
690% %
691% %
692% %
693%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
694%
695% UnregisterWEBPImage() removes format registrations made by the WebP module
696% from the list of supported formats.
697%
698% The format of the UnregisterWEBPImage method is:
699%
700% UnregisterWEBPImage(void)
701%
702*/
703ModuleExport void UnregisterWEBPImage(void)
704{
705 (void) UnregisterMagickInfo("WEBP");
706}
707#if defined(MAGICKCORE_WEBP_DELEGATE)
708
709/*
710%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
711% %
712% %
713% %
714% W r i t e W E B P I m a g e %
715% %
716% %
717% %
718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
719%
720% WriteWEBPImage() writes an image in the WebP image format.
721%
722% The format of the WriteWEBPImage method is:
723%
724% MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
725% Image *image)
726%
727% A description of each parameter follows.
728%
729% o image_info: the image info.
730%
731% o image: The image.
732%
733*/
cristy644040e2011-03-14 17:31:59 +0000734
よや5ce163f2019-02-10 22:42:15 +0900735#if WEBP_ENCODER_ABI_VERSION >= 0x0100
cristyb22ab412014-04-06 15:09:41 +0000736static int WebPEncodeProgress(int percent,const WebPPicture* picture)
737{
cristy88bf7c12014-04-06 15:21:42 +0000738#define EncodeImageTag "Encode/Image"
739
cristyb22ab412014-04-06 15:09:41 +0000740 Image
741 *image;
742
743 MagickBooleanType
744 status;
745
Cristycf4ad302017-12-23 08:32:30 -0500746 image=(Image *) picture->user_data;
cristy88bf7c12014-04-06 15:21:42 +0000747 status=SetImageProgress(image,EncodeImageTag,percent-1,100);
cristyb22ab412014-04-06 15:09:41 +0000748 return(status == MagickFalse ? 0 : 1);
749}
750#endif
751
Cristy5642ab12017-11-19 14:37:48 -0500752#if !defined(MAGICKCORE_WEBPMUX_DELEGATE)
cristyb22ab412014-04-06 15:09:41 +0000753static int WebPEncodeWriter(const unsigned char *stream,size_t length,
cristy644040e2011-03-14 17:31:59 +0000754 const WebPPicture *const picture)
755{
756 Image
757 *image;
758
759 image=(Image *) picture->custom_ptr;
Cristy6ba049c2018-01-20 15:08:44 -0500760 return(length != 0 ? (WriteBlob(image,length,stream) == (ssize_t) length) : 1);
cristy644040e2011-03-14 17:31:59 +0000761}
Cristy5642ab12017-11-19 14:37:48 -0500762#endif
cristy644040e2011-03-14 17:31:59 +0000763
tomwei717e76432020-11-26 23:34:47 +0800764static const char * WebPErrorCodeMessage(WebPEncodingError error_code)
765{
766 switch (error_code)
767 {
768 case VP8_ENC_OK:
769 return NULL;
770 case VP8_ENC_ERROR_OUT_OF_MEMORY:
771 return "out of memory";
772 case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
773 return "bitstream out of memory";
774 case VP8_ENC_ERROR_NULL_PARAMETER:
775 return "NULL parameter";
776 case VP8_ENC_ERROR_INVALID_CONFIGURATION:
777 return "invalid configuration";
778 case VP8_ENC_ERROR_BAD_DIMENSION:
779 return "bad dimension";
780 case VP8_ENC_ERROR_PARTITION0_OVERFLOW:
781 return "partition 0 overflow (> 512K)";
782 case VP8_ENC_ERROR_PARTITION_OVERFLOW:
783 return "partition overflow (> 16M)";
784 case VP8_ENC_ERROR_BAD_WRITE:
785 return "bad write";
786 case VP8_ENC_ERROR_FILE_TOO_BIG:
787 return "file too big (> 4GB)";
788#if WEBP_ENCODER_ABI_VERSION >= 0x0100
789 case VP8_ENC_ERROR_USER_ABORT:
790 return "user abort";
791 case VP8_ENC_ERROR_LAST:
792 return "error last";
793#endif
794 }
795 return "unknown exception";
796}
797
798static MagickBooleanType WriteSingleWEBPPicture(const ImageInfo *image_info,
799 Image *image,WebPConfig *configure,WebPPicture *picture,ExceptionInfo *exception)
Cristy46d4fb52019-09-29 20:32:20 -0400800{
801 MagickBooleanType
Dirk Lemstraacc06822020-12-28 10:26:17 +0100802 status;
Cristy46d4fb52019-09-29 20:32:20 -0400803
Cristyf2dc1dd2020-12-28 13:59:26 -0500804 uint32_t
Cristy46d4fb52019-09-29 20:32:20 -0400805 *magick_restrict q;
806
807 ssize_t
808 y;
809
tomwei717e76432020-11-26 23:34:47 +0800810 MemoryInfo
811 *pixel_info;
812
Cristy46d4fb52019-09-29 20:32:20 -0400813#if WEBP_ENCODER_ABI_VERSION >= 0x0100
814 picture->progress_hook=WebPEncodeProgress;
815 picture->user_data=(void *) image;
816#endif
817 picture->width=(int) image->columns;
818 picture->height=(int) image->rows;
819 picture->argb_stride=(int) image->columns;
820 picture->use_argb=1;
821
822 /*
823 Allocate memory for pixels.
824 */
825 (void) TransformImageColorspace(image,sRGBColorspace,exception);
tomwei717e76432020-11-26 23:34:47 +0800826 pixel_info=AcquireVirtualMemory(image->columns,image->rows*
Cristy46d4fb52019-09-29 20:32:20 -0400827 sizeof(*(picture->argb)));
828
tomwei717e76432020-11-26 23:34:47 +0800829 if (pixel_info == (MemoryInfo *) NULL)
Cristy46d4fb52019-09-29 20:32:20 -0400830 ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
tomwei717e76432020-11-26 23:34:47 +0800831 picture->argb=(uint32_t *) GetVirtualMemoryBlob(pixel_info);
Cristy46d4fb52019-09-29 20:32:20 -0400832 /*
833 Convert image to WebP raster pixels.
834 */
Dirk Lemstraacc06822020-12-28 10:26:17 +0100835 status=MagickFalse;
Cristy46d4fb52019-09-29 20:32:20 -0400836 q=picture->argb;
837 for (y=0; y < (ssize_t) image->rows; y++)
838 {
Cristyf2dc1dd2020-12-28 13:59:26 -0500839 const Quantum
Cristy46d4fb52019-09-29 20:32:20 -0400840 *magick_restrict p;
841
Cristyf2dc1dd2020-12-28 13:59:26 -0500842 ssize_t
Cristy46d4fb52019-09-29 20:32:20 -0400843 x;
844
845 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
846 if (p == (const Quantum *) NULL)
847 break;
848 for (x=0; x < (ssize_t) image->columns; x++)
849 {
850 *q++=(uint32_t) (image->alpha_trait != UndefinedPixelTrait ? (uint32_t)
851 ScaleQuantumToChar(GetPixelAlpha(image,p)) << 24 : 0xff000000) |
852 ((uint32_t) ScaleQuantumToChar(GetPixelRed(image,p)) << 16) |
853 ((uint32_t) ScaleQuantumToChar(GetPixelGreen(image,p)) << 8) |
854 ((uint32_t) ScaleQuantumToChar(GetPixelBlue(image,p)));
855 p+=GetPixelChannels(image);
856 }
857 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
858 image->rows);
859 if (status == MagickFalse)
860 break;
861 }
tomwei717e76432020-11-26 23:34:47 +0800862
Dirk Lemstraacc06822020-12-28 10:26:17 +0100863 if (status != MagickFalse)
864 status=(MagickBooleanType) WebPEncode(configure, picture);
tomwei717e76432020-11-26 23:34:47 +0800865
866 RelinquishVirtualMemory(pixel_info);
867
Dirk Lemstraacc06822020-12-28 10:26:17 +0100868 if (status == MagickFalse)
tomwei717e76432020-11-26 23:34:47 +0800869 (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
Dirk Lemstraacc06822020-12-28 10:26:17 +0100870 WebPErrorCodeMessage(picture->error_code),"`%s'",
871 image->filename);
tomwei717e76432020-11-26 23:34:47 +0800872
Dirk Lemstraacc06822020-12-28 10:26:17 +0100873 return(status);
tomwei717e76432020-11-26 23:34:47 +0800874}
875
876static MagickBooleanType WriteSingleWEBPImage(const ImageInfo *image_info,
Dirk Lemstrad2197622020-12-28 15:57:43 +0100877 Image *image,WebPConfig *configure,WebPMemoryWriter *writer,
Dirk Lemstraacc06822020-12-28 10:26:17 +0100878 ExceptionInfo *exception)
tomwei717e76432020-11-26 23:34:47 +0800879{
880 MagickBooleanType
Dirk Lemstraacc06822020-12-28 10:26:17 +0100881 status;
tomwei717e76432020-11-26 23:34:47 +0800882
883 WebPPicture
884 picture;
885
Dirk Lemstraacc06822020-12-28 10:26:17 +0100886 if (WebPPictureInit(&picture) == 0)
tomwei717e76432020-11-26 23:34:47 +0800887 ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
888
tomwei717e76432020-11-26 23:34:47 +0800889 picture.writer=WebPMemoryWrite;
Dirk Lemstrad2197622020-12-28 15:57:43 +0100890 picture.custom_ptr=writer;
tomwei717e76432020-11-26 23:34:47 +0800891
Dirk Lemstraacc06822020-12-28 10:26:17 +0100892 status=WriteSingleWEBPPicture(image_info,image,configure,&picture,exception);
tomwei717e76432020-11-26 23:34:47 +0800893 WebPPictureFree(&picture);
894
Dirk Lemstraacc06822020-12-28 10:26:17 +0100895 return(status);
Cristy46d4fb52019-09-29 20:32:20 -0400896}
897
898#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
899static MagickBooleanType WriteAnimatedWEBPImage(const ImageInfo *image_info,
Dirk Lemstraacc06822020-12-28 10:26:17 +0100900 Image *image,WebPConfig *configure,WebPData *webp_data,
901 ExceptionInfo *exception)
Cristy46d4fb52019-09-29 20:32:20 -0400902{
Cristy46d4fb52019-09-29 20:32:20 -0400903 Image
Dirk Lemstraabb91792019-10-26 13:23:52 +0200904 *first_image;
905
Cristy7caee072019-11-17 10:54:58 -0500906 size_t
Dirk Lemstraacc06822020-12-28 10:26:17 +0100907 effective_delta,
908 frame_timestamp;
Cristy7caee072019-11-17 10:54:58 -0500909
Dirk Lemstraabb91792019-10-26 13:23:52 +0200910 WebPAnimEncoder
911 *enc;
912
913 WebPAnimEncoderOptions
914 enc_options;
915
Dirk Lemstraabb91792019-10-26 13:23:52 +0200916 WebPPicture
917 picture;
Cristy46d4fb52019-09-29 20:32:20 -0400918
tomwei72d2e0142020-12-27 23:52:46 +0800919 MagickBooleanType
Dirk Lemstraacc06822020-12-28 10:26:17 +0100920 status;
tomwei72d2e0142020-12-27 23:52:46 +0800921
922 first_image=CoalesceImages(image,exception);
tomwei72d2e0142020-12-27 23:52:46 +0800923 if (first_image == (Image *) NULL)
924 return(MagickFalse);
Dirk Lemstraacc06822020-12-28 10:26:17 +0100925 image=first_image;
926 effective_delta=0;
927 frame_timestamp=0;
tomwei72d2e0142020-12-27 23:52:46 +0800928
Dirk Lemstraacc06822020-12-28 10:26:17 +0100929 (void) WebPAnimEncoderOptionsInit(&enc_options);
Cristy46d4fb52019-09-29 20:32:20 -0400930 if (image_info->verbose)
Dirk Lemstraacc06822020-12-28 10:26:17 +0100931 enc_options.verbose=1;
Dirk Lemstraabb91792019-10-26 13:23:52 +0200932 enc=WebPAnimEncoderNew((int) image->page.width,(int) image->page.height,
933 &enc_options);
Cristy46d4fb52019-09-29 20:32:20 -0400934
Dirk Lemstraacc06822020-12-28 10:26:17 +0100935 status=MagickTrue;
Cristy6f84d892019-10-29 21:32:11 -0400936 while (image != NULL)
Dirk Lemstraabb91792019-10-26 13:23:52 +0200937 {
Dirk Lemstraacc06822020-12-28 10:26:17 +0100938 status=(MagickBooleanType) WebPPictureInit(&picture);
939 if (status == MagickFalse)
tomwei72d2e0142020-12-27 23:52:46 +0800940 {
941 (void) ThrowMagickException(exception,GetMagickModule(),
942 ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
943 break;
944 }
Cristy46d4fb52019-09-29 20:32:20 -0400945
Dirk Lemstraacc06822020-12-28 10:26:17 +0100946 status=WriteSingleWEBPPicture(image_info,image,configure,&picture,
947 exception);
948 if (status == MagickFalse)
tomwei72d2e0142020-12-27 23:52:46 +0800949 break;
950
Dirk Lemstraacc06822020-12-28 10:26:17 +0100951 status=(MagickBooleanType) WebPAnimEncoderAdd(enc,&picture,
952 (int) frame_timestamp,configure);
tomwei717e76432020-11-26 23:34:47 +0800953 WebPPictureFree(&picture);
Dirk Lemstraacc06822020-12-28 10:26:17 +0100954 if (status == MagickFalse)
tomwei72d2e0142020-12-27 23:52:46 +0800955 {
956 (void) ThrowMagickException(exception,GetMagickModule(),
957 CoderError,WebPAnimEncoderGetError(enc),"`%s'",image->filename);
958 break;
959 }
Cristy46d4fb52019-09-29 20:32:20 -0400960
Dirk Lemstraacc06822020-12-28 10:26:17 +0100961 effective_delta=image->delay*1000/image->ticks_per_second;
Cristy46d4fb52019-09-29 20:32:20 -0400962 if (effective_delta < 10)
Dirk Lemstraacc06822020-12-28 10:26:17 +0100963 effective_delta=100; /* Consistent with gif2webp */
Cristy46d4fb52019-09-29 20:32:20 -0400964 frame_timestamp+=effective_delta;
965
tomwei72d2e0142020-12-27 23:52:46 +0800966 image=GetNextImageInList(image);
Cristy46d4fb52019-09-29 20:32:20 -0400967 }
tomwei717e76432020-11-26 23:34:47 +0800968
Dirk Lemstraacc06822020-12-28 10:26:17 +0100969 if (status != MagickFalse)
tomwei72d2e0142020-12-27 23:52:46 +0800970 {
971 // add last null frame and assemble picture.
Dirk Lemstraacc06822020-12-28 10:26:17 +0100972 status=(MagickBooleanType) WebPAnimEncoderAdd(enc,(WebPPicture *) NULL,
973 (int) frame_timestamp,configure);
974 if (status != MagickFalse)
975 status=(MagickBooleanType) WebPAnimEncoderAssemble(enc,webp_data);
tomwei72d2e0142020-12-27 23:52:46 +0800976
Dirk Lemstraacc06822020-12-28 10:26:17 +0100977 if (status == MagickFalse)
tomwei72d2e0142020-12-27 23:52:46 +0800978 (void) ThrowMagickException(exception,GetMagickModule(),
Dirk Lemstraacc06822020-12-28 10:26:17 +0100979 CoderError,WebPAnimEncoderGetError(enc),"`%s'",
980 first_image->filename);
tomwei72d2e0142020-12-27 23:52:46 +0800981 }
tomwei717e76432020-11-26 23:34:47 +0800982
Cristy46d4fb52019-09-29 20:32:20 -0400983 WebPAnimEncoderDelete(enc);
984 DestroyImageList(first_image);
tomwei72d2e0142020-12-27 23:52:46 +0800985 return(status);
Cristy46d4fb52019-09-29 20:32:20 -0400986}
tomwei717e76432020-11-26 23:34:47 +0800987
Dirk Lemstraacc06822020-12-28 10:26:17 +0100988static MagickBooleanType WriteWEBPImageProfile(Image *image,
989 WebPData *webp_data,ExceptionInfo *exception)
tomwei717e76432020-11-26 23:34:47 +0800990{
991 const StringInfo
992 *icc_profile,
993 *exif_profile,
994 *xmp_profile;
995
996 WebPData
997 chunk;
998
999 WebPMux
1000 *mux;
1001
1002 WebPMuxError
1003 mux_error=WEBP_MUX_OK;
1004
1005 icc_profile=GetImageProfile(image,"ICC");
1006 exif_profile=GetImageProfile(image,"EXIF");
1007 xmp_profile=GetImageProfile(image,"XMP");
1008
1009 if (icc_profile == (StringInfo *) NULL
1010 && exif_profile == (StringInfo *) NULL
1011 && xmp_profile == (StringInfo *) NULL)
Dirk Lemstraacc06822020-12-28 10:26:17 +01001012 return(MagickTrue);
tomwei717e76432020-11-26 23:34:47 +08001013
1014 mux=WebPMuxCreate(webp_data, 1);
1015 WebPDataClear(webp_data);
1016
1017 if (mux == NULL)
1018 (void) ThrowMagickException(exception,GetMagickModule(),
1019 ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
1020
1021 // Clean up returned data
1022 memset(webp_data, 0, sizeof(*webp_data));
1023 if (icc_profile != (StringInfo *) NULL && (mux_error == WEBP_MUX_OK))
1024 {
1025 chunk.bytes=GetStringInfoDatum(icc_profile);
1026 chunk.size=GetStringInfoLength(icc_profile);
1027 mux_error=WebPMuxSetChunk(mux,"ICCP",&chunk,0);
1028 }
1029 if (exif_profile != (StringInfo *) NULL && (mux_error == WEBP_MUX_OK))
1030 {
1031 chunk.bytes=GetStringInfoDatum(exif_profile);
1032 chunk.size=GetStringInfoLength(exif_profile);
1033 mux_error=WebPMuxSetChunk(mux,"EXIF",&chunk,0);
1034 }
1035 if (xmp_profile != (StringInfo *) NULL && (mux_error == WEBP_MUX_OK))
1036 {
1037 chunk.bytes=GetStringInfoDatum(xmp_profile);
1038 chunk.size=GetStringInfoLength(xmp_profile);
1039 mux_error=WebPMuxSetChunk(mux,"XMP",&chunk,0);
1040 }
1041 if (mux_error == WEBP_MUX_OK)
1042 mux_error=WebPMuxAssemble(mux,webp_data);
1043 WebPMuxDelete(mux);
1044
1045 if (mux_error != WEBP_MUX_OK)
1046 (void) ThrowMagickException(exception,GetMagickModule(),
1047 ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
Dirk Lemstraacc06822020-12-28 10:26:17 +01001048 return(MagickTrue);
tomwei717e76432020-11-26 23:34:47 +08001049}
Cristy46d4fb52019-09-29 20:32:20 -04001050#endif
1051
Dirk Lemstra4e056d62020-12-30 11:09:32 +01001052static inline void SetIntegerOption(const ImageInfo *image_info,
1053 const char *option,int *setting)
1054{
1055 const char
1056 *value;
1057
1058 value=GetImageOption(image_info,option);
1059 if (value != (const char *) NULL)
1060 *setting=StringToInteger(value);
1061}
1062
cristyb1860752011-03-14 00:27:46 +00001063static MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
Cristy46d4fb52019-09-29 20:32:20 -04001064 Image *image,ExceptionInfo * exception)
cristyb1860752011-03-14 00:27:46 +00001065{
cristye71e0892013-02-18 13:13:53 +00001066 const char
1067 *value;
1068
cristyffa663d2011-03-14 00:58:51 +00001069 MagickBooleanType
1070 status;
1071
cristy644040e2011-03-14 17:31:59 +00001072 WebPConfig
1073 configure;
1074
Dirk Lemstrad2197622020-12-28 15:57:43 +01001075 WebPMemoryWriter
1076 writer;
Cristy46d4fb52019-09-29 20:32:20 -04001077
cristyffa663d2011-03-14 00:58:51 +00001078 /*
1079 Open output image file.
1080 */
1081 assert(image_info != (const ImageInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001082 assert(image_info->signature == MagickCoreSignature);
cristyffa663d2011-03-14 00:58:51 +00001083 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001084 assert(image->signature == MagickCoreSignature);
cristyffa663d2011-03-14 00:58:51 +00001085 if (image->debug != MagickFalse)
1086 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy57790302013-02-18 18:19:08 +00001087 if ((image->columns > 16383UL) || (image->rows > 16383UL))
cristye4d08fd2012-04-26 17:29:08 +00001088 ThrowWriterException(ImageError,"WidthOrHeightExceedsLimit");
cristyc82a27b2011-10-21 01:07:16 +00001089 status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
cristyffa663d2011-03-14 00:58:51 +00001090 if (status == MagickFalse)
1091 return(status);
Cristy46d4fb52019-09-29 20:32:20 -04001092 if (WebPConfigInit(&configure) == 0)
1093 ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
cristy35c63c72012-10-23 14:40:54 +00001094 if (image->quality != UndefinedCompressionQuality)
1095 configure.quality=(float) image->quality;
cristy7ab1ba82014-11-13 21:37:21 +00001096 if (image->quality >= 100)
1097 configure.lossless=1;
cristy092ec8d2013-04-26 13:46:22 +00001098 value=GetImageOption(image_info,"webp:lossless");
cristye71e0892013-02-18 13:13:53 +00001099 if (value != (char *) NULL)
cristyb22ab412014-04-06 15:09:41 +00001100 configure.lossless=(int) ParseCommandOption(MagickBooleanOptions,
1101 MagickFalse,value);
cristy092ec8d2013-04-26 13:46:22 +00001102 value=GetImageOption(image_info,"webp:image-hint");
cristye71e0892013-02-18 13:13:53 +00001103 if (value != (char *) NULL)
cristyd6f2cc42013-02-18 17:24:19 +00001104 {
cristy9bb4d7a2014-04-20 01:41:32 +00001105 if (LocaleCompare(value,"default") == 0)
1106 configure.image_hint=WEBP_HINT_DEFAULT;
cristy547fe7f2013-02-18 17:27:13 +00001107 if (LocaleCompare(value,"photo") == 0)
cristyd6f2cc42013-02-18 17:24:19 +00001108 configure.image_hint=WEBP_HINT_PHOTO;
cristy547fe7f2013-02-18 17:27:13 +00001109 if (LocaleCompare(value,"picture") == 0)
cristyd6f2cc42013-02-18 17:24:19 +00001110 configure.image_hint=WEBP_HINT_PICTURE;
よや5ce163f2019-02-10 22:42:15 +09001111#if WEBP_ENCODER_ABI_VERSION >= 0x0200
cristy9bb4d7a2014-04-20 01:41:32 +00001112 if (LocaleCompare(value,"graph") == 0)
1113 configure.image_hint=WEBP_HINT_GRAPH;
1114#endif
cristyd6f2cc42013-02-18 17:24:19 +00001115 }
cristy092ec8d2013-04-26 13:46:22 +00001116 value=GetImageOption(image_info,"webp:auto-filter");
cristye71e0892013-02-18 13:13:53 +00001117 if (value != (char *) NULL)
cristyb22ab412014-04-06 15:09:41 +00001118 configure.autofilter=(int) ParseCommandOption(MagickBooleanOptions,
1119 MagickFalse,value);
Dirk Lemstra4e056d62020-12-30 11:09:32 +01001120 value=GetImageOption(image_info,"webp:target-psnr");
cristye71e0892013-02-18 13:13:53 +00001121 if (value != (char *) NULL)
Dirk Lemstra4e056d62020-12-30 11:09:32 +01001122 configure.target_PSNR=(float) StringToDouble(value,(char **) NULL);
1123 SetIntegerOption(image_info,"webp:alpha-compression",
1124 &configure.alpha_compression);
1125 SetIntegerOption(image_info,"webp:alpha-filtering",
1126 &configure.alpha_filtering);
1127 SetIntegerOption(image_info,"webp:alpha-quality",
1128 &configure.alpha_quality);
1129 SetIntegerOption(image_info,"webp:filter-strength",
1130 &configure.filter_strength);
1131 SetIntegerOption(image_info,"webp:filter-sharpness",
1132 &configure.filter_sharpness);
1133 SetIntegerOption(image_info,"webp:filter-type",&configure.filter_type);
1134 SetIntegerOption(image_info,"webp:method",&configure.method);
1135 SetIntegerOption(image_info,"webp:partitions",&configure.partitions);
1136 SetIntegerOption(image_info,"webp:partition-limit",
1137 &configure.partition_limit);
1138 SetIntegerOption(image_info,"webp:pass",&configure.pass);
1139 SetIntegerOption(image_info,"webp:preprocessing",&configure.preprocessing);
1140 SetIntegerOption(image_info,"webp:segments",&configure.segments);
1141 SetIntegerOption(image_info,"webp:show-compressed",
1142 &configure.show_compressed);
1143 SetIntegerOption(image_info,"webp:sns-strength",&configure.sns_strength);
1144 SetIntegerOption(image_info,"webp:target-size",&configure.target_size);
よや5ce163f2019-02-10 22:42:15 +09001145#if WEBP_ENCODER_ABI_VERSION >= 0x0201
cristy17e26ea2014-08-10 23:30:23 +00001146 value=GetImageOption(image_info,"webp:emulate-jpeg-size");
cristy9bb4d7a2014-04-20 01:41:32 +00001147 if (value != (char *) NULL)
1148 configure.emulate_jpeg_size=(int) ParseCommandOption(MagickBooleanOptions,
1149 MagickFalse,value);
cristy7ab1ba82014-11-13 21:37:21 +00001150 value=GetImageOption(image_info,"webp:low-memory");
1151 if (value != (char *) NULL)
1152 configure.low_memory=(int) ParseCommandOption(MagickBooleanOptions,
1153 MagickFalse,value);
Dirk Lemstra4e056d62020-12-30 11:09:32 +01001154 SetIntegerOption(image_info,"webp:thread-level",&configure.thread_level);
cristy9bb4d7a2014-04-20 01:41:32 +00001155#endif
よや5ce163f2019-02-10 22:42:15 +09001156#if WEBP_ENCODER_ABI_VERSION >= 0x020e
Dirk Lemstra0b770a92020-12-30 11:14:36 +01001157 SetIntegerOption(image_info,"webp:near-lossless",&configure.near_lossless);
Dirk Lemstra4e056d62020-12-30 11:09:32 +01001158 SetIntegerOption(image_info,"webp:use-sharp-yuv",&configure.use_sharp_yuv);
Cristyaa5bcab2019-01-31 07:38:50 -05001159#endif
cristy644040e2011-03-14 17:31:59 +00001160 if (WebPValidateConfig(&configure) == 0)
1161 ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
Cristy46d4fb52019-09-29 20:32:20 -04001162#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
Dirk Lemstrad2197622020-12-28 15:57:43 +01001163 {
1164 WebPData
1165 webp_data;
Cristy46d4fb52019-09-29 20:32:20 -04001166
Dirk Lemstrad2197622020-12-28 15:57:43 +01001167 memset(&webp_data,0,sizeof(webp_data));
1168 if ((image_info->adjoin != MagickFalse) &&
1169 (GetPreviousImageInList(image) == (Image *) NULL) &&
1170 (GetNextImageInList(image) != (Image *) NULL))
1171 status=WriteAnimatedWEBPImage(image_info,image,&configure,&webp_data,
1172 exception);
1173 else
1174 {
1175 WebPMemoryWriterInit(&writer);
1176 status=WriteSingleWEBPImage(image_info,image,&configure,&writer,
1177 exception);
1178 if (status == MagickFalse)
1179 WebPMemoryWriterClear(&writer);
1180 else
1181 {
1182 webp_data.bytes=writer.mem;
1183 webp_data.size=writer.size;
1184 }
1185 }
1186 if (status != MagickFalse)
1187 status=WriteWEBPImageProfile(image,&webp_data,exception);
1188 if (status != MagickFalse)
1189 (void) WriteBlob(image,webp_data.size,webp_data.bytes);
1190 WebPDataClear(&webp_data);
1191 }
1192#else
1193 WebPMemoryWriterInit(&writer);
1194 status=WriteSingleWEBPImage(image_info,image,&configure,&writer,
1195 exception);
Dirk Lemstraacc06822020-12-28 10:26:17 +01001196 if (status != MagickFalse)
Dirk Lemstrad2197622020-12-28 15:57:43 +01001197 (void) WriteBlob(image,writer.size,writer.mem);
1198 WebPMemoryWriterClear(&writer);
1199#endif
cristy644040e2011-03-14 17:31:59 +00001200 (void) CloseBlob(image);
tomwei717e76432020-11-26 23:34:47 +08001201 return(status);
cristyb1860752011-03-14 00:27:46 +00001202}
1203#endif