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