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