1 /*
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 %
16 % Cristy %
17 % March 2011 %
18 % %
19 % %
20 % Copyright 1999-2019 ImageMagick Studio LLC, a non-profit organization %
21 % 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 % %
26 % https://imagemagick.org/script/license.php %
27 % %
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 */
42 #include "MagickCore/studio.h"
43 #include "MagickCore/artifact.h"
44 #include "MagickCore/blob.h"
45 #include "MagickCore/blob-private.h"
46 #include "MagickCore/client.h"
47 #include "MagickCore/colorspace-private.h"
48 #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"
53 #include "MagickCore/list.h"
54 #include "MagickCore/magick.h"
55 #include "MagickCore/monitor.h"
56 #include "MagickCore/monitor-private.h"
57 #include "MagickCore/memory_.h"
58 #include "MagickCore/option.h"
59 #include "MagickCore/pixel-accessor.h"
60 #include "MagickCore/profile.h"
61 #include "MagickCore/quantum-private.h"
62 #include "MagickCore/static.h"
63 #include "MagickCore/string_.h"
64 #include "MagickCore/string-private.h"
65 #include "MagickCore/module.h"
66 #include "MagickCore/utility.h"
67 #include "MagickCore/xwindow.h"
68 #include "MagickCore/xwindow-private.h"
69 #if defined(MAGICKCORE_WEBP_DELEGATE)
70 #include <webp/decode.h>
71 #include <webp/encode.h>
72 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
73 #include <webp/mux.h>
74 #endif
75 #endif
76
77 /*
78 Forward declarations.
79 */
80 #if defined(MAGICKCORE_WEBP_DELEGATE)
81 static MagickBooleanType
82 WriteWEBPImage(const ImageInfo *,Image *,ExceptionInfo *);
83 #endif
84
85 /*
86 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
87 % %
88 % %
89 % %
90 % I s W E B P %
91 % %
92 % %
93 % %
94 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
95 %
96 % IsWEBP() returns MagickTrue if the image format type, identified by the
97 % magick string, is WebP.
98 %
99 % The format of the IsWEBP method is:
100 %
101 % MagickBooleanType IsWEBP(const unsigned char *magick,const size_t length)
102 %
103 % A description of each parameter follows:
104 %
105 % o magick: compare image format pattern against these bytes.
106 %
107 % o length: Specifies the length of the magick string.
108 %
109 */
IsWEBP(const unsigned char * magick,const size_t length)110 static MagickBooleanType IsWEBP(const unsigned char *magick,const size_t length)
111 {
112 if (length < 12)
113 return(MagickFalse);
114 if (LocaleNCompare((const char *) magick+8,"WEBP",4) == 0)
115 return(MagickTrue);
116 return(MagickFalse);
117 }
118
119 #if defined(MAGICKCORE_WEBP_DELEGATE)
120 /*
121 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
122 % %
123 % %
124 % %
125 % R e a d W E B P I m a g e %
126 % %
127 % %
128 % %
129 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
130 %
131 % ReadWEBPImage() reads an image in the WebP image format.
132 %
133 % The format of the ReadWEBPImage method is:
134 %
135 % Image *ReadWEBPImage(const ImageInfo *image_info,
136 % ExceptionInfo *exception)
137 %
138 % A description of each parameter follows:
139 %
140 % o image_info: the image info.
141 %
142 % o exception: return any errors or warnings in this structure.
143 %
144 */
145
ReadWebPLSBWord(const unsigned char * magick_restrict data)146 static inline uint32_t ReadWebPLSBWord(
147 const unsigned char *magick_restrict data)
148 {
149 register const unsigned char
150 *p;
151
152 register uint32_t
153 value;
154
155 p=data;
156 value=(uint32_t) (*p++);
157 value|=((uint32_t) (*p++)) << 8;
158 value|=((uint32_t) (*p++)) << 16;
159 value|=((uint32_t) (*p++)) << 24;
160 return(value);
161 }
162
IsWEBPImageLossless(const unsigned char * stream,const size_t length)163 static MagickBooleanType IsWEBPImageLossless(const unsigned char *stream,
164 const size_t length)
165 {
166 #define VP8_CHUNK_INDEX 15
167 #define LOSSLESS_FLAG 'L'
168 #define EXTENDED_HEADER 'X'
169 #define VP8_CHUNK_HEADER "VP8"
170 #define VP8_CHUNK_HEADER_SIZE 3
171 #define RIFF_HEADER_SIZE 12
172 #define VP8X_CHUNK_SIZE 10
173 #define TAG_SIZE 4
174 #define CHUNK_SIZE_BYTES 4
175 #define CHUNK_HEADER_SIZE 8
176 #define MAX_CHUNK_PAYLOAD (~0U-CHUNK_HEADER_SIZE-1)
177
178 ssize_t
179 offset;
180
181 /*
182 Read simple header.
183 */
184 if (length <= VP8_CHUNK_INDEX)
185 return(MagickFalse);
186 if (stream[VP8_CHUNK_INDEX] != EXTENDED_HEADER)
187 return(stream[VP8_CHUNK_INDEX] == LOSSLESS_FLAG ? MagickTrue : MagickFalse);
188 /*
189 Read extended header.
190 */
191 offset=RIFF_HEADER_SIZE+TAG_SIZE+CHUNK_SIZE_BYTES+VP8X_CHUNK_SIZE;
192 while (offset+TAG_SIZE <= (ssize_t) (length-TAG_SIZE))
193 {
194 uint32_t
195 chunk_size,
196 chunk_size_pad;
197
198 chunk_size=ReadWebPLSBWord(stream+offset+TAG_SIZE);
199 if (chunk_size > MAX_CHUNK_PAYLOAD)
200 break;
201 chunk_size_pad=(CHUNK_HEADER_SIZE+chunk_size+1) & ~1;
202 if (memcmp(stream+offset,VP8_CHUNK_HEADER,VP8_CHUNK_HEADER_SIZE) == 0)
203 return(*(stream+offset+VP8_CHUNK_HEADER_SIZE) == LOSSLESS_FLAG ?
204 MagickTrue : MagickFalse);
205 offset+=chunk_size_pad;
206 }
207 return(MagickFalse);
208 }
209
ReadWEBPImage(const ImageInfo * image_info,ExceptionInfo * exception)210 static Image *ReadWEBPImage(const ImageInfo *image_info,
211 ExceptionInfo *exception)
212 {
213 #define ThrowWEBPException(severity,tag) \
214 { \
215 if (stream != (unsigned char *) NULL) \
216 stream=(unsigned char*) RelinquishMagickMemory(stream); \
217 if (webp_image != (WebPDecBuffer *) NULL) \
218 WebPFreeDecBuffer(webp_image); \
219 ThrowReaderException(severity,tag); \
220 }
221
222 Image
223 *image;
224
225 int
226 webp_status;
227
228 MagickBooleanType
229 status;
230
231 register unsigned char
232 *p;
233
234 size_t
235 length;
236
237 ssize_t
238 count,
239 y;
240
241 unsigned char
242 header[12],
243 *stream;
244
245 WebPDecoderConfig
246 configure;
247
248 WebPDecBuffer
249 *magick_restrict webp_image = &configure.output;
250
251 WebPBitstreamFeatures
252 *magick_restrict features = &configure.input;
253
254 /*
255 Open image file.
256 */
257 assert(image_info != (const ImageInfo *) NULL);
258 assert(image_info->signature == MagickCoreSignature);
259 if (image_info->debug != MagickFalse)
260 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
261 image_info->filename);
262 assert(exception != (ExceptionInfo *) NULL);
263 assert(exception->signature == MagickCoreSignature);
264 image=AcquireImage(image_info,exception);
265 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
266 if (status == MagickFalse)
267 {
268 image=DestroyImageList(image);
269 return((Image *) NULL);
270 }
271 stream=(unsigned char *) NULL;
272 if (WebPInitDecoderConfig(&configure) == 0)
273 ThrowReaderException(ResourceLimitError,"UnableToDecodeImageFile");
274 webp_image->colorspace=MODE_RGBA;
275 count=ReadBlob(image,12,header);
276 if (count != 12)
277 ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
278 status=IsWEBP(header,count);
279 if (status == MagickFalse)
280 ThrowWEBPException(CorruptImageError,"CorruptImage");
281 length=(size_t) (ReadWebPLSBWord(header+4)+8);
282 if (length < 12)
283 ThrowWEBPException(CorruptImageError,"CorruptImage");
284 if (length > GetBlobSize(image))
285 ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
286 stream=(unsigned char *) AcquireQuantumMemory(length,sizeof(*stream));
287 if (stream == (unsigned char *) NULL)
288 ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
289 (void) memcpy(stream,header,12);
290 count=ReadBlob(image,length-12,stream+12);
291 if (count != (ssize_t) (length-12))
292 ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
293 webp_status=WebPGetFeatures(stream,length,features);
294 if (webp_status == VP8_STATUS_OK)
295 {
296 image->columns=(size_t) features->width;
297 image->rows=(size_t) features->height;
298 image->depth=8;
299 image->alpha_trait=features->has_alpha != 0 ? BlendPixelTrait :
300 UndefinedPixelTrait;
301 if (image_info->ping != MagickFalse)
302 {
303 stream=(unsigned char*) RelinquishMagickMemory(stream);
304 (void) CloseBlob(image);
305 return(GetFirstImageInList(image));
306 }
307 status=SetImageExtent(image,image->columns,image->rows,exception);
308 if (status == MagickFalse)
309 {
310 stream=(unsigned char*) RelinquishMagickMemory(stream);
311 (void) CloseBlob(image);
312 return(DestroyImageList(image));
313 }
314 if (IsWEBPImageLossless(stream,length) != MagickFalse)
315 image->quality=100;
316 webp_status=WebPDecode(stream,length,&configure);
317 }
318 if (webp_status != VP8_STATUS_OK)
319 switch (webp_status)
320 {
321 case VP8_STATUS_OUT_OF_MEMORY:
322 {
323 ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
324 break;
325 }
326 case VP8_STATUS_INVALID_PARAM:
327 {
328 ThrowWEBPException(CorruptImageError,"invalid parameter");
329 break;
330 }
331 case VP8_STATUS_BITSTREAM_ERROR:
332 {
333 ThrowWEBPException(CorruptImageError,"CorruptImage");
334 break;
335 }
336 case VP8_STATUS_UNSUPPORTED_FEATURE:
337 {
338 ThrowWEBPException(CoderError,"DataEncodingSchemeIsNotSupported");
339 break;
340 }
341 case VP8_STATUS_SUSPENDED:
342 {
343 ThrowWEBPException(CorruptImageError,"decoder suspended");
344 break;
345 }
346 case VP8_STATUS_USER_ABORT:
347 {
348 ThrowWEBPException(CorruptImageError,"user abort");
349 break;
350 }
351 case VP8_STATUS_NOT_ENOUGH_DATA:
352 {
353 ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
354 break;
355 }
356 default:
357 ThrowWEBPException(CorruptImageError,"CorruptImage");
358 }
359 p=(unsigned char *) webp_image->u.RGBA.rgba;
360 for (y=0; y < (ssize_t) image->rows; y++)
361 {
362 register Quantum
363 *q;
364
365 register ssize_t
366 x;
367
368 q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
369 if (q == (Quantum *) NULL)
370 break;
371 for (x=0; x < (ssize_t) image->columns; x++)
372 {
373 SetPixelRed(image,ScaleCharToQuantum(*p++),q);
374 SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
375 SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
376 SetPixelAlpha(image,ScaleCharToQuantum(*p++),q);
377 q+=GetPixelChannels(image);
378 }
379 if (SyncAuthenticPixels(image,exception) == MagickFalse)
380 break;
381 status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
382 image->rows);
383 if (status == MagickFalse)
384 break;
385 }
386 WebPFreeDecBuffer(webp_image);
387 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
388 {
389 StringInfo
390 *profile;
391
392 uint32_t
393 webp_flags = 0;
394
395 WebPData
396 chunk,
397 content = { stream, length };
398
399 WebPMux
400 *mux;
401
402 /*
403 Extract any profiles.
404 */
405 mux=WebPMuxCreate(&content,0);
406 (void) memset(&chunk,0,sizeof(chunk));
407 WebPMuxGetFeatures(mux,&webp_flags);
408 if (webp_flags & ICCP_FLAG)
409 {
410 WebPMuxGetChunk(mux,"ICCP",&chunk);
411 profile=BlobToStringInfo(chunk.bytes,chunk.size);
412 if (profile != (StringInfo *) NULL)
413 {
414 SetImageProfile(image,"ICC",profile,exception);
415 profile=DestroyStringInfo(profile);
416 }
417 }
418 if (webp_flags & EXIF_FLAG)
419 {
420 WebPMuxGetChunk(mux,"EXIF",&chunk);
421 profile=BlobToStringInfo(chunk.bytes,chunk.size);
422 if (profile != (StringInfo *) NULL)
423 {
424 SetImageProfile(image,"EXIF",profile,exception);
425 profile=DestroyStringInfo(profile);
426 }
427 }
428 if (webp_flags & XMP_FLAG)
429 {
430 WebPMuxGetChunk(mux,"XMP",&chunk);
431 profile=BlobToStringInfo(chunk.bytes,chunk.size);
432 if (profile != (StringInfo *) NULL)
433 {
434 SetImageProfile(image,"XMP",profile,exception);
435 profile=DestroyStringInfo(profile);
436 }
437 }
438 WebPMuxDelete(mux);
439 }
440 #endif
441 stream=(unsigned char*) RelinquishMagickMemory(stream);
442 (void) CloseBlob(image);
443 return(image);
444 }
445 #endif
446
447 /*
448 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
449 % %
450 % %
451 % %
452 % R e g i s t e r W E B P I m a g e %
453 % %
454 % %
455 % %
456 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
457 %
458 % RegisterWEBPImage() adds attributes for the WebP image format to
459 % the list of supported formats. The attributes include the image format
460 % tag, a method to read and/or write the format, whether the format
461 % supports the saving of more than one frame to the same file or blob,
462 % whether the format supports native in-memory I/O, and a brief
463 % description of the format.
464 %
465 % The format of the RegisterWEBPImage method is:
466 %
467 % size_t RegisterWEBPImage(void)
468 %
469 */
RegisterWEBPImage(void)470 ModuleExport size_t RegisterWEBPImage(void)
471 {
472 char
473 version[MagickPathExtent];
474
475 MagickInfo
476 *entry;
477
478 *version='\0';
479 entry=AcquireMagickInfo("WEBP","WEBP","WebP Image Format");
480 #if defined(MAGICKCORE_WEBP_DELEGATE)
481 entry->decoder=(DecodeImageHandler *) ReadWEBPImage;
482 entry->encoder=(EncodeImageHandler *) WriteWEBPImage;
483 (void) FormatLocaleString(version,MagickPathExtent,"libwebp %d.%d.%d [%04X]",
484 (WebPGetEncoderVersion() >> 16) & 0xff,
485 (WebPGetEncoderVersion() >> 8) & 0xff,
486 (WebPGetEncoderVersion() >> 0) & 0xff,WEBP_ENCODER_ABI_VERSION);
487 #endif
488 entry->mime_type=ConstantString("image/webp");
489 entry->flags|=CoderDecoderSeekableStreamFlag;
490 entry->flags^=CoderAdjoinFlag;
491 entry->magick=(IsImageFormatHandler *) IsWEBP;
492 if (*version != '\0')
493 entry->version=ConstantString(version);
494 (void) RegisterMagickInfo(entry);
495 return(MagickImageCoderSignature);
496 }
497
498 /*
499 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
500 % %
501 % %
502 % %
503 % U n r e g i s t e r W E B P I m a g e %
504 % %
505 % %
506 % %
507 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
508 %
509 % UnregisterWEBPImage() removes format registrations made by the WebP module
510 % from the list of supported formats.
511 %
512 % The format of the UnregisterWEBPImage method is:
513 %
514 % UnregisterWEBPImage(void)
515 %
516 */
UnregisterWEBPImage(void)517 ModuleExport void UnregisterWEBPImage(void)
518 {
519 (void) UnregisterMagickInfo("WEBP");
520 }
521 #if defined(MAGICKCORE_WEBP_DELEGATE)
522
523 /*
524 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
525 % %
526 % %
527 % %
528 % W r i t e W E B P I m a g e %
529 % %
530 % %
531 % %
532 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
533 %
534 % WriteWEBPImage() writes an image in the WebP image format.
535 %
536 % The format of the WriteWEBPImage method is:
537 %
538 % MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
539 % Image *image)
540 %
541 % A description of each parameter follows.
542 %
543 % o image_info: the image info.
544 %
545 % o image: The image.
546 %
547 */
548
549 #if WEBP_ENCODER_ABI_VERSION >= 0x0100
WebPEncodeProgress(int percent,const WebPPicture * picture)550 static int WebPEncodeProgress(int percent,const WebPPicture* picture)
551 {
552 #define EncodeImageTag "Encode/Image"
553
554 Image
555 *image;
556
557 MagickBooleanType
558 status;
559
560 image=(Image *) picture->user_data;
561 status=SetImageProgress(image,EncodeImageTag,percent-1,100);
562 return(status == MagickFalse ? 0 : 1);
563 }
564 #endif
565
566 #if !defined(MAGICKCORE_WEBPMUX_DELEGATE)
WebPEncodeWriter(const unsigned char * stream,size_t length,const WebPPicture * const picture)567 static int WebPEncodeWriter(const unsigned char *stream,size_t length,
568 const WebPPicture *const picture)
569 {
570 Image
571 *image;
572
573 image=(Image *) picture->custom_ptr;
574 return(length != 0 ? (WriteBlob(image,length,stream) == (ssize_t) length) : 1);
575 }
576 #endif
577
WriteWEBPImage(const ImageInfo * image_info,Image * image,ExceptionInfo * exception)578 static MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
579 Image *image,ExceptionInfo *exception)
580 {
581 const char
582 *value;
583
584 int
585 webp_status;
586
587 MagickBooleanType
588 status;
589
590 MemoryInfo
591 *pixel_info;
592
593 register uint32_t
594 *magick_restrict q;
595
596 ssize_t
597 y;
598
599 WebPAuxStats
600 statistics;
601
602 WebPConfig
603 configure;
604
605 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
606 WebPMemoryWriter
607 writer_info;
608 #endif
609
610 WebPPicture
611 picture;
612
613 /*
614 Open output image file.
615 */
616 assert(image_info != (const ImageInfo *) NULL);
617 assert(image_info->signature == MagickCoreSignature);
618 assert(image != (Image *) NULL);
619 assert(image->signature == MagickCoreSignature);
620 if (image->debug != MagickFalse)
621 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
622 if ((image->columns > 16383UL) || (image->rows > 16383UL))
623 ThrowWriterException(ImageError,"WidthOrHeightExceedsLimit");
624 status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
625 if (status == MagickFalse)
626 return(status);
627 if ((WebPPictureInit(&picture) == 0) || (WebPConfigInit(&configure) == 0))
628 ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
629 #if !defined(MAGICKCORE_WEBPMUX_DELEGATE)
630 picture.writer=WebPEncodeWriter;
631 picture.custom_ptr=(void *) image;
632 #else
633 WebPMemoryWriterInit(&writer_info);
634 picture.writer=WebPMemoryWrite;
635 picture.custom_ptr=(&writer_info);
636 #endif
637 #if WEBP_ENCODER_ABI_VERSION >= 0x0100
638 picture.progress_hook=WebPEncodeProgress;
639 picture.user_data=(void *) image;
640 #endif
641 picture.stats=(&statistics);
642 picture.width=(int) image->columns;
643 picture.height=(int) image->rows;
644 picture.argb_stride=(int) image->columns;
645 picture.use_argb=1;
646 if (image->quality != UndefinedCompressionQuality)
647 configure.quality=(float) image->quality;
648 if (image->quality >= 100)
649 configure.lossless=1;
650 value=GetImageOption(image_info,"webp:lossless");
651 if (value != (char *) NULL)
652 configure.lossless=(int) ParseCommandOption(MagickBooleanOptions,
653 MagickFalse,value);
654 value=GetImageOption(image_info,"webp:method");
655 if (value != (char *) NULL)
656 configure.method=StringToInteger(value);
657 value=GetImageOption(image_info,"webp:image-hint");
658 if (value != (char *) NULL)
659 {
660 if (LocaleCompare(value,"default") == 0)
661 configure.image_hint=WEBP_HINT_DEFAULT;
662 if (LocaleCompare(value,"photo") == 0)
663 configure.image_hint=WEBP_HINT_PHOTO;
664 if (LocaleCompare(value,"picture") == 0)
665 configure.image_hint=WEBP_HINT_PICTURE;
666 #if WEBP_ENCODER_ABI_VERSION >= 0x0200
667 if (LocaleCompare(value,"graph") == 0)
668 configure.image_hint=WEBP_HINT_GRAPH;
669 #endif
670 }
671 value=GetImageOption(image_info,"webp:target-size");
672 if (value != (char *) NULL)
673 configure.target_size=StringToInteger(value);
674 value=GetImageOption(image_info,"webp:target-psnr");
675 if (value != (char *) NULL)
676 configure.target_PSNR=(float) StringToDouble(value,(char **) NULL);
677 value=GetImageOption(image_info,"webp:segments");
678 if (value != (char *) NULL)
679 configure.segments=StringToInteger(value);
680 value=GetImageOption(image_info,"webp:sns-strength");
681 if (value != (char *) NULL)
682 configure.sns_strength=StringToInteger(value);
683 value=GetImageOption(image_info,"webp:filter-strength");
684 if (value != (char *) NULL)
685 configure.filter_strength=StringToInteger(value);
686 value=GetImageOption(image_info,"webp:filter-sharpness");
687 if (value != (char *) NULL)
688 configure.filter_sharpness=StringToInteger(value);
689 value=GetImageOption(image_info,"webp:filter-type");
690 if (value != (char *) NULL)
691 configure.filter_type=StringToInteger(value);
692 value=GetImageOption(image_info,"webp:auto-filter");
693 if (value != (char *) NULL)
694 configure.autofilter=(int) ParseCommandOption(MagickBooleanOptions,
695 MagickFalse,value);
696 value=GetImageOption(image_info,"webp:alpha-compression");
697 if (value != (char *) NULL)
698 configure.alpha_compression=StringToInteger(value);
699 value=GetImageOption(image_info,"webp:alpha-filtering");
700 if (value != (char *) NULL)
701 configure.alpha_filtering=StringToInteger(value);
702 value=GetImageOption(image_info,"webp:alpha-quality");
703 if (value != (char *) NULL)
704 configure.alpha_quality=StringToInteger(value);
705 value=GetImageOption(image_info,"webp:pass");
706 if (value != (char *) NULL)
707 configure.pass=StringToInteger(value);
708 value=GetImageOption(image_info,"webp:show-compressed");
709 if (value != (char *) NULL)
710 configure.show_compressed=StringToInteger(value);
711 value=GetImageOption(image_info,"webp:preprocessing");
712 if (value != (char *) NULL)
713 configure.preprocessing=StringToInteger(value);
714 value=GetImageOption(image_info,"webp:partitions");
715 if (value != (char *) NULL)
716 configure.partitions=StringToInteger(value);
717 value=GetImageOption(image_info,"webp:partition-limit");
718 if (value != (char *) NULL)
719 configure.partition_limit=StringToInteger(value);
720 #if WEBP_ENCODER_ABI_VERSION >= 0x0201
721 value=GetImageOption(image_info,"webp:emulate-jpeg-size");
722 if (value != (char *) NULL)
723 configure.emulate_jpeg_size=(int) ParseCommandOption(MagickBooleanOptions,
724 MagickFalse,value);
725 value=GetImageOption(image_info,"webp:low-memory");
726 if (value != (char *) NULL)
727 configure.low_memory=(int) ParseCommandOption(MagickBooleanOptions,
728 MagickFalse,value);
729 value=GetImageOption(image_info,"webp:thread-level");
730 if (value != (char *) NULL)
731 configure.thread_level=StringToInteger(value);
732 #endif
733 #if WEBP_ENCODER_ABI_VERSION >= 0x020e
734 value=GetImageOption(image_info,"webp:use-sharp-yuv");
735 if (value != (char *) NULL)
736 configure.use_sharp_yuv=StringToInteger(value);
737 #endif
738 if (WebPValidateConfig(&configure) == 0)
739 ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
740 /*
741 Allocate memory for pixels.
742 */
743 (void) TransformImageColorspace(image,sRGBColorspace,exception);
744 pixel_info=AcquireVirtualMemory(image->columns,image->rows*
745 sizeof(*picture.argb));
746 if (pixel_info == (MemoryInfo *) NULL)
747 ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
748 picture.argb=(uint32_t *) GetVirtualMemoryBlob(pixel_info);
749 /*
750 Convert image to WebP raster pixels.
751 */
752 q=picture.argb;
753 for (y=0; y < (ssize_t) image->rows; y++)
754 {
755 register const Quantum
756 *magick_restrict p;
757
758 register ssize_t
759 x;
760
761 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
762 if (p == (const Quantum *) NULL)
763 break;
764 for (x=0; x < (ssize_t) image->columns; x++)
765 {
766 *q++=(uint32_t) (image->alpha_trait != UndefinedPixelTrait ? (uint32_t)
767 ScaleQuantumToChar(GetPixelAlpha(image,p)) << 24 : 0xff000000) |
768 ((uint32_t) ScaleQuantumToChar(GetPixelRed(image,p)) << 16) |
769 ((uint32_t) ScaleQuantumToChar(GetPixelGreen(image,p)) << 8) |
770 ((uint32_t) ScaleQuantumToChar(GetPixelBlue(image,p)));
771 p+=GetPixelChannels(image);
772 }
773 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
774 image->rows);
775 if (status == MagickFalse)
776 break;
777 }
778 webp_status=WebPEncode(&configure,&picture);
779 if (webp_status == 0)
780 {
781 const char
782 *message;
783
784 switch (picture.error_code)
785 {
786 case VP8_ENC_ERROR_OUT_OF_MEMORY:
787 {
788 message="out of memory";
789 break;
790 }
791 case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
792 {
793 message="bitstream out of memory";
794 break;
795 }
796 case VP8_ENC_ERROR_NULL_PARAMETER:
797 {
798 message="NULL parameter";
799 break;
800 }
801 case VP8_ENC_ERROR_INVALID_CONFIGURATION:
802 {
803 message="invalid configuration";
804 break;
805 }
806 case VP8_ENC_ERROR_BAD_DIMENSION:
807 {
808 message="bad dimension";
809 break;
810 }
811 case VP8_ENC_ERROR_PARTITION0_OVERFLOW:
812 {
813 message="partition 0 overflow (> 512K)";
814 break;
815 }
816 case VP8_ENC_ERROR_PARTITION_OVERFLOW:
817 {
818 message="partition overflow (> 16M)";
819 break;
820 }
821 case VP8_ENC_ERROR_BAD_WRITE:
822 {
823 message="bad write";
824 break;
825 }
826 case VP8_ENC_ERROR_FILE_TOO_BIG:
827 {
828 message="file too big (> 4GB)";
829 break;
830 }
831 #if WEBP_ENCODER_ABI_VERSION >= 0x0100
832 case VP8_ENC_ERROR_USER_ABORT:
833 {
834 message="user abort";
835 break;
836 }
837 #endif
838 default:
839 {
840 message="unknown exception";
841 break;
842 }
843 }
844 (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
845 (char *) message,"`%s'",image->filename);
846 }
847 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
848 {
849 const StringInfo
850 *profile;
851
852 WebPData
853 chunk,
854 image_chunk = { writer_info.mem, writer_info.size };
855
856 WebPMux
857 *mux;
858
859 WebPMuxError
860 mux_error;
861
862 /*
863 Set image profiles (if any).
864 */
865 mux_error=WEBP_MUX_OK;
866 (void) memset(&chunk,0,sizeof(chunk));
867 mux=WebPMuxNew();
868 profile=GetImageProfile(image,"ICC");
869 if ((profile != (StringInfo *) NULL) && (mux_error == WEBP_MUX_OK))
870 {
871 chunk.bytes=GetStringInfoDatum(profile);
872 chunk.size=GetStringInfoLength(profile);
873 mux_error=WebPMuxSetChunk(mux,"ICCP",&chunk,0);
874 }
875 profile=GetImageProfile(image,"EXIF");
876 if ((profile != (StringInfo *) NULL) && (mux_error == WEBP_MUX_OK))
877 {
878 chunk.bytes=GetStringInfoDatum(profile);
879 chunk.size=GetStringInfoLength(profile);
880 mux_error=WebPMuxSetChunk(mux,"EXIF",&chunk,0);
881 }
882 profile=GetImageProfile(image,"XMP");
883 if ((profile != (StringInfo *) NULL) && (mux_error == WEBP_MUX_OK))
884 {
885 chunk.bytes=GetStringInfoDatum(profile);
886 chunk.size=GetStringInfoLength(profile);
887 mux_error=WebPMuxSetChunk(mux,"XMP",&chunk,0);
888 }
889 if (mux_error != WEBP_MUX_OK)
890 (void) ThrowMagickException(exception,GetMagickModule(),
891 ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
892 if (chunk.size != 0)
893 {
894 WebPData
895 picture_profiles = { writer_info.mem, writer_info.size };
896
897 /*
898 Replace original container with image profile (if any).
899 */
900 WebPMuxSetImage(mux,&image_chunk,1);
901 mux_error=WebPMuxAssemble(mux,&picture_profiles);
902 WebPMemoryWriterClear(&writer_info);
903 writer_info.size=picture_profiles.size;
904 writer_info.mem=(unsigned char *) picture_profiles.bytes;
905 }
906 WebPMuxDelete(mux);
907 }
908 (void) WriteBlob(image,writer_info.size,writer_info.mem);
909 #endif
910 picture.argb=(uint32_t *) NULL;
911 WebPPictureFree(&picture);
912 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
913 WebPMemoryWriterClear(&writer_info);
914 #endif
915 pixel_info=RelinquishVirtualMemory(pixel_info);
916 (void) CloseBlob(image);
917 return(webp_status == 0 ? MagickFalse : MagickTrue);
918 }
919 #endif
920