1 |
|
|
/* |
2 |
|
|
Teem: Tools to process and visualize scientific data and images . |
3 |
|
|
Copyright (C) 2013, 2012, 2011, 2010, 2009 University of Chicago |
4 |
|
|
Copyright (C) 2008, 2007, 2006, 2005 Gordon Kindlmann |
5 |
|
|
Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998 University of Utah |
6 |
|
|
|
7 |
|
|
This library is free software; you can redistribute it and/or |
8 |
|
|
modify it under the terms of the GNU Lesser General Public License |
9 |
|
|
(LGPL) as published by the Free Software Foundation; either |
10 |
|
|
version 2.1 of the License, or (at your option) any later version. |
11 |
|
|
The terms of redistributing and/or modifying this software also |
12 |
|
|
include exceptions to the LGPL that facilitate static linking. |
13 |
|
|
|
14 |
|
|
This library is distributed in the hope that it will be useful, |
15 |
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 |
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 |
|
|
Lesser General Public License for more details. |
18 |
|
|
|
19 |
|
|
You should have received a copy of the GNU Lesser General Public License |
20 |
|
|
along with this library; if not, write to Free Software Foundation, Inc., |
21 |
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 |
|
|
*/ |
23 |
|
|
|
24 |
|
|
#include "nrrd.h" |
25 |
|
|
#include "privateNrrd.h" |
26 |
|
|
|
27 |
|
|
#include <teemPng.h> |
28 |
|
|
#if TEEM_PNG |
29 |
|
|
#include <png.h> |
30 |
|
|
#endif |
31 |
|
|
|
32 |
|
|
#define MAGIC "\211PNG" |
33 |
|
|
|
34 |
|
|
static int |
35 |
|
|
_nrrdFormatPNG_available(void) { |
36 |
|
|
|
37 |
|
|
#if TEEM_PNG |
38 |
|
6 |
return AIR_TRUE; |
39 |
|
|
#else |
40 |
|
|
return AIR_FALSE; |
41 |
|
|
#endif |
42 |
|
|
} |
43 |
|
|
|
44 |
|
|
static int |
45 |
|
|
_nrrdFormatPNG_nameLooksLike(const char *filename) { |
46 |
|
|
|
47 |
|
|
return airEndsWith(filename, NRRD_EXT_PNG); |
48 |
|
|
} |
49 |
|
|
|
50 |
|
|
static int |
51 |
|
|
_nrrdFormatPNG_fitsInto(const Nrrd *nrrd, const NrrdEncoding *encoding, |
52 |
|
|
int useBiff) { |
53 |
|
|
static const char me[]="_nrrdFormatPNG_fitsInto"; |
54 |
|
|
|
55 |
|
|
#if !TEEM_PNG /* ------------------------------------------- */ |
56 |
|
|
|
57 |
|
|
AIR_UNUSED(nrrd); |
58 |
|
|
AIR_UNUSED(encoding); |
59 |
|
|
biffMaybeAddf(useBiff, NRRD, |
60 |
|
|
"%s: %s format not available in this Teem build", |
61 |
|
|
me, nrrdFormatPNG->name); |
62 |
|
|
return AIR_FALSE; |
63 |
|
|
|
64 |
|
|
#else /* ------------------------------------------- */ |
65 |
|
|
|
66 |
|
|
int ret; |
67 |
|
|
|
68 |
|
|
if (!( nrrd && encoding )) { |
69 |
|
|
biffMaybeAddf(useBiff, NRRD, "%s: got NULL nrrd (%p) or encoding (%p)", |
70 |
|
|
me, AIR_CVOIDP(nrrd), AIR_CVOIDP(encoding)); |
71 |
|
|
return AIR_FALSE; |
72 |
|
|
} |
73 |
|
|
if (!( nrrdTypeUChar == nrrd->type || nrrdTypeUShort == nrrd->type )) { |
74 |
|
|
biffMaybeAddf(useBiff, NRRD, |
75 |
|
|
"%s: type must be %s or %s (not %s)", me, |
76 |
|
|
airEnumStr(nrrdType, nrrdTypeUChar), |
77 |
|
|
airEnumStr(nrrdType, nrrdTypeUShort), |
78 |
|
|
airEnumStr(nrrdType, nrrd->type)); |
79 |
|
|
return AIR_FALSE; |
80 |
|
|
} |
81 |
|
|
/* else */ |
82 |
|
|
/* encoding ignored- always gzip */ |
83 |
|
|
if (2 == nrrd->dim) { |
84 |
|
|
/* its a gray-scale image */ |
85 |
|
|
ret = AIR_TRUE; |
86 |
|
|
} else if (3 == nrrd->dim) { |
87 |
|
|
if (!( 1 == nrrd->axis[0].size |
88 |
|
|
|| 2 == nrrd->axis[0].size |
89 |
|
|
|| 3 == nrrd->axis[0].size |
90 |
|
|
|| 4 == nrrd->axis[0].size )) { |
91 |
|
|
char stmp[AIR_STRLEN_SMALL]; |
92 |
|
|
biffMaybeAddf(useBiff, NRRD, |
93 |
|
|
"%s: 1st axis size is %s, not 1, 2, 3, or 4", me, |
94 |
|
|
airSprintSize_t(stmp, nrrd->axis[0].size)); |
95 |
|
|
return AIR_FALSE; |
96 |
|
|
} |
97 |
|
|
/* else */ |
98 |
|
|
ret = AIR_TRUE; |
99 |
|
|
} else { |
100 |
|
|
biffMaybeAddf(useBiff, NRRD, |
101 |
|
|
"%s: dimension is %d, not 2 or 3", me, nrrd->dim); |
102 |
|
|
return AIR_FALSE; |
103 |
|
|
} |
104 |
|
|
return ret; |
105 |
|
|
|
106 |
|
|
#endif /* ------------------------------------------- */ |
107 |
|
|
} |
108 |
|
|
|
109 |
|
|
static int |
110 |
|
|
_nrrdFormatPNG_contentStartsLike(NrrdIoState *nio) { |
111 |
|
|
|
112 |
|
|
return !strcmp(MAGIC, nio->line); |
113 |
|
|
} |
114 |
|
|
|
115 |
|
|
#if TEEM_PNG |
116 |
|
|
static void |
117 |
|
|
_nrrdErrorHandlerPNG (png_structp png, png_const_charp message) |
118 |
|
|
{ |
119 |
|
|
static const char me[]="_nrrdErrorHandlerPNG"; |
120 |
|
|
/* add PNG error message to biff */ |
121 |
|
|
biffAddf(NRRD, "%s: PNG error: %s", me, message); |
122 |
|
|
/* longjmp back to the setjmp, return 1 */ |
123 |
|
|
longjmp(png_jmpbuf(png), 1); |
124 |
|
|
} |
125 |
|
|
|
126 |
|
|
static void |
127 |
|
|
_nrrdWarningHandlerPNG (png_structp png, png_const_charp message) |
128 |
|
|
{ |
129 |
|
|
static const char me[]="_nrrdWarningHandlerPNG"; |
130 |
|
|
AIR_UNUSED(png); |
131 |
|
|
/* add the png warning message to biff */ |
132 |
|
|
biffAddf(NRRD, "%s: PNG warning: %s", me, message); |
133 |
|
|
/* no longjump, execution continues */ |
134 |
|
|
} |
135 |
|
|
|
136 |
|
|
/* we need to use the file I/O callbacks on windows |
137 |
|
|
to make sure we can mix VC6 libpng with VC7 Teem */ |
138 |
|
|
#ifdef _WIN32 |
139 |
|
|
static void |
140 |
|
|
_nrrdReadDataPNG (png_structp png, png_bytep data, png_size_t len) |
141 |
|
|
{ |
142 |
|
|
png_size_t read; |
143 |
|
|
read = (png_size_t)fread(data, (png_size_t)1, len, (FILE*)png_get_io_ptr(png)); |
144 |
|
|
if (read != len) png_error(png, "file read error"); |
145 |
|
|
} |
146 |
|
|
static void |
147 |
|
|
_nrrdWriteDataPNG (png_structp png, png_bytep data, png_size_t len) |
148 |
|
|
{ |
149 |
|
|
png_size_t written; |
150 |
|
|
written = fwrite(data, 1, len, (FILE*)png_get_io_ptr(png)); |
151 |
|
|
if (written != len) png_error(png, "file write error"); |
152 |
|
|
} |
153 |
|
|
|
154 |
|
|
static void |
155 |
|
|
_nrrdFlushDataPNG (png_structp png) |
156 |
|
|
{ |
157 |
|
|
FILE *io_ptr = png_get_io_ptr(png); |
158 |
|
|
if (io_ptr != NULL) fflush(io_ptr); |
159 |
|
|
} |
160 |
|
|
#endif /* _WIN32 */ |
161 |
|
|
#endif /* TEEM_PNG */ |
162 |
|
|
|
163 |
|
|
static int |
164 |
|
|
_nrrdFormatPNG_read(FILE *file, Nrrd *nrrd, NrrdIoState *nio) { |
165 |
|
|
static const char me[]="_nrrdFormatPNG_read"; |
166 |
|
|
#if TEEM_PNG |
167 |
|
|
png_structp png; |
168 |
|
|
png_infop info; |
169 |
|
|
png_bytep *row; |
170 |
|
|
png_uint_32 width, height, rowsize, hi; |
171 |
|
|
png_text* txt; |
172 |
|
|
int depth, type, i, channels, numtxt, ret; |
173 |
|
|
int ntype, ndim; |
174 |
|
|
size_t nsize[3]; |
175 |
|
|
#endif /* TEEM_PNG */ |
176 |
|
|
|
177 |
|
|
AIR_UNUSED(file); |
178 |
|
|
AIR_UNUSED(nrrd); |
179 |
|
|
if (!_nrrdFormatPNG_contentStartsLike(nio)) { |
180 |
|
|
biffAddf(NRRD, "%s: this doesn't look like a %s file", me, |
181 |
|
|
nrrdFormatPNG->name); |
182 |
|
|
return 1; |
183 |
|
|
} |
184 |
|
|
|
185 |
|
|
#if TEEM_PNG |
186 |
|
|
/* create png struct with the error handlers above */ |
187 |
|
|
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, |
188 |
|
|
_nrrdErrorHandlerPNG, |
189 |
|
|
_nrrdWarningHandlerPNG); |
190 |
|
|
if (png == NULL) { |
191 |
|
|
biffAddf(NRRD, "%s: failed to create PNG read struct", me); |
192 |
|
|
return 1; |
193 |
|
|
} |
194 |
|
|
/* create image info struct */ |
195 |
|
|
info = png_create_info_struct(png); |
196 |
|
|
if (info == NULL) { |
197 |
|
|
png_destroy_read_struct(&png, NULL, NULL); |
198 |
|
|
biffAddf(NRRD, "%s: failed to create PNG image info struct", me); |
199 |
|
|
return 1; |
200 |
|
|
} |
201 |
|
|
/* set up png style error handling */ |
202 |
|
|
if (setjmp(png_jmpbuf(png))) { |
203 |
|
|
/* the error is reported inside the handler, |
204 |
|
|
but we still need to clean up and return */ |
205 |
|
|
png_destroy_read_struct(&png, &info, NULL); |
206 |
|
|
return 1; |
207 |
|
|
} |
208 |
|
|
/* initialize png I/O */ |
209 |
|
|
#ifdef _WIN32 |
210 |
|
|
png_set_read_fn(png, (png_voidp)file, _nrrdReadDataPNG); |
211 |
|
|
#else |
212 |
|
|
png_init_io(png, file); |
213 |
|
|
#endif |
214 |
|
|
/* if we are here, we have already read 6 bytes from the file */ |
215 |
|
|
png_set_sig_bytes(png, 6); |
216 |
|
|
/* png_read_info() returns all information from the file |
217 |
|
|
before the first data chunk */ |
218 |
|
|
png_read_info(png, info); |
219 |
|
|
png_get_IHDR(png, info, &width, &height, &depth, &type, |
220 |
|
|
NULL, NULL, NULL); |
221 |
|
|
/* expand paletted colors into rgb triplets */ |
222 |
|
|
if (type == PNG_COLOR_TYPE_PALETTE) |
223 |
|
|
png_set_palette_to_rgb(png); |
224 |
|
|
/* expand grayscale images to 8 bits from 1, 2, or 4 bits */ |
225 |
|
|
if (type == PNG_COLOR_TYPE_GRAY && depth < 8) |
226 |
|
|
#if PNG_LIBPNG_VER_MINOR > 1 |
227 |
|
|
png_set_expand_gray_1_2_4_to_8(png); |
228 |
|
|
#else |
229 |
|
|
png_set_expand(png); |
230 |
|
|
#endif |
231 |
|
|
/* expand paletted or rgb images with transparency to full alpha |
232 |
|
|
channels so the data will be available as rgba quartets */ |
233 |
|
|
if (png_get_valid(png, info, PNG_INFO_tRNS)) |
234 |
|
|
png_set_tRNS_to_alpha(png); |
235 |
|
|
/* fix endianness for 16 bit formats */ |
236 |
|
|
if (depth > 8 && airMyEndian() == airEndianLittle) |
237 |
|
|
png_set_swap(png); |
238 |
|
|
#if 0 |
239 |
|
|
/* HEY GLK asks why is this commented out? */ |
240 |
|
|
/* set up gamma correction */ |
241 |
|
|
/* NOTE: screen_gamma is platform dependent, |
242 |
|
|
it can hardwired or set from a parameter/environment variable */ |
243 |
|
|
if (png_get_sRGB(png_ptr, info_ptr, &intent)) { |
244 |
|
|
/* if the image has sRGB info, |
245 |
|
|
pass in standard nrrd file gamma 1.0 */ |
246 |
|
|
png_set_gamma(png_ptr, screen_gamma, 1.0); |
247 |
|
|
} else { |
248 |
|
|
double gamma; |
249 |
|
|
/* set image gamma if present */ |
250 |
|
|
if (png_get_gAMA(png, info, &gamma)) |
251 |
|
|
png_set_gamma(png, screen_gamma, gamma); |
252 |
|
|
else |
253 |
|
|
png_set_gamma(png, screen_gamma, 1.0); |
254 |
|
|
} |
255 |
|
|
#endif |
256 |
|
|
/* update reader */ |
257 |
|
|
png_read_update_info(png, info); |
258 |
|
|
/* allocate memory for the image data */ |
259 |
|
|
ntype = depth > 8 ? nrrdTypeUShort : nrrdTypeUChar; |
260 |
|
|
switch (type) { |
261 |
|
|
case PNG_COLOR_TYPE_GRAY: |
262 |
|
|
ndim = 2; nsize[0] = width; nsize[1] = height; |
263 |
|
|
nsize[2] = 1; /* to simplify code below */ |
264 |
|
|
break; |
265 |
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA: |
266 |
|
|
ndim = 3; nsize[0] = 2; nsize[1] = width; nsize[2] = height; |
267 |
|
|
break; |
268 |
|
|
case PNG_COLOR_TYPE_RGB: |
269 |
|
|
ndim = 3; nsize[0] = 3; nsize[1] = width; nsize[2] = height; |
270 |
|
|
break; |
271 |
|
|
case PNG_COLOR_TYPE_RGB_ALPHA: |
272 |
|
|
ndim = 3; nsize[0] = 4; nsize[1] = width; nsize[2] = height; |
273 |
|
|
break; |
274 |
|
|
case PNG_COLOR_TYPE_PALETTE: |
275 |
|
|
/* TODO: merge this with the outer switch, needs to be tested */ |
276 |
|
|
channels = png_get_channels(png, info); |
277 |
|
|
if (channels < 2) { |
278 |
|
|
ndim = 2; nsize[0] = width; nsize[1] = height; |
279 |
|
|
} else { |
280 |
|
|
ndim = 3; nsize[0] = channels; nsize[1] = width; nsize[2] = height; |
281 |
|
|
} |
282 |
|
|
break; |
283 |
|
|
default: |
284 |
|
|
png_destroy_read_struct(&png, &info, NULL); |
285 |
|
|
biffAddf(NRRD, "%s: unknown png type: %d", me, type); |
286 |
|
|
return 1; |
287 |
|
|
break; |
288 |
|
|
} |
289 |
|
|
if (nio->oldData |
290 |
|
|
&& (nio->oldDataSize |
291 |
|
|
== (size_t)(nrrdTypeSize[ntype]*nsize[0]*nsize[1]*nsize[2]))) { |
292 |
|
|
ret = nrrdWrap_nva(nrrd, nio->oldData, ntype, ndim, nsize); |
293 |
|
|
} else { |
294 |
|
|
ret = nrrdMaybeAlloc_nva(nrrd, ntype, ndim, nsize); |
295 |
|
|
} |
296 |
|
|
if (ret) { |
297 |
|
|
png_destroy_read_struct(&png, &info, NULL); |
298 |
|
|
biffAddf(NRRD, "%s: failed to allocate nrrd", me); |
299 |
|
|
return 1; |
300 |
|
|
} |
301 |
|
|
/* query row size */ |
302 |
|
|
rowsize = png_get_rowbytes(png, info); |
303 |
|
|
/* check byte size */ |
304 |
|
|
if (nrrdElementNumber(nrrd)*nrrdElementSize(nrrd) != height*rowsize) { |
305 |
|
|
png_destroy_read_struct(&png, &info, NULL); |
306 |
|
|
biffAddf(NRRD, "%s: size mismatch", me); |
307 |
|
|
return 1; |
308 |
|
|
} |
309 |
|
|
/* set up row pointers */ |
310 |
|
|
row = (png_bytep*)malloc(sizeof(png_bytep)*height); |
311 |
|
|
for (hi=0; hi<height; hi++) { |
312 |
|
|
row[hi] = &((png_bytep)nrrd->data)[hi*rowsize]; |
313 |
|
|
} |
314 |
|
|
/* read the entire image in one pass */ |
315 |
|
|
png_read_image(png, row); |
316 |
|
|
/* read all text fields from the text chunk */ |
317 |
|
|
numtxt = png_get_text(png, info, &txt, NULL); |
318 |
|
|
for (i=0; i<numtxt; i++) { |
319 |
|
|
if (!strcmp(txt[i].key, NRRD_PNG_FIELD_KEY)) { |
320 |
|
|
nio->pos = 0; |
321 |
|
|
/* Reading PNGs teaches Gordon that his scheme for parsing nrrd header |
322 |
|
|
information is inappropriately specific to reading PNMs and NRRDs, |
323 |
|
|
since in this case the text from which we parse a nrrd field |
324 |
|
|
descriptor did NOT come from a line of text as read by |
325 |
|
|
_nrrdOneLine */ |
326 |
|
|
nio->line = (char *)airFree(nio->line); |
327 |
|
|
nio->line = airStrdup(txt[i].text); |
328 |
|
|
ret = _nrrdReadNrrdParseField(nio, AIR_FALSE); |
329 |
|
|
if (ret) { |
330 |
|
|
const char* fs = airEnumStr(nrrdField, ret); |
331 |
|
|
if (nrrdField_comment == ret) { |
332 |
|
|
ret = 0; |
333 |
|
|
goto plain; |
334 |
|
|
} |
335 |
|
|
if (!_nrrdFieldValidInImage[ret]) { |
336 |
|
|
if (1 <= nrrdStateVerboseIO) { |
337 |
|
|
fprintf(stderr, "(%s: field \"%s\" (not allowed in PNG) " |
338 |
|
|
"--> plain comment)\n", me, fs); |
339 |
|
|
} |
340 |
|
|
ret = 0; |
341 |
|
|
goto plain; |
342 |
|
|
} |
343 |
|
|
if (!nio->seen[ret] |
344 |
|
|
&& nrrdFieldInfoParse[ret](file, nrrd, nio, AIR_FALSE)) { |
345 |
|
|
if (1 <= nrrdStateVerboseIO) { |
346 |
|
|
fprintf(stderr, "(%s: unparsable info \"%s\" for field \"%s\" " |
347 |
|
|
"--> plain comment)\n", me, nio->line + nio->pos, fs); |
348 |
|
|
} |
349 |
|
|
ret = 0; |
350 |
|
|
goto plain; |
351 |
|
|
} |
352 |
|
|
nio->seen[ret] = AIR_TRUE; |
353 |
|
|
plain: |
354 |
|
|
if (!ret) { |
355 |
|
|
if (nrrdCommentAdd(nrrd, nio->line)) { |
356 |
|
|
png_destroy_read_struct(&png, &info, NULL); |
357 |
|
|
biffAddf(NRRD, "%s: couldn't add comment", me); |
358 |
|
|
return 1; |
359 |
|
|
} |
360 |
|
|
} |
361 |
|
|
} |
362 |
|
|
} else if (!strcmp(txt[i].key, NRRD_PNG_COMMENT_KEY)) { |
363 |
|
|
char *p, *c; |
364 |
|
|
c = airStrtok(txt[i].text, "\n", &p); |
365 |
|
|
while (c) { |
366 |
|
|
if (nrrdCommentAdd(nrrd, c)) { |
367 |
|
|
png_destroy_read_struct(&png, &info, NULL); |
368 |
|
|
biffAddf(NRRD, "%s: couldn't add comment", me); |
369 |
|
|
return 1; |
370 |
|
|
} |
371 |
|
|
c = airStrtok(NULL, "\n", &p); |
372 |
|
|
} |
373 |
|
|
} else { |
374 |
|
|
if (nrrdKeyValueAdd(nrrd, txt[i].key, txt[i].text)) { |
375 |
|
|
png_destroy_read_struct(&png, &info, NULL); |
376 |
|
|
biffAddf(NRRD, "%s: couldn't add key/value pair", me); |
377 |
|
|
return 1; |
378 |
|
|
} |
379 |
|
|
} |
380 |
|
|
} |
381 |
|
|
/* finish reading */ |
382 |
|
|
png_read_end(png, info); |
383 |
|
|
/* clean up */ |
384 |
|
|
row = (png_byte**)airFree(row); |
385 |
|
|
png_destroy_read_struct(&png, &info, NULL); |
386 |
|
|
|
387 |
|
|
return 0; |
388 |
|
|
#else |
389 |
|
|
biffAddf(NRRD, "%s: Sorry, this nrrd not compiled with PNG enabled", me); |
390 |
|
|
return 1; |
391 |
|
|
#endif |
392 |
|
|
} |
393 |
|
|
|
394 |
|
|
static int |
395 |
|
|
_nrrdFormatPNG_write(FILE *file, const Nrrd *nrrd, NrrdIoState *nio) { |
396 |
|
|
static const char me[]="_nrrdFormatPNG_write"; |
397 |
|
|
#if TEEM_PNG |
398 |
|
|
int fi, depth, type, csize; |
399 |
|
|
unsigned int jj, numtxt, txtidx; |
400 |
|
|
png_structp png; |
401 |
|
|
png_infop info; |
402 |
|
|
png_bytep *row; |
403 |
|
|
png_uint_32 width, height, rowsize, hi; |
404 |
|
|
png_text *txt; |
405 |
|
|
char *key, *value; |
406 |
|
|
|
407 |
|
|
/* no need to check type and format, done in FitsInFormat */ |
408 |
|
|
/* create png struct with the error handlers above */ |
409 |
|
|
png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, |
410 |
|
|
_nrrdErrorHandlerPNG, |
411 |
|
|
_nrrdWarningHandlerPNG); |
412 |
|
|
if (png == NULL) { |
413 |
|
|
biffAddf(NRRD, "%s: failed to create PNG write struct (compiled with " |
414 |
|
|
"PNG_LIBPNG_VER_STRING=" PNG_LIBPNG_VER_STRING ")", me); |
415 |
|
|
return 1; |
416 |
|
|
} |
417 |
|
|
/* create image info struct */ |
418 |
|
|
info = png_create_info_struct(png); |
419 |
|
|
if (info == NULL) { |
420 |
|
|
png_destroy_write_struct(&png, NULL); |
421 |
|
|
biffAddf(NRRD, "%s: failed to create PNG image info struct", me); |
422 |
|
|
return 1; |
423 |
|
|
} |
424 |
|
|
/* set up error png style error handling */ |
425 |
|
|
if (setjmp(png_jmpbuf(png))) |
426 |
|
|
{ |
427 |
|
|
/* the error is reported inside the error handler, |
428 |
|
|
but we still need to clean up an return with an error */ |
429 |
|
|
png_destroy_write_struct(&png, &info); |
430 |
|
|
return 1; |
431 |
|
|
} |
432 |
|
|
/* initialize png I/O */ |
433 |
|
|
#ifdef _WIN32 |
434 |
|
|
png_set_write_fn(png, file, _nrrdWriteDataPNG, _nrrdFlushDataPNG); |
435 |
|
|
#else |
436 |
|
|
png_init_io(png, file); |
437 |
|
|
#endif |
438 |
|
|
/* calculate depth, width, height, and row size */ |
439 |
|
|
depth = nrrd->type == nrrdTypeUChar ? 8 : 16; |
440 |
|
|
switch (nrrd->dim) { |
441 |
|
|
char stmp[AIR_STRLEN_SMALL]; |
442 |
|
|
case 2: /* g only */ |
443 |
|
|
width = nrrd->axis[0].size; |
444 |
|
|
height = nrrd->axis[1].size; |
445 |
|
|
type = PNG_COLOR_TYPE_GRAY; |
446 |
|
|
rowsize = width*nrrdElementSize(nrrd); |
447 |
|
|
break; |
448 |
|
|
case 3: /* g, ga, rgb, rgba */ |
449 |
|
|
width = nrrd->axis[1].size; |
450 |
|
|
height = nrrd->axis[2].size; |
451 |
|
|
rowsize = width*nrrd->axis[0].size*nrrdElementSize(nrrd); |
452 |
|
|
switch (nrrd->axis[0].size) { |
453 |
|
|
case 1: |
454 |
|
|
type = PNG_COLOR_TYPE_GRAY; |
455 |
|
|
break; |
456 |
|
|
case 2: |
457 |
|
|
type = PNG_COLOR_TYPE_GRAY_ALPHA; |
458 |
|
|
break; |
459 |
|
|
case 3: |
460 |
|
|
type = PNG_COLOR_TYPE_RGB; |
461 |
|
|
break; |
462 |
|
|
case 4: |
463 |
|
|
type = PNG_COLOR_TYPE_RGB_ALPHA; |
464 |
|
|
break; |
465 |
|
|
default: |
466 |
|
|
png_destroy_write_struct(&png, &info); |
467 |
|
|
biffAddf(NRRD, "%s: nrrd->axis[0].size (%s) not compatible with PNG", me, |
468 |
|
|
airSprintSize_t(stmp, nrrd->axis[0].size)); |
469 |
|
|
return 1; |
470 |
|
|
break; |
471 |
|
|
} |
472 |
|
|
break; |
473 |
|
|
default: |
474 |
|
|
png_destroy_write_struct(&png, &info); |
475 |
|
|
biffAddf(NRRD, "%s: dimension (%d) not compatible with PNG", |
476 |
|
|
me, nrrd->dim); |
477 |
|
|
return 1; |
478 |
|
|
break; |
479 |
|
|
} |
480 |
|
|
/* set image header info */ |
481 |
|
|
png_set_IHDR(png, info, width, height, depth, type, |
482 |
|
|
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, |
483 |
|
|
PNG_FILTER_TYPE_BASE); |
484 |
|
|
/* calculate numtxt and allocate txt[] array */ |
485 |
|
|
numtxt = 0; |
486 |
|
|
for (fi=nrrdField_unknown+1; fi<nrrdField_last; fi++) { |
487 |
|
|
if (_nrrdFieldValidInImage[fi] && _nrrdFieldInteresting(nrrd, nio, fi)) { |
488 |
|
|
numtxt++; |
489 |
|
|
} |
490 |
|
|
} |
491 |
|
|
for (jj=0; jj<nrrdKeyValueSize(nrrd); jj++) { |
492 |
|
|
nrrdKeyValueIndex(nrrd, &key, &value, jj); |
493 |
|
|
/* HEY: why is the NULL check needed?? */ |
494 |
|
|
if (NULL != key && NULL != value) { |
495 |
|
|
numtxt++; |
496 |
|
|
} |
497 |
|
|
free(key); |
498 |
|
|
free(value); |
499 |
|
|
key = NULL; |
500 |
|
|
value = NULL; |
501 |
|
|
} |
502 |
|
|
if (nrrd->cmtArr->len > 0) { |
503 |
|
|
/* all comments are put into single text field */ |
504 |
|
|
numtxt += 1; |
505 |
|
|
} |
506 |
|
|
if (0 == numtxt) { |
507 |
|
|
txt = NULL; |
508 |
|
|
} else { |
509 |
|
|
txt = AIR_CAST(png_text *, calloc(numtxt, sizeof(png_text))); |
510 |
|
|
/* add nrrd fields to the text chunk */ |
511 |
|
|
csize = 0; |
512 |
|
|
txtidx = 0; |
513 |
|
|
for (fi=nrrdField_unknown+1; fi<nrrdField_last; fi++) { |
514 |
|
|
if (_nrrdFieldValidInImage[fi] && _nrrdFieldInteresting(nrrd, nio, fi)) { |
515 |
|
|
txt[txtidx].key = airStrdup(NRRD_PNG_FIELD_KEY); |
516 |
|
|
txt[txtidx].compression = PNG_TEXT_COMPRESSION_NONE; |
517 |
|
|
_nrrdSprintFieldInfo(&(txt[txtidx].text), "", nrrd, nio, fi, |
518 |
|
|
(3 == nrrd->dim && 1 == nrrd->axis[0].size)); |
519 |
|
|
txtidx++; |
520 |
|
|
} |
521 |
|
|
} |
522 |
|
|
/* add nrrd key/value pairs to the chunk */ |
523 |
|
|
for (jj=0; jj<nrrdKeyValueSize(nrrd); jj++) { |
524 |
|
|
nrrdKeyValueIndex(nrrd, &key, &value, jj); |
525 |
|
|
if (NULL != key && NULL != value) { |
526 |
|
|
txt[txtidx].key = key; |
527 |
|
|
txt[txtidx].text = value; |
528 |
|
|
txt[txtidx].compression = PNG_TEXT_COMPRESSION_NONE; |
529 |
|
|
txtidx++; |
530 |
|
|
} |
531 |
|
|
} |
532 |
|
|
/* add nrrd comments as a single text field */ |
533 |
|
|
if (nrrd->cmtArr->len > 0) { |
534 |
|
|
txt[txtidx].key = airStrdup(NRRD_PNG_COMMENT_KEY); |
535 |
|
|
txt[txtidx].compression = PNG_TEXT_COMPRESSION_NONE; |
536 |
|
|
for (jj=0; jj<nrrd->cmtArr->len; jj++) { |
537 |
|
|
csize += airStrlen(nrrd->cmt[jj]) + 1; |
538 |
|
|
} |
539 |
|
|
txt[txtidx].text = (png_charp)malloc(csize + 1); |
540 |
|
|
txt[txtidx].text[0] = 0; |
541 |
|
|
for (jj=0; jj<nrrd->cmtArr->len; jj++) { |
542 |
|
|
strcat(txt[txtidx].text, nrrd->cmt[jj]); |
543 |
|
|
strcat(txt[txtidx].text, "\n"); |
544 |
|
|
} |
545 |
|
|
txtidx++; |
546 |
|
|
} |
547 |
|
|
png_set_text(png, info, txt, numtxt); |
548 |
|
|
} |
549 |
|
|
/* write header */ |
550 |
|
|
png_write_info(png, info); |
551 |
|
|
/* fix endianness for 16 bit formats */ |
552 |
|
|
if (depth > 8 && airMyEndian() == airEndianLittle) { |
553 |
|
|
png_set_swap(png); |
554 |
|
|
} |
555 |
|
|
/* set up row pointers */ |
556 |
|
|
row = (png_bytep*)malloc(sizeof(png_bytep)*height); |
557 |
|
|
for (hi=0; hi<height; hi++) { |
558 |
|
|
row[hi] = &((png_bytep)nrrd->data)[hi*rowsize]; |
559 |
|
|
} |
560 |
|
|
png_set_rows(png, info, row); |
561 |
|
|
/* write the entire image in one pass */ |
562 |
|
|
png_write_image(png, row); |
563 |
|
|
/* finish writing */ |
564 |
|
|
png_write_end(png, info); |
565 |
|
|
/* clean up */ |
566 |
|
|
if (txt) { |
567 |
|
|
for (jj=0; jj<numtxt; jj++) { |
568 |
|
|
txt[jj].key = (char *)airFree(txt[jj].key); |
569 |
|
|
txt[jj].text = (char *)airFree(txt[jj].text); |
570 |
|
|
} |
571 |
|
|
free(txt); |
572 |
|
|
} |
573 |
|
|
row = (png_byte**)airFree(row); |
574 |
|
|
png_destroy_write_struct(&png, &info); |
575 |
|
|
|
576 |
|
|
return 0; |
577 |
|
|
#else |
578 |
|
|
AIR_UNUSED(file); |
579 |
|
|
AIR_UNUSED(nrrd); |
580 |
|
|
AIR_UNUSED(nio); |
581 |
|
|
biffAddf(NRRD, "%s: Sorry, this nrrd not compiled with PNG enabled", me); |
582 |
|
|
return 1; |
583 |
|
|
#endif |
584 |
|
|
} |
585 |
|
|
|
586 |
|
|
const NrrdFormat |
587 |
|
|
_nrrdFormatPNG = { |
588 |
|
|
"PNG", |
589 |
|
|
AIR_TRUE, /* isImage */ |
590 |
|
|
AIR_TRUE, /* readable */ |
591 |
|
|
AIR_FALSE, /* usesDIO */ |
592 |
|
|
_nrrdFormatPNG_available, |
593 |
|
|
_nrrdFormatPNG_nameLooksLike, |
594 |
|
|
_nrrdFormatPNG_fitsInto, |
595 |
|
|
_nrrdFormatPNG_contentStartsLike, |
596 |
|
|
_nrrdFormatPNG_read, |
597 |
|
|
_nrrdFormatPNG_write |
598 |
|
|
}; |
599 |
|
|
|
600 |
|
|
const NrrdFormat *const |
601 |
|
|
nrrdFormatPNG = &_nrrdFormatPNG; |