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