/src/freeimage-svn/FreeImage/trunk/Source/FreeImage/PluginPNG.cpp
Line | Count | Source |
1 | | // ========================================================== |
2 | | // PNG Loader and Writer |
3 | | // |
4 | | // Design and implementation by |
5 | | // - Floris van den Berg (flvdberg@wxs.nl) |
6 | | // - Herve Drolon (drolon@infonie.fr) |
7 | | // - Detlev Vendt (detlev.vendt@brillit.de) |
8 | | // - Aaron Shumate (trek@startreker.com) |
9 | | // - Tanner Helland (tannerhelland@users.sf.net) |
10 | | // |
11 | | // This file is part of FreeImage 3 |
12 | | // |
13 | | // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY |
14 | | // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES |
15 | | // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE |
16 | | // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED |
17 | | // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT |
18 | | // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY |
19 | | // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL |
20 | | // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER |
21 | | // THIS DISCLAIMER. |
22 | | // |
23 | | // Use at your own risk! |
24 | | // ========================================================== |
25 | | |
26 | | #ifdef _MSC_VER |
27 | | #pragma warning (disable : 4786) // identifier was truncated to 'number' characters |
28 | | #endif |
29 | | |
30 | | #include "FreeImage.h" |
31 | | #include "Utilities.h" |
32 | | |
33 | | #include "../Metadata/FreeImageTag.h" |
34 | | |
35 | | // ---------------------------------------------------------- |
36 | | |
37 | 0 | #define PNG_BYTES_TO_CHECK 8 |
38 | | |
39 | | #undef PNG_Z_DEFAULT_COMPRESSION // already used in ../LibPNG/pnglibconf.h |
40 | | |
41 | | // ---------------------------------------------------------- |
42 | | |
43 | | #include "../ZLib/zlib.h" |
44 | | #include "../LibPNG/png.h" |
45 | | |
46 | | // ---------------------------------------------------------- |
47 | | |
48 | | typedef struct { |
49 | | FreeImageIO *s_io; |
50 | | fi_handle s_handle; |
51 | | } fi_ioStructure, *pfi_ioStructure; |
52 | | |
53 | | // ========================================================== |
54 | | // Plugin Interface |
55 | | // ========================================================== |
56 | | |
57 | | static int s_format_id; |
58 | | |
59 | | // ========================================================== |
60 | | // libpng interface |
61 | | // ========================================================== |
62 | | |
63 | | static void |
64 | 0 | _ReadProc(png_structp png_ptr, unsigned char *data, png_size_t size) { |
65 | 0 | pfi_ioStructure pfio = (pfi_ioStructure)png_get_io_ptr(png_ptr); |
66 | 0 | unsigned n = pfio->s_io->read_proc(data, (unsigned int)size, 1, pfio->s_handle); |
67 | 0 | if(size && (n == 0)) { |
68 | 0 | throw "Read error: invalid or corrupted PNG file"; |
69 | 0 | } |
70 | 0 | } |
71 | | |
72 | | static void |
73 | 0 | _WriteProc(png_structp png_ptr, unsigned char *data, png_size_t size) { |
74 | 0 | pfi_ioStructure pfio = (pfi_ioStructure)png_get_io_ptr(png_ptr); |
75 | 0 | pfio->s_io->write_proc(data, (unsigned int)size, 1, pfio->s_handle); |
76 | 0 | } |
77 | | |
78 | | static void |
79 | 0 | _FlushProc(png_structp png_ptr) { |
80 | 0 | (png_structp)png_ptr; |
81 | | // empty flush implementation |
82 | 0 | } |
83 | | |
84 | | static void |
85 | 0 | error_handler(png_structp png_ptr, const char *error) { |
86 | 0 | FreeImage_OutputMessageProc(s_format_id, error); |
87 | 0 | png_longjmp(png_ptr, 1); |
88 | 0 | } |
89 | | |
90 | | // in FreeImage warnings disabled |
91 | | |
92 | | static void |
93 | 0 | warning_handler(png_structp png_ptr, const char *warning) { |
94 | 0 | (png_structp)png_ptr; |
95 | 0 | (char*)warning; |
96 | 0 | } |
97 | | |
98 | | // ========================================================== |
99 | | // Metadata routines |
100 | | // ========================================================== |
101 | | |
102 | | static BOOL |
103 | 0 | ReadMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { |
104 | | // XMP keyword |
105 | 0 | const char *g_png_xmp_keyword = "XML:com.adobe.xmp"; |
106 | |
|
107 | 0 | FITAG *tag = NULL; |
108 | 0 | png_textp text_ptr = NULL; |
109 | 0 | png_timep mod_time = NULL; |
110 | 0 | int num_text = 0; |
111 | | |
112 | | // iTXt/tEXt/zTXt chuncks |
113 | 0 | if(png_get_text(png_ptr, info_ptr, &text_ptr, &num_text) > 0) { |
114 | 0 | for(int i = 0; i < num_text; i++) { |
115 | | // create a tag |
116 | 0 | tag = FreeImage_CreateTag(); |
117 | 0 | if(!tag) return FALSE; |
118 | | |
119 | 0 | DWORD tag_length = (DWORD) MAX(text_ptr[i].text_length, text_ptr[i].itxt_length); |
120 | |
|
121 | 0 | FreeImage_SetTagLength(tag, tag_length); |
122 | 0 | FreeImage_SetTagCount(tag, tag_length); |
123 | 0 | FreeImage_SetTagType(tag, FIDT_ASCII); |
124 | 0 | FreeImage_SetTagValue(tag, text_ptr[i].text); |
125 | |
|
126 | 0 | if(strcmp(text_ptr[i].key, g_png_xmp_keyword) == 0) { |
127 | | // store the tag as XMP |
128 | 0 | FreeImage_SetTagKey(tag, g_TagLib_XMPFieldName); |
129 | 0 | FreeImage_SetMetadata(FIMD_XMP, dib, FreeImage_GetTagKey(tag), tag); |
130 | 0 | } else { |
131 | | // store the tag as a comment |
132 | 0 | FreeImage_SetTagKey(tag, text_ptr[i].key); |
133 | 0 | FreeImage_SetMetadata(FIMD_COMMENTS, dib, FreeImage_GetTagKey(tag), tag); |
134 | 0 | } |
135 | | |
136 | | // destroy the tag |
137 | 0 | FreeImage_DeleteTag(tag); |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | | // timestamp chunk |
142 | 0 | if(png_get_tIME(png_ptr, info_ptr, &mod_time)) { |
143 | 0 | char timestamp[32]; |
144 | | // create a tag |
145 | 0 | tag = FreeImage_CreateTag(); |
146 | 0 | if(!tag) return FALSE; |
147 | | |
148 | | // convert as 'yyyy:MM:dd hh:mm:ss' |
149 | 0 | sprintf(timestamp, "%4d:%02d:%02d %2d:%02d:%02d", mod_time->year, mod_time->month, mod_time->day, mod_time->hour, mod_time->minute, mod_time->second); |
150 | |
|
151 | 0 | DWORD tag_length = (DWORD)strlen(timestamp) + 1; |
152 | 0 | FreeImage_SetTagLength(tag, tag_length); |
153 | 0 | FreeImage_SetTagCount(tag, tag_length); |
154 | 0 | FreeImage_SetTagType(tag, FIDT_ASCII); |
155 | 0 | FreeImage_SetTagID(tag, TAG_DATETIME); |
156 | 0 | FreeImage_SetTagValue(tag, timestamp); |
157 | | |
158 | | // store the tag as Exif-TIFF |
159 | 0 | FreeImage_SetTagKey(tag, "DateTime"); |
160 | 0 | FreeImage_SetMetadata(FIMD_EXIF_MAIN, dib, FreeImage_GetTagKey(tag), tag); |
161 | | |
162 | | // destroy the tag |
163 | 0 | FreeImage_DeleteTag(tag); |
164 | 0 | } |
165 | | |
166 | 0 | return TRUE; |
167 | 0 | } |
168 | | |
169 | | static BOOL |
170 | 0 | WriteMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { |
171 | | // XMP keyword |
172 | 0 | const char *g_png_xmp_keyword = "XML:com.adobe.xmp"; |
173 | |
|
174 | 0 | FITAG *tag = NULL; |
175 | 0 | FIMETADATA *mdhandle = NULL; |
176 | 0 | BOOL bResult = TRUE; |
177 | |
|
178 | 0 | png_text text_metadata; |
179 | 0 | png_time mod_time; |
180 | | |
181 | | // set the 'Comments' metadata as iTXt chuncks |
182 | |
|
183 | 0 | mdhandle = FreeImage_FindFirstMetadata(FIMD_COMMENTS, dib, &tag); |
184 | |
|
185 | 0 | if(mdhandle) { |
186 | 0 | do { |
187 | 0 | memset(&text_metadata, 0, sizeof(png_text)); |
188 | 0 | text_metadata.compression = 1; // iTXt, none |
189 | 0 | text_metadata.key = (char*)FreeImage_GetTagKey(tag); // keyword, 1-79 character description of "text" |
190 | 0 | text_metadata.text = (char*)FreeImage_GetTagValue(tag); // comment, may be an empty string (ie "") |
191 | 0 | text_metadata.text_length = FreeImage_GetTagLength(tag);// length of the text string |
192 | 0 | text_metadata.itxt_length = FreeImage_GetTagLength(tag);// length of the itxt string |
193 | 0 | text_metadata.lang = 0; // language code, 0-79 characters or a NULL pointer |
194 | 0 | text_metadata.lang_key = 0; // keyword translated UTF-8 string, 0 or more chars or a NULL pointer |
195 | | |
196 | | // set the tag |
197 | 0 | png_set_text(png_ptr, info_ptr, &text_metadata, 1); |
198 | |
|
199 | 0 | } while(FreeImage_FindNextMetadata(mdhandle, &tag)); |
200 | |
|
201 | 0 | FreeImage_FindCloseMetadata(mdhandle); |
202 | 0 | bResult &= TRUE; |
203 | 0 | } |
204 | | |
205 | | // set the 'XMP' metadata as iTXt chuncks |
206 | 0 | tag = NULL; |
207 | 0 | FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag); |
208 | 0 | if(tag && FreeImage_GetTagLength(tag)) { |
209 | 0 | memset(&text_metadata, 0, sizeof(png_text)); |
210 | 0 | text_metadata.compression = 1; // iTXt, none |
211 | 0 | text_metadata.key = (char*)g_png_xmp_keyword; // keyword, 1-79 character description of "text" |
212 | 0 | text_metadata.text = (char*)FreeImage_GetTagValue(tag); // comment, may be an empty string (ie "") |
213 | 0 | text_metadata.text_length = FreeImage_GetTagLength(tag);// length of the text string |
214 | 0 | text_metadata.itxt_length = FreeImage_GetTagLength(tag);// length of the itxt string |
215 | 0 | text_metadata.lang = 0; // language code, 0-79 characters or a NULL pointer |
216 | 0 | text_metadata.lang_key = 0; // keyword translated UTF-8 string, 0 or more chars or a NULL pointer |
217 | | |
218 | | // set the tag |
219 | 0 | png_set_text(png_ptr, info_ptr, &text_metadata, 1); |
220 | 0 | bResult &= TRUE; |
221 | 0 | } |
222 | | |
223 | | // set the Exif-TIFF 'DateTime' metadata as a tIME chunk |
224 | 0 | tag = NULL; |
225 | 0 | FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, "DateTime", &tag); |
226 | 0 | if(tag && FreeImage_GetTagLength(tag)) { |
227 | 0 | int year, month, day, hour, minute, second; |
228 | 0 | const char *value = (char*)FreeImage_GetTagValue(tag); |
229 | 0 | if(sscanf(value, "%4d:%02d:%02d %2d:%02d:%02d", &year, &month, &day, &hour, &minute, &second) == 6) { |
230 | 0 | mod_time.year = (png_uint_16)year; |
231 | 0 | mod_time.month = (png_byte)month; |
232 | 0 | mod_time.day = (png_byte)day; |
233 | 0 | mod_time.hour = (png_byte)hour; |
234 | 0 | mod_time.minute = (png_byte)minute; |
235 | 0 | mod_time.second = (png_byte)second; |
236 | 0 | png_set_tIME (png_ptr, info_ptr, &mod_time); |
237 | 0 | } |
238 | 0 | } |
239 | |
|
240 | 0 | return bResult; |
241 | 0 | } |
242 | | |
243 | | // ========================================================== |
244 | | // Plugin Implementation |
245 | | // ========================================================== |
246 | | |
247 | | static const char * DLL_CALLCONV |
248 | 2 | Format() { |
249 | 2 | return "PNG"; |
250 | 2 | } |
251 | | |
252 | | static const char * DLL_CALLCONV |
253 | 0 | Description() { |
254 | 0 | return "Portable Network Graphics"; |
255 | 0 | } |
256 | | |
257 | | static const char * DLL_CALLCONV |
258 | 0 | Extension() { |
259 | 0 | return "png"; |
260 | 0 | } |
261 | | |
262 | | static const char * DLL_CALLCONV |
263 | 0 | RegExpr() { |
264 | 0 | return "^.PNG\r"; |
265 | 0 | } |
266 | | |
267 | | static const char * DLL_CALLCONV |
268 | 0 | MimeType() { |
269 | 0 | return "image/png"; |
270 | 0 | } |
271 | | |
272 | | static BOOL DLL_CALLCONV |
273 | 21.5k | Validate(FreeImageIO *io, fi_handle handle) { |
274 | 21.5k | BYTE png_signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; |
275 | 21.5k | BYTE signature[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; |
276 | | |
277 | 21.5k | io->read_proc(&signature, 1, 8, handle); |
278 | | |
279 | 21.5k | return (memcmp(png_signature, signature, 8) == 0); |
280 | 21.5k | } |
281 | | |
282 | | static BOOL DLL_CALLCONV |
283 | 0 | SupportsExportDepth(int depth) { |
284 | 0 | return ( |
285 | 0 | (depth == 1) || |
286 | 0 | (depth == 4) || |
287 | 0 | (depth == 8) || |
288 | 0 | (depth == 24) || |
289 | 0 | (depth == 32) |
290 | 0 | ); |
291 | 0 | } |
292 | | |
293 | | static BOOL DLL_CALLCONV |
294 | 0 | SupportsExportType(FREE_IMAGE_TYPE type) { |
295 | 0 | return ( |
296 | 0 | (type == FIT_BITMAP) || |
297 | 0 | (type == FIT_UINT16) || |
298 | 0 | (type == FIT_RGB16) || |
299 | 0 | (type == FIT_RGBA16) |
300 | 0 | ); |
301 | 0 | } |
302 | | |
303 | | static BOOL DLL_CALLCONV |
304 | 0 | SupportsICCProfiles() { |
305 | 0 | return TRUE; |
306 | 0 | } |
307 | | |
308 | | static BOOL DLL_CALLCONV |
309 | 0 | SupportsNoPixels() { |
310 | 0 | return TRUE; |
311 | 0 | } |
312 | | |
313 | | // -------------------------------------------------------------------------- |
314 | | |
315 | | /** |
316 | | Configure the decoder so that decoded pixels are compatible with a FREE_IMAGE_TYPE format. |
317 | | Set conversion instructions as needed. |
318 | | @param png_ptr PNG handle |
319 | | @param info_ptr PNG info handle |
320 | | @param flags Decoder flags |
321 | | @param output_image_type Returned FreeImage converted image type |
322 | | @return Returns TRUE if successful, returns FALSE otherwise |
323 | | @see png_read_update_info |
324 | | */ |
325 | | static BOOL |
326 | 0 | ConfigureDecoder(png_structp png_ptr, png_infop info_ptr, int flags, FREE_IMAGE_TYPE *output_image_type) { |
327 | | // get original image info |
328 | 0 | const int color_type = png_get_color_type(png_ptr, info_ptr); |
329 | 0 | const int bit_depth = png_get_bit_depth(png_ptr, info_ptr); |
330 | 0 | const int pixel_depth = bit_depth * png_get_channels(png_ptr, info_ptr); |
331 | |
|
332 | 0 | FREE_IMAGE_TYPE image_type = FIT_BITMAP; // assume standard image type |
333 | | |
334 | | // check for transparency table or single transparent color |
335 | 0 | BOOL bIsTransparent = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) == PNG_INFO_tRNS ? TRUE : FALSE; |
336 | | |
337 | | // check allowed combinations of colour type and bit depth |
338 | | // then get converted FreeImage type |
339 | |
|
340 | 0 | switch(color_type) { |
341 | 0 | case PNG_COLOR_TYPE_GRAY: // color type '0', bitdepth = 1, 2, 4, 8, 16 |
342 | 0 | switch(bit_depth) { |
343 | 0 | case 1: |
344 | 0 | case 2: |
345 | 0 | case 4: |
346 | 0 | case 8: |
347 | | // expand grayscale images to the full 8-bit from 2-bit/pixel |
348 | 0 | if (pixel_depth == 2) { |
349 | 0 | png_set_expand_gray_1_2_4_to_8(png_ptr); |
350 | 0 | } |
351 | | |
352 | | // if a tRNS chunk is provided, we must also expand the grayscale data to 8-bits, |
353 | | // this allows us to make use of the transparency table with existing FreeImage methods |
354 | 0 | if (bIsTransparent && (pixel_depth < 8)) { |
355 | 0 | png_set_expand_gray_1_2_4_to_8(png_ptr); |
356 | 0 | } |
357 | 0 | break; |
358 | | |
359 | 0 | case 16: |
360 | 0 | image_type = (pixel_depth == 16) ? FIT_UINT16 : FIT_UNKNOWN; |
361 | | |
362 | | // 16-bit grayscale images can contain a transparent value (shade) |
363 | | // if found, expand the transparent value to a full alpha channel |
364 | 0 | if (bIsTransparent && (image_type != FIT_UNKNOWN)) { |
365 | | // expand tRNS to a full alpha channel |
366 | 0 | png_set_tRNS_to_alpha(png_ptr); |
367 | | |
368 | | // expand new 16-bit gray + 16-bit alpha to full 64-bit RGBA |
369 | 0 | png_set_gray_to_rgb(png_ptr); |
370 | |
|
371 | 0 | image_type = FIT_RGBA16; |
372 | 0 | } |
373 | 0 | break; |
374 | | |
375 | 0 | default: |
376 | 0 | image_type = FIT_UNKNOWN; |
377 | 0 | break; |
378 | 0 | } |
379 | 0 | break; |
380 | | |
381 | 0 | case PNG_COLOR_TYPE_RGB: // color type '2', bitdepth = 8, 16 |
382 | 0 | switch(bit_depth) { |
383 | 0 | case 8: |
384 | 0 | image_type = (pixel_depth == 24) ? FIT_BITMAP : FIT_UNKNOWN; |
385 | 0 | break; |
386 | 0 | case 16: |
387 | 0 | image_type = (pixel_depth == 48) ? FIT_RGB16 : FIT_UNKNOWN; |
388 | 0 | break; |
389 | 0 | default: |
390 | 0 | image_type = FIT_UNKNOWN; |
391 | 0 | break; |
392 | 0 | } |
393 | | // sometimes, 24- or 48-bit images may contain transparency information |
394 | | // check for this use case and convert to an alpha-compatible format |
395 | 0 | if (bIsTransparent && (image_type != FIT_UNKNOWN)) { |
396 | | // if the image is 24-bit RGB, mark it as 32-bit; if it is 48-bit, mark it as 64-bit |
397 | 0 | image_type = (pixel_depth == 24) ? FIT_BITMAP : (pixel_depth == 48) ? FIT_RGBA16 : FIT_UNKNOWN; |
398 | | // expand tRNS chunk to alpha channel |
399 | 0 | png_set_tRNS_to_alpha(png_ptr); |
400 | 0 | } |
401 | 0 | break; |
402 | | |
403 | 0 | case PNG_COLOR_TYPE_PALETTE: // color type '3', bitdepth = 1, 2, 4, 8 |
404 | 0 | switch(bit_depth) { |
405 | 0 | case 1: |
406 | 0 | case 2: |
407 | 0 | case 4: |
408 | 0 | case 8: |
409 | | // expand palette images to the full 8 bits from 2 bits/pixel |
410 | 0 | if (pixel_depth == 2) { |
411 | 0 | png_set_packing(png_ptr); |
412 | 0 | } |
413 | | |
414 | | // if a tRNS chunk is provided, we must also expand the palletized data to 8-bits, |
415 | | // this allows us to make use of the transparency table with existing FreeImage methods |
416 | 0 | if (bIsTransparent && (pixel_depth < 8)) { |
417 | 0 | png_set_packing(png_ptr); |
418 | 0 | } |
419 | 0 | break; |
420 | | |
421 | 0 | default: |
422 | 0 | image_type = FIT_UNKNOWN; |
423 | 0 | break; |
424 | 0 | } |
425 | 0 | break; |
426 | | |
427 | 0 | case PNG_COLOR_TYPE_GRAY_ALPHA: // color type '4', bitdepth = 8, 16 |
428 | 0 | switch(bit_depth) { |
429 | 0 | case 8: |
430 | | // 8-bit grayscale + 8-bit alpha => convert to 32-bit RGBA |
431 | 0 | image_type = (pixel_depth == 16) ? FIT_BITMAP : FIT_UNKNOWN; |
432 | 0 | break; |
433 | 0 | case 16: |
434 | | // 16-bit grayscale + 16-bit alpha => convert to 64-bit RGBA |
435 | 0 | image_type = (pixel_depth == 32) ? FIT_RGBA16 : FIT_UNKNOWN; |
436 | 0 | break; |
437 | 0 | default: |
438 | 0 | image_type = FIT_UNKNOWN; |
439 | 0 | break; |
440 | 0 | } |
441 | | // expand 8-bit greyscale + 8-bit alpha to 32-bit |
442 | | // expand 16-bit greyscale + 16-bit alpha to 64-bit |
443 | 0 | png_set_gray_to_rgb(png_ptr); |
444 | 0 | break; |
445 | | |
446 | 0 | case PNG_COLOR_TYPE_RGB_ALPHA: // color type '6', bitdepth = 8, 16 |
447 | 0 | switch(bit_depth) { |
448 | 0 | case 8: |
449 | 0 | break; |
450 | 0 | case 16: |
451 | 0 | image_type = (pixel_depth == 64) ? FIT_RGBA16 : FIT_UNKNOWN; |
452 | 0 | break; |
453 | 0 | default: |
454 | 0 | image_type = FIT_UNKNOWN; |
455 | 0 | break; |
456 | 0 | } |
457 | 0 | break; |
458 | 0 | } |
459 | | |
460 | | // check for unknown or invalid formats |
461 | 0 | if(image_type == FIT_UNKNOWN) { |
462 | 0 | *output_image_type = image_type; |
463 | 0 | return FALSE; |
464 | 0 | } |
465 | | |
466 | 0 | #ifndef FREEIMAGE_BIGENDIAN |
467 | 0 | if((image_type == FIT_UINT16) || (image_type == FIT_RGB16) || (image_type == FIT_RGBA16)) { |
468 | | // turn on 16-bit byte swapping |
469 | 0 | png_set_swap(png_ptr); |
470 | 0 | } |
471 | 0 | #endif |
472 | |
|
473 | 0 | #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR |
474 | 0 | if((image_type == FIT_BITMAP) && ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_RGB_ALPHA))) { |
475 | | // flip the RGB pixels to BGR (or RGBA to BGRA) |
476 | 0 | png_set_bgr(png_ptr); |
477 | 0 | } |
478 | 0 | #endif |
479 | | |
480 | | // gamma correction |
481 | | // unlike the example in the libpng documentation, we have *no* idea where |
482 | | // this file may have come from--so if it doesn't have a file gamma, don't |
483 | | // do any correction ("do no harm") |
484 | |
|
485 | 0 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA)) { |
486 | 0 | double gamma = 0; |
487 | 0 | double screen_gamma = 2.2; |
488 | |
|
489 | 0 | if (png_get_gAMA(png_ptr, info_ptr, &gamma) && ( flags & PNG_IGNOREGAMMA ) != PNG_IGNOREGAMMA) { |
490 | 0 | png_set_gamma(png_ptr, screen_gamma, gamma); |
491 | 0 | } |
492 | 0 | } |
493 | | |
494 | | // all transformations have been registered; now update info_ptr data |
495 | 0 | png_read_update_info(png_ptr, info_ptr); |
496 | | |
497 | | // return the output image type |
498 | 0 | *output_image_type = image_type; |
499 | |
|
500 | 0 | return TRUE; |
501 | 0 | } |
502 | | |
503 | | static FIBITMAP * DLL_CALLCONV |
504 | 0 | Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { |
505 | 0 | png_structp png_ptr = NULL; |
506 | 0 | png_infop info_ptr = NULL; |
507 | 0 | png_uint_32 width, height; |
508 | 0 | int color_type; |
509 | 0 | int bit_depth; |
510 | 0 | int pixel_depth = 0; // pixel_depth = bit_depth * channels |
511 | |
|
512 | 0 | FIBITMAP *dib = NULL; |
513 | 0 | png_bytepp row_pointers = NULL; |
514 | |
|
515 | 0 | fi_ioStructure fio; |
516 | 0 | fio.s_handle = handle; |
517 | 0 | fio.s_io = io; |
518 | | |
519 | 0 | if (handle) { |
520 | 0 | BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS; |
521 | |
|
522 | 0 | try { |
523 | | // check to see if the file is in fact a PNG file |
524 | |
|
525 | 0 | BYTE png_check[PNG_BYTES_TO_CHECK]; |
526 | |
|
527 | 0 | io->read_proc(png_check, PNG_BYTES_TO_CHECK, 1, handle); |
528 | |
|
529 | 0 | if (png_sig_cmp(png_check, (png_size_t)0, PNG_BYTES_TO_CHECK) != 0) { |
530 | 0 | return NULL; // Bad signature |
531 | 0 | } |
532 | | |
533 | | // create the chunk manage structure |
534 | | |
535 | 0 | png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, error_handler, warning_handler); |
536 | |
|
537 | 0 | if (!png_ptr) { |
538 | 0 | return NULL; |
539 | 0 | } |
540 | | |
541 | | // create the info structure |
542 | | |
543 | 0 | info_ptr = png_create_info_struct(png_ptr); |
544 | |
|
545 | 0 | if (!info_ptr) { |
546 | 0 | png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); |
547 | 0 | return NULL; |
548 | 0 | } |
549 | | |
550 | | // init the IO |
551 | | |
552 | 0 | png_set_read_fn(png_ptr, &fio, _ReadProc); |
553 | | |
554 | | // PNG errors will be redirected here |
555 | |
|
556 | 0 | if (setjmp(png_jmpbuf(png_ptr))) { |
557 | | // assume error_handler was called before by the PNG library |
558 | 0 | throw((const char*)NULL); |
559 | 0 | } |
560 | | |
561 | | // because we have already read the signature... |
562 | | |
563 | 0 | png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK); |
564 | | |
565 | | // read the IHDR chunk |
566 | |
|
567 | 0 | png_read_info(png_ptr, info_ptr); |
568 | 0 | png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); |
569 | | |
570 | | // configure the decoder |
571 | |
|
572 | 0 | FREE_IMAGE_TYPE image_type = FIT_BITMAP; |
573 | |
|
574 | 0 | if(!ConfigureDecoder(png_ptr, info_ptr, flags, &image_type)) { |
575 | 0 | throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; |
576 | 0 | } |
577 | | |
578 | | // update image info |
579 | | |
580 | 0 | color_type = png_get_color_type(png_ptr, info_ptr); |
581 | 0 | bit_depth = png_get_bit_depth(png_ptr, info_ptr); |
582 | 0 | pixel_depth = bit_depth * png_get_channels(png_ptr, info_ptr); |
583 | | |
584 | | // create a dib and write the bitmap header |
585 | | // set up the dib palette, if needed |
586 | |
|
587 | 0 | switch (color_type) { |
588 | 0 | case PNG_COLOR_TYPE_RGB: |
589 | 0 | case PNG_COLOR_TYPE_RGB_ALPHA: |
590 | 0 | dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); |
591 | 0 | break; |
592 | | |
593 | 0 | case PNG_COLOR_TYPE_PALETTE: |
594 | 0 | dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); |
595 | 0 | if(dib) { |
596 | 0 | png_colorp png_palette = NULL; |
597 | 0 | int palette_entries = 0; |
598 | |
|
599 | 0 | png_get_PLTE(png_ptr,info_ptr, &png_palette, &palette_entries); |
600 | |
|
601 | 0 | palette_entries = MIN((unsigned)palette_entries, FreeImage_GetColorsUsed(dib)); |
602 | | |
603 | | // store the palette |
604 | |
|
605 | 0 | RGBQUAD *palette = FreeImage_GetPalette(dib); |
606 | 0 | for(int i = 0; i < palette_entries; i++) { |
607 | 0 | palette[i].rgbRed = png_palette[i].red; |
608 | 0 | palette[i].rgbGreen = png_palette[i].green; |
609 | 0 | palette[i].rgbBlue = png_palette[i].blue; |
610 | 0 | } |
611 | 0 | } |
612 | 0 | break; |
613 | | |
614 | 0 | case PNG_COLOR_TYPE_GRAY: |
615 | 0 | dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); |
616 | |
|
617 | 0 | if(dib && (pixel_depth <= 8)) { |
618 | 0 | RGBQUAD *palette = FreeImage_GetPalette(dib); |
619 | 0 | const int palette_entries = 1 << pixel_depth; |
620 | |
|
621 | 0 | for(int i = 0; i < palette_entries; i++) { |
622 | 0 | palette[i].rgbRed = |
623 | 0 | palette[i].rgbGreen = |
624 | 0 | palette[i].rgbBlue = (BYTE)((i * 255) / (palette_entries - 1)); |
625 | 0 | } |
626 | 0 | } |
627 | 0 | break; |
628 | | |
629 | 0 | default: |
630 | 0 | throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; |
631 | 0 | } |
632 | | |
633 | 0 | if(!dib) { |
634 | 0 | throw FI_MSG_ERROR_DIB_MEMORY; |
635 | 0 | } |
636 | | |
637 | | // store the transparency table |
638 | | |
639 | 0 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { |
640 | | // array of alpha (transparency) entries for palette |
641 | 0 | png_bytep trans_alpha = NULL; |
642 | | // number of transparent entries |
643 | 0 | int num_trans = 0; |
644 | | // graylevel or color sample values of the single transparent color for non-paletted images |
645 | 0 | png_color_16p trans_color = NULL; |
646 | |
|
647 | 0 | png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color); |
648 | |
|
649 | 0 | if((color_type == PNG_COLOR_TYPE_GRAY) && trans_color) { |
650 | | // single transparent color |
651 | 0 | if (trans_color->gray < 256) { |
652 | 0 | BYTE table[256]; |
653 | 0 | memset(table, 0xFF, 256); |
654 | 0 | table[trans_color->gray] = 0; |
655 | 0 | FreeImage_SetTransparencyTable(dib, table, 256); |
656 | 0 | } |
657 | | // check for a full transparency table, too |
658 | 0 | else if ((trans_alpha) && (pixel_depth <= 8)) { |
659 | 0 | FreeImage_SetTransparencyTable(dib, (BYTE *)trans_alpha, num_trans); |
660 | 0 | } |
661 | |
|
662 | 0 | } else if((color_type == PNG_COLOR_TYPE_PALETTE) && trans_alpha) { |
663 | | // transparency table |
664 | 0 | FreeImage_SetTransparencyTable(dib, (BYTE *)trans_alpha, num_trans); |
665 | 0 | } |
666 | 0 | } |
667 | | |
668 | | // store the background color (only supported for FIT_BITMAP types) |
669 | |
|
670 | 0 | if ((image_type == FIT_BITMAP) && png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD)) { |
671 | | // Get the background color to draw transparent and alpha images over. |
672 | | // Note that even if the PNG file supplies a background, you are not required to |
673 | | // use it - you should use the (solid) application background if it has one. |
674 | |
|
675 | 0 | png_color_16p image_background = NULL; |
676 | 0 | RGBQUAD rgbBkColor; |
677 | |
|
678 | 0 | if (png_get_bKGD(png_ptr, info_ptr, &image_background)) { |
679 | 0 | rgbBkColor.rgbRed = (BYTE)image_background->red; |
680 | 0 | rgbBkColor.rgbGreen = (BYTE)image_background->green; |
681 | 0 | rgbBkColor.rgbBlue = (BYTE)image_background->blue; |
682 | 0 | rgbBkColor.rgbReserved = 0; |
683 | |
|
684 | 0 | FreeImage_SetBackgroundColor(dib, &rgbBkColor); |
685 | 0 | } |
686 | 0 | } |
687 | | |
688 | | // get physical resolution |
689 | |
|
690 | 0 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_pHYs)) { |
691 | 0 | png_uint_32 res_x, res_y; |
692 | | |
693 | | // we'll overload this var and use 0 to mean no phys data, |
694 | | // since if it's not in meters we can't use it anyway |
695 | |
|
696 | 0 | int res_unit_type = PNG_RESOLUTION_UNKNOWN; |
697 | |
|
698 | 0 | png_get_pHYs(png_ptr,info_ptr, &res_x, &res_y, &res_unit_type); |
699 | |
|
700 | 0 | if (res_unit_type == PNG_RESOLUTION_METER) { |
701 | 0 | FreeImage_SetDotsPerMeterX(dib, res_x); |
702 | 0 | FreeImage_SetDotsPerMeterY(dib, res_y); |
703 | 0 | } |
704 | 0 | } |
705 | | |
706 | | // get possible ICC profile |
707 | |
|
708 | 0 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { |
709 | 0 | png_charp profile_name = NULL; |
710 | 0 | png_bytep profile_data = NULL; |
711 | 0 | png_uint_32 profile_length = 0; |
712 | 0 | int compression_type; |
713 | |
|
714 | 0 | png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_length); |
715 | | |
716 | | // copy ICC profile data (must be done after FreeImage_AllocateHeader) |
717 | |
|
718 | 0 | FreeImage_CreateICCProfile(dib, profile_data, profile_length); |
719 | 0 | } |
720 | | |
721 | | // --- header only mode => clean-up and return |
722 | |
|
723 | 0 | if (header_only) { |
724 | | // get possible metadata (it can be located both before and after the image data) |
725 | 0 | ReadMetadata(png_ptr, info_ptr, dib); |
726 | 0 | if (png_ptr) { |
727 | | // clean up after the read, and free any memory allocated - REQUIRED |
728 | 0 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); |
729 | 0 | } |
730 | 0 | return dib; |
731 | 0 | } |
732 | | |
733 | | // set the individual row_pointers to point at the correct offsets |
734 | | |
735 | 0 | row_pointers = (png_bytepp)malloc(height * sizeof(png_bytep)); |
736 | |
|
737 | 0 | if (!row_pointers) { |
738 | 0 | png_destroy_read_struct(&png_ptr, &info_ptr, NULL); |
739 | 0 | FreeImage_Unload(dib); |
740 | 0 | return NULL; |
741 | 0 | } |
742 | | |
743 | | // read in the bitmap bits via the pointer table |
744 | | // allow loading of PNG with minor errors (such as images with several IDAT chunks) |
745 | | |
746 | 0 | for (png_uint_32 k = 0; k < height; k++) { |
747 | 0 | row_pointers[height - 1 - k] = FreeImage_GetScanLine(dib, k); |
748 | 0 | } |
749 | |
|
750 | 0 | png_set_benign_errors(png_ptr, 1); |
751 | 0 | png_read_image(png_ptr, row_pointers); |
752 | | |
753 | | // check if the bitmap contains transparency, if so enable it in the header |
754 | |
|
755 | 0 | if (FreeImage_GetBPP(dib) == 32) { |
756 | 0 | if (FreeImage_GetColorType(dib) == FIC_RGBALPHA) { |
757 | 0 | FreeImage_SetTransparent(dib, TRUE); |
758 | 0 | } else { |
759 | 0 | FreeImage_SetTransparent(dib, FALSE); |
760 | 0 | } |
761 | 0 | } |
762 | | |
763 | | // cleanup |
764 | |
|
765 | 0 | if (row_pointers) { |
766 | 0 | free(row_pointers); |
767 | 0 | row_pointers = NULL; |
768 | 0 | } |
769 | | |
770 | | // read the rest of the file, getting any additional chunks in info_ptr |
771 | |
|
772 | 0 | png_read_end(png_ptr, info_ptr); |
773 | | |
774 | | // get possible metadata (it can be located both before and after the image data) |
775 | |
|
776 | 0 | ReadMetadata(png_ptr, info_ptr, dib); |
777 | |
|
778 | 0 | if (png_ptr) { |
779 | | // clean up after the read, and free any memory allocated - REQUIRED |
780 | 0 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); |
781 | 0 | } |
782 | |
|
783 | 0 | return dib; |
784 | |
|
785 | 0 | } catch (const char *text) { |
786 | 0 | if (png_ptr) { |
787 | 0 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); |
788 | 0 | } |
789 | 0 | if (row_pointers) { |
790 | 0 | free(row_pointers); |
791 | 0 | } |
792 | 0 | if (dib) { |
793 | 0 | FreeImage_Unload(dib); |
794 | 0 | } |
795 | 0 | if (NULL != text) { |
796 | 0 | FreeImage_OutputMessageProc(s_format_id, text); |
797 | 0 | } |
798 | | |
799 | 0 | return NULL; |
800 | 0 | } |
801 | 0 | } |
802 | | |
803 | 0 | return NULL; |
804 | 0 | } |
805 | | |
806 | | // -------------------------------------------------------------------------- |
807 | | |
808 | | static BOOL DLL_CALLCONV |
809 | 0 | Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { |
810 | 0 | png_structp png_ptr; |
811 | 0 | png_infop info_ptr; |
812 | 0 | png_colorp palette = NULL; |
813 | 0 | png_uint_32 width, height; |
814 | 0 | BOOL has_alpha_channel = FALSE; |
815 | |
|
816 | 0 | RGBQUAD *pal; // pointer to dib palette |
817 | 0 | int bit_depth, pixel_depth; // pixel_depth = bit_depth * channels |
818 | 0 | int palette_entries; |
819 | 0 | int interlace_type; |
820 | |
|
821 | 0 | fi_ioStructure fio; |
822 | 0 | fio.s_handle = handle; |
823 | 0 | fio.s_io = io; |
824 | |
|
825 | 0 | if ((dib) && (handle)) { |
826 | 0 | try { |
827 | | // create the chunk manage structure |
828 | |
|
829 | 0 | png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, error_handler, warning_handler); |
830 | |
|
831 | 0 | if (!png_ptr) { |
832 | 0 | return FALSE; |
833 | 0 | } |
834 | | |
835 | | // allocate/initialize the image information data. |
836 | | |
837 | 0 | info_ptr = png_create_info_struct(png_ptr); |
838 | |
|
839 | 0 | if (!info_ptr) { |
840 | 0 | png_destroy_write_struct(&png_ptr, (png_infopp)NULL); |
841 | 0 | return FALSE; |
842 | 0 | } |
843 | | |
844 | | // Set error handling. REQUIRED if you aren't supplying your own |
845 | | // error handling functions in the png_create_write_struct() call. |
846 | | |
847 | 0 | if (setjmp(png_jmpbuf(png_ptr))) { |
848 | | // if we get here, we had a problem reading the file |
849 | |
|
850 | 0 | png_destroy_write_struct(&png_ptr, &info_ptr); |
851 | |
|
852 | 0 | return FALSE; |
853 | 0 | } |
854 | | |
855 | | // init the IO |
856 | | |
857 | 0 | png_set_write_fn(png_ptr, &fio, _WriteProc, _FlushProc); |
858 | | |
859 | | // set physical resolution |
860 | |
|
861 | 0 | png_uint_32 res_x = (png_uint_32)FreeImage_GetDotsPerMeterX(dib); |
862 | 0 | png_uint_32 res_y = (png_uint_32)FreeImage_GetDotsPerMeterY(dib); |
863 | |
|
864 | 0 | if ((res_x > 0) && (res_y > 0)) { |
865 | 0 | png_set_pHYs(png_ptr, info_ptr, res_x, res_y, PNG_RESOLUTION_METER); |
866 | 0 | } |
867 | | |
868 | | // Set the image information here. Width and height are up to 2^31, |
869 | | // bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on |
870 | | // the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, |
871 | | // PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, |
872 | | // or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or |
873 | | // PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST |
874 | | // currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED |
875 | |
|
876 | 0 | width = FreeImage_GetWidth(dib); |
877 | 0 | height = FreeImage_GetHeight(dib); |
878 | 0 | pixel_depth = FreeImage_GetBPP(dib); |
879 | |
|
880 | 0 | BOOL bInterlaced = FALSE; |
881 | 0 | if( (flags & PNG_INTERLACED) == PNG_INTERLACED) { |
882 | 0 | interlace_type = PNG_INTERLACE_ADAM7; |
883 | 0 | bInterlaced = TRUE; |
884 | 0 | } else { |
885 | 0 | interlace_type = PNG_INTERLACE_NONE; |
886 | 0 | } |
887 | | |
888 | | // set the ZLIB compression level or default to PNG default compression level (ZLIB level = 6) |
889 | 0 | int zlib_level = flags & 0x0F; |
890 | 0 | if((zlib_level >= 1) && (zlib_level <= 9)) { |
891 | 0 | png_set_compression_level(png_ptr, zlib_level); |
892 | 0 | } else if((flags & PNG_Z_NO_COMPRESSION) == PNG_Z_NO_COMPRESSION) { |
893 | 0 | png_set_compression_level(png_ptr, Z_NO_COMPRESSION); |
894 | 0 | } |
895 | | |
896 | | // filtered strategy works better for high color images |
897 | 0 | if(pixel_depth >= 16){ |
898 | 0 | png_set_compression_strategy(png_ptr, Z_FILTERED); |
899 | 0 | png_set_filter(png_ptr, 0, PNG_FILTER_NONE|PNG_FILTER_SUB|PNG_FILTER_PAETH); |
900 | 0 | } else { |
901 | 0 | png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); |
902 | 0 | } |
903 | |
|
904 | 0 | FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib); |
905 | 0 | if(image_type == FIT_BITMAP) { |
906 | | // standard image type |
907 | 0 | bit_depth = (pixel_depth > 8) ? 8 : pixel_depth; |
908 | 0 | } else { |
909 | | // 16-bit greyscale or 16-bit RGB(A) |
910 | 0 | bit_depth = 16; |
911 | 0 | } |
912 | | |
913 | | // check for transparent images |
914 | 0 | BOOL bIsTransparent = |
915 | 0 | (image_type == FIT_BITMAP) && FreeImage_IsTransparent(dib) && (FreeImage_GetTransparencyCount(dib) > 0) ? TRUE : FALSE; |
916 | |
|
917 | 0 | switch (FreeImage_GetColorType(dib)) { |
918 | 0 | case FIC_MINISWHITE: |
919 | 0 | if(!bIsTransparent) { |
920 | | // Invert monochrome files to have 0 as black and 1 as white (no break here) |
921 | 0 | png_set_invert_mono(png_ptr); |
922 | 0 | } |
923 | | // (fall through) |
924 | |
|
925 | 0 | case FIC_MINISBLACK: |
926 | 0 | if(!bIsTransparent) { |
927 | 0 | png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, |
928 | 0 | PNG_COLOR_TYPE_GRAY, interlace_type, |
929 | 0 | PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); |
930 | 0 | break; |
931 | 0 | } |
932 | | // If a monochrome image is transparent, save it with a palette |
933 | | // (fall through) |
934 | | |
935 | 0 | case FIC_PALETTE: |
936 | 0 | { |
937 | 0 | png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, |
938 | 0 | PNG_COLOR_TYPE_PALETTE, interlace_type, |
939 | 0 | PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); |
940 | | |
941 | | // set the palette |
942 | |
|
943 | 0 | palette_entries = 1 << bit_depth; |
944 | 0 | palette = (png_colorp)png_malloc(png_ptr, palette_entries * sizeof (png_color)); |
945 | 0 | pal = FreeImage_GetPalette(dib); |
946 | |
|
947 | 0 | for (int i = 0; i < palette_entries; i++) { |
948 | 0 | palette[i].red = pal[i].rgbRed; |
949 | 0 | palette[i].green = pal[i].rgbGreen; |
950 | 0 | palette[i].blue = pal[i].rgbBlue; |
951 | 0 | } |
952 | | |
953 | 0 | png_set_PLTE(png_ptr, info_ptr, palette, palette_entries); |
954 | | |
955 | | // You must not free palette here, because png_set_PLTE only makes a link to |
956 | | // the palette that you malloced. Wait until you are about to destroy |
957 | | // the png structure. |
958 | |
|
959 | 0 | break; |
960 | 0 | } |
961 | | |
962 | 0 | case FIC_RGBALPHA : |
963 | 0 | has_alpha_channel = TRUE; |
964 | |
|
965 | 0 | png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, |
966 | 0 | PNG_COLOR_TYPE_RGBA, interlace_type, |
967 | 0 | PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); |
968 | |
|
969 | 0 | #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR |
970 | | // flip BGR pixels to RGB |
971 | 0 | if(image_type == FIT_BITMAP) { |
972 | 0 | png_set_bgr(png_ptr); |
973 | 0 | } |
974 | 0 | #endif |
975 | 0 | break; |
976 | | |
977 | 0 | case FIC_RGB: |
978 | 0 | png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, |
979 | 0 | PNG_COLOR_TYPE_RGB, interlace_type, |
980 | 0 | PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); |
981 | |
|
982 | 0 | #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR |
983 | | // flip BGR pixels to RGB |
984 | 0 | if(image_type == FIT_BITMAP) { |
985 | 0 | png_set_bgr(png_ptr); |
986 | 0 | } |
987 | 0 | #endif |
988 | 0 | break; |
989 | | |
990 | 0 | case FIC_CMYK: |
991 | 0 | break; |
992 | 0 | } |
993 | | |
994 | | // write possible ICC profile |
995 | | |
996 | 0 | FIICCPROFILE *iccProfile = FreeImage_GetICCProfile(dib); |
997 | 0 | if (iccProfile->size && iccProfile->data) { |
998 | | // skip ICC profile check |
999 | 0 | png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, 1); |
1000 | 0 | png_set_iCCP(png_ptr, info_ptr, "Embedded Profile", 0, (png_const_bytep)iccProfile->data, iccProfile->size); |
1001 | 0 | } |
1002 | | |
1003 | | // write metadata |
1004 | |
|
1005 | 0 | WriteMetadata(png_ptr, info_ptr, dib); |
1006 | | |
1007 | | // Optional gamma chunk is strongly suggested if you have any guess |
1008 | | // as to the correct gamma of the image. |
1009 | | // png_set_gAMA(png_ptr, info_ptr, gamma); |
1010 | | |
1011 | | // set the transparency table |
1012 | |
|
1013 | 0 | if (bIsTransparent) { |
1014 | 0 | png_set_tRNS(png_ptr, info_ptr, FreeImage_GetTransparencyTable(dib), FreeImage_GetTransparencyCount(dib), NULL); |
1015 | 0 | } |
1016 | | |
1017 | | // set the background color |
1018 | |
|
1019 | 0 | if(FreeImage_HasBackgroundColor(dib)) { |
1020 | 0 | png_color_16 image_background; |
1021 | 0 | RGBQUAD rgbBkColor; |
1022 | |
|
1023 | 0 | FreeImage_GetBackgroundColor(dib, &rgbBkColor); |
1024 | 0 | memset(&image_background, 0, sizeof(png_color_16)); |
1025 | 0 | image_background.blue = rgbBkColor.rgbBlue; |
1026 | 0 | image_background.green = rgbBkColor.rgbGreen; |
1027 | 0 | image_background.red = rgbBkColor.rgbRed; |
1028 | 0 | image_background.index = rgbBkColor.rgbReserved; |
1029 | |
|
1030 | 0 | png_set_bKGD(png_ptr, info_ptr, &image_background); |
1031 | 0 | } |
1032 | | |
1033 | | // Write the file header information. |
1034 | |
|
1035 | 0 | png_write_info(png_ptr, info_ptr); |
1036 | | |
1037 | | // write out the image data |
1038 | |
|
1039 | 0 | #ifndef FREEIMAGE_BIGENDIAN |
1040 | 0 | if (bit_depth == 16) { |
1041 | | // turn on 16 bit byte swapping |
1042 | 0 | png_set_swap(png_ptr); |
1043 | 0 | } |
1044 | 0 | #endif |
1045 | |
|
1046 | 0 | int number_passes = 1; |
1047 | 0 | if (bInterlaced) { |
1048 | 0 | number_passes = png_set_interlace_handling(png_ptr); |
1049 | 0 | } |
1050 | |
|
1051 | 0 | if ((pixel_depth == 32) && (!has_alpha_channel)) { |
1052 | 0 | BYTE *buffer = (BYTE *)malloc(width * 3); |
1053 | | |
1054 | | // transparent conversion to 24-bit |
1055 | | // the number of passes is either 1 for non-interlaced images, or 7 for interlaced images |
1056 | 0 | for (int pass = 0; pass < number_passes; pass++) { |
1057 | 0 | for (png_uint_32 k = 0; k < height; k++) { |
1058 | 0 | FreeImage_ConvertLine32To24(buffer, FreeImage_GetScanLine(dib, height - k - 1), width); |
1059 | 0 | png_write_row(png_ptr, buffer); |
1060 | 0 | } |
1061 | 0 | } |
1062 | 0 | free(buffer); |
1063 | 0 | } else { |
1064 | | // the number of passes is either 1 for non-interlaced images, or 7 for interlaced images |
1065 | 0 | for (int pass = 0; pass < number_passes; pass++) { |
1066 | 0 | for (png_uint_32 k = 0; k < height; k++) { |
1067 | 0 | png_write_row(png_ptr, FreeImage_GetScanLine(dib, height - k - 1)); |
1068 | 0 | } |
1069 | 0 | } |
1070 | 0 | } |
1071 | | |
1072 | | // It is REQUIRED to call this to finish writing the rest of the file |
1073 | | // Bug with png_flush |
1074 | |
|
1075 | 0 | png_write_end(png_ptr, info_ptr); |
1076 | | |
1077 | | // clean up after the write, and free any memory allocated |
1078 | 0 | if (palette) { |
1079 | 0 | png_free(png_ptr, palette); |
1080 | 0 | } |
1081 | |
|
1082 | 0 | png_destroy_write_struct(&png_ptr, &info_ptr); |
1083 | |
|
1084 | 0 | return TRUE; |
1085 | |
|
1086 | 0 | } catch (const char *text) { |
1087 | 0 | if(png_ptr) { |
1088 | 0 | png_destroy_write_struct(&png_ptr, &info_ptr); |
1089 | 0 | } |
1090 | 0 | FreeImage_OutputMessageProc(s_format_id, text); |
1091 | 0 | } |
1092 | 0 | } |
1093 | | |
1094 | 0 | return FALSE; |
1095 | 0 | } |
1096 | | |
1097 | | // ========================================================== |
1098 | | // Init |
1099 | | // ========================================================== |
1100 | | |
1101 | | void DLL_CALLCONV |
1102 | 2 | InitPNG(Plugin *plugin, int format_id) { |
1103 | 2 | s_format_id = format_id; |
1104 | | |
1105 | 2 | plugin->format_proc = Format; |
1106 | 2 | plugin->description_proc = Description; |
1107 | 2 | plugin->extension_proc = Extension; |
1108 | 2 | plugin->regexpr_proc = RegExpr; |
1109 | 2 | plugin->open_proc = NULL; |
1110 | 2 | plugin->close_proc = NULL; |
1111 | 2 | plugin->pagecount_proc = NULL; |
1112 | | plugin->pagecapability_proc = NULL; |
1113 | 2 | plugin->load_proc = Load; |
1114 | 2 | plugin->save_proc = Save; |
1115 | 2 | plugin->validate_proc = Validate; |
1116 | 2 | plugin->mime_proc = MimeType; |
1117 | 2 | plugin->supports_export_bpp_proc = SupportsExportDepth; |
1118 | 2 | plugin->supports_export_type_proc = SupportsExportType; |
1119 | 2 | plugin->supports_icc_profiles_proc = SupportsICCProfiles; |
1120 | 2 | plugin->supports_no_pixels_proc = SupportsNoPixels; |
1121 | 2 | } |