blob: e994886c219e7447f1c0a1f2163517de4a17d558 [file] [log] [blame]
cristyb1860752011-03-14 00:27:46 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% W W EEEEE BBBB PPPP %
7% W W E B B P P %
8% W W W EEE BBBB PPPP %
9% WW WW E B B P %
10% W W EEEEE BBBB P %
11% %
12% %
13% Read/Write WebP Image Format %
14% %
15% Software Design %
cristyef632a52013-12-03 11:06:33 +000016% John Cristy %
cristyb1860752011-03-14 00:27:46 +000017% March 2011 %
18% %
19% %
cristyef632a52013-12-03 11:06:33 +000020% Copyright 1999-2013 ImageMagick Studio LLC, a non-profit organization %
cristyb1860752011-03-14 00:27:46 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.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*/
cristy4c08aed2011-07-01 19:47:50 +000042#include "MagickCore/studio.h"
cristy76ce6e12013-04-05 14:33:38 +000043#include "MagickCore/artifact.h"
cristy4c08aed2011-07-01 19:47:50 +000044#include "MagickCore/blob.h"
45#include "MagickCore/blob-private.h"
46#include "MagickCore/client.h"
cristye65bb192013-08-01 11:22:06 +000047#include "MagickCore/colorspace-private.h"
cristy4c08aed2011-07-01 19:47:50 +000048#include "MagickCore/display.h"
49#include "MagickCore/exception.h"
50#include "MagickCore/exception-private.h"
51#include "MagickCore/image.h"
52#include "MagickCore/image-private.h"
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/quantum-private.h"
61#include "MagickCore/static.h"
62#include "MagickCore/string_.h"
cristye71e0892013-02-18 13:13:53 +000063#include "MagickCore/string-private.h"
cristy4c08aed2011-07-01 19:47:50 +000064#include "MagickCore/module.h"
65#include "MagickCore/utility.h"
66#include "MagickCore/xwindow.h"
67#include "MagickCore/xwindow-private.h"
cristy644040e2011-03-14 17:31:59 +000068#if defined(MAGICKCORE_WEBP_DELEGATE)
69#include <webp/decode.h>
70#include <webp/encode.h>
71#endif
cristyb1860752011-03-14 00:27:46 +000072
73/*
74 Forward declarations.
75*/
76#if defined(MAGICKCORE_WEBP_DELEGATE)
77static MagickBooleanType
cristy018f07f2011-09-04 21:15:19 +000078 WriteWEBPImage(const ImageInfo *,Image *,ExceptionInfo *);
cristyb1860752011-03-14 00:27:46 +000079#endif
80
cristydffa79e2013-02-20 00:57:26 +000081/*
82%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83% %
84% %
85% %
86% I s W E B P %
87% %
88% %
89% %
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91%
92% IsWEBP() returns MagickTrue if the image format type, identified by the
93% magick string, is WebP.
94%
95% The format of the IsWEBP method is:
96%
97% MagickBooleanType IsWEBP(const unsigned char *magick,const size_t length)
98%
99% A description of each parameter follows:
100%
101% o magick: compare image format pattern against these bytes.
102%
103% o length: Specifies the length of the magick string.
104%
105*/
106static MagickBooleanType IsWEBP(const unsigned char *magick,const size_t length)
107{
108 if (length < 12)
109 return(MagickFalse);
110 if (LocaleNCompare((const char *) magick+8,"WEBP",4) == 0)
111 return(MagickTrue);
112 return(MagickFalse);
113}
114
cristyb1860752011-03-14 00:27:46 +0000115#if defined(MAGICKCORE_WEBP_DELEGATE)
116/*
117%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
118% %
119% %
120% %
121% R e a d W E B P I m a g e %
122% %
123% %
124% %
125%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
126%
127% ReadWEBPImage() reads an image in the WebP image format.
128%
129% The format of the ReadWEBPImage method is:
130%
131% Image *ReadWEBPImage(const ImageInfo *image_info,
132% ExceptionInfo *exception)
133%
134% A description of each parameter follows:
135%
136% o image_info: the image info.
137%
138% o exception: return any errors or warnings in this structure.
139%
140*/
cristy8f25aa82013-02-23 02:14:37 +0000141
dirk05d2ff72015-11-18 23:13:43 +0100142static inline uint32_t ReadWebPLSBWord(
143 const unsigned char *magick_restrict data)
cristy8f25aa82013-02-23 02:14:37 +0000144{
cristy0b7a0332013-02-23 02:28:08 +0000145 register const unsigned char
146 *p;
cristy8f25aa82013-02-23 02:14:37 +0000147
cristy0b7a0332013-02-23 02:28:08 +0000148 register uint32_t
149 value;
150
151 p=data;
152 value=(uint32_t) (*p++);
153 value|=((uint32_t) (*p++)) << 8;
154 value|=((uint32_t) (*p++)) << 16;
155 value|=((uint32_t) (*p++)) << 24;
156 return(value);
cristy8f25aa82013-02-23 02:14:37 +0000157}
158
159static MagickBooleanType IsWEBPImageLossless(const unsigned char *stream,
160 const size_t length)
161{
cristy0b7a0332013-02-23 02:28:08 +0000162#define VP8_CHUNK_INDEX 15
cristy8f25aa82013-02-23 02:14:37 +0000163#define LOSSLESS_FLAG 'L'
164#define EXTENDED_HEADER 'X'
165#define VP8_CHUNK_HEADER "VP8"
166#define VP8_CHUNK_HEADER_SIZE 3
cristy0b7a0332013-02-23 02:28:08 +0000167#define RIFF_HEADER_SIZE 12
cristy8f25aa82013-02-23 02:14:37 +0000168#define VP8X_CHUNK_SIZE 10
169#define TAG_SIZE 4
170#define CHUNK_SIZE_BYTES 4
171#define CHUNK_HEADER_SIZE 8
cristya230a612014-01-05 20:51:47 +0000172#define MAX_CHUNK_PAYLOAD (~0U-CHUNK_HEADER_SIZE-1)
cristy8f25aa82013-02-23 02:14:37 +0000173
174 ssize_t
175 offset;
176
177 /*
178 Read simple header.
179 */
180 if (stream[VP8_CHUNK_INDEX] != EXTENDED_HEADER)
cristyef632a52013-12-03 11:06:33 +0000181 return(stream[VP8_CHUNK_INDEX] == LOSSLESS_FLAG ? MagickTrue : MagickFalse);
cristy8f25aa82013-02-23 02:14:37 +0000182 /*
183 Read extended header.
184 */
185 offset=RIFF_HEADER_SIZE+TAG_SIZE+CHUNK_SIZE_BYTES+VP8X_CHUNK_SIZE;
cristyb0de93f2013-05-03 13:39:25 +0000186 while (offset <= (ssize_t) length)
cristy8f25aa82013-02-23 02:14:37 +0000187 {
188 uint32_t
189 chunk_size,
190 chunk_size_pad;
191
cristyef978292013-02-23 02:32:06 +0000192 chunk_size=ReadWebPLSBWord(stream+offset+TAG_SIZE);
cristy8f25aa82013-02-23 02:14:37 +0000193 if (chunk_size > MAX_CHUNK_PAYLOAD)
194 break;
195 chunk_size_pad=(CHUNK_HEADER_SIZE+chunk_size+1) & ~1;
cristy5def2032013-06-30 17:44:08 +0000196 if (memcmp(stream+offset,VP8_CHUNK_HEADER,VP8_CHUNK_HEADER_SIZE) == 0)
cristyef978292013-02-23 02:32:06 +0000197 return(*(stream+offset+VP8_CHUNK_HEADER_SIZE) == LOSSLESS_FLAG ?
cristy8f25aa82013-02-23 02:14:37 +0000198 MagickTrue : MagickFalse);
199 offset+=chunk_size_pad;
200 }
201 return(MagickFalse);
202}
203
cristyb1860752011-03-14 00:27:46 +0000204static Image *ReadWEBPImage(const ImageInfo *image_info,
205 ExceptionInfo *exception)
206{
cristyffa663d2011-03-14 00:58:51 +0000207 Image
208 *image;
209
cristyef632a52013-12-03 11:06:33 +0000210 int
211 webp_status;
212
cristyffa663d2011-03-14 00:58:51 +0000213 MagickBooleanType
214 status;
215
cristy644040e2011-03-14 17:31:59 +0000216 register unsigned char
217 *p;
218
219 size_t
220 length;
221
222 ssize_t
223 count,
224 y;
225
226 unsigned char
cristy3ad1e882014-07-19 21:24:42 +0000227 header[12],
cristyaf29b622013-02-18 15:06:48 +0000228 *stream;
229
230 WebPDecoderConfig
231 configure;
232
233 WebPDecBuffer
dirk05d2ff72015-11-18 23:13:43 +0100234 *magick_restrict webp_image = &configure.output;
cristyaf29b622013-02-18 15:06:48 +0000235
236 WebPBitstreamFeatures
dirk05d2ff72015-11-18 23:13:43 +0100237 *magick_restrict features = &configure.input;
cristy644040e2011-03-14 17:31:59 +0000238
cristyffa663d2011-03-14 00:58:51 +0000239 /*
240 Open image file.
241 */
242 assert(image_info != (const ImageInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000243 assert(image_info->signature == MagickCoreSignature);
cristyffa663d2011-03-14 00:58:51 +0000244 if (image_info->debug != MagickFalse)
245 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
246 image_info->filename);
247 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000248 assert(exception->signature == MagickCoreSignature);
cristy9950d572011-10-01 18:22:35 +0000249 image=AcquireImage(image_info,exception);
cristyffa663d2011-03-14 00:58:51 +0000250 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
251 if (status == MagickFalse)
252 {
253 image=DestroyImageList(image);
254 return((Image *) NULL);
255 }
cristyaf29b622013-02-18 15:06:48 +0000256 if (WebPInitDecoderConfig(&configure) == 0)
257 ThrowReaderException(ResourceLimitError,"UnableToDecodeImageFile");
cristyef632a52013-12-03 11:06:33 +0000258 webp_image->colorspace=MODE_RGBA;
cristy3ad1e882014-07-19 21:24:42 +0000259 count=ReadBlob(image,12,header);
260 if (count != 12)
261 ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile");
262 status=IsWEBP(header,count);
263 if (status == MagickFalse)
264 ThrowReaderException(CorruptImageError,"CorruptImage");
265 length=(size_t) (ReadWebPLSBWord(header+4)+8);
266 if (length < 12)
267 ThrowReaderException(CorruptImageError,"CorruptImage");
cristy644040e2011-03-14 17:31:59 +0000268 stream=(unsigned char *) AcquireQuantumMemory(length,sizeof(*stream));
269 if (stream == (unsigned char *) NULL)
270 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ad1e882014-07-19 21:24:42 +0000271 memcpy(stream,header,12);
272 count=ReadBlob(image,length-12,stream+12);
273 if (count != (ssize_t) (length-12))
cristy644040e2011-03-14 17:31:59 +0000274 ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile");
cristyef632a52013-12-03 11:06:33 +0000275 webp_status=WebPGetFeatures(stream,length,features);
276 if (webp_status == VP8_STATUS_OK)
277 {
cristy106ce7d2013-12-12 13:02:12 +0000278 image->columns=(size_t) features->width;
279 image->rows=(size_t) features->height;
cristyef632a52013-12-03 11:06:33 +0000280 image->depth=8;
281 image->alpha_trait=features->has_alpha != 0 ? BlendPixelTrait :
282 UndefinedPixelTrait;
283 if (image_info->ping != MagickFalse)
284 {
285 stream=(unsigned char*) RelinquishMagickMemory(stream);
286 (void) CloseBlob(image);
287 return(GetFirstImageInList(image));
288 }
cristyacabb842014-12-14 23:36:33 +0000289 status=SetImageExtent(image,image->columns,image->rows,exception);
290 if (status == MagickFalse)
291 return(DestroyImageList(image));
cristyef632a52013-12-03 11:06:33 +0000292 webp_status=WebPDecode(stream,length,&configure);
293 }
294 if (webp_status != VP8_STATUS_OK)
cristyaf29b622013-02-18 15:06:48 +0000295 {
296 stream=(unsigned char*) RelinquishMagickMemory(stream);
cristyef632a52013-12-03 11:06:33 +0000297 switch (webp_status)
298 {
299 case VP8_STATUS_OUT_OF_MEMORY:
300 {
301 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
302 break;
303 }
cristyeb043f22014-04-06 00:30:25 +0000304 case VP8_STATUS_INVALID_PARAM:
305 {
306 ThrowReaderException(CorruptImageError,"invalid parameter");
307 break;
308 }
cristyef632a52013-12-03 11:06:33 +0000309 case VP8_STATUS_BITSTREAM_ERROR:
310 {
311 ThrowReaderException(CorruptImageError,"CorruptImage");
312 break;
313 }
314 case VP8_STATUS_UNSUPPORTED_FEATURE:
315 {
316 ThrowReaderException(CoderError,"DataEncodingSchemeIsNotSupported");
317 break;
318 }
cristyeb043f22014-04-06 00:30:25 +0000319 case VP8_STATUS_SUSPENDED:
320 {
321 ThrowReaderException(CorruptImageError,"decoder suspended");
322 break;
323 }
324 case VP8_STATUS_USER_ABORT:
325 {
326 ThrowReaderException(CorruptImageError,"user abort");
327 break;
328 }
cristyef632a52013-12-03 11:06:33 +0000329 case VP8_STATUS_NOT_ENOUGH_DATA:
330 {
331 ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile");
332 break;
333 }
334 default:
335 ThrowReaderException(CorruptImageError,"CorruptImage");
336 }
cristyaf29b622013-02-18 15:06:48 +0000337 }
cristy8f25aa82013-02-23 02:14:37 +0000338 if (IsWEBPImageLossless(stream,length) != MagickFalse)
cristye71e0892013-02-18 13:13:53 +0000339 image->quality=100;
cristyb22ab412014-04-06 15:09:41 +0000340 p=(unsigned char *) webp_image->u.RGBA.rgba;
cristy644040e2011-03-14 17:31:59 +0000341 for (y=0; y < (ssize_t) image->rows; y++)
342 {
cristyaf29b622013-02-18 15:06:48 +0000343 register Quantum
344 *q;
345
346 register ssize_t
347 x;
348
cristy644040e2011-03-14 17:31:59 +0000349 q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000350 if (q == (Quantum *) NULL)
cristy644040e2011-03-14 17:31:59 +0000351 break;
352 for (x=0; x < (ssize_t) image->columns; x++)
353 {
cristy4c08aed2011-07-01 19:47:50 +0000354 SetPixelRed(image,ScaleCharToQuantum(*p++),q);
355 SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
356 SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
357 SetPixelAlpha(image,ScaleCharToQuantum(*p++),q);
cristyed231572011-07-14 02:18:59 +0000358 q+=GetPixelChannels(image);
cristy644040e2011-03-14 17:31:59 +0000359 }
360 if (SyncAuthenticPixels(image,exception) == MagickFalse)
361 break;
362 status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
363 image->rows);
364 if (status == MagickFalse)
365 break;
366 }
cristyaf29b622013-02-18 15:06:48 +0000367 WebPFreeDecBuffer(webp_image);
cristye71e0892013-02-18 13:13:53 +0000368 stream=(unsigned char*) RelinquishMagickMemory(stream);
cristyffa663d2011-03-14 00:58:51 +0000369 return(image);
cristyb1860752011-03-14 00:27:46 +0000370}
371#endif
372
373/*
374%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
375% %
376% %
377% %
378% R e g i s t e r W E B P I m a g e %
379% %
380% %
381% %
382%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
383%
384% RegisterWEBPImage() adds attributes for the WebP image format to
385% the list of supported formats. The attributes include the image format
386% tag, a method to read and/or write the format, whether the format
387% supports the saving of more than one frame to the same file or blob,
388% whether the format supports native in-memory I/O, and a brief
389% description of the format.
390%
391% The format of the RegisterWEBPImage method is:
392%
393% size_t RegisterWEBPImage(void)
394%
395*/
396ModuleExport size_t RegisterWEBPImage(void)
397{
cristyaf29b622013-02-18 15:06:48 +0000398 char
cristy151b66d2015-04-15 10:50:31 +0000399 version[MagickPathExtent];
cristyaf29b622013-02-18 15:06:48 +0000400
cristyb1860752011-03-14 00:27:46 +0000401 MagickInfo
402 *entry;
403
cristyaf29b622013-02-18 15:06:48 +0000404 *version='\0';
dirk06b627a2015-04-06 18:59:17 +0000405 entry=AcquireMagickInfo("WEBP","WEBP","WebP Image Format");
cristyb1860752011-03-14 00:27:46 +0000406#if defined(MAGICKCORE_WEBP_DELEGATE)
407 entry->decoder=(DecodeImageHandler *) ReadWEBPImage;
408 entry->encoder=(EncodeImageHandler *) WriteWEBPImage;
cristy151b66d2015-04-15 10:50:31 +0000409 (void) FormatLocaleString(version,MagickPathExtent,"libwebp %d.%d.%d [%04X]",
cristyaf29b622013-02-18 15:06:48 +0000410 (WebPGetDecoderVersion() >> 16) & 0xff,
411 (WebPGetDecoderVersion() >> 8) & 0xff,
cristyd69e3042014-08-10 23:23:22 +0000412 (WebPGetDecoderVersion() >> 0) & 0xff,WEBP_DECODER_ABI_VERSION);
cristyb1860752011-03-14 00:27:46 +0000413#endif
dirk8648daf2015-09-14 22:26:43 +0200414 entry->mime_type=ConstantString("image/webp");
dirk08e9a112015-02-22 01:51:41 +0000415 entry->flags^=CoderAdjoinFlag;
cristydffa79e2013-02-20 00:57:26 +0000416 entry->magick=(IsImageFormatHandler *) IsWEBP;
cristyaf29b622013-02-18 15:06:48 +0000417 if (*version != '\0')
418 entry->version=ConstantString(version);
cristyb1860752011-03-14 00:27:46 +0000419 (void) RegisterMagickInfo(entry);
420 return(MagickImageCoderSignature);
421}
422
423/*
424%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
425% %
426% %
427% %
428% U n r e g i s t e r W E B P I m a g e %
429% %
430% %
431% %
432%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
433%
434% UnregisterWEBPImage() removes format registrations made by the WebP module
435% from the list of supported formats.
436%
437% The format of the UnregisterWEBPImage method is:
438%
439% UnregisterWEBPImage(void)
440%
441*/
442ModuleExport void UnregisterWEBPImage(void)
443{
444 (void) UnregisterMagickInfo("WEBP");
445}
446#if defined(MAGICKCORE_WEBP_DELEGATE)
447
448/*
449%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
450% %
451% %
452% %
453% W r i t e W E B P I m a g e %
454% %
455% %
456% %
457%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
458%
459% WriteWEBPImage() writes an image in the WebP image format.
460%
461% The format of the WriteWEBPImage method is:
462%
463% MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
464% Image *image)
465%
466% A description of each parameter follows.
467%
468% o image_info: the image info.
469%
470% o image: The image.
471%
472*/
cristy644040e2011-03-14 17:31:59 +0000473
cristyda680982014-08-10 23:24:02 +0000474#if WEBP_DECODER_ABI_VERSION >= 0x0100
cristyb22ab412014-04-06 15:09:41 +0000475static int WebPEncodeProgress(int percent,const WebPPicture* picture)
476{
cristy88bf7c12014-04-06 15:21:42 +0000477#define EncodeImageTag "Encode/Image"
478
cristyb22ab412014-04-06 15:09:41 +0000479 Image
480 *image;
481
482 MagickBooleanType
483 status;
484
485 image=(Image *) picture->custom_ptr;
cristy88bf7c12014-04-06 15:21:42 +0000486 status=SetImageProgress(image,EncodeImageTag,percent-1,100);
cristyb22ab412014-04-06 15:09:41 +0000487 return(status == MagickFalse ? 0 : 1);
488}
489#endif
490
491static int WebPEncodeWriter(const unsigned char *stream,size_t length,
cristy644040e2011-03-14 17:31:59 +0000492 const WebPPicture *const picture)
493{
494 Image
495 *image;
496
497 image=(Image *) picture->custom_ptr;
498 return(length != 0 ? (int) WriteBlob(image,length,stream) : 1);
499}
500
cristyb1860752011-03-14 00:27:46 +0000501static MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
cristy018f07f2011-09-04 21:15:19 +0000502 Image *image,ExceptionInfo *exception)
cristyb1860752011-03-14 00:27:46 +0000503{
cristye71e0892013-02-18 13:13:53 +0000504 const char
505 *value;
506
cristy644040e2011-03-14 17:31:59 +0000507 int
508 webp_status;
509
cristyffa663d2011-03-14 00:58:51 +0000510 MagickBooleanType
511 status;
512
cristy5def2032013-06-30 17:44:08 +0000513 MemoryInfo
514 *pixel_info;
515
cristye71e0892013-02-18 13:13:53 +0000516 register uint32_t
dirk05d2ff72015-11-18 23:13:43 +0100517 *magick_restrict q;
cristy644040e2011-03-14 17:31:59 +0000518
519 ssize_t
520 y;
521
cristy644040e2011-03-14 17:31:59 +0000522 WebPConfig
523 configure;
524
525 WebPPicture
526 picture;
527
528 WebPAuxStats
529 statistics;
530
cristyffa663d2011-03-14 00:58:51 +0000531 /*
532 Open output image file.
533 */
534 assert(image_info != (const ImageInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000535 assert(image_info->signature == MagickCoreSignature);
cristyffa663d2011-03-14 00:58:51 +0000536 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000537 assert(image->signature == MagickCoreSignature);
cristyffa663d2011-03-14 00:58:51 +0000538 if (image->debug != MagickFalse)
539 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy57790302013-02-18 18:19:08 +0000540 if ((image->columns > 16383UL) || (image->rows > 16383UL))
cristye4d08fd2012-04-26 17:29:08 +0000541 ThrowWriterException(ImageError,"WidthOrHeightExceedsLimit");
cristyc82a27b2011-10-21 01:07:16 +0000542 status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
cristyffa663d2011-03-14 00:58:51 +0000543 if (status == MagickFalse)
544 return(status);
cristyaf29b622013-02-18 15:06:48 +0000545 if ((WebPPictureInit(&picture) == 0) || (WebPConfigInit(&configure) == 0))
546 ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
cristyb22ab412014-04-06 15:09:41 +0000547 picture.writer=WebPEncodeWriter;
cristy644040e2011-03-14 17:31:59 +0000548 picture.custom_ptr=(void *) image;
cristyda680982014-08-10 23:24:02 +0000549#if WEBP_DECODER_ABI_VERSION >= 0x0100
cristyb22ab412014-04-06 15:09:41 +0000550 picture.progress_hook=WebPEncodeProgress;
551#endif
cristy644040e2011-03-14 17:31:59 +0000552 picture.stats=(&statistics);
553 picture.width=(int) image->columns;
554 picture.height=(int) image->rows;
cristye71e0892013-02-18 13:13:53 +0000555 picture.argb_stride=(int) image->columns;
556 picture.use_argb=1;
cristy35c63c72012-10-23 14:40:54 +0000557 if (image->quality != UndefinedCompressionQuality)
558 configure.quality=(float) image->quality;
cristy7ab1ba82014-11-13 21:37:21 +0000559 if (image->quality >= 100)
560 configure.lossless=1;
cristy092ec8d2013-04-26 13:46:22 +0000561 value=GetImageOption(image_info,"webp:lossless");
cristye71e0892013-02-18 13:13:53 +0000562 if (value != (char *) NULL)
cristyb22ab412014-04-06 15:09:41 +0000563 configure.lossless=(int) ParseCommandOption(MagickBooleanOptions,
564 MagickFalse,value);
cristy092ec8d2013-04-26 13:46:22 +0000565 value=GetImageOption(image_info,"webp:method");
cristye71e0892013-02-18 13:13:53 +0000566 if (value != (char *) NULL)
567 configure.method=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000568 value=GetImageOption(image_info,"webp:image-hint");
cristye71e0892013-02-18 13:13:53 +0000569 if (value != (char *) NULL)
cristyd6f2cc42013-02-18 17:24:19 +0000570 {
cristy9bb4d7a2014-04-20 01:41:32 +0000571 if (LocaleCompare(value,"default") == 0)
572 configure.image_hint=WEBP_HINT_DEFAULT;
cristy547fe7f2013-02-18 17:27:13 +0000573 if (LocaleCompare(value,"photo") == 0)
cristyd6f2cc42013-02-18 17:24:19 +0000574 configure.image_hint=WEBP_HINT_PHOTO;
cristy547fe7f2013-02-18 17:27:13 +0000575 if (LocaleCompare(value,"picture") == 0)
cristyd6f2cc42013-02-18 17:24:19 +0000576 configure.image_hint=WEBP_HINT_PICTURE;
cristyda680982014-08-10 23:24:02 +0000577#if WEBP_DECODER_ABI_VERSION >= 0x0200
cristy9bb4d7a2014-04-20 01:41:32 +0000578 if (LocaleCompare(value,"graph") == 0)
579 configure.image_hint=WEBP_HINT_GRAPH;
580#endif
cristyd6f2cc42013-02-18 17:24:19 +0000581 }
cristy092ec8d2013-04-26 13:46:22 +0000582 value=GetImageOption(image_info,"webp:target-size");
cristye71e0892013-02-18 13:13:53 +0000583 if (value != (char *) NULL)
584 configure.target_size=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000585 value=GetImageOption(image_info,"webp:target-psnr");
cristye71e0892013-02-18 13:13:53 +0000586 if (value != (char *) NULL)
587 configure.target_PSNR=(float) StringToDouble(value,(char **) NULL);
cristy092ec8d2013-04-26 13:46:22 +0000588 value=GetImageOption(image_info,"webp:segments");
cristye71e0892013-02-18 13:13:53 +0000589 if (value != (char *) NULL)
590 configure.segments=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000591 value=GetImageOption(image_info,"webp:sns-strength");
cristye71e0892013-02-18 13:13:53 +0000592 if (value != (char *) NULL)
593 configure.sns_strength=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000594 value=GetImageOption(image_info,"webp:filter-strength");
cristye71e0892013-02-18 13:13:53 +0000595 if (value != (char *) NULL)
596 configure.filter_strength=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000597 value=GetImageOption(image_info,"webp:filter-sharpness");
cristye71e0892013-02-18 13:13:53 +0000598 if (value != (char *) NULL)
599 configure.filter_sharpness=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000600 value=GetImageOption(image_info,"webp:filter-type");
cristye71e0892013-02-18 13:13:53 +0000601 if (value != (char *) NULL)
602 configure.filter_type=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000603 value=GetImageOption(image_info,"webp:auto-filter");
cristye71e0892013-02-18 13:13:53 +0000604 if (value != (char *) NULL)
cristyb22ab412014-04-06 15:09:41 +0000605 configure.autofilter=(int) ParseCommandOption(MagickBooleanOptions,
606 MagickFalse,value);
cristy092ec8d2013-04-26 13:46:22 +0000607 value=GetImageOption(image_info,"webp:alpha-compression");
cristye71e0892013-02-18 13:13:53 +0000608 if (value != (char *) NULL)
609 configure.alpha_compression=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000610 value=GetImageOption(image_info,"webp:alpha-filtering");
cristye71e0892013-02-18 13:13:53 +0000611 if (value != (char *) NULL)
612 configure.alpha_filtering=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000613 value=GetImageOption(image_info,"webp:alpha-quality");
cristye71e0892013-02-18 13:13:53 +0000614 if (value != (char *) NULL)
615 configure.alpha_quality=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000616 value=GetImageOption(image_info,"webp:pass");
cristye71e0892013-02-18 13:13:53 +0000617 if (value != (char *) NULL)
618 configure.pass=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000619 value=GetImageOption(image_info,"webp:show-compressed");
cristye71e0892013-02-18 13:13:53 +0000620 if (value != (char *) NULL)
621 configure.show_compressed=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000622 value=GetImageOption(image_info,"webp:preprocessing");
cristye71e0892013-02-18 13:13:53 +0000623 if (value != (char *) NULL)
624 configure.preprocessing=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000625 value=GetImageOption(image_info,"webp:partitions");
cristye71e0892013-02-18 13:13:53 +0000626 if (value != (char *) NULL)
627 configure.partitions=StringToInteger(value);
cristy092ec8d2013-04-26 13:46:22 +0000628 value=GetImageOption(image_info,"webp:partition-limit");
cristye71e0892013-02-18 13:13:53 +0000629 if (value != (char *) NULL)
630 configure.partition_limit=StringToInteger(value);
cristyda680982014-08-10 23:24:02 +0000631#if WEBP_DECODER_ABI_VERSION >= 0x0201
cristy17e26ea2014-08-10 23:30:23 +0000632 value=GetImageOption(image_info,"webp:emulate-jpeg-size");
cristy9bb4d7a2014-04-20 01:41:32 +0000633 if (value != (char *) NULL)
634 configure.emulate_jpeg_size=(int) ParseCommandOption(MagickBooleanOptions,
635 MagickFalse,value);
cristy7ab1ba82014-11-13 21:37:21 +0000636 value=GetImageOption(image_info,"webp:low-memory");
637 if (value != (char *) NULL)
638 configure.low_memory=(int) ParseCommandOption(MagickBooleanOptions,
639 MagickFalse,value);
cristy9bb4d7a2014-04-20 01:41:32 +0000640 value=GetImageOption(image_info,"webp:thread-level");
641 if (value != (char *) NULL)
642 configure.thread_level=StringToInteger(value);
643#endif
cristy644040e2011-03-14 17:31:59 +0000644 if (WebPValidateConfig(&configure) == 0)
645 ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
646 /*
647 Allocate memory for pixels.
648 */
cristyaf8d3912014-02-21 14:50:33 +0000649 (void) TransformImageColorspace(image,sRGBColorspace,exception);
cristy5def2032013-06-30 17:44:08 +0000650 pixel_info=AcquireVirtualMemory(image->columns,image->rows*
cristye71e0892013-02-18 13:13:53 +0000651 sizeof(*picture.argb));
cristy5def2032013-06-30 17:44:08 +0000652 if (pixel_info == (MemoryInfo *) NULL)
cristy644040e2011-03-14 17:31:59 +0000653 ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
cristy5def2032013-06-30 17:44:08 +0000654 picture.argb=(uint32_t *) GetVirtualMemoryBlob(pixel_info);
cristy644040e2011-03-14 17:31:59 +0000655 /*
656 Convert image to WebP raster pixels.
657 */
cristye71e0892013-02-18 13:13:53 +0000658 q=picture.argb;
cristy644040e2011-03-14 17:31:59 +0000659 for (y=0; y < (ssize_t) image->rows; y++)
660 {
cristyaf29b622013-02-18 15:06:48 +0000661 register const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100662 *magick_restrict p;
cristyaf29b622013-02-18 15:06:48 +0000663
664 register ssize_t
665 x;
666
cristyc82a27b2011-10-21 01:07:16 +0000667 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000668 if (p == (const Quantum *) NULL)
cristy644040e2011-03-14 17:31:59 +0000669 break;
670 for (x=0; x < (ssize_t) image->columns; x++)
671 {
cristy17f11b02014-12-20 19:37:04 +0000672 *q++=(uint32_t) (image->alpha_trait != UndefinedPixelTrait ?
cristyb0de93f2013-05-03 13:39:25 +0000673 ScaleQuantumToChar(GetPixelAlpha(image,p)) << 24 : 0xff000000) |
cristyaf29b622013-02-18 15:06:48 +0000674 (ScaleQuantumToChar(GetPixelRed(image,p)) << 16) |
675 (ScaleQuantumToChar(GetPixelGreen(image,p)) << 8) |
676 (ScaleQuantumToChar(GetPixelBlue(image,p)));
cristyed231572011-07-14 02:18:59 +0000677 p+=GetPixelChannels(image);
cristy644040e2011-03-14 17:31:59 +0000678 }
679 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
680 image->rows);
681 if (status == MagickFalse)
682 break;
683 }
cristy644040e2011-03-14 17:31:59 +0000684 webp_status=WebPEncode(&configure,&picture);
cristycc361672014-04-06 15:13:32 +0000685 if (webp_status == 0)
cristyf13926d2014-03-13 12:38:55 +0000686 {
687 const char
688 *message;
689
690 switch (picture.error_code)
691 {
692 case VP8_ENC_ERROR_OUT_OF_MEMORY:
693 {
694 message="out of memory";
695 break;
696 }
697 case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
698 {
699 message="bitstream out of memory";
700 break;
701 }
702 case VP8_ENC_ERROR_NULL_PARAMETER:
703 {
704 message="NULL parameter";
705 break;
706 }
707 case VP8_ENC_ERROR_INVALID_CONFIGURATION:
708 {
709 message="invalid configuration";
710 break;
711 }
712 case VP8_ENC_ERROR_BAD_DIMENSION:
713 {
714 message="bad dimension";
715 break;
716 }
717 case VP8_ENC_ERROR_PARTITION0_OVERFLOW:
718 {
719 message="partition 0 overflow (> 512K)";
720 break;
721 }
722 case VP8_ENC_ERROR_PARTITION_OVERFLOW:
723 {
724 message="partition overflow (> 16M)";
725 break;
726 }
727 case VP8_ENC_ERROR_BAD_WRITE:
728 {
729 message="bad write";
730 break;
731 }
732 case VP8_ENC_ERROR_FILE_TOO_BIG:
733 {
734 message="file too big (> 4GB)";
735 break;
736 }
cristyda680982014-08-10 23:24:02 +0000737#if WEBP_DECODER_ABI_VERSION >= 0x0100
cristyf13926d2014-03-13 12:38:55 +0000738 case VP8_ENC_ERROR_USER_ABORT:
739 {
740 message="user abort";
741 break;
742 }
743#endif
744 default:
745 {
746 message="unknown exception";
747 break;
748 }
749 }
750 (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
751 (char *) message,"`%s'",image->filename);
752 }
cristy7ab1ba82014-11-13 21:37:21 +0000753 picture.argb=(uint32_t *) NULL;
cristy05df1122012-09-26 09:43:12 +0000754 WebPPictureFree(&picture);
cristy5def2032013-06-30 17:44:08 +0000755 pixel_info=RelinquishVirtualMemory(pixel_info);
cristy644040e2011-03-14 17:31:59 +0000756 (void) CloseBlob(image);
757 return(webp_status == 0 ? MagickFalse : MagickTrue);
cristyb1860752011-03-14 00:27:46 +0000758}
759#endif