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