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             &notefileSpec,            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