document the new test image functions
[imager.git] / pnm.c
CommitLineData
92bda632 1#include "imager.h"
02d1d628 2#include "log.h"
067d6bdc 3#include "iolayer.h"
92bda632 4#include "imageri.h"
02d1d628
AMH
5
6#include <stdlib.h>
f0e7b647 7#include <errno.h>
02d1d628
AMH
8
9
10/*
11=head1 NAME
12
13pnm.c - implements reading and writing ppm/pnm/pbm files, uses io layer.
14
15=head1 SYNOPSIS
16
17 io_glue *ig = io_new_fd( fd );
9c106321 18 i_img *im = i_readpnm_wiol(ig, 0); // no limit on how much is read
02d1d628
AMH
19 // or
20 io_glue *ig = io_new_fd( fd );
21 return_code = i_writepnm_wiol(im, ig);
22
23=head1 DESCRIPTION
24
25pnm.c implements the basic functions to read and write portable
26anymap files. It uses the iolayer and needs either a seekable source
27or an entire memory mapped buffer.
28
29=head1 FUNCTION REFERENCE
30
31Some of these functions are internal.
32
b8c2033e 33=over
02d1d628
AMH
34
35=cut
36*/
37
38
39#define BSIZ 1024
40#define misspace(x) (x==' ' || x=='\n' || x=='\r' || x=='\t' || x=='\f' || x=='\v')
41#define misnumber(x) (x <= '9' && x>='0')
42
43static char *typenames[]={"ascii pbm", "ascii pgm", "ascii ppm", "binary pbm", "binary pgm", "binary ppm"};
44
45/*
46 * Type to encapsulate the local buffer
47 * management skipping over in a file
48 */
49
50typedef struct {
51 io_glue *ig;
52 int len;
53 int cp;
54 char buf[BSIZ];
55} mbuf;
56
57
58static
59void init_buf(mbuf *mb, io_glue *ig) {
60 mb->len = 0;
61 mb->cp = 0;
62 mb->ig = ig;
63}
64
65
66
67/*
68=item gnext(mbuf *mb)
69
70Fetches a character and advances in stream by one character.
71Returns a pointer to the byte or NULL on failure (internal).
72
73 mb - buffer object
74
75=cut
76*/
77
9c106321
TC
78#define gnext(mb) (((mb)->cp == (mb)->len) ? gnextf(mb) : (mb)->buf + (mb)->cp++)
79
02d1d628
AMH
80static
81char *
9c106321 82gnextf(mbuf *mb) {
02d1d628
AMH
83 io_glue *ig = mb->ig;
84 if (mb->cp == mb->len) {
85 mb->cp = 0;
86 mb->len = ig->readcb(ig, mb->buf, BSIZ);
87 if (mb->len == -1) {
fac8664e 88 i_push_error(errno, "file read error");
02d1d628
AMH
89 mm_log((1, "i_readpnm: read error\n"));
90 return NULL;
91 }
92 if (mb->len == 0) {
93 mm_log((1, "i_readpnm: end of file\n"));
94 return NULL;
95 }
96 }
97 return &mb->buf[mb->cp++];
98}
99
100
101/*
fdd00d54 102=item gpeek(mbuf *mb)
02d1d628
AMH
103
104Fetches a character but does NOT advance. Returns a pointer to
105the byte or NULL on failure (internal).
106
107 mb - buffer object
108
109=cut
110*/
111
9c106321
TC
112#define gpeek(mb) ((mb)->cp == (mb)->len ? gpeekf(mb) : (mb)->buf + (mb)->cp)
113
02d1d628
AMH
114static
115char *
9c106321 116gpeekf(mbuf *mb) {
02d1d628
AMH
117 io_glue *ig = mb->ig;
118 if (mb->cp == mb->len) {
119 mb->cp = 0;
120 mb->len = ig->readcb(ig, mb->buf, BSIZ);
121 if (mb->len == -1) {
fac8664e 122 i_push_error(errno, "read error");
02d1d628
AMH
123 mm_log((1, "i_readpnm: read error\n"));
124 return NULL;
125 }
126 if (mb->len == 0) {
127 mm_log((1, "i_readpnm: end of file\n"));
128 return NULL;
129 }
130 }
131 return &mb->buf[mb->cp];
132}
133
9c106321
TC
134int
135gread(mbuf *mb, unsigned char *buf, size_t read_size) {
136 int total_read = 0;
137 if (mb->cp != mb->len) {
138 int avail_size = mb->len - mb->cp;
139 int use_size = read_size > avail_size ? avail_size : read_size;
140 memcpy(buf, mb->buf+mb->cp, use_size);
141 mb->cp += use_size;
142 total_read += use_size;
143 read_size -= use_size;
144 buf += use_size;
145 }
146 if (read_size) {
147 io_glue *ig = mb->ig;
148 int read_res = i_io_read(ig, buf, read_size);
149 if (read_res >= 0) {
150 total_read += read_res;
151 }
152 }
153 return total_read;
154}
02d1d628
AMH
155
156
157/*
158=item skip_spaces(mb)
159
160Advances in stream until it is positioned at a
161non white space character. (internal)
162
163 mb - buffer object
164
165=cut
166*/
167
168static
169int
170skip_spaces(mbuf *mb) {
171 char *cp;
172 while( (cp = gpeek(mb)) && misspace(*cp) ) if ( !gnext(mb) ) break;
173 if (!cp) return 0;
174 return 1;
175}
176
177
178/*
fdd00d54 179=item skip_comment(mb)
02d1d628
AMH
180
181Advances in stream over whitespace and a comment if one is found. (internal)
182
183 mb - buffer object
184
185=cut
186*/
187
188static
189int
190skip_comment(mbuf *mb) {
191 char *cp;
192
193 if (!skip_spaces(mb)) return 0;
194
195 if (!(cp = gpeek(mb))) return 0;
196 if (*cp == '#') {
197 while( (cp = gpeek(mb)) && (*cp != '\n' && *cp != '\r') ) {
198 if ( !gnext(mb) ) break;
199 }
200 }
201 if (!cp) return 0;
202
203 return 1;
204}
205
206
207/*
208=item gnum(mb, i)
209
210Fetches the next number from stream and stores in i, returns true
211on success else false.
212
213 mb - buffer object
214 i - integer to store result in
215
216=cut
217*/
218
219static
220int
221gnum(mbuf *mb, int *i) {
222 char *cp;
223 *i = 0;
224
225 if (!skip_spaces(mb)) return 0;
226
9c106321
TC
227 if (!(cp = gpeek(mb)))
228 return 0;
229 if (!misnumber(*cp))
230 return 0;
02d1d628 231 while( (cp = gpeek(mb)) && misnumber(*cp) ) {
8d14daab
TC
232 int work = *i*10+(*cp-'0');
233 if (work < *i) {
234 /* overflow */
235 i_push_error(0, "integer overflow");
236 return 0;
237 }
238 *i = work;
02d1d628
AMH
239 cp = gnext(mb);
240 }
241 return 1;
242}
243
9c106321
TC
244static
245i_img *
246read_pgm_ppm_bin8(mbuf *mb, i_img *im, int width, int height,
d87dc9a4 247 int channels, int maxval, int allow_incomplete) {
9c106321
TC
248 i_color *line, *linep;
249 int read_size;
250 unsigned char *read_buf, *readp;
251 int x, y, ch;
252 int rounder = maxval / 2;
253
254 line = mymalloc(width * sizeof(i_color));
255 read_size = channels * width;
256 read_buf = mymalloc(read_size);
257 for(y=0;y<height;y++) {
258 linep = line;
259 readp = read_buf;
260 if (gread(mb, read_buf, read_size) != read_size) {
261 myfree(line);
262 myfree(read_buf);
d87dc9a4 263 if (allow_incomplete) {
9c106321
TC
264 i_tags_setn(&im->tags, "i_incomplete", 1);
265 i_tags_setn(&im->tags, "i_lines_read", y);
266 return im;
267 }
268 else {
269 i_push_error(0, "short read - file truncated?");
270 i_img_destroy(im);
271 return NULL;
272 }
273 }
274 if (maxval == 255) {
275 for(x=0; x<width; x++) {
276 for(ch=0; ch<channels; ch++) {
277 linep->channel[ch] = *readp++;
278 }
279 ++linep;
280 }
281 }
282 else {
283 for(x=0; x<width; x++) {
284 for(ch=0; ch<channels; ch++) {
285 /* we just clamp samples to the correct range */
286 unsigned sample = *readp++;
287 if (sample > maxval)
288 sample = maxval;
289 linep->channel[ch] = (sample * 255 + rounder) / maxval;
290 }
291 ++linep;
292 }
293 }
294 i_plin(im, 0, width, y, line);
295 }
296 myfree(read_buf);
297 myfree(line);
298
299 return im;
300}
301
302static
303i_img *
304read_pgm_ppm_bin16(mbuf *mb, i_img *im, int width, int height,
d87dc9a4 305 int channels, int maxval, int allow_incomplete) {
9c106321
TC
306 i_fcolor *line, *linep;
307 int read_size;
308 unsigned char *read_buf, *readp;
309 int x, y, ch;
310 double maxvalf = maxval;
311
312 line = mymalloc(width * sizeof(i_fcolor));
313 read_size = channels * width * 2;
314 read_buf = mymalloc(read_size);
315 for(y=0;y<height;y++) {
316 linep = line;
317 readp = read_buf;
318 if (gread(mb, read_buf, read_size) != read_size) {
319 myfree(line);
320 myfree(read_buf);
d87dc9a4 321 if (allow_incomplete) {
9c106321
TC
322 i_tags_setn(&im->tags, "i_incomplete", 1);
323 i_tags_setn(&im->tags, "i_lines_read", y);
324 return im;
325 }
326 else {
327 i_push_error(0, "short read - file truncated?");
328 i_img_destroy(im);
329 return NULL;
330 }
331 }
332 for(x=0; x<width; x++) {
333 for(ch=0; ch<channels; ch++) {
334 unsigned sample = (readp[0] << 8) + readp[1];
335 if (sample > maxval)
336 sample = maxval;
337 readp += 2;
338 linep->channel[ch] = sample / maxvalf;
339 }
340 ++linep;
341 }
342 i_plinf(im, 0, width, y, line);
343 }
344 myfree(read_buf);
345 myfree(line);
346
347 return im;
348}
349
350static
351i_img *
d87dc9a4 352read_pbm_bin(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
9c106321
TC
353 i_palidx *line, *linep;
354 int read_size;
355 unsigned char *read_buf, *readp;
356 int x, y;
357 unsigned mask;
358
359 line = mymalloc(width * sizeof(i_palidx));
360 read_size = (width + 7) / 8;
361 read_buf = mymalloc(read_size);
362 for(y = 0; y < height; y++) {
363 if (gread(mb, read_buf, read_size) != read_size) {
364 myfree(line);
365 myfree(read_buf);
d87dc9a4 366 if (allow_incomplete) {
9c106321
TC
367 i_tags_setn(&im->tags, "i_incomplete", 1);
368 i_tags_setn(&im->tags, "i_lines_read", y);
369 return im;
370 }
371 else {
372 i_push_error(0, "short read - file truncated?");
373 i_img_destroy(im);
374 return NULL;
375 }
376 }
377 linep = line;
378 readp = read_buf;
379 mask = 0x80;
380 for(x = 0; x < width; ++x) {
381 *linep++ = *readp & mask ? 1 : 0;
382 mask >>= 1;
383 if (mask == 0) {
384 ++readp;
385 mask = 0x80;
386 }
387 }
388 i_ppal(im, 0, width, y, line);
389 }
390 myfree(read_buf);
391 myfree(line);
392
393 return im;
394}
395
396/* unlike pgm/ppm pbm:
397 - doesn't require spaces between samples (bits)
398 - 1 (maxval) is black instead of white
399*/
400static
401i_img *
d87dc9a4 402read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
9c106321
TC
403 i_palidx *line, *linep;
404 int x, y;
405
406 line = mymalloc(width * sizeof(i_palidx));
407 for(y = 0; y < height; y++) {
408 linep = line;
409 for(x = 0; x < width; ++x) {
410 char *cp;
411 skip_spaces(mb);
412 if (!(cp = gnext(mb)) || (*cp != '0' && *cp != '1')) {
413 myfree(line);
d87dc9a4 414 if (allow_incomplete) {
9c106321
TC
415 i_tags_setn(&im->tags, "i_incomplete", 1);
416 i_tags_setn(&im->tags, "i_lines_read", y);
417 return im;
418 }
419 else {
420 if (cp)
421 i_push_error(0, "invalid data for ascii pnm");
422 else
423 i_push_error(0, "short read - file truncated?");
424 i_img_destroy(im);
425 return NULL;
426 }
427 }
428 *linep++ = *cp == '0' ? 0 : 1;
429 }
430 i_ppal(im, 0, width, y, line);
431 }
432 myfree(line);
433
434 return im;
435}
436
437static
438i_img *
439read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels,
d87dc9a4 440 int maxval, int allow_incomplete) {
9c106321
TC
441 i_color *line, *linep;
442 int x, y, ch;
443 int rounder = maxval / 2;
444
445 line = mymalloc(width * sizeof(i_color));
446 for(y=0;y<height;y++) {
447 linep = line;
448 for(x=0; x<width; x++) {
449 for(ch=0; ch<channels; ch++) {
450 int sample;
451
452 if (!gnum(mb, &sample)) {
453 myfree(line);
d87dc9a4 454 if (allow_incomplete) {
9c106321
TC
455 i_tags_setn(&im->tags, "i_incomplete", 1);
456 i_tags_setn(&im->tags, "i_lines_read", 1);
457 return im;
458 }
459 else {
460 if (gpeek(mb))
461 i_push_error(0, "invalid data for ascii pnm");
462 else
463 i_push_error(0, "short read - file truncated?");
464 i_img_destroy(im);
465 return NULL;
466 }
467 }
468 if (sample > maxval)
469 sample = maxval;
470 linep->channel[ch] = (sample * 255 + rounder) / maxval;
471 }
472 ++linep;
473 }
474 i_plin(im, 0, width, y, line);
475 }
476 myfree(line);
477
478 return im;
479}
480
481static
482i_img *
483read_pgm_ppm_ascii_16(mbuf *mb, i_img *im, int width, int height,
d87dc9a4 484 int channels, int maxval, int allow_incomplete) {
9c106321
TC
485 i_fcolor *line, *linep;
486 int x, y, ch;
487 double maxvalf = maxval;
488
489 line = mymalloc(width * sizeof(i_fcolor));
490 for(y=0;y<height;y++) {
491 linep = line;
492 for(x=0; x<width; x++) {
493 for(ch=0; ch<channels; ch++) {
494 int sample;
495
496 if (!gnum(mb, &sample)) {
497 myfree(line);
d87dc9a4
TC
498 if (allow_incomplete) {
499 i_tags_setn(&im->tags, "i_incomplete", 1);
500 i_tags_setn(&im->tags, "i_lines_read", y);
501 return im;
9c106321
TC
502 }
503 else {
504 if (gpeek(mb))
505 i_push_error(0, "invalid data for ascii pnm");
506 else
507 i_push_error(0, "short read - file truncated?");
508 i_img_destroy(im);
509 return NULL;
510 }
511 }
512 if (sample > maxval)
513 sample = maxval;
514 linep->channel[ch] = sample / maxvalf;
515 }
516 ++linep;
517 }
518 i_plinf(im, 0, width, y, line);
519 }
520 myfree(line);
521
522 return im;
523}
02d1d628
AMH
524
525/*
d87dc9a4 526=item i_readpnm_wiol(ig, allow_incomplete)
02d1d628
AMH
527
528Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
529
530 ig - io_glue object
d87dc9a4 531 allow_incomplete - allows a partial file to be read successfully
02d1d628
AMH
532
533=cut
534*/
2086be61 535static i_img *i_readpnm_wiol_low( mbuf*, int);
02d1d628
AMH
536
537i_img *
d87dc9a4 538i_readpnm_wiol(io_glue *ig, int allow_incomplete) {
2086be61
TC
539 mbuf buf;
540 io_glue_commit_types(ig);
541 init_buf(&buf, ig);
542
543 return i_readpnm_wiol_low( &buf, allow_incomplete );
544}
545
546static i_img *
547i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) {
02d1d628
AMH
548 i_img* im;
549 int type;
8d14daab 550 int width, height, maxval, channels;
8b695554 551 int rounder;
02d1d628 552 char *cp;
02d1d628 553
fac8664e 554 i_clear_error();
2086be61 555 mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", buf->ig, allow_incomplete));
02d1d628 556
2086be61 557 cp = gnext(buf);
02d1d628
AMH
558
559 if (!cp || *cp != 'P') {
fac8664e 560 i_push_error(0, "bad header magic, not a PNM file");
02d1d628
AMH
561 mm_log((1, "i_readpnm: Could not read header of file\n"));
562 return NULL;
563 }
564
2086be61 565 if ( !(cp = gnext(buf)) ) {
02d1d628
AMH
566 mm_log((1, "i_readpnm: Could not read header of file\n"));
567 return NULL;
568 }
569
570 type = *cp-'0';
571
572 if (type < 1 || type > 6) {
fac8664e 573 i_push_error(0, "unknown PNM file type, not a PNM file");
02d1d628
AMH
574 mm_log((1, "i_readpnm: Not a pnm file\n"));
575 return NULL;
576 }
577
2086be61 578 if ( !(cp = gnext(buf)) ) {
02d1d628
AMH
579 mm_log((1, "i_readpnm: Could not read header of file\n"));
580 return NULL;
581 }
582
583 if ( !misspace(*cp) ) {
fac8664e 584 i_push_error(0, "unexpected character, not a PNM file");
02d1d628
AMH
585 mm_log((1, "i_readpnm: Not a pnm file\n"));
586 return NULL;
587 }
588
589 mm_log((1, "i_readpnm: image is a %s\n", typenames[type-1] ));
590
591
592 /* Read sizes and such */
593
2086be61 594 if (!skip_comment(buf)) {
fac8664e 595 i_push_error(0, "while skipping to width");
02d1d628
AMH
596 mm_log((1, "i_readpnm: error reading before width\n"));
597 return NULL;
598 }
599
2086be61 600 if (!gnum(buf, &width)) {
fac8664e 601 i_push_error(0, "could not read image width");
02d1d628
AMH
602 mm_log((1, "i_readpnm: error reading width\n"));
603 return NULL;
604 }
605
2086be61 606 if (!skip_comment(buf)) {
fac8664e 607 i_push_error(0, "while skipping to height");
02d1d628
AMH
608 mm_log((1, "i_readpnm: error reading before height\n"));
609 return NULL;
610 }
611
2086be61 612 if (!gnum(buf, &height)) {
fac8664e 613 i_push_error(0, "could not read image height");
02d1d628
AMH
614 mm_log((1, "i_readpnm: error reading height\n"));
615 return NULL;
616 }
617
618 if (!(type == 1 || type == 4)) {
2086be61 619 if (!skip_comment(buf)) {
fac8664e 620 i_push_error(0, "while skipping to maxval");
02d1d628
AMH
621 mm_log((1, "i_readpnm: error reading before maxval\n"));
622 return NULL;
623 }
624
2086be61 625 if (!gnum(buf, &maxval)) {
fac8664e 626 i_push_error(0, "could not read maxval");
02d1d628
AMH
627 mm_log((1, "i_readpnm: error reading maxval\n"));
628 return NULL;
629 }
8b695554
TC
630
631 if (maxval == 0) {
632 i_push_error(0, "maxval is zero - invalid pnm file");
633 mm_log((1, "i_readpnm: maxval is zero, invalid pnm file\n"));
634 return NULL;
635 }
636 else if (maxval > 65535) {
637 i_push_errorf(0, "maxval of %d is over 65535 - invalid pnm file",
638 maxval);
8d14daab 639 mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n", maxval));
8b695554
TC
640 return NULL;
641 }
02d1d628 642 } else maxval=1;
8b695554 643 rounder = maxval / 2;
02d1d628 644
2086be61 645 if (!(cp = gnext(buf)) || !misspace(*cp)) {
fac8664e 646 i_push_error(0, "garbage in header, invalid PNM file");
02d1d628
AMH
647 mm_log((1, "i_readpnm: garbage in header\n"));
648 return NULL;
649 }
650
651 channels = (type == 3 || type == 6) ? 3:1;
02d1d628 652
77157728
TC
653 if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) {
654 mm_log((1, "i_readpnm: image size exceeds limits\n"));
655 return NULL;
656 }
657
02d1d628 658 mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval));
02d1d628 659
9c106321
TC
660 if (type == 1 || type == 4) {
661 i_color pbm_pal[2];
662 pbm_pal[0].channel[0] = 255;
663 pbm_pal[1].channel[0] = 0;
664
665 im = i_img_pal_new(width, height, 1, 256);
666 i_addcolors(im, pbm_pal, 2);
667 }
668 else {
669 if (maxval > 255)
670 im = i_img_16_new(width, height, channels);
671 else
672 im = i_img_8_new(width, height, channels);
673 }
642a675b 674
02d1d628
AMH
675 switch (type) {
676 case 1: /* Ascii types */
2086be61 677 im = read_pbm_ascii(buf, im, width, height, allow_incomplete);
9c106321
TC
678 break;
679
02d1d628
AMH
680 case 2:
681 case 3:
9c106321 682 if (maxval > 255)
2086be61 683 im = read_pgm_ppm_ascii_16(buf, im, width, height, channels, maxval, allow_incomplete);
9c106321 684 else
2086be61 685 im = read_pgm_ppm_ascii(buf, im, width, height, channels, maxval, allow_incomplete);
02d1d628
AMH
686 break;
687
688 case 4: /* binary pbm */
2086be61 689 im = read_pbm_bin(buf, im, width, height, allow_incomplete);
02d1d628
AMH
690 break;
691
692 case 5: /* binary pgm */
693 case 6: /* binary ppm */
9c106321 694 if (maxval > 255)
2086be61 695 im = read_pgm_ppm_bin16(buf, im, width, height, channels, maxval, allow_incomplete);
9c106321 696 else
2086be61 697 im = read_pgm_ppm_bin8(buf, im, width, height, channels, maxval, allow_incomplete);
02d1d628 698 break;
9c106321 699
02d1d628
AMH
700 default:
701 mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type));
702 return NULL;
703 }
9c106321
TC
704
705 if (!im)
706 return NULL;
707
708 i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0);
709 i_tags_setn(&im->tags, "pnm_maxval", maxval);
710 i_tags_setn(&im->tags, "pnm_type", type);
711
02d1d628
AMH
712 return im;
713}
714
2086be61
TC
715static void free_images(i_img **imgs, int count) {
716 int i;
717
718 if (count) {
719 for (i = 0; i < count; ++i)
720 i_img_destroy(imgs[i]);
721 myfree(imgs);
722 }
723}
724
725i_img **i_readpnm_multi_wiol(io_glue *ig, int *count, int allow_incomplete) {
726 i_img **results = NULL;
727 i_img *img = NULL;
728 char *cp = NULL;
729 mbuf buf;
730 int result_alloc = 0,
731 value = 0,
732 eof = 0;
733 *count=0;
734 io_glue_commit_types(ig);
735 init_buf(&buf, ig);
736 do {
737 mm_log((1, "read image %i\n", 1+*count));
738 img = i_readpnm_wiol_low( &buf, allow_incomplete );
739 if( !img ) {
740 free_images( results, *count );
741 return NULL;
742 }
743 ++*count;
744 if (*count > result_alloc) {
745 if (result_alloc == 0) {
746 result_alloc = 5;
747 results = mymalloc(result_alloc * sizeof(i_img *));
748 }
749 else {
750 /* myrealloc never fails (it just dies if it can't allocate) */
751 result_alloc *= 2;
752 results = myrealloc(results, result_alloc * sizeof(i_img *));
753 }
754 }
755 results[*count-1] = img;
756
757
758 if( i_tags_get_int(&img->tags, "i_incomplete", 0, &value ) && value) {
759 eof = 1;
760 }
761 else if( skip_spaces( &buf ) && ( cp=gpeek( &buf ) ) && *cp == 'P' ) {
762 eof = 0;
763 }
764 else {
765 eof = 1;
766 }
767 } while(!eof);
768 return results;
769}
770
771
772
9c106321
TC
773static
774int
775write_pbm(i_img *im, io_glue *ig, int zero_is_white) {
776 int x, y;
777 i_palidx *line;
8d14daab 778 i_img_dim write_size;
9c106321
TC
779 unsigned char *write_buf;
780 unsigned char *writep;
781 char header[255];
782 unsigned mask;
783
8d14daab
TC
784 sprintf(header, "P4\012# CREATOR: Imager\012%" i_DF " %" i_DF "\012",
785 i_DFc(im->xsize), i_DFc(im->ysize));
9c106321
TC
786 if (i_io_write(ig, header, strlen(header)) < 0) {
787 i_push_error(0, "could not write pbm header");
788 return 0;
789 }
790 write_size = (im->xsize + 7) / 8;
791 line = mymalloc(sizeof(i_palidx) * im->xsize);
792 write_buf = mymalloc(write_size);
793 for (y = 0; y < im->ysize; ++y) {
794 i_gpal(im, 0, im->xsize, y, line);
795 mask = 0x80;
796 writep = write_buf;
797 memset(write_buf, 0, write_size);
798 for (x = 0; x < im->xsize; ++x) {
799 if (zero_is_white ? line[x] : !line[x])
800 *writep |= mask;
801 mask >>= 1;
802 if (!mask) {
803 ++writep;
804 mask = 0x80;
805 }
806 }
807 if (i_io_write(ig, write_buf, write_size) != write_size) {
808 i_push_error(0, "write failure");
809 myfree(write_buf);
810 myfree(line);
811 return 0;
812 }
813 }
814 myfree(write_buf);
815 myfree(line);
816
817 return 1;
818}
819
820static
821int
fa90de94 822write_ppm_data_8(i_img *im, io_glue *ig, int want_channels) {
8d14daab
TC
823 size_t write_size = im->xsize * want_channels;
824 size_t buf_size = im->xsize * im->channels;
2a31a4b4 825 unsigned char *data = mymalloc(buf_size);
8d14daab 826 i_img_dim y = 0;
9c106321 827 int rc = 1;
fa90de94 828 i_color bg;
9c106321 829
fa90de94 830 i_get_file_background(im, &bg);
9c106321 831 while (y < im->ysize && rc >= 0) {
2a31a4b4 832 i_gsamp_bg(im, 0, im->xsize, y, data, want_channels, &bg);
9c106321
TC
833 if (i_io_write(ig, data, write_size) != write_size) {
834 i_push_error(errno, "could not write ppm data");
835 rc = 0;
836 break;
837 }
838 ++y;
839 }
840 myfree(data);
841
842 return rc;
843}
844
845static
846int
fa90de94 847write_ppm_data_16(i_img *im, io_glue *ig, int want_channels) {
8d14daab
TC
848 size_t line_size = im->channels * im->xsize * sizeof(i_fsample_t);
849 size_t sample_count = want_channels * im->xsize;
850 size_t write_size = sample_count * 2;
2a31a4b4
TC
851 i_fsample_t *line_buf = mymalloc(line_size);
852 i_fsample_t *samplep;
9c106321 853 unsigned char *write_buf = mymalloc(write_size);
2a31a4b4 854 unsigned char *writep;
8d14daab
TC
855 size_t sample_num;
856 i_img_dim y = 0;
9c106321 857 int rc = 1;
fa90de94
TC
858 i_fcolor bg;
859
860 i_get_file_backgroundf(im, &bg);
9c106321
TC
861
862 while (y < im->ysize) {
2a31a4b4
TC
863 i_gsampf_bg(im, 0, im->xsize, y, line_buf, want_channels, &bg);
864 samplep = line_buf;
865 writep = write_buf;
866 for (sample_num = 0; sample_num < sample_count; ++sample_num) {
867 unsigned sample16 = SampleFTo16(*samplep++);
868 *writep++ = sample16 >> 8;
869 *writep++ = sample16 & 0xFF;
9c106321
TC
870 }
871 if (i_io_write(ig, write_buf, write_size) != write_size) {
872 i_push_error(errno, "could not write ppm data");
873 rc = 0;
874 break;
875 }
876 ++y;
877 }
878 myfree(line_buf);
879 myfree(write_buf);
880
881 return rc;
882}
02d1d628 883
067d6bdc
AMH
884undef_int
885i_writeppm_wiol(i_img *im, io_glue *ig) {
886 char header[255];
9c106321
TC
887 int zero_is_white;
888 int wide_data;
02d1d628 889
067d6bdc
AMH
890 mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig));
891 i_clear_error();
02d1d628 892
067d6bdc
AMH
893 /* Add code to get the filename info from the iolayer */
894 /* Also add code to check for mmapped code */
02d1d628 895
067d6bdc 896 io_glue_commit_types(ig);
02d1d628 897
9c106321
TC
898 if (i_img_is_monochrome(im, &zero_is_white)) {
899 return write_pbm(im, ig, zero_is_white);
900 }
901 else {
902 int type;
903 int maxval;
fa90de94
TC
904 int want_channels = im->channels;
905
906 if (want_channels == 2 || want_channels == 4)
907 --want_channels;
faa9b3e7 908
9c106321
TC
909 if (!i_tags_get_int(&im->tags, "pnm_write_wide_data", 0, &wide_data))
910 wide_data = 0;
911
fa90de94 912 if (want_channels == 3) {
9c106321 913 type = 6;
faa9b3e7 914 }
fa90de94 915 else if (want_channels == 1) {
9c106321 916 type = 5;
faa9b3e7 917 }
9c106321
TC
918 else {
919 i_push_error(0, "can only save 1 or 3 channel images to pnm");
920 mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels));
067d6bdc
AMH
921 return(0);
922 }
9c106321
TC
923 if (im->bits <= 8 || !wide_data)
924 maxval = 255;
925 else
926 maxval = 65535;
927
8d14daab
TC
928 sprintf(header,"P%d\n#CREATOR: Imager\n%" i_DF " %" i_DF"\n%d\n",
929 type, i_DFc(im->xsize), i_DFc(im->ysize), maxval);
9c106321
TC
930
931 if (ig->writecb(ig,header,strlen(header)) != strlen(header)) {
932 i_push_error(errno, "could not write ppm header");
933 mm_log((1,"i_writeppm: unable to write ppm header.\n"));
067d6bdc
AMH
934 return(0);
935 }
faa9b3e7 936
fa90de94
TC
937 if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type
938 && im->channels == want_channels) {
9c106321
TC
939 if (ig->writecb(ig,im->idata,im->bytes) != im->bytes) {
940 i_push_error(errno, "could not write ppm data");
faa9b3e7
TC
941 return 0;
942 }
943 }
9c106321 944 else if (maxval == 255) {
fa90de94 945 if (!write_ppm_data_8(im, ig, want_channels))
9c106321
TC
946 return 0;
947 }
948 else {
fa90de94 949 if (!write_ppm_data_16(im, ig, want_channels))
9c106321 950 return 0;
067d6bdc 951 }
067d6bdc 952 }
10461f9a 953 ig->closecb(ig);
faa9b3e7 954
067d6bdc
AMH
955 return(1);
956}
b8c2033e
AMH
957
958/*
959=back
960
961=head1 AUTHOR
962
5b480b14 963Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tonyc@cpan.org>,
2086be61 964Philip Gwyn <gwyn@cpan.org>.
b8c2033e
AMH
965
966=head1 SEE ALSO
967
968Imager(3)
969
970=cut
971*/