1 // ==========================================================
2 // Google WebP Loader & Writer
3 //
4 // Design and implementation by
5 // - Herve Drolon (drolon@infonie.fr)
6 //
7 // This file is part of FreeImage 3
8 //
9 // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
10 // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
11 // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
12 // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
13 // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
14 // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
15 // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
16 // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
17 // THIS DISCLAIMER.
18 //
19 // Use at your own risk!
20 // ==========================================================
21
22 #include "FreeImage.h"
23 #include "Utilities.h"
24
25 #include "../Metadata/FreeImageTag.h"
26
27 #include "../LibWebP/src/webp/decode.h"
28 #include "../LibWebP/src/webp/encode.h"
29 #include "../LibWebP/src/webp/mux.h"
30
31 // ==========================================================
32 // Plugin Interface
33 // ==========================================================
34
35 static int s_format_id;
36
37 // ----------------------------------------------------------
38 // Helpers for the load function
39 // ----------------------------------------------------------
40
41 /**
42 Read the whole file into memory
43 */
44 static BOOL
ReadFileToWebPData(FreeImageIO * io,fi_handle handle,WebPData * const bitstream)45 ReadFileToWebPData(FreeImageIO *io, fi_handle handle, WebPData * const bitstream) {
46 uint8_t *raw_data = NULL;
47
48 try {
49 // Read the input file and put it in memory
50 long start_pos = io->tell_proc(handle);
51 io->seek_proc(handle, 0, SEEK_END);
52 size_t file_length = (size_t)(io->tell_proc(handle) - start_pos);
53 io->seek_proc(handle, start_pos, SEEK_SET);
54 raw_data = (uint8_t*)malloc(file_length * sizeof(uint8_t));
55 if(!raw_data) {
56 throw FI_MSG_ERROR_MEMORY;
57 }
58 if(io->read_proc(raw_data, 1, (unsigned)file_length, handle) != file_length) {
59 throw "Error while reading input stream";
60 }
61
62 // copy pointers (must be released later using free)
63 bitstream->bytes = raw_data;
64 bitstream->size = file_length;
65
66 return TRUE;
67
68 } catch(const char *text) {
69 if(raw_data) {
70 free(raw_data);
71 }
72 memset(bitstream, 0, sizeof(WebPData));
73 if(NULL != text) {
74 FreeImage_OutputMessageProc(s_format_id, text);
75 }
76 return FALSE;
77 }
78 }
79
80 // ----------------------------------------------------------
81 // Helpers for the save function
82 // ----------------------------------------------------------
83
84 /**
85 Output function. Should return 1 if writing was successful.
86 data/data_size is the segment of data to write, and 'picture' is for
87 reference (and so one can make use of picture->custom_ptr).
88 */
89 static int
WebP_MemoryWriter(const BYTE * data,size_t data_size,const WebPPicture * const picture)90 WebP_MemoryWriter(const BYTE *data, size_t data_size, const WebPPicture* const picture) {
91 FIMEMORY *hmem = (FIMEMORY*)picture->custom_ptr;
92 return data_size ? (FreeImage_WriteMemory(data, 1, (unsigned)data_size, hmem) == data_size) : 0;
93 }
94
95 // ==========================================================
96 // Plugin Implementation
97 // ==========================================================
98
99 static const char * DLL_CALLCONV
Format()100 Format() {
101 return "WebP";
102 }
103
104 static const char * DLL_CALLCONV
Description()105 Description() {
106 return "Google WebP image format";
107 }
108
109 static const char * DLL_CALLCONV
Extension()110 Extension() {
111 return "webp";
112 }
113
114 static const char * DLL_CALLCONV
RegExpr()115 RegExpr() {
116 return NULL;
117 }
118
119 static const char * DLL_CALLCONV
MimeType()120 MimeType() {
121 return "image/webp";
122 }
123
124 static BOOL DLL_CALLCONV
Validate(FreeImageIO * io,fi_handle handle)125 Validate(FreeImageIO *io, fi_handle handle) {
126 BYTE riff_signature[4] = { 0x52, 0x49, 0x46, 0x46 };
127 BYTE webp_signature[4] = { 0x57, 0x45, 0x42, 0x50 };
128 BYTE signature[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
129
130 io->read_proc(signature, 1, 12, handle);
131
132 if(memcmp(riff_signature, signature, 4) == 0) {
133 if(memcmp(webp_signature, signature + 8, 4) == 0) {
134 return TRUE;
135 }
136 }
137
138 return FALSE;
139 }
140
141 static BOOL DLL_CALLCONV
SupportsExportDepth(int depth)142 SupportsExportDepth(int depth) {
143 return (
144 (depth == 24) ||
145 (depth == 32)
146 );
147 }
148
149 static BOOL DLL_CALLCONV
SupportsExportType(FREE_IMAGE_TYPE type)150 SupportsExportType(FREE_IMAGE_TYPE type) {
151 return (type == FIT_BITMAP) ? TRUE : FALSE;
152 }
153
154 static BOOL DLL_CALLCONV
SupportsICCProfiles()155 SupportsICCProfiles() {
156 return TRUE;
157 }
158
159 static BOOL DLL_CALLCONV
SupportsNoPixels()160 SupportsNoPixels() {
161 return TRUE;
162 }
163
164 // ----------------------------------------------------------
165
166 static void * DLL_CALLCONV
Open(FreeImageIO * io,fi_handle handle,BOOL read)167 Open(FreeImageIO *io, fi_handle handle, BOOL read) {
168 WebPMux *mux = NULL;
169 int copy_data = 1; // 1 : copy data into the mux, 0 : keep a link to local data
170
171 if(read) {
172 // create the MUX object from the input stream
173 WebPData bitstream;
174 // read the input file and put it in memory
175 if(!ReadFileToWebPData(io, handle, &bitstream)) {
176 return NULL;
177 }
178 // create the MUX object
179 mux = WebPMuxCreate(&bitstream, copy_data);
180 // no longer needed since copy_data == 1
181 free((void*)bitstream.bytes);
182 if(mux == NULL) {
183 FreeImage_OutputMessageProc(s_format_id, "Failed to create mux object from file");
184 return NULL;
185 }
186 } else {
187 // creates an empty mux object
188 mux = WebPMuxNew();
189 if(mux == NULL) {
190 FreeImage_OutputMessageProc(s_format_id, "Failed to create empty mux object");
191 return NULL;
192 }
193 }
194
195 return mux;
196 }
197
198 static void DLL_CALLCONV
Close(FreeImageIO * io,fi_handle handle,void * data)199 Close(FreeImageIO *io, fi_handle handle, void *data) {
200 WebPMux *mux = (WebPMux*)data;
201 if(mux != NULL) {
202 // free the MUX object
203 WebPMuxDelete(mux);
204 }
205 }
206
207 // ----------------------------------------------------------
208
209 /**
210 Decode a WebP image and returns a FIBITMAP image
211 @param webp_image Raw WebP image
212 @param flags FreeImage load flags
213 @return Returns a dib if successfull, returns NULL otherwise
214 */
215 static FIBITMAP *
DecodeImage(WebPData * webp_image,int flags)216 DecodeImage(WebPData *webp_image, int flags) {
217 FIBITMAP *dib = NULL;
218
219 const uint8_t* data = webp_image->bytes; // raw image data
220 const size_t data_size = webp_image->size; // raw image size
221
222 VP8StatusCode webp_status = VP8_STATUS_OK;
223
224 BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;
225
226 // Main object storing the configuration for advanced decoding
227 WebPDecoderConfig decoder_config;
228 // Output buffer
229 WebPDecBuffer* const output_buffer = &decoder_config.output;
230 // Features gathered from the bitstream
231 WebPBitstreamFeatures* const bitstream = &decoder_config.input;
232
233 try {
234 // Initialize the configuration as empty
235 // This function must always be called first, unless WebPGetFeatures() is to be called
236 if(!WebPInitDecoderConfig(&decoder_config)) {
237 throw "Library version mismatch";
238 }
239
240 // Retrieve features from the bitstream
241 // The bitstream structure is filled with information gathered from the bitstream
242 webp_status = WebPGetFeatures(data, data_size, bitstream);
243 if(webp_status != VP8_STATUS_OK) {
244 throw FI_MSG_ERROR_PARSING;
245 }
246
247 // Allocate output dib
248
249 unsigned bpp = bitstream->has_alpha ? 32 : 24;
250 unsigned width = (unsigned)bitstream->width;
251 unsigned height = (unsigned)bitstream->height;
252
253 dib = FreeImage_AllocateHeader(header_only, width, height, bpp, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
254 if(!dib) {
255 throw FI_MSG_ERROR_DIB_MEMORY;
256 }
257
258 if(header_only) {
259 WebPFreeDecBuffer(output_buffer);
260 return dib;
261 }
262
263 // --- Set decoding options ---
264
265 // use multi-threaded decoding
266 decoder_config.options.use_threads = 1;
267 // set output color space
268 output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR;
269
270 // ---
271
272 // decode the input stream, taking 'config' into account.
273
274 webp_status = WebPDecode(data, data_size, &decoder_config);
275 if(webp_status != VP8_STATUS_OK) {
276 throw FI_MSG_ERROR_PARSING;
277 }
278
279 // fill the dib with the decoded data
280
281 const BYTE *src_bitmap = output_buffer->u.RGBA.rgba;
282 const unsigned src_pitch = (unsigned)output_buffer->u.RGBA.stride;
283
284 switch(bpp) {
285 case 24:
286 for(unsigned y = 0; y < height; y++) {
287 const BYTE *src_bits = src_bitmap + y * src_pitch;
288 BYTE *dst_bits = (BYTE*)FreeImage_GetScanLine(dib, height-1-y);
289 for(unsigned x = 0; x < width; x++) {
290 dst_bits[FI_RGBA_BLUE] = src_bits[0]; // B
291 dst_bits[FI_RGBA_GREEN] = src_bits[1]; // G
292 dst_bits[FI_RGBA_RED] = src_bits[2]; // R
293 src_bits += 3;
294 dst_bits += 3;
295 }
296 }
297 break;
298 case 32:
299 for(unsigned y = 0; y < height; y++) {
300 const BYTE *src_bits = src_bitmap + y * src_pitch;
301 BYTE *dst_bits = (BYTE*)FreeImage_GetScanLine(dib, height-1-y);
302 for(unsigned x = 0; x < width; x++) {
303 dst_bits[FI_RGBA_BLUE] = src_bits[0]; // B
304 dst_bits[FI_RGBA_GREEN] = src_bits[1]; // G
305 dst_bits[FI_RGBA_RED] = src_bits[2]; // R
306 dst_bits[FI_RGBA_ALPHA] = src_bits[3]; // A
307 src_bits += 4;
308 dst_bits += 4;
309 }
310 }
311 break;
312 }
313
314 // Free the decoder
315 WebPFreeDecBuffer(output_buffer);
316
317 return dib;
318
319 } catch (const char *text) {
320 if(dib) {
321 FreeImage_Unload(dib);
322 }
323 WebPFreeDecBuffer(output_buffer);
324
325 if(NULL != text) {
326 FreeImage_OutputMessageProc(s_format_id, text);
327 }
328
329 return NULL;
330 }
331 }
332
333 static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO * io,fi_handle handle,int page,int flags,void * data)334 Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
335 WebPMux *mux = NULL;
336 WebPMuxFrameInfo webp_frame = { 0 }; // raw image
337 WebPData color_profile; // ICC raw data
338 WebPData xmp_metadata; // XMP raw data
339 WebPData exif_metadata; // EXIF raw data
340 FIBITMAP *dib = NULL;
341 WebPMuxError error_status;
342
343 if(!handle) {
344 return NULL;
345 }
346
347 try {
348 // get the MUX object
349 mux = (WebPMux*)data;
350 if(!mux) {
351 throw (1);
352 }
353
354 // gets the feature flags from the mux object
355 uint32_t webp_flags = 0;
356 error_status = WebPMuxGetFeatures(mux, &webp_flags);
357 if(error_status != WEBP_MUX_OK) {
358 throw (1);
359 }
360
361 // get image data
362 error_status = WebPMuxGetFrame(mux, 1, &webp_frame);
363
364 if(error_status == WEBP_MUX_OK) {
365 // decode the data (can be limited to the header if flags uses FIF_LOAD_NOPIXELS)
366 dib = DecodeImage(&webp_frame.bitstream, flags);
367 if(!dib) {
368 throw (1);
369 }
370
371 // get ICC profile
372 if(webp_flags & ICCP_FLAG) {
373 error_status = WebPMuxGetChunk(mux, "ICCP", &color_profile);
374 if(error_status == WEBP_MUX_OK) {
375 FreeImage_CreateICCProfile(dib, (void*)color_profile.bytes, (long)color_profile.size);
376 }
377 }
378
379 // get XMP metadata
380 if(webp_flags & XMP_FLAG) {
381 error_status = WebPMuxGetChunk(mux, "XMP ", &xmp_metadata);
382 if(error_status == WEBP_MUX_OK) {
383 // create a tag
384 FITAG *tag = FreeImage_CreateTag();
385 if(tag) {
386 FreeImage_SetTagKey(tag, g_TagLib_XMPFieldName);
387 FreeImage_SetTagLength(tag, (DWORD)xmp_metadata.size);
388 FreeImage_SetTagCount(tag, (DWORD)xmp_metadata.size);
389 FreeImage_SetTagType(tag, FIDT_ASCII);
390 FreeImage_SetTagValue(tag, xmp_metadata.bytes);
391
392 // store the tag
393 FreeImage_SetMetadata(FIMD_XMP, dib, FreeImage_GetTagKey(tag), tag);
394
395 // destroy the tag
396 FreeImage_DeleteTag(tag);
397 }
398 }
399 }
400
401 // get Exif metadata
402 if(webp_flags & EXIF_FLAG) {
403 error_status = WebPMuxGetChunk(mux, "EXIF", &exif_metadata);
404 if(error_status == WEBP_MUX_OK) {
405 // read the Exif raw data as a blob
406 jpeg_read_exif_profile_raw(dib, exif_metadata.bytes, (unsigned)exif_metadata.size);
407 // read and decode the Exif data
408 jpeg_read_exif_profile(dib, exif_metadata.bytes, (unsigned)exif_metadata.size);
409 }
410 }
411 }
412
413 WebPDataClear(&webp_frame.bitstream);
414
415 return dib;
416
417 } catch(int) {
418 WebPDataClear(&webp_frame.bitstream);
419 return NULL;
420 }
421 }
422
423 // --------------------------------------------------------------------------
424
425 /**
426 Encode a FIBITMAP to a WebP image
427 @param hmem Memory output stream, containing on return the encoded image
428 @param dib The FIBITMAP to encode
429 @param flags FreeImage save flags
430 @return Returns TRUE if successfull, returns FALSE otherwise
431 */
432 static BOOL
EncodeImage(FIMEMORY * hmem,FIBITMAP * dib,int flags)433 EncodeImage(FIMEMORY *hmem, FIBITMAP *dib, int flags) {
434 WebPPicture picture; // Input buffer
435 WebPConfig config; // Coding parameters
436
437 BOOL bIsFlipped = FALSE;
438
439 try {
440 const unsigned width = FreeImage_GetWidth(dib);
441 const unsigned height = FreeImage_GetHeight(dib);
442 const unsigned bpp = FreeImage_GetBPP(dib);
443 const unsigned pitch = FreeImage_GetPitch(dib);
444
445 // check image type
446 FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib);
447
448 if( !((image_type == FIT_BITMAP) && ((bpp == 24) || (bpp == 32))) ) {
449 throw FI_MSG_ERROR_UNSUPPORTED_FORMAT;
450 }
451
452 // check format limits
453 if(MAX(width, height) > WEBP_MAX_DIMENSION) {
454 FreeImage_OutputMessageProc(s_format_id, "Unsupported image size: width x height = %d x %d", width, height);
455 return FALSE;
456 }
457
458 // Initialize output I/O
459 if(WebPPictureInit(&picture) == 1) {
460 picture.writer = WebP_MemoryWriter;
461 picture.custom_ptr = hmem;
462 picture.width = (int)width;
463 picture.height = (int)height;
464 } else {
465 throw "Couldn't initialize WebPPicture";
466 }
467
468 // --- Set encoding parameters ---
469
470 // Initialize encoding parameters to default values
471 WebPConfigInit(&config);
472
473 // quality/speed trade-off (0=fast, 6=slower-better)
474 config.method = 6;
475
476 if((flags & WEBP_LOSSLESS) == WEBP_LOSSLESS) {
477 // lossless encoding
478 config.lossless = 1;
479 picture.use_argb = 1;
480 } else if((flags & 0x7F) > 0) {
481 // lossy encoding
482 config.lossless = 0;
483 // quality is between 1 (smallest file) and 100 (biggest) - default to 75
484 config.quality = (float)(flags & 0x7F);
485 if(config.quality > 100) {
486 config.quality = 100;
487 }
488 }
489
490 // validate encoding parameters
491 if(WebPValidateConfig(&config) == 0) {
492 throw "Failed to initialize encoder";
493 }
494
495 // --- Perform encoding ---
496
497 // Invert dib scanlines
498 bIsFlipped = FreeImage_FlipVertical(dib);
499
500
501 // convert dib buffer to output stream
502
503 const BYTE *bits = FreeImage_GetBits(dib);
504
505 #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
506 switch(bpp) {
507 case 24:
508 WebPPictureImportBGR(&picture, bits, pitch);
509 break;
510 case 32:
511 WebPPictureImportBGRA(&picture, bits, pitch);
512 break;
513 }
514 #else
515 switch(bpp) {
516 case 24:
517 WebPPictureImportRGB(&picture, bits, pitch);
518 break;
519 case 32:
520 WebPPictureImportRGBA(&picture, bits, pitch);
521 break;
522 }
523
524 #endif // FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
525
526 if(!WebPEncode(&config, &picture)) {
527 throw "Failed to encode image";
528 }
529
530 WebPPictureFree(&picture);
531
532 if(bIsFlipped) {
533 // invert dib scanlines
534 FreeImage_FlipVertical(dib);
535 }
536
537 return TRUE;
538
539 } catch (const char* text) {
540
541 WebPPictureFree(&picture);
542
543 if(bIsFlipped) {
544 // invert dib scanlines
545 FreeImage_FlipVertical(dib);
546 }
547
548 if(NULL != text) {
549 FreeImage_OutputMessageProc(s_format_id, text);
550 }
551 }
552
553 return FALSE;
554 }
555
556 static BOOL DLL_CALLCONV
Save(FreeImageIO * io,FIBITMAP * dib,fi_handle handle,int page,int flags,void * data)557 Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) {
558 WebPMux *mux = NULL;
559 FIMEMORY *hmem = NULL;
560 WebPData webp_image;
561 WebPData output_data = { 0 };
562 WebPMuxError error_status;
563
564 int copy_data = 1; // 1 : copy data into the mux, 0 : keep a link to local data
565
566 if(!dib || !handle || !data) {
567 return FALSE;
568 }
569
570 try {
571
572 // get the MUX object
573 mux = (WebPMux*)data;
574 if(!mux) {
575 return FALSE;
576 }
577
578 // --- prepare image data ---
579
580 // encode image as a WebP blob
581 hmem = FreeImage_OpenMemory();
582 if(!hmem || !EncodeImage(hmem, dib, flags)) {
583 throw (1);
584 }
585 // store the blob into the mux
586 BYTE *data = NULL;
587 DWORD data_size = 0;
588 FreeImage_AcquireMemory(hmem, &data, &data_size);
589 webp_image.bytes = data;
590 webp_image.size = data_size;
591 error_status = WebPMuxSetImage(mux, &webp_image, copy_data);
592 // no longer needed since copy_data == 1
593 FreeImage_CloseMemory(hmem);
594 hmem = NULL;
595 if(error_status != WEBP_MUX_OK) {
596 throw (1);
597 }
598
599 // --- set metadata ---
600
601 // set ICC color profile
602 {
603 FIICCPROFILE *iccProfile = FreeImage_GetICCProfile(dib);
604 if (iccProfile->size && iccProfile->data) {
605 WebPData icc_profile;
606 icc_profile.bytes = (uint8_t*)iccProfile->data;
607 icc_profile.size = (size_t)iccProfile->size;
608 error_status = WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data);
609 if(error_status != WEBP_MUX_OK) {
610 throw (1);
611 }
612 }
613 }
614
615 // set XMP metadata
616 {
617 FITAG *tag = NULL;
618 if(FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag)) {
619 WebPData xmp_profile;
620 xmp_profile.bytes = (uint8_t*)FreeImage_GetTagValue(tag);
621 xmp_profile.size = (size_t)FreeImage_GetTagLength(tag);
622 error_status = WebPMuxSetChunk(mux, "XMP ", &xmp_profile, copy_data);
623 if(error_status != WEBP_MUX_OK) {
624 throw (1);
625 }
626 }
627 }
628
629 // set Exif metadata
630 {
631 FITAG *tag = NULL;
632 if(FreeImage_GetMetadata(FIMD_EXIF_RAW, dib, g_TagLib_ExifRawFieldName, &tag)) {
633 WebPData exif_profile;
634 exif_profile.bytes = (uint8_t*)FreeImage_GetTagValue(tag);
635 exif_profile.size = (size_t)FreeImage_GetTagLength(tag);
636 error_status = WebPMuxSetChunk(mux, "EXIF", &exif_profile, copy_data);
637 if(error_status != WEBP_MUX_OK) {
638 throw (1);
639 }
640 }
641 }
642
643 // get data from mux in WebP RIFF format
644 error_status = WebPMuxAssemble(mux, &output_data);
645 if(error_status != WEBP_MUX_OK) {
646 FreeImage_OutputMessageProc(s_format_id, "Failed to create webp output file");
647 throw (1);
648 }
649
650 // write the file to the output stream
651 if(io->write_proc((void*)output_data.bytes, 1, (unsigned)output_data.size, handle) != output_data.size) {
652 FreeImage_OutputMessageProc(s_format_id, "Failed to write webp output file");
653 throw (1);
654 }
655
656 // free WebP output file
657 WebPDataClear(&output_data);
658
659 return TRUE;
660
661 } catch(int) {
662 if(hmem) {
663 FreeImage_CloseMemory(hmem);
664 }
665
666 WebPDataClear(&output_data);
667
668 return FALSE;
669 }
670 }
671
672 // ==========================================================
673 // Init
674 // ==========================================================
675
676 void DLL_CALLCONV
InitWEBP(Plugin * plugin,int format_id)677 InitWEBP(Plugin *plugin, int format_id) {
678 s_format_id = format_id;
679
680 plugin->format_proc = Format;
681 plugin->description_proc = Description;
682 plugin->extension_proc = Extension;
683 plugin->regexpr_proc = RegExpr;
684 plugin->open_proc = Open;
685 plugin->close_proc = Close;
686 plugin->pagecount_proc = NULL;
687 plugin->pagecapability_proc = NULL;
688 plugin->load_proc = Load;
689 plugin->save_proc = Save;
690 plugin->validate_proc = Validate;
691 plugin->mime_proc = MimeType;
692 plugin->supports_export_bpp_proc = SupportsExportDepth;
693 plugin->supports_export_type_proc = SupportsExportType;
694 plugin->supports_icc_profiles_proc = SupportsICCProfiles;
695 plugin->supports_no_pixels_proc = SupportsNoPixels;
696 }
697
698