/src/leptonica/src/pnmio.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*====================================================================* |
2 | | - Copyright (C) 2001 Leptonica. All rights reserved. |
3 | | - |
4 | | - Redistribution and use in source and binary forms, with or without |
5 | | - modification, are permitted provided that the following conditions |
6 | | - are met: |
7 | | - 1. Redistributions of source code must retain the above copyright |
8 | | - notice, this list of conditions and the following disclaimer. |
9 | | - 2. Redistributions in binary form must reproduce the above |
10 | | - copyright notice, this list of conditions and the following |
11 | | - disclaimer in the documentation and/or other materials |
12 | | - provided with the distribution. |
13 | | - |
14 | | - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
15 | | - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
16 | | - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
17 | | - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY |
18 | | - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | | - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | | - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | | - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | | - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
23 | | - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
24 | | - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | | *====================================================================*/ |
26 | | |
27 | | /*! |
28 | | * \file pnmio.c |
29 | | * <pre> |
30 | | * |
31 | | * Stream interface |
32 | | * PIX *pixReadStreamPnm() |
33 | | * l_int32 readHeaderPnm() |
34 | | * l_int32 freadHeaderPnm() |
35 | | * l_int32 pixWriteStreamPnm() |
36 | | * l_int32 pixWriteStreamAsciiPnm() |
37 | | * l_int32 pixWriteStreamPam() |
38 | | * |
39 | | * Read/write to memory |
40 | | * PIX *pixReadMemPnm() |
41 | | * l_int32 readHeaderMemPnm() |
42 | | * l_int32 pixWriteMemPnm() |
43 | | * l_int32 pixWriteMemPam() |
44 | | * |
45 | | * Local helpers |
46 | | * static l_int32 pnmReadNextAsciiValue(); |
47 | | * static l_int32 pnmReadNextNumber(); |
48 | | * static l_int32 pnmReadNextString(); |
49 | | * static l_int32 pnmSkipCommentLines(); |
50 | | * |
51 | | * These are here by popular demand, with the help of Mattias |
52 | | * Kregert (mattias@kregert.se), who provided the first implementation. |
53 | | * |
54 | | * The pnm formats are exceedingly simple, because they have |
55 | | * no compression and no colormaps. They support images that |
56 | | * are 1 bpp; 2, 4, 8 and 16 bpp grayscale; and rgb. |
57 | | * |
58 | | * The original pnm formats ("ASCII") are included for completeness, |
59 | | * but their use is deprecated for all but tiny iconic images. |
60 | | * They are extremely wasteful of memory; for example, the P1 binary |
61 | | * ASCII format is 16 times as big as the packed uncompressed |
62 | | * format, because 2 characters are used to represent every bit |
63 | | * (pixel) in the image. Reading is slow because we check for extra |
64 | | * white space and EOL at every sample value. |
65 | | * |
66 | | * The packed pnm formats ("raw") give file sizes similar to |
67 | | * bmp files, which are uncompressed packed. However, bmp |
68 | | * are more flexible, because they can support colormaps. |
69 | | * |
70 | | * We don't differentiate between the different types ("pbm", |
71 | | * "pgm", "ppm") at the interface level, because this is really a |
72 | | * "distinction without a difference." You read a file, you get |
73 | | * the appropriate Pix. You write a file from a Pix, you get the |
74 | | * appropriate type of file. If there is a colormap on the Pix, |
75 | | * and the Pix is more than 1 bpp, you get either an 8 bpp pgm |
76 | | * or a 24 bpp RGB pnm, depending on whether the colormap colors |
77 | | * are gray or rgb, respectively. |
78 | | * |
79 | | * This follows the general policy that the I/O routines don't |
80 | | * make decisions about the content of the image -- you do that |
81 | | * with image processing before you write it out to file. |
82 | | * The I/O routines just try to make the closest connection |
83 | | * possible between the file and the Pix in memory. |
84 | | * |
85 | | * On systems like Windows without fmemopen() and open_memstream(), |
86 | | * we write data to a temp file and read it back for operations |
87 | | * between pix and compressed-data, such as pixReadMemPnm() and |
88 | | * pixWriteMemPnm(). |
89 | | * |
90 | | * The P7 format is new. It introduced a header with multiple |
91 | | * lines containing distinct tags for the various fields. |
92 | | * See: http://netpbm.sourceforge.net/doc/pam.html |
93 | | * |
94 | | * WIDTH <int> ; mandatory, exactly once |
95 | | * HEIGHT <int> ; mandatory, exactly once |
96 | | * DEPTH <int> ; mandatory, exactly once, |
97 | | * ; its meaning is equivalent to spp |
98 | | * MAXVAL <int> ; mandatory, one of 1, 3, 15, 255 or 65535 |
99 | | * TUPLTYPE <string> ; optional; BLACKANDWHITE, GRAYSCALE, RGB |
100 | | * ; and optional suffix _ALPHA, e.g. RGB_ALPHA |
101 | | * ENDHDR ; mandatory, last header line |
102 | | * |
103 | | * Reading BLACKANDWHITE_ALPHA and GRAYSCALE_ALPHA, which have a DEPTH |
104 | | * value of 2, is supported. The original image is converted to a Pix |
105 | | * with 32-bpp and alpha channel (spp == 4). |
106 | | * |
107 | | * Writing P7 format is currently selected for 32-bpp with alpha |
108 | | * channel, i.e. for Pix which have spp == 4, using pixWriteStreamPam(). |
109 | | * |
110 | | * Jürgen Buchmüller provided the implementation for the P7 (pam) format. |
111 | | * |
112 | | * Giulio Lunati made an elegant reimplementation of the static helper |
113 | | * functions using fscanf() instead of fseek(), so that it works with |
114 | | * pnm data from stdin. |
115 | | * </pre> |
116 | | */ |
117 | | |
118 | | #ifdef HAVE_CONFIG_H |
119 | | #include <config_auto.h> |
120 | | #endif /* HAVE_CONFIG_H */ |
121 | | |
122 | | #include <string.h> |
123 | | #include <ctype.h> |
124 | | #include "allheaders.h" |
125 | | |
126 | | /* --------------------------------------------*/ |
127 | | #if USE_PNMIO /* defined in environ.h */ |
128 | | /* --------------------------------------------*/ |
129 | | |
130 | | static l_int32 pnmReadNextAsciiValue(FILE *fp, l_int32 *pval); |
131 | | static l_int32 pnmReadNextNumber(FILE *fp, l_int32 *pval); |
132 | | static l_int32 pnmReadNextString(FILE *fp, char *buff, l_int32 size); |
133 | | static l_int32 pnmSkipCommentLines(FILE *fp); |
134 | | |
135 | | /* a sanity check on the size read from file */ |
136 | | static const l_int32 MAX_PNM_WIDTH = 100000; |
137 | | static const l_int32 MAX_PNM_HEIGHT = 100000; |
138 | | |
139 | | |
140 | | /*--------------------------------------------------------------------* |
141 | | * Stream interface * |
142 | | *--------------------------------------------------------------------*/ |
143 | | /*! |
144 | | * \brief pixReadStreamPnm() |
145 | | * |
146 | | * \param[in] fp file stream opened for read |
147 | | * \return pix, or NULL on error |
148 | | */ |
149 | | PIX * |
150 | | pixReadStreamPnm(FILE *fp) |
151 | 0 | { |
152 | 0 | l_uint8 val8, rval8, gval8, bval8, aval8, mask8; |
153 | 0 | l_uint16 val16, rval16, gval16, bval16, aval16; |
154 | 0 | l_int32 w, h, d, bps, spp, bpl, wpl, i, j, type; |
155 | 0 | l_int32 val, rval, gval, bval; |
156 | 0 | l_uint32 rgbval; |
157 | 0 | l_uint32 *line, *data; |
158 | 0 | PIX *pix; |
159 | |
|
160 | 0 | if (!fp) |
161 | 0 | return (PIX *)ERROR_PTR("fp not defined", __func__, NULL); |
162 | | |
163 | 0 | if (freadHeaderPnm(fp, &w, &h, &d, &type, &bps, &spp)) |
164 | 0 | return (PIX *)ERROR_PTR("header read failed", __func__, NULL); |
165 | 0 | if (bps < 1 || bps > 16) |
166 | 0 | return (PIX *)ERROR_PTR("invalid bps", __func__, NULL); |
167 | 0 | if (spp < 1 || spp > 4) |
168 | 0 | return (PIX *)ERROR_PTR("invalid spp", __func__, NULL); |
169 | 0 | if ((pix = pixCreate(w, h, d)) == NULL) |
170 | 0 | return (PIX *)ERROR_PTR("pix not made", __func__, NULL); |
171 | 0 | pixSetInputFormat(pix, IFF_PNM); |
172 | 0 | data = pixGetData(pix); |
173 | 0 | wpl = pixGetWpl(pix); |
174 | | |
175 | | /* If type == 6 and bps == 16, we use the code in type 7 |
176 | | * to read 6 bytes/pixel from the input file. */ |
177 | 0 | if (type == 6 && bps == 16) |
178 | 0 | type = 7; |
179 | |
|
180 | 0 | switch (type) { |
181 | 0 | case 1: |
182 | 0 | case 2: |
183 | | /* Old "ASCII" binary or gray format */ |
184 | 0 | for (i = 0; i < h; i++) { |
185 | 0 | for (j = 0; j < w; j++) { |
186 | 0 | if (pnmReadNextAsciiValue(fp, &val)) { |
187 | 0 | pixDestroy(&pix); |
188 | 0 | return (PIX *)ERROR_PTR("read abend", __func__, NULL); |
189 | 0 | } |
190 | 0 | pixSetPixel(pix, j, i, val); |
191 | 0 | } |
192 | 0 | } |
193 | 0 | break; |
194 | | |
195 | 0 | case 3: |
196 | | /* Old "ASCII" rgb format */ |
197 | 0 | for (i = 0; i < h; i++) { |
198 | 0 | for (j = 0; j < w; j++) { |
199 | 0 | if (pnmReadNextAsciiValue(fp, &rval)) { |
200 | 0 | pixDestroy(&pix); |
201 | 0 | return (PIX *)ERROR_PTR("read abend", __func__, NULL); |
202 | 0 | } |
203 | 0 | if (pnmReadNextAsciiValue(fp, &gval)) { |
204 | 0 | pixDestroy(&pix); |
205 | 0 | return (PIX *)ERROR_PTR("read abend", __func__, NULL); |
206 | 0 | } |
207 | 0 | if (pnmReadNextAsciiValue(fp, &bval)) { |
208 | 0 | pixDestroy(&pix); |
209 | 0 | return (PIX *)ERROR_PTR("read abend", __func__, NULL); |
210 | 0 | } |
211 | 0 | composeRGBPixel(rval, gval, bval, &rgbval); |
212 | 0 | pixSetPixel(pix, j, i, rgbval); |
213 | 0 | } |
214 | 0 | } |
215 | 0 | break; |
216 | | |
217 | 0 | case 4: |
218 | | /* "raw" format for 1 bpp */ |
219 | 0 | bpl = (d * w + 7) / 8; |
220 | 0 | for (i = 0; i < h; i++) { |
221 | 0 | line = data + i * wpl; |
222 | 0 | for (j = 0; j < bpl; j++) { |
223 | 0 | if (fread(&val8, 1, 1, fp) != 1) { |
224 | 0 | pixDestroy(&pix); |
225 | 0 | return (PIX *)ERROR_PTR("read error in 4", __func__, NULL); |
226 | 0 | } |
227 | 0 | SET_DATA_BYTE(line, j, val8); |
228 | 0 | } |
229 | 0 | } |
230 | 0 | break; |
231 | | |
232 | 0 | case 5: |
233 | | /* "raw" format for grayscale */ |
234 | 0 | for (i = 0; i < h; i++) { |
235 | 0 | line = data + i * wpl; |
236 | 0 | if (d != 16) { |
237 | 0 | for (j = 0; j < w; j++) { |
238 | 0 | if (fread(&val8, 1, 1, fp) != 1) { |
239 | 0 | pixDestroy(&pix); |
240 | 0 | return (PIX *)ERROR_PTR("error in 5", __func__, NULL); |
241 | 0 | } |
242 | 0 | if (d == 2) |
243 | 0 | SET_DATA_DIBIT(line, j, val8); |
244 | 0 | else if (d == 4) |
245 | 0 | SET_DATA_QBIT(line, j, val8); |
246 | 0 | else /* d == 8 */ |
247 | 0 | SET_DATA_BYTE(line, j, val8); |
248 | 0 | } |
249 | 0 | } else { /* d == 16 */ |
250 | 0 | for (j = 0; j < w; j++) { |
251 | 0 | if (fread(&val16, 2, 1, fp) != 1) { |
252 | 0 | pixDestroy(&pix); |
253 | 0 | return (PIX *)ERROR_PTR("16 bpp error", __func__, NULL); |
254 | 0 | } |
255 | 0 | SET_DATA_TWO_BYTES(line, j, val16); |
256 | 0 | } |
257 | 0 | } |
258 | 0 | } |
259 | 0 | break; |
260 | | |
261 | 0 | case 6: |
262 | | /* "raw" format, type == 6; 8 bps, rgb */ |
263 | 0 | for (i = 0; i < h; i++) { |
264 | 0 | line = data + i * wpl; |
265 | 0 | for (j = 0; j < wpl; j++) { |
266 | 0 | if (fread(&rval8, 1, 1, fp) != 1) { |
267 | 0 | pixDestroy(&pix); |
268 | 0 | return (PIX *)ERROR_PTR("read error type 6", |
269 | 0 | __func__, NULL); |
270 | 0 | } |
271 | 0 | if (fread(&gval8, 1, 1, fp) != 1) { |
272 | 0 | pixDestroy(&pix); |
273 | 0 | return (PIX *)ERROR_PTR("read error type 6", |
274 | 0 | __func__, NULL); |
275 | 0 | } |
276 | 0 | if (fread(&bval8, 1, 1, fp) != 1) { |
277 | 0 | pixDestroy(&pix); |
278 | 0 | return (PIX *)ERROR_PTR("read error type 6", |
279 | 0 | __func__, NULL); |
280 | 0 | } |
281 | 0 | composeRGBPixel(rval8, gval8, bval8, &rgbval); |
282 | 0 | line[j] = rgbval; |
283 | 0 | } |
284 | 0 | } |
285 | 0 | break; |
286 | | |
287 | 0 | case 7: |
288 | | /* "arbitrary" format; type == 7; */ |
289 | 0 | if (bps != 16) { |
290 | 0 | mask8 = (1 << bps) - 1; |
291 | 0 | switch (spp) { |
292 | 0 | case 1: /* 1, 2, 4, 8 bpp grayscale */ |
293 | 0 | for (i = 0; i < h; i++) { |
294 | 0 | for (j = 0; j < w; j++) { |
295 | 0 | if (fread(&val8, 1, 1, fp) != 1) { |
296 | 0 | pixDestroy(&pix); |
297 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
298 | 0 | __func__, NULL); |
299 | 0 | } |
300 | 0 | val8 = val8 & mask8; |
301 | 0 | if (bps == 1) val8 ^= 1; /* white-is-1 photometry */ |
302 | 0 | pixSetPixel(pix, j, i, val8); |
303 | 0 | } |
304 | 0 | } |
305 | 0 | break; |
306 | | |
307 | 0 | case 2: /* 1, 2, 4, 8 bpp grayscale + alpha */ |
308 | 0 | for (i = 0; i < h; i++) { |
309 | 0 | for (j = 0; j < w; j++) { |
310 | 0 | if (fread(&val8, 1, 1, fp) != 1) { |
311 | 0 | pixDestroy(&pix); |
312 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
313 | 0 | __func__, NULL); |
314 | 0 | } |
315 | 0 | if (fread(&aval8, 1, 1, fp) != 1) { |
316 | 0 | pixDestroy(&pix); |
317 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
318 | 0 | __func__, NULL); |
319 | 0 | } |
320 | 0 | val8 = val8 & mask8; |
321 | 0 | aval8 = aval8 & mask8; |
322 | 0 | composeRGBAPixel(val8, val8, val8, aval8, &rgbval); |
323 | 0 | pixSetPixel(pix, j, i, rgbval); |
324 | 0 | } |
325 | 0 | } |
326 | 0 | pixSetSpp(pix, 4); |
327 | 0 | break; |
328 | | |
329 | 0 | case 3: /* rgb */ |
330 | 0 | for (i = 0; i < h; i++) { |
331 | 0 | line = data + i * wpl; |
332 | 0 | for (j = 0; j < wpl; j++) { |
333 | 0 | if (fread(&rval8, 1, 1, fp) != 1) { |
334 | 0 | pixDestroy(&pix); |
335 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
336 | 0 | __func__, NULL); |
337 | 0 | } |
338 | 0 | if (fread(&gval8, 1, 1, fp) != 1) { |
339 | 0 | pixDestroy(&pix); |
340 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
341 | 0 | __func__, NULL); |
342 | 0 | } |
343 | 0 | if (fread(&bval8, 1, 1, fp) != 1) { |
344 | 0 | pixDestroy(&pix); |
345 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
346 | 0 | __func__, NULL); |
347 | 0 | } |
348 | 0 | rval8 = rval8 & mask8; |
349 | 0 | gval8 = gval8 & mask8; |
350 | 0 | bval8 = bval8 & mask8; |
351 | 0 | composeRGBPixel(rval8, gval8, bval8, &rgbval); |
352 | 0 | line[j] = rgbval; |
353 | 0 | } |
354 | 0 | } |
355 | 0 | break; |
356 | | |
357 | 0 | case 4: /* rgba */ |
358 | 0 | for (i = 0; i < h; i++) { |
359 | 0 | line = data + i * wpl; |
360 | 0 | for (j = 0; j < wpl; j++) { |
361 | 0 | if (fread(&rval8, 1, 1, fp) != 1) { |
362 | 0 | pixDestroy(&pix); |
363 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
364 | 0 | __func__, NULL); |
365 | 0 | } |
366 | 0 | if (fread(&gval8, 1, 1, fp) != 1) { |
367 | 0 | pixDestroy(&pix); |
368 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
369 | 0 | __func__, NULL); |
370 | 0 | } |
371 | 0 | if (fread(&bval8, 1, 1, fp) != 1) { |
372 | 0 | pixDestroy(&pix); |
373 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
374 | 0 | __func__, NULL); |
375 | 0 | } |
376 | 0 | if (fread(&aval8, 1, 1, fp) != 1) { |
377 | 0 | pixDestroy(&pix); |
378 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
379 | 0 | __func__, NULL); |
380 | 0 | } |
381 | 0 | rval8 = rval8 & mask8; |
382 | 0 | gval8 = gval8 & mask8; |
383 | 0 | bval8 = bval8 & mask8; |
384 | 0 | aval8 = aval8 & mask8; |
385 | 0 | composeRGBAPixel(rval8, gval8, bval8, aval8, &rgbval); |
386 | 0 | line[j] = rgbval; |
387 | 0 | } |
388 | 0 | } |
389 | 0 | pixSetSpp(pix, 4); |
390 | 0 | break; |
391 | 0 | } |
392 | 0 | } else { /* bps == 16 */ |
393 | | /* I have only seen one example that is type 6, 16 bps. |
394 | | * It was 3 spp (rgb), and the 8 bps of real data was stored |
395 | | * in the second byte. In the following, I make the wild |
396 | | * assumption that for all 16 bpp pnm/pam files, we can |
397 | | * take the second byte. */ |
398 | 0 | switch (spp) { |
399 | 0 | case 1: /* 16 bps grayscale */ |
400 | 0 | for (i = 0; i < h; i++) { |
401 | 0 | for (j = 0; j < w; j++) { |
402 | 0 | if (fread(&val16, 2, 1, fp) != 1) { |
403 | 0 | pixDestroy(&pix); |
404 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
405 | 0 | __func__, NULL); |
406 | 0 | } |
407 | 0 | val8 = val16 & 0xff; |
408 | 0 | pixSetPixel(pix, j, i, val8); |
409 | 0 | } |
410 | 0 | } |
411 | 0 | break; |
412 | | |
413 | 0 | case 2: /* 16 bps grayscale + alpha */ |
414 | 0 | for (i = 0; i < h; i++) { |
415 | 0 | for (j = 0; j < w; j++) { |
416 | 0 | if (fread(&val16, 2, 1, fp) != 1) { |
417 | 0 | pixDestroy(&pix); |
418 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
419 | 0 | __func__, NULL); |
420 | 0 | } |
421 | 0 | if (fread(&aval16, 2, 1, fp) != 1) { |
422 | 0 | pixDestroy(&pix); |
423 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
424 | 0 | __func__, NULL); |
425 | 0 | } |
426 | 0 | val8 = val16 & 0xff; |
427 | 0 | aval8 = aval16 & 0xff; |
428 | 0 | composeRGBAPixel(val8, val8, val8, aval8, &rgbval); |
429 | 0 | pixSetPixel(pix, j, i, rgbval); |
430 | 0 | } |
431 | 0 | } |
432 | 0 | pixSetSpp(pix, 4); |
433 | 0 | break; |
434 | | |
435 | 0 | case 3: /* 16bps rgb */ |
436 | 0 | for (i = 0; i < h; i++) { |
437 | 0 | line = data + i * wpl; |
438 | 0 | for (j = 0; j < wpl; j++) { |
439 | 0 | if (fread(&rval16, 2, 1, fp) != 1) { |
440 | 0 | pixDestroy(&pix); |
441 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
442 | 0 | __func__, NULL); |
443 | 0 | } |
444 | 0 | if (fread(&gval16, 2, 1, fp) != 1) { |
445 | 0 | pixDestroy(&pix); |
446 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
447 | 0 | __func__, NULL); |
448 | 0 | } |
449 | 0 | if (fread(&bval16, 2, 1, fp) != 1) { |
450 | 0 | pixDestroy(&pix); |
451 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
452 | 0 | __func__, NULL); |
453 | 0 | } |
454 | 0 | rval8 = rval16 & 0xff; |
455 | 0 | gval8 = gval16 & 0xff; |
456 | 0 | bval8 = bval16 & 0xff; |
457 | 0 | composeRGBPixel(rval8, gval8, bval8, &rgbval); |
458 | 0 | line[j] = rgbval; |
459 | 0 | } |
460 | 0 | } |
461 | 0 | break; |
462 | | |
463 | 0 | case 4: /* 16bps rgba */ |
464 | 0 | for (i = 0; i < h; i++) { |
465 | 0 | line = data + i * wpl; |
466 | 0 | for (j = 0; j < wpl; j++) { |
467 | 0 | if (fread(&rval16, 2, 1, fp) != 1) { |
468 | 0 | pixDestroy(&pix); |
469 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
470 | 0 | __func__, NULL); |
471 | 0 | } |
472 | 0 | if (fread(&gval16, 2, 1, fp) != 1) { |
473 | 0 | pixDestroy(&pix); |
474 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
475 | 0 | __func__, NULL); |
476 | 0 | } |
477 | 0 | if (fread(&bval16, 2, 1, fp) != 1) { |
478 | 0 | pixDestroy(&pix); |
479 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
480 | 0 | __func__, NULL); |
481 | 0 | } |
482 | 0 | if (fread(&aval16, 2, 1, fp) != 1) { |
483 | 0 | pixDestroy(&pix); |
484 | 0 | return (PIX *)ERROR_PTR("read error type 7", |
485 | 0 | __func__, NULL); |
486 | 0 | } |
487 | 0 | rval8 = rval16 & 0xff; |
488 | 0 | gval8 = gval16 & 0xff; |
489 | 0 | bval8 = bval16 & 0xff; |
490 | 0 | aval8 = aval16 & 0xff; |
491 | 0 | composeRGBAPixel(rval8, gval8, bval8, aval8, &rgbval); |
492 | 0 | line[j] = rgbval; |
493 | 0 | } |
494 | 0 | } |
495 | 0 | pixSetSpp(pix, 4); |
496 | 0 | break; |
497 | 0 | } |
498 | 0 | } |
499 | 0 | break; |
500 | 0 | } |
501 | 0 | return pix; |
502 | 0 | } |
503 | | |
504 | | |
505 | | /*! |
506 | | * \brief readHeaderPnm() |
507 | | * |
508 | | * \param[in] filename |
509 | | * \param[out] pw [optional] |
510 | | * \param[out] ph [optional] |
511 | | * \param[out] pd [optional] |
512 | | * \param[out] ptype [optional] pnm type |
513 | | * \param[out] pbps [optional] bits/sample |
514 | | * \param[out] pspp [optional] samples/pixel |
515 | | * \return 0 if OK, 1 on error |
516 | | */ |
517 | | l_ok |
518 | | readHeaderPnm(const char *filename, |
519 | | l_int32 *pw, |
520 | | l_int32 *ph, |
521 | | l_int32 *pd, |
522 | | l_int32 *ptype, |
523 | | l_int32 *pbps, |
524 | | l_int32 *pspp) |
525 | 0 | { |
526 | 0 | l_int32 ret; |
527 | 0 | FILE *fp; |
528 | |
|
529 | 0 | if (pw) *pw = 0; |
530 | 0 | if (ph) *ph = 0; |
531 | 0 | if (pd) *pd = 0; |
532 | 0 | if (ptype) *ptype = 0; |
533 | 0 | if (pbps) *pbps = 0; |
534 | 0 | if (pspp) *pspp = 0; |
535 | 0 | if (!filename) |
536 | 0 | return ERROR_INT("filename not defined", __func__, 1); |
537 | | |
538 | 0 | if ((fp = fopenReadStream(filename)) == NULL) |
539 | 0 | return ERROR_INT_1("image file not found", filename, __func__, 1); |
540 | 0 | ret = freadHeaderPnm(fp, pw, ph, pd, ptype, pbps, pspp); |
541 | 0 | fclose(fp); |
542 | 0 | return ret; |
543 | 0 | } |
544 | | |
545 | | |
546 | | /*! |
547 | | * \brief freadHeaderPnm() |
548 | | * |
549 | | * \param[in] fp file stream opened for read |
550 | | * \param[out] pw [optional] |
551 | | * \param[out] ph [optional] |
552 | | * \param[out] pd [optional] |
553 | | * \param[out] ptype [optional] pnm type |
554 | | * \param[out] pbps [optional] bits/sample |
555 | | * \param[out] pspp [optional] samples/pixel |
556 | | * \return 0 if OK, 1 on error |
557 | | */ |
558 | | l_ok |
559 | | freadHeaderPnm(FILE *fp, |
560 | | l_int32 *pw, |
561 | | l_int32 *ph, |
562 | | l_int32 *pd, |
563 | | l_int32 *ptype, |
564 | | l_int32 *pbps, |
565 | | l_int32 *pspp) |
566 | 0 | { |
567 | 0 | char tag[16], tupltype[32]; |
568 | 0 | l_int32 i, w, h, d, bps, spp, type; |
569 | 0 | l_int32 maxval; |
570 | 0 | l_int32 ch; |
571 | |
|
572 | 0 | if (pw) *pw = 0; |
573 | 0 | if (ph) *ph = 0; |
574 | 0 | if (pd) *pd = 0; |
575 | 0 | if (ptype) *ptype = 0; |
576 | 0 | if (pbps) *pbps = 0; |
577 | 0 | if (pspp) *pspp = 0; |
578 | 0 | if (!fp) |
579 | 0 | return ERROR_INT("fp not defined", __func__, 1); |
580 | | |
581 | 0 | if (fscanf(fp, "P%d\n", &type) != 1) |
582 | 0 | return ERROR_INT("invalid read for type", __func__, 1); |
583 | 0 | if (type < 1 || type > 7) |
584 | 0 | return ERROR_INT("invalid pnm file", __func__, 1); |
585 | | |
586 | 0 | if (pnmSkipCommentLines(fp)) |
587 | 0 | return ERROR_INT("no data in file", __func__, 1); |
588 | | |
589 | 0 | if (type == 7) { |
590 | 0 | w = h = d = bps = spp = maxval = 0; |
591 | 0 | for (i = 0; i < 10; i++) { /* limit to 10 lines of this header */ |
592 | 0 | if (pnmReadNextString(fp, tag, sizeof(tag))) |
593 | 0 | return ERROR_INT("found no next tag", __func__, 1); |
594 | 0 | if (!strcmp(tag, "WIDTH")) { |
595 | 0 | if (pnmReadNextNumber(fp, &w)) |
596 | 0 | return ERROR_INT("failed reading width", __func__, 1); |
597 | 0 | continue; |
598 | 0 | } |
599 | 0 | if (!strcmp(tag, "HEIGHT")) { |
600 | 0 | if (pnmReadNextNumber(fp, &h)) |
601 | 0 | return ERROR_INT("failed reading height", __func__, 1); |
602 | 0 | continue; |
603 | 0 | } |
604 | 0 | if (!strcmp(tag, "DEPTH")) { |
605 | 0 | if (pnmReadNextNumber(fp, &spp)) |
606 | 0 | return ERROR_INT("failed reading depth", __func__, 1); |
607 | 0 | continue; |
608 | 0 | } |
609 | 0 | if (!strcmp(tag, "MAXVAL")) { |
610 | 0 | if (pnmReadNextNumber(fp, &maxval)) |
611 | 0 | return ERROR_INT("failed reading maxval", __func__, 1); |
612 | 0 | continue; |
613 | 0 | } |
614 | 0 | if (!strcmp(tag, "TUPLTYPE")) { |
615 | 0 | if (pnmReadNextString(fp, tupltype, sizeof(tupltype))) |
616 | 0 | return ERROR_INT("failed reading tuple type", __func__, 1); |
617 | 0 | continue; |
618 | 0 | } |
619 | 0 | if (!strcmp(tag, "ENDHDR")) { |
620 | 0 | if ('\n' != (ch = fgetc(fp))) |
621 | 0 | return ERROR_INT("missing LF after ENDHDR", __func__, 1); |
622 | 0 | break; |
623 | 0 | } |
624 | 0 | } |
625 | 0 | if (w <= 0 || h <= 0 || w > MAX_PNM_WIDTH || h > MAX_PNM_HEIGHT) { |
626 | 0 | L_INFO("invalid size: w = %d, h = %d\n", __func__, w, h); |
627 | 0 | return 1; |
628 | 0 | } |
629 | 0 | if (maxval == 1) { |
630 | 0 | d = bps = 1; |
631 | 0 | } else if (maxval == 3) { |
632 | 0 | d = bps = 2; |
633 | 0 | } else if (maxval == 15) { |
634 | 0 | d = bps = 4; |
635 | 0 | } else if (maxval == 255) { |
636 | 0 | d = bps = 8; |
637 | 0 | } else if (maxval == 0xffff) { |
638 | 0 | d = bps = 16; |
639 | 0 | } else { |
640 | 0 | L_INFO("invalid maxval = %d\n", __func__, maxval); |
641 | 0 | return 1; |
642 | 0 | } |
643 | 0 | switch (spp) { |
644 | 0 | case 1: |
645 | | /* d and bps are already set */ |
646 | 0 | break; |
647 | 0 | case 2: |
648 | 0 | case 3: |
649 | 0 | case 4: |
650 | | /* create a 32 bpp Pix */ |
651 | 0 | d = 32; |
652 | 0 | break; |
653 | 0 | default: |
654 | 0 | L_INFO("invalid depth = %d\n", __func__, spp); |
655 | 0 | return 1; |
656 | 0 | } |
657 | 0 | } else { |
658 | |
|
659 | 0 | if (fscanf(fp, "%d %d\n", &w, &h) != 2) |
660 | 0 | return ERROR_INT("invalid read for w,h", __func__, 1); |
661 | 0 | if (w <= 0 || h <= 0 || w > MAX_PNM_WIDTH || h > MAX_PNM_HEIGHT) { |
662 | 0 | L_INFO("invalid size: w = %d, h = %d\n", __func__, w, h); |
663 | 0 | return 1; |
664 | 0 | } |
665 | | |
666 | | /* Get depth of pix. For types 2 and 5, we use the maxval. |
667 | | * Important implementation note: |
668 | | * - You can't use fscanf(), which throws away whitespace, |
669 | | * and will discard binary data if it starts with whitespace(s). |
670 | | * - You can't use fgets(), which stops at newlines, but this |
671 | | * dumb format doesn't require a newline after the maxval |
672 | | * number -- it just requires one whitespace character. |
673 | | * - Which leaves repeated calls to fgetc, including swallowing |
674 | | * the single whitespace character. */ |
675 | 0 | if (type == 1 || type == 4) { |
676 | 0 | d = 1; |
677 | 0 | spp = 1; |
678 | 0 | bps = 1; |
679 | 0 | } else if (type == 2 || type == 5) { |
680 | 0 | if (pnmReadNextNumber(fp, &maxval)) |
681 | 0 | return ERROR_INT("invalid read for maxval (2,5)", __func__, 1); |
682 | 0 | if (maxval == 3) { |
683 | 0 | d = 2; |
684 | 0 | } else if (maxval == 15) { |
685 | 0 | d = 4; |
686 | 0 | } else if (maxval == 255) { |
687 | 0 | d = 8; |
688 | 0 | } else if (maxval == 0xffff) { |
689 | 0 | d = 16; |
690 | 0 | } else { |
691 | 0 | lept_stderr("maxval = %d\n", maxval); |
692 | 0 | return ERROR_INT("invalid maxval", __func__, 1); |
693 | 0 | } |
694 | 0 | bps = d; |
695 | 0 | spp = 1; |
696 | 0 | } else { /* type == 3 || type == 6; this is rgb */ |
697 | 0 | if (pnmReadNextNumber(fp, &maxval)) |
698 | 0 | return ERROR_INT("invalid read for maxval (3,6)", __func__, 1); |
699 | 0 | if (maxval != 255 && maxval != 0xffff) { |
700 | 0 | L_ERROR("unexpected maxval = %d\n", __func__, maxval); |
701 | 0 | return 1; |
702 | 0 | } |
703 | 0 | bps = (maxval == 255) ? 8 : 16; |
704 | 0 | d = 32; |
705 | 0 | spp = 3; |
706 | 0 | } |
707 | 0 | } |
708 | 0 | if (pw) *pw = w; |
709 | 0 | if (ph) *ph = h; |
710 | 0 | if (pd) *pd = d; |
711 | 0 | if (ptype) *ptype = type; |
712 | 0 | if (pbps) *pbps = bps; |
713 | 0 | if (pspp) *pspp = spp; |
714 | 0 | return 0; |
715 | 0 | } |
716 | | |
717 | | |
718 | | /*! |
719 | | * \brief pixWriteStreamPnm() |
720 | | * |
721 | | * \param[in] fp file stream opened for write |
722 | | * \param[in] pix |
723 | | * \return 0 if OK; 1 on error |
724 | | * |
725 | | * <pre> |
726 | | * Notes: |
727 | | * (1) This writes "raw" packed format only: |
728 | | * 1 bpp --> pbm (P4) |
729 | | * 2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm (P5) |
730 | | * 2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm (P6) |
731 | | * (2) 24 bpp rgb are not supported in leptonica, but this will |
732 | | * write them out as a packed array of bytes (3 to a pixel). |
733 | | * </pre> |
734 | | */ |
735 | | l_ok |
736 | | pixWriteStreamPnm(FILE *fp, |
737 | | PIX *pix) |
738 | 0 | { |
739 | 0 | l_uint8 val8; |
740 | 0 | l_uint8 pel[4]; |
741 | 0 | l_uint16 val16; |
742 | 0 | l_int32 h, w, d, ds, i, j, wpls, bpl, filebpl, writeerror, maxval; |
743 | 0 | l_uint32 *pword, *datas, *lines; |
744 | 0 | PIX *pixs; |
745 | |
|
746 | 0 | if (!fp) |
747 | 0 | return ERROR_INT("fp not defined", __func__, 1); |
748 | 0 | if (!pix) |
749 | 0 | return ERROR_INT("pix not defined", __func__, 1); |
750 | | |
751 | 0 | pixGetDimensions(pix, &w, &h, &d); |
752 | 0 | if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 24 && d != 32) |
753 | 0 | return ERROR_INT("d not in {1,2,4,8,16,24,32}", __func__, 1); |
754 | 0 | if (d == 32 && pixGetSpp(pix) == 4) |
755 | 0 | return pixWriteStreamPam(fp, pix); |
756 | | |
757 | | /* If a colormap exists, remove and convert to grayscale or rgb */ |
758 | 0 | if (pixGetColormap(pix) != NULL) |
759 | 0 | pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); |
760 | 0 | else |
761 | 0 | pixs = pixClone(pix); |
762 | 0 | ds = pixGetDepth(pixs); |
763 | 0 | datas = pixGetData(pixs); |
764 | 0 | wpls = pixGetWpl(pixs); |
765 | |
|
766 | 0 | writeerror = 0; |
767 | |
|
768 | 0 | if (ds == 1) { /* binary */ |
769 | 0 | fprintf(fp, "P4\n# Raw PBM file written by leptonica " |
770 | 0 | "(www.leptonica.com)\n%d %d\n", w, h); |
771 | |
|
772 | 0 | bpl = (w + 7) / 8; |
773 | 0 | for (i = 0; i < h; i++) { |
774 | 0 | lines = datas + i * wpls; |
775 | 0 | for (j = 0; j < bpl; j++) { |
776 | 0 | val8 = GET_DATA_BYTE(lines, j); |
777 | 0 | fwrite(&val8, 1, 1, fp); |
778 | 0 | } |
779 | 0 | } |
780 | 0 | } else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) { /* grayscale */ |
781 | 0 | maxval = (1 << ds) - 1; |
782 | 0 | fprintf(fp, "P5\n# Raw PGM file written by leptonica " |
783 | 0 | "(www.leptonica.com)\n%d %d\n%d\n", w, h, maxval); |
784 | |
|
785 | 0 | if (ds != 16) { |
786 | 0 | for (i = 0; i < h; i++) { |
787 | 0 | lines = datas + i * wpls; |
788 | 0 | for (j = 0; j < w; j++) { |
789 | 0 | if (ds == 2) |
790 | 0 | val8 = GET_DATA_DIBIT(lines, j); |
791 | 0 | else if (ds == 4) |
792 | 0 | val8 = GET_DATA_QBIT(lines, j); |
793 | 0 | else /* ds == 8 */ |
794 | 0 | val8 = GET_DATA_BYTE(lines, j); |
795 | 0 | fwrite(&val8, 1, 1, fp); |
796 | 0 | } |
797 | 0 | } |
798 | 0 | } else { /* ds == 16 */ |
799 | 0 | for (i = 0; i < h; i++) { |
800 | 0 | lines = datas + i * wpls; |
801 | 0 | for (j = 0; j < w; j++) { |
802 | 0 | val16 = GET_DATA_TWO_BYTES(lines, j); |
803 | 0 | fwrite(&val16, 2, 1, fp); |
804 | 0 | } |
805 | 0 | } |
806 | 0 | } |
807 | 0 | } else { /* rgb color */ |
808 | 0 | fprintf(fp, "P6\n# Raw PPM file written by leptonica " |
809 | 0 | "(www.leptonica.com)\n%d %d\n255\n", w, h); |
810 | |
|
811 | 0 | if (d == 24) { /* packed, 3 bytes to a pixel */ |
812 | 0 | filebpl = 3 * w; |
813 | 0 | for (i = 0; i < h; i++) { /* write out each raster line */ |
814 | 0 | lines = datas + i * wpls; |
815 | 0 | if (fwrite(lines, 1, filebpl, fp) != filebpl) |
816 | 0 | writeerror = 1; |
817 | 0 | } |
818 | 0 | } else { /* 32 bpp rgb */ |
819 | 0 | for (i = 0; i < h; i++) { |
820 | 0 | lines = datas + i * wpls; |
821 | 0 | for (j = 0; j < wpls; j++) { |
822 | 0 | pword = lines + j; |
823 | 0 | pel[0] = GET_DATA_BYTE(pword, COLOR_RED); |
824 | 0 | pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN); |
825 | 0 | pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE); |
826 | 0 | if (fwrite(pel, 1, 3, fp) != 3) |
827 | 0 | writeerror = 1; |
828 | 0 | } |
829 | 0 | } |
830 | 0 | } |
831 | 0 | } |
832 | |
|
833 | 0 | pixDestroy(&pixs); |
834 | 0 | if (writeerror) |
835 | 0 | return ERROR_INT("image write fail", __func__, 1); |
836 | 0 | return 0; |
837 | 0 | } |
838 | | |
839 | | |
840 | | /*! |
841 | | * \brief pixWriteStreamAsciiPnm() |
842 | | * |
843 | | * \param[in] fp file stream opened for write |
844 | | * \param[in] pix |
845 | | * \return 0 if OK; 1 on error |
846 | | * |
847 | | * Writes "ASCII" format only: |
848 | | * 1 bpp --> pbm P1 |
849 | | * 2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm P2 |
850 | | * 2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm P3 |
851 | | */ |
852 | | l_ok |
853 | | pixWriteStreamAsciiPnm(FILE *fp, |
854 | | PIX *pix) |
855 | 0 | { |
856 | 0 | char buffer[256]; |
857 | 0 | l_uint8 cval[3]; |
858 | 0 | l_int32 h, w, d, ds, i, j, k, maxval, count; |
859 | 0 | l_uint32 val; |
860 | 0 | PIX *pixs; |
861 | |
|
862 | 0 | if (!fp) |
863 | 0 | return ERROR_INT("fp not defined", __func__, 1); |
864 | 0 | if (!pix) |
865 | 0 | return ERROR_INT("pix not defined", __func__, 1); |
866 | | |
867 | 0 | pixGetDimensions(pix, &w, &h, &d); |
868 | 0 | if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32) |
869 | 0 | return ERROR_INT("d not in {1,2,4,8,16,32}", __func__, 1); |
870 | | |
871 | | /* If a colormap exists, remove and convert to grayscale or rgb */ |
872 | 0 | if (pixGetColormap(pix) != NULL) |
873 | 0 | pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); |
874 | 0 | else |
875 | 0 | pixs = pixClone(pix); |
876 | 0 | ds = pixGetDepth(pixs); |
877 | |
|
878 | 0 | if (ds == 1) { /* binary */ |
879 | 0 | fprintf(fp, "P1\n# Ascii PBM file written by leptonica " |
880 | 0 | "(www.leptonica.com)\n%d %d\n", w, h); |
881 | |
|
882 | 0 | count = 0; |
883 | 0 | for (i = 0; i < h; i++) { |
884 | 0 | for (j = 0; j < w; j++) { |
885 | 0 | pixGetPixel(pixs, j, i, &val); |
886 | 0 | if (val == 0) |
887 | 0 | fputc('0', fp); |
888 | 0 | else /* val == 1 */ |
889 | 0 | fputc('1', fp); |
890 | 0 | fputc(' ', fp); |
891 | 0 | count += 2; |
892 | 0 | if (count >= 70) { |
893 | 0 | fputc('\n', fp); |
894 | 0 | count = 0; |
895 | 0 | } |
896 | 0 | } |
897 | 0 | } |
898 | 0 | } else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) { /* grayscale */ |
899 | 0 | maxval = (1 << ds) - 1; |
900 | 0 | fprintf(fp, "P2\n# Ascii PGM file written by leptonica " |
901 | 0 | "(www.leptonica.com)\n%d %d\n%d\n", w, h, maxval); |
902 | |
|
903 | 0 | count = 0; |
904 | 0 | for (i = 0; i < h; i++) { |
905 | 0 | for (j = 0; j < w; j++) { |
906 | 0 | pixGetPixel(pixs, j, i, &val); |
907 | 0 | if (ds == 2) { |
908 | 0 | snprintf(buffer, sizeof(buffer), "%1d ", val); |
909 | 0 | fwrite(buffer, 1, 2, fp); |
910 | 0 | count += 2; |
911 | 0 | } else if (ds == 4) { |
912 | 0 | snprintf(buffer, sizeof(buffer), "%2d ", val); |
913 | 0 | fwrite(buffer, 1, 3, fp); |
914 | 0 | count += 3; |
915 | 0 | } else if (ds == 8) { |
916 | 0 | snprintf(buffer, sizeof(buffer), "%3d ", val); |
917 | 0 | fwrite(buffer, 1, 4, fp); |
918 | 0 | count += 4; |
919 | 0 | } else { /* ds == 16 */ |
920 | 0 | snprintf(buffer, sizeof(buffer), "%5d ", val); |
921 | 0 | fwrite(buffer, 1, 6, fp); |
922 | 0 | count += 6; |
923 | 0 | } |
924 | 0 | if (count >= 60) { |
925 | 0 | fputc('\n', fp); |
926 | 0 | count = 0; |
927 | 0 | } |
928 | 0 | } |
929 | 0 | } |
930 | 0 | } else { /* rgb color */ |
931 | 0 | fprintf(fp, "P3\n# Ascii PPM file written by leptonica " |
932 | 0 | "(www.leptonica.com)\n%d %d\n255\n", w, h); |
933 | 0 | count = 0; |
934 | 0 | for (i = 0; i < h; i++) { |
935 | 0 | for (j = 0; j < w; j++) { |
936 | 0 | pixGetPixel(pixs, j, i, &val); |
937 | 0 | cval[0] = GET_DATA_BYTE(&val, COLOR_RED); |
938 | 0 | cval[1] = GET_DATA_BYTE(&val, COLOR_GREEN); |
939 | 0 | cval[2] = GET_DATA_BYTE(&val, COLOR_BLUE); |
940 | 0 | for (k = 0; k < 3; k++) { |
941 | 0 | snprintf(buffer, sizeof(buffer), "%3d ", cval[k]); |
942 | 0 | fwrite(buffer, 1, 4, fp); |
943 | 0 | count += 4; |
944 | 0 | if (count >= 60) { |
945 | 0 | fputc('\n', fp); |
946 | 0 | count = 0; |
947 | 0 | } |
948 | 0 | } |
949 | 0 | } |
950 | 0 | } |
951 | 0 | } |
952 | |
|
953 | 0 | pixDestroy(&pixs); |
954 | 0 | return 0; |
955 | 0 | } |
956 | | |
957 | | |
958 | | /*! |
959 | | * \brief pixWriteStreamPam() |
960 | | * |
961 | | * \param[in] fp file stream opened for write |
962 | | * \param[in] pix |
963 | | * \return 0 if OK; 1 on error |
964 | | * |
965 | | * <pre> |
966 | | * Notes: |
967 | | * (1) This writes arbitrary PAM (P7) packed format. |
968 | | * (2) 24 bpp rgb are not supported in leptonica, but this will |
969 | | * write them out as a packed array of bytes (3 to a pixel). |
970 | | * </pre> |
971 | | */ |
972 | | l_ok |
973 | | pixWriteStreamPam(FILE *fp, |
974 | | PIX *pix) |
975 | 0 | { |
976 | 0 | l_uint8 val8; |
977 | 0 | l_uint8 pel[8]; |
978 | 0 | l_uint16 val16; |
979 | 0 | l_int32 h, w, d, ds, i, j; |
980 | 0 | l_int32 wpls, spps, filebpl, writeerror, maxval; |
981 | 0 | l_uint32 *pword, *datas, *lines; |
982 | 0 | PIX *pixs; |
983 | |
|
984 | 0 | if (!fp) |
985 | 0 | return ERROR_INT("fp not defined", __func__, 1); |
986 | 0 | if (!pix) |
987 | 0 | return ERROR_INT("pix not defined", __func__, 1); |
988 | | |
989 | 0 | pixGetDimensions(pix, &w, &h, &d); |
990 | 0 | if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 24 && d != 32) |
991 | 0 | return ERROR_INT("d not in {1,2,4,8,16,24,32}", __func__, 1); |
992 | | |
993 | | /* If a colormap exists, remove and convert to grayscale or rgb */ |
994 | 0 | if (pixGetColormap(pix) != NULL) |
995 | 0 | pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); |
996 | 0 | else |
997 | 0 | pixs = pixClone(pix); |
998 | 0 | ds = pixGetDepth(pixs); |
999 | 0 | datas = pixGetData(pixs); |
1000 | 0 | wpls = pixGetWpl(pixs); |
1001 | 0 | spps = pixGetSpp(pixs); |
1002 | 0 | if (ds < 24) |
1003 | 0 | maxval = (1 << ds) - 1; |
1004 | 0 | else |
1005 | 0 | maxval = 255; |
1006 | |
|
1007 | 0 | writeerror = 0; |
1008 | 0 | fprintf(fp, "P7\n# Arbitrary PAM file written by leptonica " |
1009 | 0 | "(www.leptonica.com)\n"); |
1010 | 0 | fprintf(fp, "WIDTH %d\n", w); |
1011 | 0 | fprintf(fp, "HEIGHT %d\n", h); |
1012 | 0 | fprintf(fp, "DEPTH %d\n", spps); |
1013 | 0 | fprintf(fp, "MAXVAL %d\n", maxval); |
1014 | 0 | if (spps == 1 && ds == 1) |
1015 | 0 | fprintf(fp, "TUPLTYPE BLACKANDWHITE\n"); |
1016 | 0 | else if (spps == 1) |
1017 | 0 | fprintf(fp, "TUPLTYPE GRAYSCALE\n"); |
1018 | 0 | else if (spps == 3) |
1019 | 0 | fprintf(fp, "TUPLTYPE RGB\n"); |
1020 | 0 | else if (spps == 4) |
1021 | 0 | fprintf(fp, "TUPLTYPE RGB_ALPHA\n"); |
1022 | 0 | fprintf(fp, "ENDHDR\n"); |
1023 | |
|
1024 | 0 | switch (d) { |
1025 | 0 | case 1: |
1026 | 0 | for (i = 0; i < h; i++) { |
1027 | 0 | lines = datas + i * wpls; |
1028 | 0 | for (j = 0; j < w; j++) { |
1029 | 0 | val8 = GET_DATA_BIT(lines, j); |
1030 | 0 | val8 ^= 1; /* pam apparently uses white-is-1 photometry */ |
1031 | 0 | if (fwrite(&val8, 1, 1, fp) != 1) |
1032 | 0 | writeerror = 1; |
1033 | 0 | } |
1034 | 0 | } |
1035 | 0 | break; |
1036 | | |
1037 | 0 | case 2: |
1038 | 0 | for (i = 0; i < h; i++) { |
1039 | 0 | lines = datas + i * wpls; |
1040 | 0 | for (j = 0; j < w; j++) { |
1041 | 0 | val8 = GET_DATA_DIBIT(lines, j); |
1042 | 0 | if (fwrite(&val8, 1, 1, fp) != 1) |
1043 | 0 | writeerror = 1; |
1044 | 0 | } |
1045 | 0 | } |
1046 | 0 | break; |
1047 | | |
1048 | 0 | case 4: |
1049 | 0 | for (i = 0; i < h; i++) { |
1050 | 0 | lines = datas + i * wpls; |
1051 | 0 | for (j = 0; j < w; j++) { |
1052 | 0 | val8 = GET_DATA_QBIT(lines, j); |
1053 | 0 | if (fwrite(&val8, 1, 1, fp) != 1) |
1054 | 0 | writeerror = 1; |
1055 | 0 | } |
1056 | 0 | } |
1057 | 0 | break; |
1058 | | |
1059 | 0 | case 8: |
1060 | 0 | for (i = 0; i < h; i++) { |
1061 | 0 | lines = datas + i * wpls; |
1062 | 0 | for (j = 0; j < w; j++) { |
1063 | 0 | val8 = GET_DATA_BYTE(lines, j); |
1064 | 0 | if (fwrite(&val8, 1, 1, fp) != 1) |
1065 | 0 | writeerror = 1; |
1066 | 0 | } |
1067 | 0 | } |
1068 | 0 | break; |
1069 | | |
1070 | 0 | case 16: |
1071 | 0 | for (i = 0; i < h; i++) { |
1072 | 0 | lines = datas + i * wpls; |
1073 | 0 | for (j = 0; j < w; j++) { |
1074 | 0 | val16 = GET_DATA_TWO_BYTES(lines, j); |
1075 | 0 | if (fwrite(&val16, 2, 1, fp) != 1) |
1076 | 0 | writeerror = 1; |
1077 | 0 | } |
1078 | 0 | } |
1079 | 0 | break; |
1080 | | |
1081 | 0 | case 24: |
1082 | 0 | filebpl = 3 * w; |
1083 | 0 | for (i = 0; i < h; i++) { |
1084 | 0 | lines = datas + i * wpls; |
1085 | 0 | if (fwrite(lines, 1, filebpl, fp) != filebpl) |
1086 | 0 | writeerror = 1; |
1087 | 0 | } |
1088 | 0 | break; |
1089 | | |
1090 | 0 | case 32: |
1091 | 0 | switch (spps) { |
1092 | 0 | case 3: |
1093 | 0 | for (i = 0; i < h; i++) { |
1094 | 0 | lines = datas + i * wpls; |
1095 | 0 | for (j = 0; j < wpls; j++) { |
1096 | 0 | pword = lines + j; |
1097 | 0 | pel[0] = GET_DATA_BYTE(pword, COLOR_RED); |
1098 | 0 | pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN); |
1099 | 0 | pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE); |
1100 | 0 | if (fwrite(pel, 1, 3, fp) != 3) |
1101 | 0 | writeerror = 1; |
1102 | 0 | } |
1103 | 0 | } |
1104 | 0 | break; |
1105 | 0 | case 4: |
1106 | 0 | for (i = 0; i < h; i++) { |
1107 | 0 | lines = datas + i * wpls; |
1108 | 0 | for (j = 0; j < wpls; j++) { |
1109 | 0 | pword = lines + j; |
1110 | 0 | pel[0] = GET_DATA_BYTE(pword, COLOR_RED); |
1111 | 0 | pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN); |
1112 | 0 | pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE); |
1113 | 0 | pel[3] = GET_DATA_BYTE(pword, L_ALPHA_CHANNEL); |
1114 | 0 | if (fwrite(pel, 1, 4, fp) != 4) |
1115 | 0 | writeerror = 1; |
1116 | 0 | } |
1117 | 0 | } |
1118 | 0 | break; |
1119 | 0 | } |
1120 | 0 | break; |
1121 | 0 | } |
1122 | | |
1123 | 0 | pixDestroy(&pixs); |
1124 | 0 | if (writeerror) |
1125 | 0 | return ERROR_INT("image write fail", __func__, 1); |
1126 | 0 | return 0; |
1127 | 0 | } |
1128 | | |
1129 | | |
1130 | | /*---------------------------------------------------------------------* |
1131 | | * Read/write to memory * |
1132 | | *---------------------------------------------------------------------*/ |
1133 | | |
1134 | | /*! |
1135 | | * \brief pixReadMemPnm() |
1136 | | * |
1137 | | * \param[in] data const; pnm-encoded |
1138 | | * \param[in] size of data |
1139 | | * \return pix, or NULL on error |
1140 | | * |
1141 | | * <pre> |
1142 | | * Notes: |
1143 | | * (1) The %size byte of %data must be a null character. |
1144 | | * </pre> |
1145 | | */ |
1146 | | PIX * |
1147 | | pixReadMemPnm(const l_uint8 *data, |
1148 | | size_t size) |
1149 | 0 | { |
1150 | 0 | FILE *fp; |
1151 | 0 | PIX *pix; |
1152 | |
|
1153 | 0 | if (!data) |
1154 | 0 | return (PIX *)ERROR_PTR("data not defined", __func__, NULL); |
1155 | 0 | if ((fp = fopenReadFromMemory(data, size)) == NULL) |
1156 | 0 | return (PIX *)ERROR_PTR("stream not opened", __func__, NULL); |
1157 | 0 | pix = pixReadStreamPnm(fp); |
1158 | 0 | fclose(fp); |
1159 | 0 | if (!pix) L_ERROR("pix not read\n", __func__); |
1160 | 0 | return pix; |
1161 | 0 | } |
1162 | | |
1163 | | |
1164 | | /*! |
1165 | | * \brief readHeaderMemPnm() |
1166 | | * |
1167 | | * \param[in] data const; pnm-encoded |
1168 | | * \param[in] size of data |
1169 | | * \param[out] pw [optional] |
1170 | | * \param[out] ph [optional] |
1171 | | * \param[out] pd [optional] |
1172 | | * \param[out] ptype [optional] pnm type |
1173 | | * \param[out] pbps [optional] bits/sample |
1174 | | * \param[out] pspp [optional] samples/pixel |
1175 | | * \return 0 if OK, 1 on error |
1176 | | */ |
1177 | | l_ok |
1178 | | readHeaderMemPnm(const l_uint8 *data, |
1179 | | size_t size, |
1180 | | l_int32 *pw, |
1181 | | l_int32 *ph, |
1182 | | l_int32 *pd, |
1183 | | l_int32 *ptype, |
1184 | | l_int32 *pbps, |
1185 | | l_int32 *pspp) |
1186 | 0 | { |
1187 | 0 | l_int32 ret; |
1188 | 0 | FILE *fp; |
1189 | |
|
1190 | 0 | if (!data) |
1191 | 0 | return ERROR_INT("data not defined", __func__, 1); |
1192 | | |
1193 | 0 | if ((fp = fopenReadFromMemory(data, size)) == NULL) |
1194 | 0 | return ERROR_INT("stream not opened", __func__, 1); |
1195 | 0 | ret = freadHeaderPnm(fp, pw, ph, pd, ptype, pbps, pspp); |
1196 | 0 | fclose(fp); |
1197 | 0 | if (ret) |
1198 | 0 | return ERROR_INT("header data read failed", __func__, 1); |
1199 | 0 | return 0; |
1200 | 0 | } |
1201 | | |
1202 | | |
1203 | | /*! |
1204 | | * \brief pixWriteMemPnm() |
1205 | | * |
1206 | | * \param[out] pdata data of PNM image |
1207 | | * \param[out] psize size of returned data |
1208 | | * \param[in] pix |
1209 | | * \return 0 if OK, 1 on error |
1210 | | * |
1211 | | * <pre> |
1212 | | * Notes: |
1213 | | * (1) See pixWriteStreamPnm() for usage. This version writes to |
1214 | | * memory instead of to a file stream. |
1215 | | * </pre> |
1216 | | */ |
1217 | | l_ok |
1218 | | pixWriteMemPnm(l_uint8 **pdata, |
1219 | | size_t *psize, |
1220 | | PIX *pix) |
1221 | 0 | { |
1222 | 0 | l_int32 ret; |
1223 | 0 | FILE *fp; |
1224 | |
|
1225 | 0 | if (pdata) *pdata = NULL; |
1226 | 0 | if (psize) *psize = 0; |
1227 | 0 | if (!pdata) |
1228 | 0 | return ERROR_INT("&data not defined", __func__, 1 ); |
1229 | 0 | if (!psize) |
1230 | 0 | return ERROR_INT("&size not defined", __func__, 1 ); |
1231 | 0 | if (!pix) |
1232 | 0 | return ERROR_INT("&pix not defined", __func__, 1 ); |
1233 | | |
1234 | 0 | #if HAVE_FMEMOPEN |
1235 | 0 | if ((fp = open_memstream((char **)pdata, psize)) == NULL) |
1236 | 0 | return ERROR_INT("stream not opened", __func__, 1); |
1237 | 0 | ret = pixWriteStreamPnm(fp, pix); |
1238 | 0 | fputc('\0', fp); |
1239 | 0 | fclose(fp); |
1240 | 0 | if (*psize > 0) *psize = *psize - 1; |
1241 | | #else |
1242 | | L_INFO("no fmemopen API --> work-around: write to temp file\n", __func__); |
1243 | | #ifdef _WIN32 |
1244 | | if ((fp = fopenWriteWinTempfile()) == NULL) |
1245 | | return ERROR_INT("tmpfile stream not opened", __func__, 1); |
1246 | | #else |
1247 | | if ((fp = tmpfile()) == NULL) |
1248 | | return ERROR_INT("tmpfile stream not opened", __func__, 1); |
1249 | | #endif /* _WIN32 */ |
1250 | | ret = pixWriteStreamPnm(fp, pix); |
1251 | | rewind(fp); |
1252 | | *pdata = l_binaryReadStream(fp, psize); |
1253 | | fclose(fp); |
1254 | | #endif /* HAVE_FMEMOPEN */ |
1255 | 0 | return ret; |
1256 | 0 | } |
1257 | | |
1258 | | |
1259 | | /*! |
1260 | | * \brief pixWriteMemPam() |
1261 | | * |
1262 | | * \param[out] pdata data of PAM image |
1263 | | * \param[out] psize size of returned data |
1264 | | * \param[in] pix |
1265 | | * \return 0 if OK, 1 on error |
1266 | | * |
1267 | | * <pre> |
1268 | | * Notes: |
1269 | | * (1) See pixWriteStreamPnm() for usage. This version writes to |
1270 | | * memory instead of to a file stream. |
1271 | | * </pre> |
1272 | | */ |
1273 | | l_ok |
1274 | | pixWriteMemPam(l_uint8 **pdata, |
1275 | | size_t *psize, |
1276 | | PIX *pix) |
1277 | 0 | { |
1278 | 0 | l_int32 ret; |
1279 | 0 | FILE *fp; |
1280 | |
|
1281 | 0 | if (pdata) *pdata = NULL; |
1282 | 0 | if (psize) *psize = 0; |
1283 | 0 | if (!pdata) |
1284 | 0 | return ERROR_INT("&data not defined", __func__, 1 ); |
1285 | 0 | if (!psize) |
1286 | 0 | return ERROR_INT("&size not defined", __func__, 1 ); |
1287 | 0 | if (!pix) |
1288 | 0 | return ERROR_INT("&pix not defined", __func__, 1 ); |
1289 | | |
1290 | 0 | #if HAVE_FMEMOPEN |
1291 | 0 | if ((fp = open_memstream((char **)pdata, psize)) == NULL) |
1292 | 0 | return ERROR_INT("stream not opened", __func__, 1); |
1293 | 0 | ret = pixWriteStreamPam(fp, pix); |
1294 | 0 | fputc('\0', fp); |
1295 | 0 | fclose(fp); |
1296 | 0 | if (*psize > 0) *psize = *psize - 1; |
1297 | | #else |
1298 | | L_INFO("no fmemopen API --> work-around: write to temp file\n", __func__); |
1299 | | #ifdef _WIN32 |
1300 | | if ((fp = fopenWriteWinTempfile()) == NULL) |
1301 | | return ERROR_INT("tmpfile stream not opened", __func__, 1); |
1302 | | #else |
1303 | | if ((fp = tmpfile()) == NULL) |
1304 | | return ERROR_INT("tmpfile stream not opened", __func__, 1); |
1305 | | #endif /* _WIN32 */ |
1306 | | ret = pixWriteStreamPam(fp, pix); |
1307 | | rewind(fp); |
1308 | | *pdata = l_binaryReadStream(fp, psize); |
1309 | | fclose(fp); |
1310 | | #endif /* HAVE_FMEMOPEN */ |
1311 | 0 | return ret; |
1312 | 0 | } |
1313 | | |
1314 | | |
1315 | | /*--------------------------------------------------------------------* |
1316 | | * Static helpers * |
1317 | | *--------------------------------------------------------------------*/ |
1318 | | /*! |
1319 | | * \brief pnmReadNextAsciiValue() |
1320 | | * |
1321 | | * Return: 0 if OK, 1 on error or EOF. |
1322 | | * |
1323 | | * Notes: |
1324 | | * (1) This reads the next sample value in ASCII from the file. |
1325 | | */ |
1326 | | static l_int32 |
1327 | | pnmReadNextAsciiValue(FILE *fp, |
1328 | | l_int32 *pval) |
1329 | 0 | { |
1330 | 0 | l_int32 ignore; |
1331 | |
|
1332 | 0 | if (!pval) |
1333 | 0 | return ERROR_INT("&val not defined", __func__, 1); |
1334 | 0 | *pval = 0; |
1335 | 0 | if (!fp) |
1336 | 0 | return ERROR_INT("stream not open", __func__, 1); |
1337 | | |
1338 | 0 | if (EOF == fscanf(fp, " ")) |
1339 | 0 | return 1; |
1340 | 0 | if (1 != fscanf(fp, "%d", pval)) |
1341 | 0 | return 1; |
1342 | | |
1343 | 0 | return 0; |
1344 | 0 | } |
1345 | | |
1346 | | |
1347 | | /*! |
1348 | | * \brief pnmReadNextNumber() |
1349 | | * |
1350 | | * \param[in] fp file stream |
1351 | | * \param[out] pval value as an integer |
1352 | | * \return 0 if OK, 1 on error or EOF. |
1353 | | * |
1354 | | * <pre> |
1355 | | * Notes: |
1356 | | * (1) This reads the next set of numeric chars, returning |
1357 | | * the value and swallowing initial whitespaces and ONE |
1358 | | * trailing whitespace character. This is needed to read |
1359 | | * the maxval in the header, which precedes the binary data. |
1360 | | * </pre> |
1361 | | */ |
1362 | | static l_int32 |
1363 | | pnmReadNextNumber(FILE *fp, |
1364 | | l_int32 *pval) |
1365 | 0 | { |
1366 | 0 | char buf[8]; |
1367 | 0 | l_int32 i, c, foundws; |
1368 | |
|
1369 | 0 | if (!pval) |
1370 | 0 | return ERROR_INT("&val not defined", __func__, 1); |
1371 | 0 | *pval = 0; |
1372 | 0 | if (!fp) |
1373 | 0 | return ERROR_INT("stream not open", __func__, 1); |
1374 | | |
1375 | | /* Swallow whitespace */ |
1376 | 0 | if (fscanf(fp, " ") == EOF) |
1377 | 0 | return ERROR_INT("end of file reached", __func__, 1); |
1378 | | |
1379 | | /* The ASCII characters for the number are followed by exactly |
1380 | | * one whitespace character. */ |
1381 | 0 | foundws = FALSE; |
1382 | 0 | for (i = 0; i < 8; i++) |
1383 | 0 | buf[i] = '\0'; |
1384 | 0 | for (i = 0; i < 8; i++) { |
1385 | 0 | if ((c = fgetc(fp)) == EOF) |
1386 | 0 | return ERROR_INT("end of file reached", __func__, 1); |
1387 | 0 | if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { |
1388 | 0 | foundws = TRUE; |
1389 | 0 | buf[i] = '\n'; |
1390 | 0 | break; |
1391 | 0 | } |
1392 | 0 | if (!isdigit(c)) |
1393 | 0 | return ERROR_INT("char read is not a digit", __func__, 1); |
1394 | 0 | buf[i] = c; |
1395 | 0 | } |
1396 | 0 | if (!foundws) |
1397 | 0 | return ERROR_INT("no whitespace found", __func__, 1); |
1398 | 0 | if (sscanf(buf, "%d", pval) != 1) |
1399 | 0 | return ERROR_INT("invalid read", __func__, 1); |
1400 | 0 | return 0; |
1401 | 0 | } |
1402 | | |
1403 | | /*! |
1404 | | * \brief pnmReadNextString() |
1405 | | * |
1406 | | * \param[in] fp file stream |
1407 | | * \param[out] buff pointer to the string buffer |
1408 | | * \param[in] size max. number of characters in buffer |
1409 | | * \return 0 if OK, 1 on error or EOF. |
1410 | | * |
1411 | | * <pre> |
1412 | | * Notes: |
1413 | | * (1) This reads the next set of alphanumeric chars, returning the string. |
1414 | | * This is needed to read header lines, which precede the P7 |
1415 | | * format binary data. |
1416 | | * </pre> |
1417 | | */ |
1418 | | static l_int32 |
1419 | | pnmReadNextString(FILE *fp, |
1420 | | char *buff, |
1421 | | l_int32 size) |
1422 | 0 | { |
1423 | 0 | char fmtString[7]; /* must contain "%9999s" [*] */ |
1424 | |
|
1425 | 0 | if (!buff) |
1426 | 0 | return ERROR_INT("buff not defined", __func__, 1); |
1427 | 0 | *buff = '\0'; |
1428 | 0 | if (size > 10000) /* size - 1 has > 4 digits [*] */ |
1429 | 0 | return ERROR_INT("size is too big", __func__, 1); |
1430 | 0 | if (size <= 0) |
1431 | 0 | return ERROR_INT("size is too small", __func__, 1); |
1432 | 0 | if (!fp) |
1433 | 0 | return ERROR_INT("stream not open", __func__, 1); |
1434 | | |
1435 | | /* Skip whitespace */ |
1436 | 0 | if (fscanf(fp, " ") == EOF) |
1437 | 0 | return 1; |
1438 | | |
1439 | | /* Comment lines are allowed to appear anywhere in the header lines */ |
1440 | 0 | if (pnmSkipCommentLines(fp)) |
1441 | 0 | return ERROR_INT("end of file reached", __func__, 1); |
1442 | | |
1443 | 0 | snprintf(fmtString, 7, "%%%ds", size - 1); |
1444 | 0 | if (fscanf(fp, fmtString, buff) == EOF) |
1445 | 0 | return 1; |
1446 | | |
1447 | 0 | return 0; |
1448 | 0 | } |
1449 | | |
1450 | | |
1451 | | /*! |
1452 | | * \brief pnmSkipCommentLines() |
1453 | | * |
1454 | | * Return: 0 if OK, 1 on error or EOF |
1455 | | * |
1456 | | * Notes: |
1457 | | * (1) Comment lines begin with '#' |
1458 | | * (2) Usage: caller should check return value for EOF |
1459 | | * (3) The previous implementation used fseek(fp, -1L, SEEK_CUR) |
1460 | | * to back up one character, which doesn't work with stdin. |
1461 | | */ |
1462 | | static l_int32 |
1463 | | pnmSkipCommentLines(FILE *fp) |
1464 | 0 | { |
1465 | 0 | l_int32 i; |
1466 | 0 | char c; |
1467 | |
|
1468 | 0 | if (!fp) |
1469 | 0 | return ERROR_INT("stream not open", __func__, 1); |
1470 | 0 | while ((i = fscanf(fp, "#%c", &c))) { |
1471 | 0 | if (i == EOF) return 1; |
1472 | 0 | while (c != '\n') { |
1473 | 0 | if (fscanf(fp, "%c", &c) == EOF) |
1474 | 0 | return 1; |
1475 | 0 | } |
1476 | 0 | } |
1477 | 0 | return 0; |
1478 | 0 | } |
1479 | | |
1480 | | |
1481 | | /* --------------------------------------------*/ |
1482 | | #endif /* USE_PNMIO */ |
1483 | | /* --------------------------------------------*/ |