1 /*====================================================================*
2 - Copyright (C) 2001 Leptonica. All rights reserved.
3 - This software is distributed in the hope that it will be
4 - useful, but with NO WARRANTY OF ANY KIND.
5 - No author or distributor accepts responsibility to anyone for the
6 - consequences of using this software, or for whether it serves any
7 - particular purpose or works at all, unless he or she says so in
8 - writing. Everyone is granted permission to copy, modify and
9 - redistribute this source code, for commercial or non-commercial
10 - purposes, with the following restrictions: (1) the origin of this
11 - source code must not be misrepresented; (2) modified versions must
12 - be plainly marked as such; and (3) this notice may not be removed
13 - or altered from any source or modified source distribution.
14 *====================================================================*/
15
16 /*
17 * pnmio.c
18 *
19 * Stream interface
20 * PIX *pixReadStreamPnm()
21 * l_int32 pixWriteStreamPnm()
22 * l_int32 pixWriteStreamAsciiPnm()
23 *
24 * Read/write to memory [not on windows]
25 * PIX *pixReadMemPnm()
26 * l_int32 pixWriteMemPnm()
27 *
28 * Local helpers
29 * static l_int32 pnmReadNextAsciiValue();
30 * static l_int32 pnmSkipCommentLines();
31 *
32 * These are here by popular demand, with the help of Mattias
33 * Kregert (mattias@kregert.se), who provided the first implementation.
34 *
35 * The pnm formats are exceedingly simple, because they have
36 * no compression and no colormaps. They support images that
37 * are 1 bpp; 2, 4, 8 and 16 bpp grayscale; and rgb.
38 *
39 * The original pnm formats ("ascii") are included for completeness,
40 * but their use is deprecated for all but tiny iconic images.
41 * They are extremely wasteful of memory; for example, the P1 binary
42 * ascii format is 16 times as big as the packed uncompressed
43 * format, because 2 characters are used to represent every bit
44 * (pixel) in the image. Reading is slow because we check for extra
45 * white space and EOL at every sample value.
46 *
47 * The packed pnm formats ("raw") give file sizes similar to
48 * bmp files, which are uncompressed packed. However, bmp
49 * are more flexible, because they can support colormaps.
50 *
51 * We don't differentiate between the different types ("pbm",
52 * "pgm", "ppm") at the interface level, because this is really a
53 * "distinction without a difference." You read a file, you get
54 * the appropriate Pix. You write a file from a Pix, you get the
55 * appropriate type of file. If there is a colormap on the Pix,
56 * and the Pix is more than 1 bpp, you get either an 8 bpp pgm
57 * or a 24 bpp RGB pnm, depending on whether the colormap colors
58 * are gray or rgb, respectively.
59 *
60 * This follows the general policy that the I/O routines don't
61 * make decisions about the content of the image -- you do that
62 * with image processing before you write it out to file.
63 * The I/O routines just try to make the closest connection
64 * possible between the file and the Pix in memory.
65 */
66
67 #include <stdio.h>
68 #include <string.h>
69 #include <stdlib.h>
70 #include "allheaders.h"
71
72 /* --------------------------------------------*/
73 #if USE_PNMIO /* defined in environ.h */
74 /* --------------------------------------------*/
75
76
77 static l_int32 pnmReadNextAsciiValue(FILE *fp, l_int32 *pval);
78 static l_int32 pnmSkipCommentLines(FILE *fp);
79
80 /* a sanity check on the size read from file */
81 static const l_int32 MAX_PNM_WIDTH = 100000;
82 static const l_int32 MAX_PNM_HEIGHT = 100000;
83
84
85 /*--------------------------------------------------------------------*
86 * Stream interface *
87 *--------------------------------------------------------------------*/
88 /*!
89 * pixReadStreamPnm()
90 *
91 * Input: stream opened for read
92 * Return: pix, or null on error
93 */
94 PIX *
pixReadStreamPnm(FILE * fp)95 pixReadStreamPnm(FILE *fp)
96 {
97 l_uint8 val8, rval8, gval8, bval8;
98 l_uint16 val16;
99 l_int32 w, h, d, bpl, wpl, i, j, type;
100 l_int32 maxval, val, rval, gval, bval;
101 l_uint32 rgbval;
102 l_uint32 *line, *data;
103 PIX *pix;
104
105 PROCNAME("pixReadStreamPnm");
106
107 if (!fp)
108 return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
109
110 fscanf(fp, "P%d\n", &type);
111 if (type < 1 || type > 6)
112 return (PIX *)ERROR_PTR("invalid pnm file", procName, NULL);
113
114 if (pnmSkipCommentLines(fp))
115 return (PIX *)ERROR_PTR("no data in file", procName, NULL);
116
117 fscanf(fp, "%d %d\n", &w, &h);
118 if (w <= 0 || h <= 0 || w > MAX_PNM_WIDTH || h > MAX_PNM_HEIGHT)
119 return (PIX *)ERROR_PTR("invalid sizes", procName, NULL);
120
121 /* Get depth of pix */
122 if (type == 1 || type == 4)
123 d = 1;
124 else if (type == 2 || type == 5) {
125 fscanf(fp, "%d\n", &maxval);
126 if (maxval == 3)
127 d = 2;
128 else if (maxval == 15)
129 d = 4;
130 else if (maxval == 255)
131 d = 8;
132 else if (maxval == 0xffff)
133 d = 16;
134 else {
135 fprintf(stderr, "maxval = %d\n", maxval);
136 return (PIX *)ERROR_PTR("invalid maxval", procName, NULL);
137 }
138 }
139 else { /* type == 3 || type == 6; this is rgb */
140 fscanf(fp, "%d\n", &maxval);
141 if (maxval != 255)
142 L_WARNING_INT("unexpected maxval = %d", procName, maxval);
143 d = 32;
144 }
145
146 if ((pix = pixCreate(w, h, d)) == NULL)
147 return (PIX *)ERROR_PTR( "pix not made", procName, NULL);
148 data = pixGetData(pix);
149 wpl = pixGetWpl(pix);
150
151 /* Old "ascii" format */
152 if (type <= 3) {
153 for (i = 0; i < h; i++) {
154 for (j = 0; j < w; j++) {
155 if (type == 1 || type == 2) {
156 if (pnmReadNextAsciiValue(fp, &val))
157 return (PIX *)ERROR_PTR( "read abend", procName, pix);
158 pixSetPixel(pix, j, i, val);
159 }
160 else { /* type == 3 */
161 if (pnmReadNextAsciiValue(fp, &rval))
162 return (PIX *)ERROR_PTR( "read abend", procName, pix);
163 if (pnmReadNextAsciiValue(fp, &gval))
164 return (PIX *)ERROR_PTR( "read abend", procName, pix);
165 if (pnmReadNextAsciiValue(fp, &bval))
166 return (PIX *)ERROR_PTR( "read abend", procName, pix);
167 composeRGBPixel(rval, gval, bval, &rgbval);
168 pixSetPixel(pix, j, i, rgbval);
169 }
170 }
171 }
172 return pix;
173 }
174
175 /* "raw" format for 1 bpp */
176 if (type == 4) {
177 bpl = (d * w + 7) / 8;
178 for (i = 0; i < h; i++) {
179 line = data + i * wpl;
180 for (j = 0; j < bpl; j++) {
181 fread(&val8, 1, 1, fp);
182 SET_DATA_BYTE(line, j, val8);
183 }
184 }
185 return pix;
186 }
187
188 /* "raw" format for grayscale */
189 if (type == 5) {
190 bpl = (d * w + 7) / 8;
191 for (i = 0; i < h; i++) {
192 line = data + i * wpl;
193 if (d != 16) {
194 for (j = 0; j < w; j++) {
195 fread(&val8, 1, 1, fp);
196 if (d == 2)
197 SET_DATA_DIBIT(line, j, val8);
198 else if (d == 4)
199 SET_DATA_QBIT(line, j, val8);
200 else /* d == 8 */
201 SET_DATA_BYTE(line, j, val8);
202 }
203 }
204 else { /* d == 16 */
205 for (j = 0; j < w; j++) {
206 fread(&val16, 2, 1, fp);
207 SET_DATA_TWO_BYTES(line, j, val16);
208 }
209 }
210 }
211 return pix;
212 }
213
214 /* "raw" format, type == 6; rgb */
215 for (i = 0; i < h; i++) {
216 line = data + i * wpl;
217 for (j = 0; j < wpl; j++) {
218 fread(&rval8, 1, 1, fp);
219 fread(&gval8, 1, 1, fp);
220 fread(&bval8, 1, 1, fp);
221 composeRGBPixel(rval8, gval8, bval8, &rgbval);
222 line[j] = rgbval;
223 }
224 }
225 return pix;
226 }
227
228
229 /*!
230 * pixWriteStreamPnm()
231 *
232 * Input: stream opened for write
233 * pix
234 * Return: 0 if OK; 1 on error
235 *
236 * Notes:
237 * (1) This writes "raw" packed format only:
238 * 1 bpp --> pbm (P4)
239 * 2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm (P5)
240 * 2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm (P6)
241 * (2) 24 bpp rgb are not supported in leptonica, but this will
242 * write them out as a packed array of bytes (3 to a pixel).
243 */
244 l_int32
pixWriteStreamPnm(FILE * fp,PIX * pix)245 pixWriteStreamPnm(FILE *fp,
246 PIX *pix)
247 {
248 l_uint8 val8;
249 l_uint8 pel[4];
250 l_uint16 val16;
251 l_int32 h, w, d, ds, i, j, wpls, bpl, filebpl, writeerror, maxval;
252 l_uint32 *pword, *datas, *lines;
253 PIX *pixs;
254
255 PROCNAME("pixWriteStreamPnm");
256
257 if (!fp)
258 return ERROR_INT("fp not defined", procName, 1);
259 if (!pix)
260 return ERROR_INT("pix not defined", procName, 1);
261
262 pixGetDimensions(pix, &w, &h, &d);
263 if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 24 && d != 32)
264 return ERROR_INT("d not in {1,2,4,8,16,24,32}", procName, 1);
265
266 /* If a colormap exists, remove and convert to grayscale or rgb */
267 if (pixGetColormap(pix) != NULL)
268 pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
269 else
270 pixs = pixClone(pix);
271 ds = pixGetDepth(pixs);
272 datas = pixGetData(pixs);
273 wpls = pixGetWpl(pixs);
274
275 writeerror = 0;
276 if (ds == 1) { /* binary */
277 fprintf(fp, "P4\n# Raw PBM file written by leptonlib (www.leptonica.com)\n%d %d\n", w, h);
278
279 bpl = (w + 7) / 8;
280 for (i = 0; i < h; i++) {
281 lines = datas + i * wpls;
282 for (j = 0; j < bpl; j++) {
283 val8 = GET_DATA_BYTE(lines, j);
284 fwrite(&val8, 1, 1, fp);
285 }
286 }
287 }
288 else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) { /* grayscale */
289 maxval = (1 << ds) - 1;
290 fprintf(fp, "P5\n# Raw PGM file written by leptonlib (www.leptonica.com)\n%d %d\n%d\n", w, h, maxval);
291
292 if (ds != 16) {
293 for (i = 0; i < h; i++) {
294 lines = datas + i * wpls;
295 for (j = 0; j < w; j++) {
296 if (ds == 2)
297 val8 = GET_DATA_DIBIT(lines, j);
298 else if (ds == 4)
299 val8 = GET_DATA_QBIT(lines, j);
300 else /* ds == 8 */
301 val8 = GET_DATA_BYTE(lines, j);
302 fwrite(&val8, 1, 1, fp);
303 }
304 }
305 }
306 else { /* ds == 16 */
307 for (i = 0; i < h; i++) {
308 lines = datas + i * wpls;
309 for (j = 0; j < w; j++) {
310 val16 = GET_DATA_TWO_BYTES(lines, j);
311 fwrite(&val16, 2, 1, fp);
312 }
313 }
314 }
315 }
316 else { /* rgb color */
317 fprintf(fp, "P6\n# Raw PPM file written by leptonlib (www.leptonica.com)\n%d %d\n255\n", w, h);
318
319 if (d == 24) { /* packed, 3 bytes to a pixel */
320 filebpl = 3 * w;
321 for (i = 0; i < h; i++) { /* write out each raster line */
322 lines = datas + i * wpls;
323 if (fwrite(lines, 1, filebpl, fp) != filebpl)
324 writeerror = 1;
325 }
326 }
327 else { /* 32 bpp rgb */
328 for (i = 0; i < h; i++) {
329 lines = datas + i * wpls;
330 for (j = 0; j < wpls; j++) {
331 pword = lines + j;
332 pel[0] = *((l_uint8 *)pword + 3); /* red */
333 pel[1] = *((l_uint8 *)pword + 2); /* green */
334 pel[2] = *((l_uint8 *)pword + 1); /* blue */
335 if (fwrite(&pel, 1, 3, fp) != 3)
336 writeerror = 1;
337 }
338 }
339 }
340 }
341
342 pixDestroy(&pixs);
343 if (writeerror)
344 return ERROR_INT("image write fail", procName, 1);
345 return 0;
346 }
347
348
349 /*!
350 * pixWriteStreamAsciiPnm()
351 *
352 * Input: stream opened for write
353 * pix
354 * Return: 0 if OK; 1 on error
355 *
356 * Writes "ascii" format only:
357 * 1 bpp --> pbm (P1)
358 * 2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm (P2)
359 * 2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm (P3)
360 */
361 l_int32
pixWriteStreamAsciiPnm(FILE * fp,PIX * pix)362 pixWriteStreamAsciiPnm(FILE *fp,
363 PIX *pix)
364 {
365 char buffer[256];
366 l_uint8 cval[3];
367 l_int32 h, w, d, ds, i, j, k, maxval, count;
368 l_uint32 val;
369 PIX *pixs;
370
371 PROCNAME("pixWriteStreamAsciiPnm");
372
373 if (!fp)
374 return ERROR_INT("fp not defined", procName, 1);
375 if (!pix)
376 return ERROR_INT("pix not defined", procName, 1);
377
378 pixGetDimensions(pix, &w, &h, &d);
379 if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
380 return ERROR_INT("d not in {1,2,4,8,16,32}", procName, 1);
381
382 /* If a colormap exists, remove and convert to grayscale or rgb */
383 if (pixGetColormap(pix) != NULL)
384 pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
385 else
386 pixs = pixClone(pix);
387 ds = pixGetDepth(pixs);
388
389 if (ds == 1) { /* binary */
390 fprintf(fp, "P1\n# Ascii PBM file written by leptonlib (www.leptonica.com)\n%d %d\n", w, h);
391
392 count = 0;
393 for (i = 0; i < h; i++) {
394 for (j = 0; j < w; j++) {
395 pixGetPixel(pixs, j, i, &val);
396 if (val == 0)
397 fputc('0', fp);
398 else /* val == 1 */
399 fputc('1', fp);
400 fputc(' ', fp);
401 count += 2;
402 if (count >= 70)
403 fputc('\n', fp);
404 }
405 }
406 }
407 else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) { /* grayscale */
408 maxval = (1 << ds) - 1;
409 fprintf(fp, "P2\n# Ascii PGM file written by leptonlib (www.leptonica.com)\n%d %d\n%d\n", w, h, maxval);
410
411 count = 0;
412 for (i = 0; i < h; i++) {
413 for (j = 0; j < w; j++) {
414 pixGetPixel(pixs, j, i, &val);
415 if (ds == 2) {
416 sprintf(buffer, "%1d ", val);
417 fwrite(buffer, 1, 2, fp);
418 count += 2;
419 }
420 else if (ds == 4) {
421 sprintf(buffer, "%2d ", val);
422 fwrite(buffer, 1, 3, fp);
423 count += 3;
424 }
425 else if (ds == 8) {
426 sprintf(buffer, "%3d ", val);
427 fwrite(buffer, 1, 4, fp);
428 count += 4;
429 }
430 else { /* ds == 16 */
431 sprintf(buffer, "%5d ", val);
432 fwrite(buffer, 1, 6, fp);
433 count += 6;
434 }
435 if (count >= 60) {
436 fputc('\n', fp);
437 count = 0;
438 }
439 }
440 }
441 }
442 else { /* rgb color */
443 fprintf(fp, "P3\n# Ascii PPM file written by leptonlib (www.leptonica.com)\n%d %d\n255\n", w, h);
444
445 count = 0;
446 for (i = 0; i < h; i++) {
447 for (j = 0; j < w; j++) {
448 pixGetPixel(pixs, j, i, &val);
449 cval[0] = GET_DATA_BYTE(&val, COLOR_RED);
450 cval[1] = GET_DATA_BYTE(&val, COLOR_GREEN);
451 cval[2] = GET_DATA_BYTE(&val, COLOR_BLUE);
452 for (k = 0; k < 3; k++) {
453 sprintf(buffer, "%3d ", cval[k]);
454 fwrite(buffer, 1, 4, fp);
455 count += 4;
456 if (count >= 60) {
457 fputc('\n', fp);
458 count = 0;
459 }
460 }
461 }
462 }
463 }
464
465 pixDestroy(&pixs);
466 return 0;
467 }
468
469
470 /*---------------------------------------------------------------------*
471 * Read/write to memory *
472 *---------------------------------------------------------------------*/
473 #ifdef HAVE_CONFIG_H
474 #include "config_auto.h"
475 #endif /* HAVE_CONFIG_H */
476
477 #if HAVE_FMEMOPEN
478
479 #include "_stdio.h"
480 /* extern FILE *open_memstream(char **data, size_t *size); */
481 /* extern FILE *fmemopen(void *data, size_t size, const char *mode); */
482
483 /*!
484 * pixReadMemPnm()
485 *
486 * Input: cdata (const; pnm-encoded)
487 * size (of data)
488 * Return: pix, or null on error
489 *
490 * Notes:
491 * (1) The @size byte of @data must be a null character.
492 */
493 PIX *
pixReadMemPnm(const l_uint8 * cdata,size_t size)494 pixReadMemPnm(const l_uint8 *cdata,
495 size_t size)
496 {
497 l_uint8 *data;
498 FILE *fp;
499 PIX *pix;
500
501 PROCNAME("pixReadMemPnm");
502
503 if (!cdata)
504 return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
505
506 data = (l_uint8 *)cdata; /* we're really not going to change this */
507 if ((fp = fmemopen(data, size, "r")) == NULL)
508 return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
509 pix = pixReadStreamPnm(fp);
510 fclose(fp);
511 return pix;
512 }
513
514
515 /*!
516 * pixWriteMemPnm()
517 *
518 * Input: &data (<return> data of tiff compressed image)
519 * &size (<return> size of returned data)
520 * pix
521 * Return: 0 if OK, 1 on error
522 *
523 * Notes:
524 * (1) See pixWriteStreamPnm() for usage. This version writes to
525 * memory instead of to a file stream.
526 */
527 l_int32
pixWriteMemPnm(l_uint8 ** pdata,size_t * psize,PIX * pix)528 pixWriteMemPnm(l_uint8 **pdata,
529 size_t *psize,
530 PIX *pix)
531 {
532 l_int32 ret;
533 FILE *fp;
534
535 PROCNAME("pixWriteMemPnm");
536
537 if (!pdata)
538 return ERROR_INT("&data not defined", procName, 1 );
539 if (!psize)
540 return ERROR_INT("&size not defined", procName, 1 );
541 if (!pix)
542 return ERROR_INT("&pix not defined", procName, 1 );
543
544 if ((fp = open_memstream((char **)pdata, psize)) == NULL)
545 return ERROR_INT("stream not opened", procName, 1);
546 ret = pixWriteStreamPnm(fp, pix);
547 fclose(fp);
548 return ret;
549 }
550
551 #else
552
553 PIX *
pixReadMemPnm(const l_uint8 * cdata,size_t size)554 pixReadMemPnm(const l_uint8 *cdata,
555 size_t size)
556 {
557 return (PIX *)ERROR_PTR(
558 "pnm read from memory not implemented on this platform",
559 "pixReadMemPnm", NULL);
560 }
561
562
563 l_int32
pixWriteMemPnm(l_uint8 ** pdata,size_t * psize,PIX * pix)564 pixWriteMemPnm(l_uint8 **pdata,
565 size_t *psize,
566 PIX *pix)
567 {
568 return ERROR_INT(
569 "pnm write to memory not implemented on this platform",
570 "pixWriteMemPnm", 1);
571 }
572
573 #endif /* HAVE_FMEMOPEN */
574
575
576 /*--------------------------------------------------------------------*
577 * Static helpers *
578 *--------------------------------------------------------------------*/
579 /*!
580 * pnmReadNextAsciiValue()
581 *
582 * Return: 0 if OK, 1 on error or EOF.
583 *
584 * Notes:
585 * (1) This reads the next sample value in ascii from the the file.
586 */
587 static l_int32
pnmReadNextAsciiValue(FILE * fp,l_int32 * pval)588 pnmReadNextAsciiValue(FILE *fp,
589 l_int32 *pval)
590 {
591 l_int32 c;
592
593 PROCNAME("pnmReadNextAsciiValue");
594
595 if (!fp)
596 return ERROR_INT("stream not open", procName, 1);
597 if (!pval)
598 return ERROR_INT("&val not defined", procName, 1);
599 *pval = 0;
600 do { /* skip whitespace */
601 if ((c = fgetc(fp)) == EOF)
602 return 1;
603 } while (c == ' ' || c == '\t' || c == '\n' || c == '\r');
604
605 fseek(fp, -1L, SEEK_CUR); /* back up one byte */
606 fscanf(fp, "%d", pval);
607 return 0;
608 }
609
610
611 /*!
612 * pnmSkipCommentLines()
613 *
614 * Return: 0 if OK, 1 on error or EOF
615 *
616 * Notes:
617 * (1) Comment lines begin with '#'
618 * (2) Usage: caller should check return value for EOF
619 */
620 static l_int32
pnmSkipCommentLines(FILE * fp)621 pnmSkipCommentLines(FILE *fp)
622 {
623 l_int32 c;
624
625 PROCNAME("pnmSkipCommentLines");
626
627 if (!fp)
628 return ERROR_INT("stream not open", procName, 1);
629 if ((c = fgetc(fp)) == EOF)
630 return 1;
631 if (c == '#') {
632 do { /* each line starting with '#' */
633 do { /* this entire line */
634 if ((c = fgetc(fp)) == EOF)
635 return 1;
636 } while (c != '\n');
637 if ((c = fgetc(fp)) == EOF)
638 return 1;
639 } while (c == '#');
640 }
641
642 /* Back up one byte */
643 fseek(fp, -1L, SEEK_CUR);
644 return 0;
645 }
646
647 /* --------------------------------------------*/
648 #endif /* USE_PNMIO */
649 /* --------------------------------------------*/
650