• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  pnm2png.c --- conversion from PBM/PGM/PPM-file to PNG-file
3  *  copyright (C) 1999-2019 by Willem van Schaik <willem at schaik dot com>
4  *
5  *  This software is released under the MIT license. For conditions of
6  *  distribution and use, see the LICENSE file part of this package.
7  */
8 
9 #include <limits.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <fcntl.h>
13 
14 #ifndef BOOL
15 #define BOOL unsigned char
16 #endif
17 #ifndef TRUE
18 #define TRUE ((BOOL) 1)
19 #endif
20 #ifndef FALSE
21 #define FALSE ((BOOL) 0)
22 #endif
23 
24 #include "png.h"
25 
26 /* function prototypes */
27 
28 int main (int argc, char *argv[]);
29 void usage ();
30 BOOL pnm2png (FILE *pnm_file, FILE *png_file, FILE *alpha_file,
31               BOOL interlace, BOOL alpha);
32 BOOL do_pnm2png (png_struct *png_ptr, png_info *info_ptr,
33                  FILE *pnm_file, FILE *alpha_file,
34                  BOOL interlace, BOOL alpha);
35 int fscan_pnm_magic (FILE *pnm_file, char *magic_buf, size_t magic_buf_size);
36 int fscan_pnm_token (FILE *pnm_file, char *token_buf, size_t token_buf_size);
37 int fscan_pnm_uint_32 (FILE *pnm_file, png_uint_32 *num_ptr);
38 png_uint_32 get_pnm_data (FILE *pnm_file, int depth);
39 png_uint_32 get_pnm_value (FILE *pnm_file, int depth);
40 
41 /*
42  *  main
43  */
44 
main(int argc,char * argv[])45 int main (int argc, char *argv[])
46 {
47   FILE *fp_rd = stdin;
48   FILE *fp_al = NULL;
49   FILE *fp_wr = stdout;
50   const char *fname_wr = NULL;
51   BOOL interlace = FALSE;
52   BOOL alpha = FALSE;
53   int argi;
54   int ret;
55 
56   for (argi = 1; argi < argc; argi++)
57   {
58     if (argv[argi][0] == '-')
59     {
60       switch (argv[argi][1])
61       {
62         case 'i':
63           interlace = TRUE;
64           break;
65         case 'a':
66           alpha = TRUE;
67           argi++;
68           if ((fp_al = fopen (argv[argi], "rb")) == NULL)
69           {
70             fprintf (stderr, "PNM2PNG\n");
71             fprintf (stderr, "Error:  alpha-channel file %s does not exist\n",
72                      argv[argi]);
73             exit (1);
74           }
75           break;
76         case 'h':
77         case '?':
78           usage ();
79           exit (0);
80           break;
81         default:
82           fprintf (stderr, "PNM2PNG\n");
83           fprintf (stderr, "Error:  unknown option %s\n", argv[argi]);
84           usage ();
85           exit (1);
86           break;
87       } /* end switch */
88     }
89     else if (fp_rd == stdin)
90     {
91       if ((fp_rd = fopen (argv[argi], "rb")) == NULL)
92       {
93         fprintf (stderr, "PNM2PNG\n");
94         fprintf (stderr, "Error:  file %s does not exist\n", argv[argi]);
95         exit (1);
96       }
97     }
98     else if (fp_wr == stdout)
99     {
100       fname_wr = argv[argi];
101       if ((fp_wr = fopen (argv[argi], "wb")) == NULL)
102       {
103         fprintf (stderr, "PNM2PNG\n");
104         fprintf (stderr, "Error:  cannot create PNG-file %s\n", argv[argi]);
105         exit (1);
106       }
107     }
108     else
109     {
110       fprintf (stderr, "PNM2PNG\n");
111       fprintf (stderr, "Error:  too many parameters\n");
112       usage ();
113       exit (1);
114     }
115   } /* end for */
116 
117 #if defined(O_BINARY) && (O_BINARY != 0)
118   /* set stdin/stdout to binary,
119    * we're reading the PNM always! in binary format
120    */
121   if (fp_rd == stdin)
122     setmode (fileno (stdin), O_BINARY);
123   if (fp_wr == stdout)
124     setmode (fileno (stdout), O_BINARY);
125 #endif
126 
127   /* call the conversion program itself */
128   ret = pnm2png (fp_rd, fp_wr, fp_al, interlace, alpha);
129 
130   /* close input file */
131   fclose (fp_rd);
132   /* close output file */
133   fclose (fp_wr);
134   /* close alpha file */
135   if (alpha)
136     fclose (fp_al);
137 
138   if (!ret)
139   {
140     fprintf (stderr, "PNM2PNG\n");
141     fprintf (stderr, "Error:  unsuccessful converting to PNG-image\n");
142     if (fname_wr)
143       remove (fname_wr); /* no broken output file shall remain behind */
144     exit (1);
145   }
146 
147   return 0;
148 }
149 
150 /*
151  *  usage
152  */
153 
usage()154 void usage ()
155 {
156   fprintf (stderr, "PNM2PNG\n");
157   fprintf (stderr, "   by Willem van Schaik, 1999\n");
158   fprintf (stderr, "Usage:  pnm2png [options] <file>.<pnm> [<file>.png]\n");
159   fprintf (stderr, "   or:  ... | pnm2png [options]\n");
160   fprintf (stderr, "Options:\n");
161   fprintf (stderr, "   -i[nterlace]   write png-file with interlacing on\n");
162   fprintf (stderr,
163       "   -a[lpha] <file>.pgm read PNG alpha channel as pgm-file\n");
164   fprintf (stderr, "   -h | -?  print this help-information\n");
165 }
166 
167 /*
168  *  pnm2png
169  */
170 
pnm2png(FILE * pnm_file,FILE * png_file,FILE * alpha_file,BOOL interlace,BOOL alpha)171 BOOL pnm2png (FILE *pnm_file, FILE *png_file, FILE *alpha_file,
172               BOOL interlace, BOOL alpha)
173 {
174   png_struct    *png_ptr;
175   png_info      *info_ptr;
176   BOOL          ret;
177 
178   /* initialize the libpng context for writing to png_file */
179 
180   png_ptr = png_create_write_struct (png_get_libpng_ver(NULL),
181                                      NULL, NULL, NULL);
182   if (!png_ptr)
183     return FALSE; /* out of memory */
184 
185   info_ptr = png_create_info_struct (png_ptr);
186   if (!info_ptr)
187   {
188     png_destroy_write_struct (&png_ptr, NULL);
189     return FALSE; /* out of memory */
190   }
191 
192   if (setjmp (png_jmpbuf (png_ptr)))
193   {
194     png_destroy_write_struct (&png_ptr, &info_ptr);
195     return FALSE; /* generic libpng error */
196   }
197 
198   png_init_io (png_ptr, png_file);
199 
200   /* do the actual conversion */
201   ret = do_pnm2png (png_ptr, info_ptr, pnm_file, alpha_file, interlace, alpha);
202 
203   /* clean up the libpng structures and their internally-managed data */
204   png_destroy_write_struct (&png_ptr, &info_ptr);
205 
206   return ret;
207 }
208 
209 /*
210  *  do_pnm2png - does the conversion in a fully-initialized libpng context
211  */
212 
do_pnm2png(png_struct * png_ptr,png_info * info_ptr,FILE * pnm_file,FILE * alpha_file,BOOL interlace,BOOL alpha)213 BOOL do_pnm2png (png_struct *png_ptr, png_info *info_ptr,
214                  FILE *pnm_file, FILE *alpha_file,
215                  BOOL interlace, BOOL alpha)
216 {
217   png_byte      **row_pointers;
218   png_byte      *pix_ptr;
219   int           bit_depth;
220   int           color_type;
221   int           channels;
222   char          magic_token[4];
223   BOOL          raw;
224   png_uint_32   width, height, maxval;
225   png_uint_32   row_bytes;
226   png_uint_32   row, col;
227   png_uint_32   val16, i;
228   png_uint_32   alpha_width = 0, alpha_height = 0;
229   int           alpha_depth = 0, alpha_present = 0;
230   BOOL          alpha_raw = FALSE;
231   BOOL          packed_bitmap = FALSE;
232 
233   /* read header of PNM file */
234 
235   if (fscan_pnm_magic (pnm_file, magic_token, sizeof (magic_token)) != 1)
236     return FALSE; /* not a PNM file */
237 
238   if ((magic_token[1] == '1') || (magic_token[1] == '4'))
239   {
240     if ((fscan_pnm_uint_32 (pnm_file, &width) != 1) ||
241         (fscan_pnm_uint_32 (pnm_file, &height) != 1))
242       return FALSE; /* bad PBM file header */
243   } else if ((magic_token[1] == '2') || (magic_token[1] == '5') ||
244              (magic_token[1] == '3') || (magic_token[1] == '6'))
245   {
246     if ((fscan_pnm_uint_32 (pnm_file, &width) != 1) ||
247         (fscan_pnm_uint_32 (pnm_file, &height) != 1) ||
248         (fscan_pnm_uint_32 (pnm_file, &maxval) != 1))
249       return FALSE; /* bad PGM/PPM file header */
250   }
251 
252   if ((magic_token[1] == '1') || (magic_token[1] == '4'))
253   {
254     raw = (magic_token[1] == '4');
255     bit_depth = 1;
256     color_type = PNG_COLOR_TYPE_GRAY;
257     packed_bitmap = TRUE;
258   }
259   else if ((magic_token[1] == '2') || (magic_token[1] == '5'))
260   {
261     raw = (magic_token[1] == '5');
262     color_type = PNG_COLOR_TYPE_GRAY;
263     if (maxval == 0)
264       return FALSE;
265     else if (maxval == 1)
266       bit_depth = 1;
267     else if (maxval <= 3)
268       bit_depth = 2;
269     else if (maxval <= 15)
270       bit_depth = 4;
271     else if (maxval <= 255)
272       bit_depth = 8;
273     else if (maxval <= 65535U)
274       bit_depth = 16;
275     else /* maxval > 65535U */
276       return FALSE;
277   }
278   else if ((magic_token[1] == '3') || (magic_token[1] == '6'))
279   {
280     raw = (magic_token[1] == '6');
281     color_type = PNG_COLOR_TYPE_RGB;
282     if (maxval == 0)
283       return FALSE;
284     else if (maxval == 1)
285       bit_depth = 1;
286     else if (maxval <= 3)
287       bit_depth = 2;
288     else if (maxval <= 15)
289       bit_depth = 4;
290     else if (maxval <= 255)
291       bit_depth = 8;
292     else if (maxval <= 65535U)
293       bit_depth = 16;
294     else /* maxval > 65535U */
295       return FALSE;
296   }
297   else if (magic_token[1] == '7')
298   {
299     fprintf (stderr, "PNM2PNG can't read PAM (P7) files\n");
300     return FALSE;
301   }
302   else
303   {
304     return FALSE;
305   }
306 
307   /* read header of PGM file with alpha channel */
308 
309   if (alpha)
310   {
311     if ((fscan_pnm_magic (alpha_file, magic_token, sizeof (magic_token)) != 1)
312         || ((magic_token[1] != '2') && (magic_token[1] != '5')))
313       return FALSE; /* not a PGM file */
314 
315     if ((fscan_pnm_uint_32 (alpha_file, &alpha_width) != 1) ||
316         (fscan_pnm_uint_32 (alpha_file, &alpha_height) != 1) ||
317         (fscan_pnm_uint_32 (alpha_file, &maxval) != 1))
318       return FALSE; /* bad PGM file header */
319 
320     if ((alpha_width != width) || (alpha_height != height))
321       return FALSE; /* mismatched PGM dimensions */
322 
323     alpha_raw = (magic_token[1] == '5');
324     color_type |= PNG_COLOR_MASK_ALPHA;
325     if (maxval == 0)
326       return FALSE;
327     else if (maxval == 1)
328       alpha_depth = 1;
329     else if (maxval <= 3)
330       alpha_depth = 2;
331     else if (maxval <= 15)
332       alpha_depth = 4;
333     else if (maxval <= 255)
334       alpha_depth = 8;
335     else if (maxval <= 65535U)
336       alpha_depth = 16;
337     else /* maxval > 65535U */
338       return FALSE;
339     if (alpha_depth != bit_depth)
340       return FALSE;
341   } /* end if alpha */
342 
343   /* calculate the number of channels and store alpha-presence */
344   if (color_type == PNG_COLOR_TYPE_GRAY)
345     channels = 1;
346   else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
347     channels = 2;
348   else if (color_type == PNG_COLOR_TYPE_RGB)
349     channels = 3;
350   else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
351     channels = 4;
352   else
353     return FALSE; /* NOTREACHED */
354 
355   alpha_present = (channels - 1) % 2;
356 
357   if (packed_bitmap)
358   {
359     /* row data is as many bytes as can fit width x channels x bit_depth */
360     row_bytes = (width * channels * bit_depth + 7) / 8;
361   }
362   else
363   {
364     /* row_bytes is the width x number of channels x (bit-depth / 8) */
365     row_bytes = width * channels * ((bit_depth <= 8) ? 1 : 2);
366   }
367 
368   if ((row_bytes == 0) ||
369       ((size_t) height > (size_t) (-1) / (size_t) row_bytes))
370   {
371     /* too big */
372     return FALSE;
373   }
374 
375   /* allocate the rows using the same memory layout as libpng, and transfer
376    * their ownership to libpng, with the responsibility to clean everything up;
377    * please note the use of png_calloc instead of png_malloc */
378   row_pointers = (png_byte **)
379                  png_calloc (png_ptr, height * sizeof (png_byte *));
380   png_set_rows (png_ptr, info_ptr, row_pointers);
381   png_data_freer (png_ptr, info_ptr, PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_ALL);
382   for (row = 0; row < height; row++)
383   {
384     /* the individual rows should only be allocated after all the previous
385      * steps completed successfully, because libpng must handle correctly
386      * any image allocation left incomplete after an out-of-memory error */
387     row_pointers[row] = (png_byte *) png_malloc (png_ptr, row_bytes);
388   }
389 
390   /* read the data from PNM file */
391 
392   for (row = 0; row < height; row++)
393   {
394     pix_ptr = row_pointers[row];
395     if (packed_bitmap)
396     {
397       for (i = 0; i < row_bytes; i++)
398       {
399         /* png supports this format natively so no conversion is needed */
400         *pix_ptr++ = get_pnm_data (pnm_file, 8);
401       }
402     }
403     else
404     {
405       for (col = 0; col < width; col++)
406       {
407         for (i = 0; i < (png_uint_32) (channels - alpha_present); i++)
408         {
409           if (raw)
410           {
411             *pix_ptr++ = get_pnm_data (pnm_file, bit_depth);
412             if (bit_depth == 16)
413               *pix_ptr++ = get_pnm_data (pnm_file, bit_depth);
414           }
415           else
416           {
417             if (bit_depth <= 8)
418             {
419               *pix_ptr++ = get_pnm_value (pnm_file, bit_depth);
420             }
421             else
422             {
423               val16 = get_pnm_value (pnm_file, bit_depth);
424               *pix_ptr = (png_byte) ((val16 >> 8) & 0xFF);
425               pix_ptr++;
426               *pix_ptr = (png_byte) (val16 & 0xFF);
427               pix_ptr++;
428             }
429           }
430         }
431 
432         if (alpha) /* read alpha-channel from pgm file */
433         {
434           if (alpha_raw)
435           {
436             *pix_ptr++ = get_pnm_data (alpha_file, alpha_depth);
437             if (alpha_depth == 16)
438               *pix_ptr++ = get_pnm_data (alpha_file, alpha_depth);
439           }
440           else
441           {
442             if (alpha_depth <= 8)
443             {
444               *pix_ptr++ = get_pnm_value (alpha_file, bit_depth);
445             }
446             else
447             {
448               val16 = get_pnm_value (alpha_file, bit_depth);
449               *pix_ptr++ = (png_byte) ((val16 >> 8) & 0xFF);
450               *pix_ptr++ = (png_byte) (val16 & 0xFF);
451             }
452           }
453         } /* end if alpha */
454       } /* end if packed_bitmap */
455     } /* end for col */
456   } /* end for row */
457 
458   /* we're going to write more or less the same PNG as the input file */
459   png_set_IHDR (png_ptr, info_ptr, width, height, bit_depth, color_type,
460                 (!interlace) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7,
461                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
462 
463   if (packed_bitmap == TRUE)
464   {
465     png_set_packing (png_ptr);
466     png_set_invert_mono (png_ptr);
467   }
468 
469   /* write the file header information */
470   png_write_info (png_ptr, info_ptr);
471 
472   /* write out the entire image data in one call */
473   png_write_image (png_ptr, row_pointers);
474 
475   /* write the additional chunks to the PNG file (not really needed) */
476   png_write_end (png_ptr, info_ptr);
477 
478   return TRUE;
479 } /* end of pnm2png */
480 
481 /*
482  * fscan_pnm_magic - like fscan_pnm_token below, but expects the magic string
483  *                   to start immediately, without any comment or whitespace,
484  *                   and to match the regex /^P[1-9]$/
485  */
486 
fscan_pnm_magic(FILE * pnm_file,char * magic_buf,size_t magic_buf_size)487 int fscan_pnm_magic (FILE *pnm_file, char *magic_buf, size_t magic_buf_size)
488 {
489   int ret;
490 
491   ret = fgetc (pnm_file);
492   if (ret == EOF) return 0;
493   ungetc (ret, pnm_file);
494   if (ret != 'P') return 0;
495 
496   /* the string buffer must be at least four bytes long, i.e., the capacity
497    * required for strings of at least three characters long, i.e., the minimum
498    * required for ensuring that our magic string is exactly two characters long
499    */
500   if (magic_buf_size < 4) return -1;
501 
502   ret = fscan_pnm_token (pnm_file, magic_buf, magic_buf_size);
503   if (ret < 1) return ret;
504 
505   if ((magic_buf[1] < '1') || (magic_buf[1] > '9')) return 0;
506   if (magic_buf[2] != '\0') return 0;
507 
508   return 1;
509 }
510 
511 /*
512  * fscan_pnm_token - extracts the first string token after whitespace,
513  *                   and (like fscanf) returns the number of successful
514  *                   extractions, which can be either 0 or 1
515  */
516 
fscan_pnm_token(FILE * pnm_file,char * token_buf,size_t token_buf_size)517 int fscan_pnm_token (FILE *pnm_file, char *token_buf, size_t token_buf_size)
518 {
519   size_t i = 0;
520   int ret;
521 
522   /* remove white-space and comment lines */
523   do
524   {
525     ret = fgetc (pnm_file);
526     if (ret == '#')
527     {
528       /* the rest of this line is a comment */
529       do
530       {
531         ret = fgetc (pnm_file);
532       }
533       while ((ret != '\n') && (ret != '\r') && (ret != EOF));
534     }
535     if (ret == EOF) break;
536     token_buf[i] = (char) ret;
537   }
538   while ((ret == '\n') || (ret == '\r') || (ret == ' '));
539 
540   /* read string */
541   do
542   {
543     ret = fgetc (pnm_file);
544     if (ret == EOF) break;
545     if (ret == '0')
546     {
547       /* avoid storing more than one leading '0' in the token buffer,
548        * to ensure that all valid (in-range) numeric inputs can fit in. */
549       if ((i == 0) && (token_buf[i] == '0')) continue;
550     }
551     if (++i == token_buf_size - 1) break;
552     token_buf[i] = (char) ret;
553   }
554   while ((ret != '\n') && (ret != '\r') && (ret != ' '));
555 
556   token_buf[i] = '\0';
557   return (i > 0) ? 1 : 0;
558 }
559 
560 /*
561  * fscan_pnm_uint_32 - like fscan_token above, but expects the extracted token
562  *                     to be numeric, and converts it to an unsigned 32-bit int
563  */
564 
fscan_pnm_uint_32(FILE * pnm_file,png_uint_32 * num_ptr)565 int fscan_pnm_uint_32 (FILE *pnm_file, png_uint_32 *num_ptr)
566 {
567   char token[16];
568   unsigned long token_value;
569   int ret;
570 
571   ret = fscan_pnm_token (pnm_file, token, sizeof (token));
572   if (ret < 1) return ret;
573 
574   if ((token[0] < '0') && (token[0] > '9'))
575     return 0; /* the token starts with junk, or a +/- sign, which is invalid */
576 
577   ret = sscanf (token, "%lu%*c", &token_value);
578   if (ret != 1)
579     return 0; /* the token ends with junk */
580 
581   *num_ptr = (png_uint_32) token_value;
582 
583 #if ULONG_MAX > 0xFFFFFFFFUL
584   /* saturate the converted number, following the fscanf convention */
585   if (token_value > 0xFFFFFFFFUL)
586     *num_ptr = 0xFFFFFFFFUL;
587 #endif
588 
589   return 1;
590 }
591 
592 /*
593  *  get_pnm_data - takes first byte and converts into next pixel value,
594  *                 taking as many bits as defined by bit-depth and
595  *                 using the bit-depth to fill up a byte (0x0A -> 0xAA)
596  */
597 
get_pnm_data(FILE * pnm_file,int depth)598 png_uint_32 get_pnm_data (FILE *pnm_file, int depth)
599 {
600   static int bits_left = 0;
601   static int old_value = 0;
602   static int mask = 0;
603   png_uint_32 ret_value;
604   int i;
605 
606   if (mask == 0)
607     for (i = 0; i < depth; i++)
608       mask = (mask >> 1) | 0x80;
609 
610   if (bits_left <= 0)
611   {
612     /* FIXME:
613      * signal the premature end of file, instead of pretending to read zeroes
614      */
615     old_value = fgetc (pnm_file);
616     if (old_value == EOF) return 0;
617     bits_left = 8;
618   }
619 
620   ret_value = old_value & mask;
621   for (i = 1; i < (8 / depth); i++)
622     ret_value = ret_value || (ret_value >> depth);
623 
624   old_value = (old_value << depth) & 0xFF;
625   bits_left -= depth;
626 
627   return ret_value;
628 }
629 
630 /*
631  *  get_pnm_value - takes first (numeric) string and converts into number,
632  *                  using the bit-depth to fill up a byte (0x0A -> 0xAA)
633  */
634 
get_pnm_value(FILE * pnm_file,int depth)635 png_uint_32 get_pnm_value (FILE *pnm_file, int depth)
636 {
637   static png_uint_32 mask = 0;
638   png_uint_32 ret_value;
639   int i;
640 
641   if (mask == 0)
642     for (i = 0; i < depth; i++)
643       mask = (mask << 1) | 0x01;
644 
645   if (fscan_pnm_uint_32 (pnm_file, &ret_value) != 1)
646   {
647     /* FIXME:
648      * signal the invalid numeric tokens or the premature end of file,
649      * instead of pretending to read zeroes
650      */
651     return 0;
652   }
653 
654   ret_value &= mask;
655 
656   if (depth < 8)
657     for (i = 0; i < (8 / depth); i++)
658       ret_value = (ret_value << depth) || ret_value;
659 
660   return ret_value;
661 }
662 
663 /* end of source */
664