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-2021 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/layer.h"
54 #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"
61 #include "MagickCore/profile.h"
62 #include "MagickCore/property.h"
63 #include "MagickCore/quantum-private.h"
64 #include "MagickCore/static.h"
65 #include "MagickCore/string_.h"
66 #include "MagickCore/string-private.h"
67 #include "MagickCore/module.h"
68 #include "MagickCore/utility.h"
69 #include "MagickCore/xwindow.h"
70 #include "MagickCore/xwindow-private.h"
71 #if defined(MAGICKCORE_WEBP_DELEGATE)
72 #include <webp/decode.h>
73 #include <webp/encode.h>
74 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
75 #include <webp/mux.h>
76 #include <webp/demux.h>
77 #endif
78 #endif
79 
80 /*
81   Forward declarations.
82 */
83 #if defined(MAGICKCORE_WEBP_DELEGATE)
84 static MagickBooleanType
85   WriteWEBPImage(const ImageInfo *,Image *,ExceptionInfo *);
86 #endif
87 
88 /*
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 */
IsWEBP(const unsigned char * magick,const size_t length)113 static 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 
122 #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 */
148 
ReadWebPLSBWord(const unsigned char * magick_restrict data)149 static inline uint32_t ReadWebPLSBWord(
150   const unsigned char *magick_restrict data)
151 {
152   const unsigned char
153     *p;
154 
155   uint32_t
156     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);
164 }
165 
IsWEBPImageLossless(const unsigned char * stream,const size_t length)166 static MagickBooleanType IsWEBPImageLossless(const unsigned char *stream,
167   const size_t length)
168 {
169 #define VP8_CHUNK_INDEX  15
170 #define LOSSLESS_FLAG  'L'
171 #define EXTENDED_HEADER  'X'
172 #define VP8_CHUNK_HEADER  "VP8"
173 #define VP8_CHUNK_HEADER_SIZE  3
174 #define RIFF_HEADER_SIZE  12
175 #define VP8X_CHUNK_SIZE  10
176 #define TAG_SIZE  4
177 #define CHUNK_SIZE_BYTES  4
178 #define CHUNK_HEADER_SIZE  8
179 #define MAX_CHUNK_PAYLOAD  (~0U-CHUNK_HEADER_SIZE-1)
180 
181   size_t
182     offset;
183 
184   /*
185     Read simple header.
186   */
187   if (length <= VP8_CHUNK_INDEX)
188     return(MagickFalse);
189   if (stream[VP8_CHUNK_INDEX] != EXTENDED_HEADER)
190     return(stream[VP8_CHUNK_INDEX] == LOSSLESS_FLAG ? MagickTrue : MagickFalse);
191   /*
192     Read extended header.
193   */
194   offset=RIFF_HEADER_SIZE+TAG_SIZE+CHUNK_SIZE_BYTES+VP8X_CHUNK_SIZE;
195   while (offset <= (length-TAG_SIZE-TAG_SIZE-4))
196   {
197     uint32_t
198       chunk_size,
199       chunk_size_pad;
200 
201     chunk_size=ReadWebPLSBWord(stream+offset+TAG_SIZE);
202     if (chunk_size > MAX_CHUNK_PAYLOAD)
203       break;
204     chunk_size_pad=(CHUNK_HEADER_SIZE+chunk_size+1) & ~1;
205     if (memcmp(stream+offset,VP8_CHUNK_HEADER,VP8_CHUNK_HEADER_SIZE) == 0)
206       return(*(stream+offset+VP8_CHUNK_HEADER_SIZE) == LOSSLESS_FLAG ?
207         MagickTrue : MagickFalse);
208     offset+=chunk_size_pad;
209   }
210   return(MagickFalse);
211 }
212 
FillBasicWEBPInfo(Image * image,const uint8_t * stream,size_t length,WebPDecoderConfig * configure)213 static int FillBasicWEBPInfo(Image *image,const uint8_t *stream,size_t length,
214   WebPDecoderConfig *configure)
215 {
216   WebPBitstreamFeatures
217     *magick_restrict features = &configure->input;
218 
219   int
220     webp_status;
221 
222   webp_status=WebPGetFeatures(stream,length,features);
223 
224   if (webp_status != VP8_STATUS_OK)
225     return(webp_status);
226 
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 
233   return(webp_status);
234 }
235 
ReadSingleWEBPImage(Image * image,const uint8_t * stream,size_t length,WebPDecoderConfig * configure,ExceptionInfo * exception,MagickBooleanType is_first)236 static int ReadSingleWEBPImage(Image *image,const uint8_t *stream,
237   size_t length,WebPDecoderConfig *configure,ExceptionInfo *exception,
238   MagickBooleanType is_first)
239 {
240   int
241     webp_status;
242 
243   unsigned char
244     *p;
245 
246   size_t
247     canvas_width,
248     canvas_height,
249     image_width,
250     image_height;
251 
252   ssize_t
253     x_offset,
254     y_offset,
255     y;
256 
257   WebPDecBuffer
258     *magick_restrict webp_image;
259 
260   MagickBooleanType
261     status;
262 
263   webp_image=&configure->output;
264   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       canvas_width=0;
276       canvas_height=0;
277       x_offset=0;
278       y_offset=0;
279     }
280   webp_status=FillBasicWEBPInfo(image,stream,length,configure);
281   image_width=image->columns;
282   image_height=image->rows;
283   if (is_first)
284     {
285       image->columns=canvas_width;
286       image->rows=canvas_height;
287     }
288 
289   if (webp_status != VP8_STATUS_OK)
290     return(webp_status);
291 
292   if (IsWEBPImageLossless(stream,length) != MagickFalse)
293     image->quality=100;
294 
295   webp_status=WebPDecode(stream,length,configure);
296   if (webp_status != VP8_STATUS_OK)
297     return(webp_status);
298 
299   p=(unsigned char *) webp_image->u.RGBA.rgba;
300   for (y=0; y < (ssize_t) image->rows; y++)
301   {
302     Quantum
303       *q;
304 
305     ssize_t
306       x;
307 
308     q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
309     if (q == (Quantum *) NULL)
310       break;
311     for (x=0; x < (ssize_t) image->columns; x++)
312     {
313       if ((x >= x_offset && x < (ssize_t) (x_offset+image_width)) &&
314           (y >= y_offset && y < (ssize_t) (y_offset+image_height)))
315         {
316           SetPixelRed(image,ScaleCharToQuantum(*p++),q);
317           SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
318           SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
319           SetPixelAlpha(image,ScaleCharToQuantum(*p++),q);
320         }
321       else
322         {
323           SetPixelRed(image,0,q);
324           SetPixelGreen(image,0,q);
325           SetPixelBlue(image,0,q);
326           SetPixelAlpha(image,0,q);
327         }
328       q+=GetPixelChannels(image);
329     }
330     if (SyncAuthenticPixels(image,exception) == MagickFalse)
331       break;
332     status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
333       image->rows);
334     if (status == MagickFalse)
335       break;
336   }
337   WebPFreeDecBuffer(webp_image);
338 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
339   {
340     StringInfo
341       *profile;
342 
343     uint32_t
344       webp_flags = 0;
345 
346     WebPData
347      chunk,
348      content;
349 
350     WebPMux
351       *mux;
352 
353     /*
354       Extract any profiles:
355       https://developers.google.com/speed/webp/docs/container-api.
356     */
357     content.bytes=stream;
358     content.size=length;
359     mux=WebPMuxCreate(&content,0);
360     (void) memset(&chunk,0,sizeof(chunk));
361     WebPMuxGetFeatures(mux,&webp_flags);
362     if (webp_flags & ICCP_FLAG)
363       {
364         WebPMuxGetChunk(mux,"ICCP",&chunk);
365         profile=BlobToStringInfo(chunk.bytes,chunk.size);
366         if (profile != (StringInfo *) NULL)
367           {
368             SetImageProfile(image,"ICC",profile,exception);
369             profile=DestroyStringInfo(profile);
370           }
371       }
372     if (webp_flags & EXIF_FLAG)
373       {
374         WebPMuxGetChunk(mux,"EXIF",&chunk);
375         profile=BlobToStringInfo(chunk.bytes,chunk.size);
376         if (profile != (StringInfo *) NULL)
377           {
378             SetImageProfile(image,"EXIF",profile,exception);
379             profile=DestroyStringInfo(profile);
380           }
381       }
382     if (webp_flags & XMP_FLAG)
383       {
384         WebPMuxGetChunk(mux,"XMP",&chunk);
385         profile=BlobToStringInfo(chunk.bytes,chunk.size);
386         if (profile != (StringInfo *) NULL)
387           {
388             SetImageProfile(image,"XMP",profile,exception);
389             profile=DestroyStringInfo(profile);
390           }
391       }
392     WebPMuxDelete(mux);
393   }
394 #endif
395   return(webp_status);
396 }
397 
398 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
ReadAnimatedWEBPImage(const ImageInfo * image_info,Image * image,uint8_t * stream,size_t length,WebPDecoderConfig * configure,ExceptionInfo * exception)399 static int ReadAnimatedWEBPImage(const ImageInfo *image_info,Image *image,
400   uint8_t *stream,size_t length,WebPDecoderConfig *configure,
401   ExceptionInfo *exception)
402 {
403   Image
404     *original_image;
405 
406   int
407     image_count,
408     webp_status;
409 
410   size_t
411     canvas_width,
412     canvas_height;
413 
414   WebPData
415     data;
416 
417   WebPDemuxer
418     *demux;
419 
420   WebPIterator
421     iter;
422 
423   image_count=0;
424   webp_status=0;
425   original_image=image;
426   webp_status=FillBasicWEBPInfo(image,stream,length,configure);
427   canvas_width=image->columns;
428   canvas_height=image->rows;
429   data.bytes=stream;
430   data.size=length;
431   {
432     WebPMux
433       *mux;
434 
435     WebPMuxAnimParams
436       params;
437 
438     WebPMuxError
439       status;
440 
441     mux=WebPMuxCreate(&data,0);
442     status=WebPMuxGetAnimationParams(mux,&params);
443     if (status >= 0)
444       image->iterations=params.loop_count;
445     WebPMuxDelete(mux);
446   }
447   demux=WebPDemux(&data);
448   if (WebPDemuxGetFrame(demux,1,&iter))
449     {
450       do
451       {
452         if (image_count != 0)
453           {
454             AcquireNextImage(image_info,image,exception);
455             if (GetNextImageInList(image) == (Image *) NULL)
456               break;
457             image=SyncNextImageInList(image);
458             CloneImageProperties(image,original_image);
459             image->page.x=iter.x_offset;
460             image->page.y=iter.y_offset;
461             webp_status=ReadSingleWEBPImage(image,iter.fragment.bytes,
462               iter.fragment.size,configure,exception,MagickFalse);
463           }
464         else
465           {
466             image->page.x=iter.x_offset;
467             image->page.y=iter.y_offset;
468             webp_status=ReadSingleWEBPImage(image,iter.fragment.bytes,
469               iter.fragment.size,configure,exception,MagickTrue);
470           }
471         if (webp_status != VP8_STATUS_OK)
472           break;
473         image->page.width=canvas_width;
474         image->page.height=canvas_height;
475         image->ticks_per_second=100;
476         image->delay=iter.duration/10;
477         image->dispose=NoneDispose;
478         if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND)
479           image->dispose=BackgroundDispose;
480         (void) SetImageProperty(image,"webp:mux-blend",
481           "AtopPreviousAlphaBlend",exception);
482         if (iter.blend_method == WEBP_MUX_BLEND)
483           (void) SetImageProperty(image,"webp:mux-blend",
484             "AtopBackgroundAlphaBlend",exception);
485         image_count++;
486       } while (WebPDemuxNextFrame(&iter));
487       WebPDemuxReleaseIterator(&iter);
488     }
489   WebPDemuxDelete(demux);
490   return(webp_status);
491 }
492 #endif
493 
ReadWEBPImage(const ImageInfo * image_info,ExceptionInfo * exception)494 static Image *ReadWEBPImage(const ImageInfo *image_info,
495   ExceptionInfo *exception)
496 {
497 #define ThrowWEBPException(severity,tag) \
498 { \
499   if (stream != (unsigned char *) NULL) \
500     stream=(unsigned char*) RelinquishMagickMemory(stream); \
501   if (webp_image != (WebPDecBuffer *) NULL) \
502     WebPFreeDecBuffer(webp_image); \
503   ThrowReaderException(severity,tag); \
504 }
505 
506   Image
507     *image;
508 
509   int
510     webp_status;
511 
512   MagickBooleanType
513     status;
514 
515   size_t
516     length;
517 
518   ssize_t
519     count;
520 
521   unsigned char
522     header[12],
523     *stream;
524 
525   WebPDecoderConfig
526     configure;
527 
528   WebPDecBuffer
529     *magick_restrict webp_image = &configure.output;
530 
531   /*
532     Open image file.
533   */
534   assert(image_info != (const ImageInfo *) NULL);
535   assert(image_info->signature == MagickCoreSignature);
536   if (image_info->debug != MagickFalse)
537     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
538       image_info->filename);
539   assert(exception != (ExceptionInfo *) NULL);
540   assert(exception->signature == MagickCoreSignature);
541   image=AcquireImage(image_info,exception);
542   status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
543   if (status == MagickFalse)
544     {
545       image=DestroyImageList(image);
546       return((Image *) NULL);
547     }
548   stream=(unsigned char *) NULL;
549   if (WebPInitDecoderConfig(&configure) == 0)
550     ThrowReaderException(ResourceLimitError,"UnableToDecodeImageFile");
551   webp_image->colorspace=MODE_RGBA;
552   count=ReadBlob(image,12,header);
553   if (count != 12)
554     ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
555   status=IsWEBP(header,count);
556   if (status == MagickFalse)
557     ThrowWEBPException(CorruptImageError,"CorruptImage");
558   length=(size_t) (ReadWebPLSBWord(header+4)+8);
559   if (length < 12)
560     ThrowWEBPException(CorruptImageError,"CorruptImage");
561   if (length > GetBlobSize(image))
562     ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
563   stream=(unsigned char *) AcquireQuantumMemory(length,sizeof(*stream));
564   if (stream == (unsigned char *) NULL)
565     ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
566   (void) memcpy(stream,header,12);
567   count=ReadBlob(image,length-12,stream+12);
568   if (count != (ssize_t) (length-12))
569     ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
570 
571   webp_status=FillBasicWEBPInfo(image,stream,length,&configure);
572   if (webp_status == VP8_STATUS_OK) {
573     if (configure.input.has_animation) {
574 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
575       webp_status=ReadAnimatedWEBPImage(image_info,image,stream,length,
576         &configure,exception);
577 #else
578       webp_status=VP8_STATUS_UNSUPPORTED_FEATURE;
579 #endif
580     } else {
581       webp_status=ReadSingleWEBPImage(image,stream,length,&configure,exception,MagickFalse);
582     }
583   }
584 
585   if (webp_status != VP8_STATUS_OK)
586     switch (webp_status)
587     {
588       case VP8_STATUS_OUT_OF_MEMORY:
589       {
590         ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
591         break;
592       }
593       case VP8_STATUS_INVALID_PARAM:
594       {
595         ThrowWEBPException(CorruptImageError,"invalid parameter");
596         break;
597       }
598       case VP8_STATUS_BITSTREAM_ERROR:
599       {
600         ThrowWEBPException(CorruptImageError,"CorruptImage");
601         break;
602       }
603       case VP8_STATUS_UNSUPPORTED_FEATURE:
604       {
605         ThrowWEBPException(CoderError,"DataEncodingSchemeIsNotSupported");
606         break;
607       }
608       case VP8_STATUS_SUSPENDED:
609       {
610         ThrowWEBPException(CorruptImageError,"decoder suspended");
611         break;
612       }
613       case VP8_STATUS_USER_ABORT:
614       {
615         ThrowWEBPException(CorruptImageError,"user abort");
616         break;
617       }
618       case VP8_STATUS_NOT_ENOUGH_DATA:
619       {
620         ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
621         break;
622       }
623       default:
624         ThrowWEBPException(CorruptImageError,"CorruptImage");
625     }
626 
627   stream=(unsigned char*) RelinquishMagickMemory(stream);
628   (void) CloseBlob(image);
629   return(image);
630 }
631 #endif
632 
633 /*
634 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
635 %                                                                             %
636 %                                                                             %
637 %                                                                             %
638 %   R e g i s t e r W E B P I m a g e                                         %
639 %                                                                             %
640 %                                                                             %
641 %                                                                             %
642 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
643 %
644 %  RegisterWEBPImage() adds attributes for the WebP image format to
645 %  the list of supported formats.  The attributes include the image format
646 %  tag, a method to read and/or write the format, whether the format
647 %  supports the saving of more than one frame to the same file or blob,
648 %  whether the format supports native in-memory I/O, and a brief
649 %  description of the format.
650 %
651 %  The format of the RegisterWEBPImage method is:
652 %
653 %      size_t RegisterWEBPImage(void)
654 %
655 */
RegisterWEBPImage(void)656 ModuleExport size_t RegisterWEBPImage(void)
657 {
658   char
659     version[MagickPathExtent];
660 
661   MagickInfo
662     *entry;
663 
664   *version='\0';
665   entry=AcquireMagickInfo("WEBP","WEBP","WebP Image Format");
666 #if defined(MAGICKCORE_WEBP_DELEGATE)
667   entry->decoder=(DecodeImageHandler *) ReadWEBPImage;
668   entry->encoder=(EncodeImageHandler *) WriteWEBPImage;
669   (void) FormatLocaleString(version,MagickPathExtent,"libwebp %d.%d.%d [%04X]",
670     (WebPGetEncoderVersion() >> 16) & 0xff,
671     (WebPGetEncoderVersion() >> 8) & 0xff,
672     (WebPGetEncoderVersion() >> 0) & 0xff,WEBP_ENCODER_ABI_VERSION);
673 #endif
674   entry->mime_type=ConstantString("image/webp");
675   entry->flags|=CoderDecoderSeekableStreamFlag;
676 #if !defined(MAGICKCORE_WEBPMUX_DELEGATE)
677   entry->flags^=CoderAdjoinFlag;
678 #endif
679   entry->magick=(IsImageFormatHandler *) IsWEBP;
680   if (*version != '\0')
681     entry->version=ConstantString(version);
682   (void) RegisterMagickInfo(entry);
683   return(MagickImageCoderSignature);
684 }
685 
686 /*
687 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
688 %                                                                             %
689 %                                                                             %
690 %                                                                             %
691 %   U n r e g i s t e r W E B P I m a g e                                     %
692 %                                                                             %
693 %                                                                             %
694 %                                                                             %
695 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
696 %
697 %  UnregisterWEBPImage() removes format registrations made by the WebP module
698 %  from the list of supported formats.
699 %
700 %  The format of the UnregisterWEBPImage method is:
701 %
702 %      UnregisterWEBPImage(void)
703 %
704 */
UnregisterWEBPImage(void)705 ModuleExport void UnregisterWEBPImage(void)
706 {
707   (void) UnregisterMagickInfo("WEBP");
708 }
709 #if defined(MAGICKCORE_WEBP_DELEGATE)
710 
711 /*
712 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
713 %                                                                             %
714 %                                                                             %
715 %                                                                             %
716 %   W r i t e W E B P I m a g e                                               %
717 %                                                                             %
718 %                                                                             %
719 %                                                                             %
720 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
721 %
722 %  WriteWEBPImage() writes an image in the WebP image format.
723 %
724 %  The format of the WriteWEBPImage method is:
725 %
726 %      MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
727 %        Image *image)
728 %
729 %  A description of each parameter follows.
730 %
731 %    o image_info: the image info.
732 %
733 %    o image:  The image.
734 %
735 */
736 
737 #if WEBP_ENCODER_ABI_VERSION >= 0x0100
WebPEncodeProgress(int percent,const WebPPicture * picture)738 static int WebPEncodeProgress(int percent,const WebPPicture* picture)
739 {
740 #define EncodeImageTag  "Encode/Image"
741 
742   Image
743     *image;
744 
745   MagickBooleanType
746     status;
747 
748   image=(Image *) picture->user_data;
749   status=SetImageProgress(image,EncodeImageTag,percent-1,100);
750   return(status == MagickFalse ? 0 : 1);
751 }
752 #endif
753 
WebPErrorCodeMessage(WebPEncodingError error_code)754 static const char * WebPErrorCodeMessage(WebPEncodingError error_code)
755 {
756   switch (error_code)
757   {
758     case VP8_ENC_OK:
759       return NULL;
760     case VP8_ENC_ERROR_OUT_OF_MEMORY:
761       return "out of memory";
762     case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
763       return "bitstream out of memory";
764     case VP8_ENC_ERROR_NULL_PARAMETER:
765       return "NULL parameter";
766     case VP8_ENC_ERROR_INVALID_CONFIGURATION:
767       return "invalid configuration";
768     case VP8_ENC_ERROR_BAD_DIMENSION:
769       return "bad dimension";
770     case VP8_ENC_ERROR_PARTITION0_OVERFLOW:
771       return "partition 0 overflow (> 512K)";
772     case VP8_ENC_ERROR_PARTITION_OVERFLOW:
773       return "partition overflow (> 16M)";
774     case VP8_ENC_ERROR_BAD_WRITE:
775       return "bad write";
776     case VP8_ENC_ERROR_FILE_TOO_BIG:
777       return "file too big (> 4GB)";
778 #if WEBP_ENCODER_ABI_VERSION >= 0x0100
779     case VP8_ENC_ERROR_USER_ABORT:
780       return "user abort";
781     case VP8_ENC_ERROR_LAST:
782       return "error last";
783 #endif
784   }
785   return "unknown exception";
786 }
787 
WriteSingleWEBPPicture(const ImageInfo * image_info,Image * image,WebPConfig * configure,WebPPicture * picture,ExceptionInfo * exception)788 static MagickBooleanType WriteSingleWEBPPicture(const ImageInfo *image_info,
789   Image *image,WebPConfig *configure,WebPPicture *picture,ExceptionInfo *exception)
790 {
791   MagickBooleanType
792     status;
793 
794   uint32_t
795     *magick_restrict q;
796 
797   ssize_t
798     y;
799 
800   MemoryInfo
801     *pixel_info;
802 
803 #if WEBP_ENCODER_ABI_VERSION >= 0x0100
804   picture->progress_hook=WebPEncodeProgress;
805   picture->user_data=(void *) image;
806 #endif
807   picture->width=(int) image->columns;
808   picture->height=(int) image->rows;
809   picture->argb_stride=(int) image->columns;
810   picture->use_argb=1;
811 
812   /*
813     Allocate memory for pixels.
814   */
815   (void) TransformImageColorspace(image,sRGBColorspace,exception);
816   pixel_info=AcquireVirtualMemory(image->columns,image->rows*
817     sizeof(*(picture->argb)));
818 
819   if (pixel_info == (MemoryInfo *) NULL)
820     ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
821   picture->argb=(uint32_t *) GetVirtualMemoryBlob(pixel_info);
822   /*
823     Convert image to WebP raster pixels.
824   */
825   status=MagickFalse;
826   q=picture->argb;
827   for (y=0; y < (ssize_t) image->rows; y++)
828   {
829     const Quantum
830       *magick_restrict p;
831 
832     ssize_t
833       x;
834 
835     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
836     if (p == (const Quantum *) NULL)
837       break;
838     for (x=0; x < (ssize_t) image->columns; x++)
839     {
840       *q++=(uint32_t) (image->alpha_trait != UndefinedPixelTrait ? (uint32_t)
841         ScaleQuantumToChar(GetPixelAlpha(image,p)) << 24 : 0xff000000) |
842         ((uint32_t) ScaleQuantumToChar(GetPixelRed(image,p)) << 16) |
843         ((uint32_t) ScaleQuantumToChar(GetPixelGreen(image,p)) << 8) |
844         ((uint32_t) ScaleQuantumToChar(GetPixelBlue(image,p)));
845       p+=GetPixelChannels(image);
846     }
847     status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
848       image->rows);
849     if (status == MagickFalse)
850       break;
851   }
852 
853   if (status != MagickFalse)
854     status=(MagickBooleanType) WebPEncode(configure, picture);
855 
856   RelinquishVirtualMemory(pixel_info);
857 
858   if (status == MagickFalse)
859     (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
860       WebPErrorCodeMessage(picture->error_code),"`%s'",
861       image->filename);
862 
863   return(status);
864 }
865 
WriteSingleWEBPImage(const ImageInfo * image_info,Image * image,WebPConfig * configure,WebPMemoryWriter * writer,ExceptionInfo * exception)866 static MagickBooleanType WriteSingleWEBPImage(const ImageInfo *image_info,
867   Image *image,WebPConfig *configure,WebPMemoryWriter *writer,
868   ExceptionInfo *exception)
869 {
870   MagickBooleanType
871     status;
872 
873   WebPPicture
874     picture;
875 
876   if (WebPPictureInit(&picture) == 0)
877     ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
878 
879   picture.writer=WebPMemoryWrite;
880   picture.custom_ptr=writer;
881 
882   status=WriteSingleWEBPPicture(image_info,image,configure,&picture,exception);
883   WebPPictureFree(&picture);
884 
885   return(status);
886 }
887 
888 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
WriteAnimatedWEBPImage(const ImageInfo * image_info,Image * image,WebPConfig * configure,WebPData * webp_data,ExceptionInfo * exception)889 static MagickBooleanType WriteAnimatedWEBPImage(const ImageInfo *image_info,
890   Image *image,WebPConfig *configure,WebPData *webp_data,
891   ExceptionInfo *exception)
892 {
893   Image
894     *first_image;
895 
896   size_t
897     effective_delta,
898     frame_timestamp;
899 
900   WebPAnimEncoder
901     *enc;
902 
903   WebPAnimEncoderOptions
904     enc_options;
905 
906   WebPPicture
907     picture;
908 
909   MagickBooleanType
910     status;
911 
912   first_image=CoalesceImages(image,exception);
913   if (first_image == (Image *) NULL)
914     return(MagickFalse);
915   image=first_image;
916   effective_delta=0;
917   frame_timestamp=0;
918 
919   (void) WebPAnimEncoderOptionsInit(&enc_options);
920   if (image_info->verbose)
921     enc_options.verbose=1;
922   enc=WebPAnimEncoderNew((int) image->page.width,(int) image->page.height,
923     &enc_options);
924 
925   status=MagickTrue;
926   while (image != NULL)
927   {
928     status=(MagickBooleanType) WebPPictureInit(&picture);
929     if (status == MagickFalse)
930       {
931         (void) ThrowMagickException(exception,GetMagickModule(),
932           ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
933         break;
934       }
935 
936     status=WriteSingleWEBPPicture(image_info,image,configure,&picture,
937       exception);
938     if (status == MagickFalse)
939       break;
940 
941     status=(MagickBooleanType) WebPAnimEncoderAdd(enc,&picture,
942       (int) frame_timestamp,configure);
943     WebPPictureFree(&picture);
944     if (status == MagickFalse)
945       {
946         (void) ThrowMagickException(exception,GetMagickModule(),
947           CoderError,WebPAnimEncoderGetError(enc),"`%s'",image->filename);
948         break;
949       }
950 
951     effective_delta=image->delay*1000*PerceptibleReciprocal(
952       image->ticks_per_second);
953     if (effective_delta < 10)
954       effective_delta=100; /* Consistent with gif2webp */
955     frame_timestamp+=effective_delta;
956 
957     image=GetNextImageInList(image);
958   }
959 
960   if (status != MagickFalse)
961     {
962       // add last null frame and assemble picture.
963       status=(MagickBooleanType) WebPAnimEncoderAdd(enc,(WebPPicture *) NULL,
964         (int) frame_timestamp,configure);
965       if (status != MagickFalse)
966         status=(MagickBooleanType) WebPAnimEncoderAssemble(enc,webp_data);
967 
968       if (status == MagickFalse)
969           (void) ThrowMagickException(exception,GetMagickModule(),
970             CoderError,WebPAnimEncoderGetError(enc),"`%s'",
971             first_image->filename);
972     }
973 
974   WebPAnimEncoderDelete(enc);
975   DestroyImageList(first_image);
976   return(status);
977 }
978 
WriteWEBPImageProfile(Image * image,WebPData * webp_data,ExceptionInfo * exception)979 static MagickBooleanType WriteWEBPImageProfile(Image *image,
980   WebPData *webp_data,ExceptionInfo *exception)
981 {
982   const StringInfo
983     *icc_profile,
984     *exif_profile,
985     *xmp_profile;
986 
987   WebPData
988     chunk;
989 
990   WebPMux
991     *mux;
992 
993   WebPMuxError
994     mux_error;
995 
996   WebPMuxAnimParams
997     new_params;
998 
999   icc_profile=GetImageProfile(image,"ICC");
1000   exif_profile=GetImageProfile(image,"EXIF");
1001   xmp_profile=GetImageProfile(image,"XMP");
1002 
1003   if (icc_profile == (StringInfo *) NULL
1004           && exif_profile == (StringInfo *) NULL
1005           && xmp_profile == (StringInfo *) NULL
1006           && image->iterations == 0)
1007     return(MagickTrue);
1008 
1009   mux=WebPMuxCreate(webp_data, 1);
1010   WebPDataClear(webp_data);
1011 
1012   if (mux == NULL)
1013       (void) ThrowMagickException(exception,GetMagickModule(),
1014         ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
1015 
1016   // Clean up returned data
1017   memset(webp_data, 0, sizeof(*webp_data));
1018   mux_error=WEBP_MUX_OK;
1019   if (image->iterations > 0)
1020     {
1021       mux_error=WebPMuxGetAnimationParams(mux, &new_params);
1022       /*
1023         If there is only 1 frame webp_data will be created by WriteSingleWEBPImage
1024         and WebPMuxGetAnimationParams will return WEBP_MUX_NOT_FOUND
1025       */
1026       if (mux_error == WEBP_MUX_NOT_FOUND)
1027         mux_error=WEBP_MUX_OK;
1028       else
1029         if (mux_error == WEBP_MUX_OK)
1030           {
1031             new_params.loop_count=MagickMin((int) image->iterations,65535);
1032             mux_error=WebPMuxSetAnimationParams(mux, &new_params);
1033           }
1034     }
1035   if ((icc_profile != (StringInfo *) NULL) && (mux_error == WEBP_MUX_OK))
1036     {
1037       chunk.bytes=GetStringInfoDatum(icc_profile);
1038       chunk.size=GetStringInfoLength(icc_profile);
1039       mux_error=WebPMuxSetChunk(mux,"ICCP",&chunk,0);
1040     }
1041   if ((exif_profile != (StringInfo *) NULL) && (mux_error == WEBP_MUX_OK))
1042     {
1043       chunk.bytes=GetStringInfoDatum(exif_profile);
1044       chunk.size=GetStringInfoLength(exif_profile);
1045       if ((chunk.size >= 6) &&
1046           (chunk.bytes[0] == 'E') && (chunk.bytes[1] == 'x') &&
1047           (chunk.bytes[2] == 'i') && (chunk.bytes[3] == 'f') &&
1048           (chunk.bytes[4] == '\0') && (chunk.bytes[5] == '\0'))
1049         {
1050           chunk.bytes=GetStringInfoDatum(exif_profile)+6;
1051           chunk.size-=6;
1052         }
1053       mux_error=WebPMuxSetChunk(mux,"EXIF",&chunk,0);
1054     }
1055   if ((xmp_profile != (StringInfo *) NULL) && (mux_error == WEBP_MUX_OK))
1056     {
1057       chunk.bytes=GetStringInfoDatum(xmp_profile);
1058       chunk.size=GetStringInfoLength(xmp_profile);
1059       mux_error=WebPMuxSetChunk(mux,"XMP",&chunk,0);
1060     }
1061   if (mux_error == WEBP_MUX_OK)
1062       mux_error=WebPMuxAssemble(mux,webp_data);
1063   WebPMuxDelete(mux);
1064 
1065   if (mux_error != WEBP_MUX_OK)
1066     (void) ThrowMagickException(exception,GetMagickModule(),
1067       ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
1068   return(MagickTrue);
1069 }
1070 #endif
1071 
SetBooleanOption(const ImageInfo * image_info,const char * option,int * setting)1072 static inline void SetBooleanOption(const ImageInfo *image_info,
1073   const char *option,int *setting)
1074 {
1075   const char
1076     *value;
1077 
1078   value=GetImageOption(image_info,option);
1079   if (value != (char *) NULL)
1080     *setting=(int) ParseCommandOption(MagickBooleanOptions,MagickFalse,value);
1081 }
1082 
SetIntegerOption(const ImageInfo * image_info,const char * option,int * setting)1083 static inline void SetIntegerOption(const ImageInfo *image_info,
1084   const char *option,int *setting)
1085 {
1086   const char
1087     *value;
1088 
1089   value=GetImageOption(image_info,option);
1090   if (value != (const char *) NULL)
1091     *setting=StringToInteger(value);
1092 }
1093 
WriteWEBPImage(const ImageInfo * image_info,Image * image,ExceptionInfo * exception)1094 static MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
1095   Image *image,ExceptionInfo * exception)
1096 {
1097   const char
1098     *value;
1099 
1100   MagickBooleanType
1101     status;
1102 
1103   WebPConfig
1104     configure;
1105 
1106   WebPMemoryWriter
1107     writer;
1108 
1109   /*
1110     Open output image file.
1111   */
1112   assert(image_info != (const ImageInfo *) NULL);
1113   assert(image_info->signature == MagickCoreSignature);
1114   assert(image != (Image *) NULL);
1115   assert(image->signature == MagickCoreSignature);
1116   if (image->debug != MagickFalse)
1117     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1118   if ((image->columns > 16383UL) || (image->rows > 16383UL))
1119     ThrowWriterException(ImageError,"WidthOrHeightExceedsLimit");
1120   status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
1121   if (status == MagickFalse)
1122     return(status);
1123   if (WebPConfigInit(&configure) == 0)
1124     ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
1125   if (image->quality != UndefinedCompressionQuality)
1126     configure.quality=(float) image->quality;
1127   if (image->quality >= 100)
1128     configure.lossless=1;
1129   SetBooleanOption(image_info,"webp:lossless",&configure.lossless);
1130   value=GetImageOption(image_info,"webp:image-hint");
1131   if (value != (char *) NULL)
1132     {
1133       if (LocaleCompare(value,"default") == 0)
1134         configure.image_hint=WEBP_HINT_DEFAULT;
1135       if (LocaleCompare(value,"photo") == 0)
1136         configure.image_hint=WEBP_HINT_PHOTO;
1137       if (LocaleCompare(value,"picture") == 0)
1138         configure.image_hint=WEBP_HINT_PICTURE;
1139 #if WEBP_ENCODER_ABI_VERSION >= 0x0200
1140       if (LocaleCompare(value,"graph") == 0)
1141         configure.image_hint=WEBP_HINT_GRAPH;
1142 #endif
1143     }
1144   SetBooleanOption(image_info,"webp:auto-filter",&configure.autofilter);
1145   value=GetImageOption(image_info,"webp:target-psnr");
1146   if (value != (char *) NULL)
1147     configure.target_PSNR=(float) StringToDouble(value,(char **) NULL);
1148   SetIntegerOption(image_info,"webp:alpha-compression",
1149     &configure.alpha_compression);
1150   SetIntegerOption(image_info,"webp:alpha-filtering",
1151     &configure.alpha_filtering);
1152   SetIntegerOption(image_info,"webp:alpha-quality",
1153     &configure.alpha_quality);
1154   SetIntegerOption(image_info,"webp:filter-strength",
1155     &configure.filter_strength);
1156   SetIntegerOption(image_info,"webp:filter-sharpness",
1157     &configure.filter_sharpness);
1158   SetIntegerOption(image_info,"webp:filter-type",&configure.filter_type);
1159   SetIntegerOption(image_info,"webp:method",&configure.method);
1160   SetIntegerOption(image_info,"webp:partitions",&configure.partitions);
1161   SetIntegerOption(image_info,"webp:partition-limit",
1162     &configure.partition_limit);
1163   SetIntegerOption(image_info,"webp:pass",&configure.pass);
1164   SetIntegerOption(image_info,"webp:preprocessing",&configure.preprocessing);
1165   SetIntegerOption(image_info,"webp:segments",&configure.segments);
1166   SetIntegerOption(image_info,"webp:show-compressed",
1167     &configure.show_compressed);
1168   SetIntegerOption(image_info,"webp:sns-strength",&configure.sns_strength);
1169   SetIntegerOption(image_info,"webp:target-size",&configure.target_size);
1170 #if WEBP_ENCODER_ABI_VERSION >= 0x0201
1171   SetBooleanOption(image_info,"webp:emulate-jpeg-size",
1172     &configure.emulate_jpeg_size);
1173   SetBooleanOption(image_info,"webp:low-memory",&configure.low_memory);
1174   SetIntegerOption(image_info,"webp:thread-level",&configure.thread_level);
1175 #endif
1176 #if WEBP_ENCODER_ABI_VERSION >= 0x0209
1177   SetBooleanOption(image_info,"webp:exact",&configure.exact);
1178 #endif
1179 #if WEBP_ENCODER_ABI_VERSION >= 0x020e
1180   SetIntegerOption(image_info,"webp:near-lossless",&configure.near_lossless);
1181   SetBooleanOption(image_info,"webp:use-sharp-yuv",&configure.use_sharp_yuv);
1182 #endif
1183   if (WebPValidateConfig(&configure) == 0)
1184     ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
1185 #if defined(MAGICKCORE_WEBPMUX_DELEGATE)
1186   {
1187     WebPData
1188       webp_data;
1189 
1190     memset(&webp_data,0,sizeof(webp_data));
1191     if ((image_info->adjoin != MagickFalse) &&
1192         (GetPreviousImageInList(image) == (Image *) NULL) &&
1193         (GetNextImageInList(image) != (Image *) NULL))
1194       status=WriteAnimatedWEBPImage(image_info,image,&configure,&webp_data,
1195         exception);
1196     else
1197       {
1198         WebPMemoryWriterInit(&writer);
1199         status=WriteSingleWEBPImage(image_info,image,&configure,&writer,
1200           exception);
1201         if (status == MagickFalse)
1202           WebPMemoryWriterClear(&writer);
1203         else
1204           {
1205             webp_data.bytes=writer.mem;
1206             webp_data.size=writer.size;
1207           }
1208       }
1209     if (status != MagickFalse)
1210       status=WriteWEBPImageProfile(image,&webp_data,exception);
1211     if (status != MagickFalse)
1212       (void) WriteBlob(image,webp_data.size,webp_data.bytes);
1213     WebPDataClear(&webp_data);
1214   }
1215 #else
1216   WebPMemoryWriterInit(&writer);
1217   status=WriteSingleWEBPImage(image_info,image,&configure,&writer,
1218     exception);
1219   if (status != MagickFalse)
1220     (void) WriteBlob(image,writer.size,writer.mem);
1221   WebPMemoryWriterClear(&writer);
1222 #endif
1223   (void) CloseBlob(image);
1224   return(status);
1225 }
1226 #endif
1227