1 /*=============================================================================
2 pamtopdbimg
3 ===============================================================================
4
5 Convert Palm Pilot PDB Image format (for viewing by
6 Pilot Image Viewer) to Netpbm image.
7
8 Bryan Henderson derived this from Eric Howe's program named
9 'imgvtopnm', in September 2010.
10 =============================================================================*/
11 /*
12 * Copyright (C) 1997 Eric A. Howe
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 *
28 * Authors: Eric A. Howe (mu@trends.net)
29 * Bryan Henderson
30 */
31 #include <stdlib.h>
32 #include <assert.h>
33
34 #include "pm_c_util.h"
35 #include "mallocvar.h"
36 #include "nstring.h"
37 #include "shhopt.h"
38 #include "pam.h"
39
40 #include "ipdb.h"
41
42
43 struct cmdlineInfo {
44 /* All the information the user supplied in the command line,
45 in a form easy for the program to use.
46 */
47 const char * inputFileName; /* '-' if stdin */
48 const char * notefile; /* NULL if not specified */
49 unsigned int verbose;
50 };
51
52
53
54 static void
parseCommandLine(int argc,const char ** argv,struct cmdlineInfo * const cmdlineP)55 parseCommandLine(int argc, const char ** argv,
56 struct cmdlineInfo * const cmdlineP) {
57 /*----------------------------------------------------------------------------
58 parse program command line described in Unix standard form by argc
59 and argv. Return the information in the options as *cmdlineP.
60
61 If command line is internally inconsistent (invalid options, etc.),
62 issue error message to stderr and abort program.
63
64 Note that the strings we return are stored in the storage that
65 was passed to us as the argv array. We also trash *argv.
66 -----------------------------------------------------------------------------*/
67 optEntry *option_def;
68 /* Instructions to pm_optParseOptions3 on how to parse our options.
69 */
70 optStruct3 opt;
71
72 unsigned int option_def_index;
73
74 unsigned int notefileSpec;
75
76 MALLOCARRAY_NOFAIL(option_def, 100);
77
78 option_def_index = 0; /* incremented by OPTENT3 */
79 OPTENT3(0, "notefile", OPT_STRING, &cmdlineP->notefile,
80 ¬efileSpec, 0);
81 OPTENT3(0, "verbose", OPT_FLAG, NULL,
82 &cmdlineP->verbose, 0);
83
84 opt.opt_table = option_def;
85 opt.short_allowed = false; /* We have no short (old-fashioned) options */
86 opt.allowNegNum = false; /* We have no parms that are negative numbers */
87
88 pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
89 /* Uses and sets argc, argv, and some of *cmdlineP and others. */
90
91 if (!notefileSpec)
92 cmdlineP->notefile = NULL;
93
94 if (argc-1 < 1)
95 cmdlineP->inputFileName = "-";
96 else if (argc-1 == 1)
97 cmdlineP->inputFileName = argv[1];
98 else
99 pm_error("Program takes at most one argument: input file name");
100
101 free(option_def);
102 }
103
104
105
106 #define getg16pixel(b,o) (((b) >> (4 - 4*(o))) & 0x0f)
107 #define getgpixel(b,o) (((b) >> (6 - 2*(o))) & 0x03)
108 #define getmpixel(b,o) (((b) >> (7 - (o))) & 0x01)
109
110
111 static void
abortShort()112 abortShort() {
113 pm_error("Invalid image. Compression algorithm runs out of "
114 "compressed data before generating the expected "
115 "amount of image data");
116 }
117
118
119
120 static void
abortOverrun()121 abortOverrun() {
122 pm_error("Invalid image. Compression algorithm finds the end of "
123 "the image in the middle of a run");
124 }
125
126
127
128 static void
decompress(const uint8_t * const compressed,size_t const compressedSize,size_t const imageSize,uint8_t ** const uncompressedP)129 decompress(const uint8_t * const compressed,
130 size_t const compressedSize,
131 size_t const imageSize,
132 uint8_t ** const uncompressedP) {
133 /*----------------------------------------------------------------------------
134 Decompress the data 'compressed', which is 'compressedSize' bytes long.
135 Return the decompressed data in newly malloced storage as
136 *decompressedP. Decompression should yield exactly 'imageSize' bytes.
137 -----------------------------------------------------------------------------*/
138 /*
139 * The compression scheme used is a simple RLE; the control codes,
140 * CODE, are one byte and have the following meanings:
141 *
142 * CODE > 0x80 Insert (CODE + 1 - 0x80) copies of the next byte.
143 * CODE <= 0x80 Insert the next (CODE + 1) literal bytes.
144 *
145 * Compressed pieces can (and do) cross row boundaries.
146 */
147 uint8_t * uncompressed;
148
149 MALLOCARRAY(uncompressed, imageSize);
150
151 if (uncompressed) {
152 const uint8_t * inP;
153 uint8_t * outP;
154 size_t bytesLeft;
155
156 for (bytesLeft = imageSize,
157 inP = &compressed[0], outP = &uncompressed[0];
158 bytesLeft > 0;
159 ) {
160
161 int got, put;
162
163 if (inP > compressed + compressedSize)
164 abortShort();
165
166 if (*inP > 0x80) {
167 put = *inP++ + 1 - 0x80;
168 if (outP + put > uncompressed + imageSize)
169 abortOverrun();
170 memset(outP, *inP, put);
171 got = 1;
172 } else {
173 put = *inP++ + 1;
174 if (inP + put > compressed + compressedSize)
175 abortShort();
176 if (outP + put > uncompressed + imageSize)
177 abortOverrun();
178 memcpy(outP, inP, put);
179 got = put;
180 }
181 inP += got;
182 outP += put;
183 assert(bytesLeft >= put);
184 bytesLeft -= put;
185 }
186 }
187 *uncompressedP = uncompressed;
188 }
189
190
191
192 #define UNKNOWN_OFFSET (uint32_t)-1
193
194 static void
readCompressed(IMAGE * const imgP,uint32_t const end_offset,FILE * const fP,size_t * const dataSizeP,uint8_t ** const dataP,int * const retvalP)195 readCompressed(IMAGE * const imgP,
196 uint32_t const end_offset,
197 FILE * const fP,
198 size_t * const dataSizeP,
199 uint8_t ** const dataP,
200 int * const retvalP) {
201 /*----------------------------------------------------------------------------
202 Read the compressed data from file *fP (actually, if the image isn't
203 compressed, then it's just the regular data).
204
205 Return the data in newly malloced storage as *dataP, which is
206 *dataSizeP bytes long.
207 -----------------------------------------------------------------------------*/
208 int retval;
209 uint8_t * buffer;
210 size_t dataSize;
211
212 dataSize = 0; /* initial value */
213
214 if (end_offset == UNKNOWN_OFFSET) {
215 /*
216 * Read sufficient amount for worst-case compressed image,
217 * or until EOF. Some of them have an extra zero byte
218 * dangling off the end. I originally thought this was
219 * an empty note record (even though there was no record
220 * header for it); however, the release notes for Image
221 * Compression Manager 1.1 on http://www.pilotgear.com
222 * note this extra byte as a bug in Image Compression
223 * Manager 1.0 which 1.1 fixes. We'll just blindly read
224 * this extra byte and ignore it by paying attention to
225 * the image dimensions.
226 */
227 size_t const maxCompressedSizeWithBloat = ipdb_img_size(imgP) * 2;
228 /*
229 * Provide a buffer large enough for the worst case.
230 * See note in lib/util/runlength.c .
231 * We do not use pm_rlenc_allocoutbuf() because there is no
232 * guarantee that the encoder that produced the image was
233 * efficient.
234 */
235 MALLOCARRAY(buffer, maxCompressedSizeWithBloat);
236
237 if (buffer == NULL)
238 retval = ENOMEM;
239 else {
240 dataSize = fread(buffer, 1, maxCompressedSizeWithBloat, fP);
241 if (dataSize <= 0)
242 retval = EIO;
243 else
244 retval = 0;
245
246 if (retval != 0)
247 free(buffer);
248 }
249 } else {
250 /*
251 * Read to the indicated offset.
252 */
253 dataSize = end_offset - ftell(fP) + 1;
254
255 MALLOCARRAY(buffer, dataSize);
256
257 if (buffer == NULL)
258 retval = ENOMEM;
259 else {
260 ssize_t rc;
261 rc = fread(buffer, 1, dataSize, fP);
262 if (rc != dataSize)
263 retval = EIO;
264 else
265 retval = 0;
266
267 if (retval != 0)
268 free(buffer);
269 }
270 }
271 *dataSizeP = dataSize;
272 *dataP = buffer;
273 *retvalP = retval;
274 }
275
276
277
278 static void
imageReadHeader(FILE * const fileP,IMAGE * const imgP,bool const dump)279 imageReadHeader(FILE * const fileP,
280 IMAGE * const imgP,
281 bool const dump) {
282
283 fread(&imgP->name, 1, 32, fileP);
284 pm_readcharu(fileP, &imgP->version);
285 pm_readcharu(fileP, &imgP->type);
286 fread(&imgP->reserved1, 1, 4, fileP);
287 fread(&imgP->note, 1, 4, fileP);
288 pm_readbigshortu(fileP, &imgP->x_last);
289 pm_readbigshortu(fileP, &imgP->y_last);
290 fread(&imgP->reserved2, 1, 4, fileP);
291 pm_readbigshortu(fileP, &imgP->x_anchor);
292 pm_readbigshortu(fileP, &imgP->y_anchor);
293 pm_readbigshortu(fileP, &imgP->width);
294 pm_readbigshortu(fileP, &imgP->height);
295
296 if (dump) {
297 pm_message("PDB IMAGE header:");
298 pm_message(" Name: '%.*s'", (int)sizeof(imgP->name), imgP->name);
299 pm_message(" Version: %02x", imgP->version);
300 pm_message(" Type: %s", ipdb_typeName(imgP->type));
301 pm_message(" Note: %02x %02x %02x %02x",
302 imgP->note[0], imgP->note[1], imgP->note[2], imgP->note[3]);
303 pm_message(" X_last: %u", imgP->x_last);
304 pm_message(" Y_last: %u", imgP->y_last);
305 pm_message(" X_anchor: %u", imgP->x_anchor);
306 pm_message(" Y_anchor: %u", imgP->y_anchor);
307 pm_message(" Width: %u", imgP->width);
308 pm_message(" Height: %u", imgP->height);
309 pm_message("Pixels per byte: %u", ipdb_img_ppb(imgP));
310 pm_message("Image size: %lu bytes",
311 (unsigned long)ipdb_img_size(imgP));
312 }
313 }
314
315
316 static int
imageReadData(FILE * const fileP,IMAGE * const imgP,uint32_t const end_offset)317 imageReadData(FILE * const fileP,
318 IMAGE * const imgP,
319 uint32_t const end_offset) {
320
321 size_t const imageSize = ipdb_img_size(imgP);
322
323 int retval;
324 size_t dataSize;
325 uint8_t * buffer;
326
327 readCompressed(imgP, end_offset, fileP, &dataSize, &buffer, &retval);
328
329 if (retval == 0) {
330 /*
331 * Compressed data can cross row boundaries so we decompress
332 * the data here to avoid messiness in the row access functions.
333 */
334 if (dataSize < imageSize || imgP->version == 1) {
335 if (imgP->version == 0)
336 pm_message("Image header says raster data is uncompressed. "
337 "Encountered only %u instead of the "
338 "required %u bytes. Assuming compressed mode.",
339 (unsigned)dataSize, (unsigned)imageSize);
340 decompress(buffer, dataSize, imageSize, &imgP->data);
341 if (imgP->data == NULL)
342 retval = ENOMEM;
343 else
344 imgP->compressed = true;
345 free(buffer);
346 } else {
347 if (dataSize > imageSize)
348 pm_message("Image header says raster data is uncompressed. "
349 "Encountered %u instead of the required %u bytes. "
350 "Assuming uncompressed mode.",
351 (unsigned)dataSize, (unsigned)imageSize);
352 imgP->compressed = false;
353 imgP->data = buffer;
354 /* Storage at 'buffer' now belongs to *imgP */
355 }
356 }
357 return retval;
358 }
359
360
361
362 static int
imageRead(IMAGE * const imgP,uint32_t const end_offset,FILE * const fileP,bool const verbose)363 imageRead(IMAGE * const imgP,
364 uint32_t const end_offset,
365 FILE * const fileP,
366 bool const verbose) {
367
368 if (imgP) {
369 imgP->r->offset = (uint32_t)ftell(fileP);
370
371 imageReadHeader(fileP, imgP, verbose);
372
373 imageReadData(fileP, imgP, end_offset);
374 }
375 return 0;
376 }
377
378
379
380 static int
textRead(TEXT * const textP,FILE * const fileP)381 textRead(TEXT * const textP,
382 FILE * const fileP) {
383
384 int retval;
385 char * s;
386 char buf[128];
387 int used, alloced, len;
388
389 if (textP == NULL)
390 return 0;
391
392 textP->r->offset = (uint32_t)ftell(fileP);
393
394 /*
395 * What a pain in the ass! Why the hell isn't there a length
396 * attached to the text record? I suppose the designer wasn't
397 * concerned about non-seekable (i.e. pipes) input streams.
398 * Perhaps I'm being a little harsh, the lack of a length probably
399 * isn't much of an issue on the Pilot.
400 */
401 used = 0;
402 alloced = 0;
403 s = NULL;
404 retval = 0; /* initial value */
405 while ((len = fread(buf, 1, sizeof(buf), fileP)) != 0 && retval == 0) {
406 if (buf[len - 1] == '\0')
407 --len;
408 if (used + len > alloced) {
409 alloced += 2 * sizeof(buf);
410 REALLOCARRAY(s, alloced);
411
412 if (s == NULL)
413 retval = ENOMEM;
414 }
415 if (retval == 0) {
416 memcpy(s + used, buf, len);
417 used += len;
418 }
419 }
420 if (retval == 0) {
421 textP->data = calloc(1, used + 1);
422 if (textP->data == NULL)
423 retval = ENOMEM;
424 else
425 memcpy(textP->data, s, used);
426 }
427 if (s)
428 free(s);
429
430 return retval;
431 }
432
433
434
435 static int
pdbheadRead(PDBHEAD * const pdbHeadP,FILE * const fileP)436 pdbheadRead(PDBHEAD * const pdbHeadP,
437 FILE * const fileP) {
438
439 int retval;
440
441 fread(pdbHeadP->name, 1, 32, fileP);
442 pm_readbigshortu(fileP, &pdbHeadP->flags);
443 pm_readbigshortu(fileP, &pdbHeadP->version);
444 pm_readbiglongu2(fileP, &pdbHeadP->ctime);
445 pm_readbiglongu2(fileP, &pdbHeadP->mtime);
446 pm_readbiglongu2(fileP, &pdbHeadP->btime);
447 pm_readbiglongu2(fileP, &pdbHeadP->mod_num);
448 pm_readbiglongu2(fileP, &pdbHeadP->app_info);
449 pm_readbiglongu2(fileP, &pdbHeadP->sort_info);
450 fread(pdbHeadP->type, 1, 4, fileP);
451 fread(pdbHeadP->id, 1, 4, fileP);
452 pm_readbiglongu2(fileP, &pdbHeadP->uniq_seed);
453 pm_readbiglongu2(fileP, &pdbHeadP->next_rec);
454 pm_readbigshortu(fileP, &pdbHeadP->num_recs);
455
456 if (!memeq(pdbHeadP->type, IPDB_vIMG, 4)
457 || !memeq(pdbHeadP->id, IPDB_View, 4))
458 retval = E_NOTIMAGE;
459 else
460 retval = 0;
461
462 return retval;
463 }
464
465
466
467 static int
rechdrRead(RECHDR * const rechdrP,FILE * const fileP)468 rechdrRead(RECHDR * const rechdrP,
469 FILE * const fileP) {
470
471 int retval;
472 off_t len;
473
474 pm_readbiglongu2(fileP, &rechdrP->offset);
475
476 len = (off_t)rechdrP->offset - ftell(fileP);
477 switch(len) {
478 case 4:
479 case 12:
480 /*
481 * Version zero (eight bytes of record header) or version
482 * two with a note (two chunks of eight record header bytes).
483 */
484 fread(&rechdrP->unknown[0], 1, 3, fileP);
485 fread(&rechdrP->rec_type, 1, 1, fileP);
486 rechdrP->n_extra = 0;
487 rechdrP->extra = NULL;
488 retval = 0;
489 break;
490 case 6:
491 /*
492 * Version one (ten bytes of record header).
493 */
494 fread(&rechdrP->unknown[0], 1, 3, fileP);
495 fread(&rechdrP->rec_type, 1, 1, fileP);
496 rechdrP->n_extra = 2;
497 MALLOCARRAY(rechdrP->extra, rechdrP->n_extra);
498 if (rechdrP->extra == NULL)
499 retval = ENOMEM;
500 else {
501 fread(rechdrP->extra, 1, rechdrP->n_extra, fileP);
502 retval = 0;
503 }
504 break;
505 default:
506 /*
507 * hmmm.... I'll assume this is the record header
508 * for a text record.
509 */
510 fread(&rechdrP->unknown[0], 1, 3, fileP);
511 fread(&rechdrP->rec_type, 1, 1, fileP);
512 rechdrP->n_extra = 0;
513 rechdrP->extra = NULL;
514 retval = 0;
515 break;
516 }
517 if (retval == 0) {
518 if ((rechdrP->rec_type != IMG_REC && rechdrP->rec_type != TEXT_REC)
519 || !memeq(rechdrP->unknown, IPDB_MYST, 3))
520 retval = E_NOTRECHDR;
521 }
522 return retval;
523 }
524
525
526
527 static int
ipdbRead(IPDB * const pdbP,FILE * const fileP,bool const verbose)528 ipdbRead(IPDB * const pdbP,
529 FILE * const fileP,
530 bool const verbose) {
531
532 int retval;
533
534 ipdb_clear(pdbP);
535
536 pdbP->p = ipdb_pdbhead_alloc(NULL);
537
538 if (pdbP->p == NULL)
539 retval = ENOMEM;
540 else {
541 int status;
542
543 status = pdbheadRead(pdbP->p, fileP);
544
545 if (status != 0)
546 retval = status;
547 else {
548 pdbP->i = ipdb_image_alloc(pdbP->p->name, IMG_GRAY, 0, 0);
549 if (pdbP->i == NULL)
550 retval = ENOMEM;
551 else {
552 int status;
553 status = rechdrRead(pdbP->i->r, fileP);
554 if (status != 0)
555 retval = status;
556 else {
557 if (pdbP->p->num_recs > 1) {
558 pdbP->t = ipdb_text_alloc(NULL);
559 if (pdbP->t == NULL)
560 retval = ENOMEM;
561 else {
562 int status;
563 status = rechdrRead(pdbP->t->r, fileP);
564 if (status != 0)
565 retval = status;
566 else
567 retval = 0;
568 }
569 } else
570 retval = 0;
571
572 if (retval == 0) {
573 uint32_t const offset =
574 pdbP->t == NULL ?
575 UNKNOWN_OFFSET : pdbP->t->r->offset - 1;
576
577 int status;
578
579 status = imageRead(pdbP->i, offset, fileP, verbose);
580 if (status != 0)
581 retval = status;
582 else {
583 if (pdbP->t != NULL) {
584 int status;
585
586 status = textRead(pdbP->t, fileP);
587 if (status != 0)
588 retval = status;
589 }
590 }
591 }
592 }
593 }
594 }
595 }
596 return retval;
597 }
598
599
600
601 static void
g16unpack(const uint8_t * const p,uint8_t * const g,int const w)602 g16unpack(const uint8_t * const p,
603 uint8_t * const g,
604 int const w) {
605
606 static const uint8_t pal[] =
607 {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
608 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
609 const uint8_t * seg;
610 unsigned int i;
611
612 for (i = 0, seg = p; i < w; i += 2, ++seg) {
613 g[i + 0] = pal[getg16pixel(*seg, 0)];
614 g[i + 1] = pal[getg16pixel(*seg, 1)];
615 }
616 }
617
618
619
620 static void
gunpack(const uint8_t * const p,uint8_t * const g,int const w)621 gunpack(const uint8_t * const p,
622 uint8_t * const g,
623 int const w) {
624
625 static const uint8_t pal[] = {0xff, 0xaa, 0x55, 0x00};
626 const uint8_t * seg;
627 unsigned int i;
628
629 for (i = 0, seg = p; i < w; i += 4, ++seg) {
630 g[i + 0] = pal[getgpixel(*seg, 0)];
631 g[i + 1] = pal[getgpixel(*seg, 1)];
632 g[i + 2] = pal[getgpixel(*seg, 2)];
633 g[i + 3] = pal[getgpixel(*seg, 3)];
634 }
635 }
636
637
638
639 static void
munpack(const uint8_t * const p,uint8_t * const b,int const w)640 munpack(const uint8_t * const p,
641 uint8_t * const b,
642 int const w) {
643
644 static const uint8_t pal[] = {PAM_BW_WHITE, PAM_BLACK};
645 const uint8_t * seg;
646 unsigned int i;
647
648 for (i = 0, seg = p; i < w; i += 8, ++seg) {
649 b[i + 0] = pal[getmpixel(*seg, 0)];
650 b[i + 1] = pal[getmpixel(*seg, 1)];
651 b[i + 2] = pal[getmpixel(*seg, 2)];
652 b[i + 3] = pal[getmpixel(*seg, 3)];
653 b[i + 4] = pal[getmpixel(*seg, 4)];
654 b[i + 5] = pal[getmpixel(*seg, 5)];
655 b[i + 6] = pal[getmpixel(*seg, 6)];
656 b[i + 7] = pal[getmpixel(*seg, 7)];
657 }
658 }
659
660
661
662 static void
g16row(IPDB * const pdbP,unsigned int const row,uint8_t * const buffer)663 g16row(IPDB * const pdbP,
664 unsigned int const row,
665 uint8_t * const buffer) {
666
667 g16unpack(ipdb_img_row(pdbP->i, row), buffer, ipdb_width(pdbP));
668 }
669
670
671
672 static void
grow(IPDB * const pdbP,unsigned int const row,uint8_t * const buffer)673 grow(IPDB * const pdbP,
674 unsigned int const row,
675 uint8_t * const buffer) {
676
677 gunpack(ipdb_img_row(pdbP->i, row), buffer, ipdb_width(pdbP));
678 }
679
680
681
682 static void
mrow(IPDB * const pdbP,unsigned int const row,uint8_t * const buffer)683 mrow(IPDB * const pdbP,
684 unsigned int const row,
685 uint8_t * const buffer) {
686
687 munpack(ipdb_img_row(pdbP->i, row), buffer, ipdb_width(pdbP));
688 }
689
690
691
692 static void
writeImgPam(IPDB * const pdbP,FILE * const ofP)693 writeImgPam(IPDB * const pdbP,
694 FILE * const ofP) {
695
696 struct pam pam;
697 tuple * tupleRow;
698 unsigned int row;
699 uint8_t * imgRow;
700
701 MALLOCARRAY(imgRow, ipdb_width(pdbP));
702
703 pam.size = sizeof(pam);
704 pam.len = PAM_STRUCT_SIZE(tuple_type);
705 pam.file = ofP;
706 pam.plainformat = 0;
707 pam.width = ipdb_width(pdbP);
708 pam.height = ipdb_height(pdbP);
709 pam.depth = 1;
710 pam.maxval = ipdb_type(pdbP) == IMG_MONO ? 1 : 255;
711 pam.bytes_per_sample = pnm_bytespersample(pam.maxval);
712 pam.format = PAM_FORMAT;
713 strcpy(pam.tuple_type,
714 ipdb_type(pdbP) == IMG_MONO ?
715 PAM_PBM_TUPLETYPE : PAM_PGM_TUPLETYPE);
716
717 pnm_writepaminit(&pam);
718
719 tupleRow = pnm_allocpamrow(&pam);
720
721 for (row = 0; row < pam.height; ++row) {
722 unsigned int col;
723
724
725 if (ipdb_type(pdbP) == IMG_MONO)
726 mrow(pdbP, row, imgRow);
727 else if (ipdb_type(pdbP) == IMG_GRAY)
728 grow(pdbP, row, imgRow);
729 else
730 g16row(pdbP, row, imgRow);
731
732 for (col = 0; col < pam.width; ++col)
733 tupleRow[col][0] = imgRow[col];
734
735 pnm_writepamrow(&pam, tupleRow);
736 }
737 pnm_freepamrow(tupleRow);
738
739 free(imgRow);
740 }
741
742
743
744 static void
writeText(IPDB * const pdbP,const char * const name)745 writeText(IPDB * const pdbP,
746 const char * const name) {
747
748 const char * const note = ipdb_text(pdbP);
749
750 FILE * fP;
751
752 if (name == NULL || note == NULL) {
753 } else {
754 fP = pm_openw(name);
755 if (fP == NULL)
756 pm_error("Could not open note file '%s' for output", name);
757
758 fprintf(fP, "%s\n", note);
759
760 pm_close(fP);
761 }
762 }
763
764
765
766 int
main(int argc,const char ** argv)767 main(int argc, const char ** argv) {
768
769 struct cmdlineInfo cmdline;
770 FILE * ifP;
771 IPDB * pdbP;
772 int status;
773
774 pm_proginit(&argc, argv);
775
776 parseCommandLine(argc, argv, &cmdline);
777
778 ifP = pm_openr(cmdline.inputFileName);
779
780 pdbP = ipdb_alloc(NULL);
781 if (pdbP == NULL)
782 pm_error("Could not allocate IPDB structure.");
783
784 status = ipdbRead(pdbP, ifP, cmdline.verbose);
785 if (status != 0)
786 pm_error("Image header read error: %s.", ipdb_err(status));
787
788 writeImgPam(pdbP, stdout);
789
790 writeText(pdbP, cmdline.notefile);
791
792 ipdb_free(pdbP);
793
794 pm_close(ifP);
795
796 return EXIT_SUCCESS;
797 }
798