1 /*---------------------------------------------------------------------------
3 wpng - simple PNG-writing program wpng.c
5 This program converts certain NetPBM binary files (grayscale and RGB,
6 maxval = 255) to PNG. Non-interlaced PNGs are written progressively;
7 interlaced PNGs are read and written in one memory-intensive blast.
8 Thanks to Jean-loup Gailly for providing the necessary trick to read
9 interactive text from the keyboard while stdin is redirected.
11 NOTE: includes provisional support for PNM type "8" (portable alphamap)
12 images, presumed to be a 32-bit interleaved RGBA format; no pro-
13 vision for possible interleaved grayscale+alpha (16-bit) format.
14 THIS IS UNLIKELY TO BECOME AN OFFICIAL NETPBM ALPHA FORMAT!
17 - delete output file if quit before calling any writepng routines
18 - process backspace with -text option under DOS/Win? (currently get ^H)
20 ---------------------------------------------------------------------------
23 - 1.01: initial public release
24 - 1.02: modified to allow abbreviated options
25 - 1.03: removed extraneous character from usage screen; fixed bug in
28 ---------------------------------------------------------------------------
30 Copyright (c) 1998-2000 Greg Roelofs. All rights reserved.
32 This software is provided "as is," without warranty of any kind,
33 express or implied. In no event shall the author or contributors
34 be held liable for any damages arising in any way from the use of
37 Permission is granted to anyone to use this software for any purpose,
38 including commercial applications, and to alter it and redistribute
39 it freely, subject to the following restrictions:
41 1. Redistributions of source code must retain the above copyright
42 notice, disclaimer, and this list of conditions.
43 2. Redistributions in binary form must reproduce the above copyright
44 notice, disclaimer, and this list of conditions in the documenta-
45 tion and/or other materials provided with the distribution.
46 3. All advertising materials mentioning features or use of this
47 software must display the following acknowledgment:
49 This product includes software developed by Greg Roelofs
50 and contributors for the book, "PNG: The Definitive Guide,"
51 published by O'Reilly and Associates.
53 ---------------------------------------------------------------------------*/
55 #define PROGNAME "wpng"
56 #define VERSION "1.03 of 19 March 2000"
57 #define APPNAME "Simple PGM/PPM/PAM to PNG Converter"
59 #if defined(__MSDOS__) || defined(__OS2__)
61 #elif defined(_WIN32) || defined(__WIN32__)
68 #include <setjmp.h> /* for jmpbuf declaration in writepng.h */
72 # include <io.h> /* for isatty(), setmode() prototypes */
73 # include <fcntl.h> /* O_BINARY for fdopen() without text translation */
76 # define getch() _read_kbd(0, 1, 0) /* need getche() */
81 # define getch() getkey() /* GRR: need getche() */
83 # include <conio.h> /* for getche() console input */
85 # endif /* ?__EMX__ */
86 # define FGETS(buf,len,stream) dos_kbd_gets(buf,len)
88 # include <unistd.h> /* for isatty() prototype */
92 /* #define DEBUG : this enables the Trace() macros */
94 /* #define FORBID_LATIN1_CTRL : this requires the user to re-enter any
95 text that includes control characters discouraged by the PNG spec; text
96 that includes an escape character (27) must be re-entered regardless */
98 #include "writepng.h" /* typedefs, common macros, writepng prototypes */
102 /* local prototypes */
104 static int wpng_isvalid_latin1(uch *p, int len);
105 static void wpng_cleanup(void);
108 static char *dos_kbd_gets(char *buf, int len);
113 static mainprog_info wpng_info; /* lone global */
117 int main(int argc, char **argv)
123 FILE *tmpfile; /* or we could just use keybd, since no overlap */
126 char *inname = NULL, outname[256];
127 char *p, pnmchar, pnmline[256];
128 char *bgstr, *textbuf = NULL;
134 double LUT_exponent; /* just the lookup table */
135 double CRT_exponent = 2.2; /* just the monitor */
136 double default_display_exponent; /* whole display system */
137 double default_gamma = 0.0;
140 wpng_info.infile = NULL;
141 wpng_info.outfile = NULL;
142 wpng_info.image_data = NULL;
143 wpng_info.row_pointers = NULL;
144 wpng_info.filter = FALSE;
145 wpng_info.interlaced = FALSE;
146 wpng_info.have_bg = FALSE;
147 wpng_info.have_time = FALSE;
148 wpng_info.have_text = 0;
149 wpng_info.gamma = 0.0;
152 /* First get the default value for our display-system exponent, i.e.,
153 * the product of the CRT exponent and the exponent corresponding to
154 * the frame-buffer's lookup table (LUT), if any. If the PNM image
155 * looks correct on the user's display system, its file gamma is the
156 * inverse of this value. (Note that this is not an exhaustive list
157 * of LUT values--e.g., OpenStep has a lot of weird ones--but it should
158 * cover 99% of the current possibilities. This section must ensure
159 * that default_display_exponent is positive.) */
162 /* third-party utilities can modify the default LUT exponent */
163 LUT_exponent = 1.0 / 2.2;
165 if (some_next_function_that_returns_gamma(&next_gamma))
166 LUT_exponent = 1.0 / next_gamma;
169 LUT_exponent = 1.0 / 1.7;
170 /* there doesn't seem to be any documented function to
171 * get the "gamma" value, so we do it the hard way */
172 tmpfile = fopen("/etc/config/system.glGammaVal", "r");
176 fgets(tmpline, 80, tmpfile);
178 sgi_gamma = atof(tmpline);
180 LUT_exponent = 1.0 / sgi_gamma;
182 #elif defined(Macintosh)
183 LUT_exponent = 1.8 / 2.61;
185 if (some_mac_function_that_returns_gamma(&mac_gamma))
186 LUT_exponent = mac_gamma / 2.61;
189 LUT_exponent = 1.0; /* assume no LUT: most PCs */
192 /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
193 default_display_exponent = LUT_exponent * CRT_exponent;
196 /* If the user has set the SCREEN_GAMMA environment variable as suggested
197 * (somewhat imprecisely) in the libpng documentation, use that; otherwise
198 * use the default value we just calculated. Either way, the user may
199 * override this via a command-line option. */
201 if ((p = getenv("SCREEN_GAMMA")) != NULL) {
202 double exponent = atof(p);
205 default_gamma = 1.0 / exponent;
208 if (default_gamma == 0.0)
209 default_gamma = 1.0 / default_display_exponent;
212 /* Now parse the command line for options and the PNM filename. */
214 while (*++argv && !error) {
215 if (!strncmp(*argv, "-i", 2)) {
216 wpng_info.interlaced = TRUE;
217 } else if (!strncmp(*argv, "-time", 3)) {
218 wpng_info.modtime = time(NULL);
219 wpng_info.have_time = TRUE;
220 } else if (!strncmp(*argv, "-text", 3)) {
222 } else if (!strncmp(*argv, "-gamma", 2)) {
226 wpng_info.gamma = atof(*argv);
227 if (wpng_info.gamma <= 0.0)
229 else if (wpng_info.gamma > 1.01)
230 fprintf(stderr, PROGNAME
231 " warning: file gammas are usually less than 1.0\n");
233 } else if (!strncmp(*argv, "-bgcolor", 4)) {
238 if (strlen(bgstr) != 7 || bgstr[0] != '#')
241 unsigned r, g, b; /* this way quiets compiler warnings */
243 sscanf(bgstr+1, "%2x%2x%2x", &r, &g, &b);
244 wpng_info.bg_red = (uch)r;
245 wpng_info.bg_green = (uch)g;
246 wpng_info.bg_blue = (uch)b;
247 wpng_info.have_bg = TRUE;
253 if (argv[1]) /* shouldn't be any more args after filename */
256 ++error; /* not expecting any other options */
261 /* open the input and output files, or register an error and abort */
265 fprintf(stderr, PROGNAME
266 ": must give input filename or provide image data via stdin\n");
270 /* some buggy C libraries require BOTH setmode() and fdopen(bin) */
271 setmode(fileno(stdin), O_BINARY);
272 setmode(fileno(stdout), O_BINARY);
274 if ((wpng_info.infile = fdopen(fileno(stdin), "rb")) == NULL) {
275 fprintf(stderr, PROGNAME
276 ": unable to reopen stdin in binary mode\n");
279 if ((wpng_info.outfile = fdopen(fileno(stdout), "wb")) == NULL) {
280 fprintf(stderr, PROGNAME
281 ": unable to reopen stdout in binary mode\n");
282 fclose(wpng_info.infile);
285 wpng_info.filter = TRUE;
287 } else if ((len = strlen(inname)) > 250) {
288 fprintf(stderr, PROGNAME ": input filename is too long [%d chars]\n",
291 } else if (!(wpng_info.infile = fopen(inname, "rb"))) {
292 fprintf(stderr, PROGNAME ": can't open input file [%s]\n", inname);
297 fgets(pnmline, 256, wpng_info.infile);
298 if (pnmline[0] != 'P' || ((pnmchar = pnmline[1]) != '5' &&
299 pnmchar != '6' && pnmchar != '8'))
301 fprintf(stderr, PROGNAME
302 ": input file [%s] is not a binary PGM, PPM or PAM file\n",
306 wpng_info.pnmtype = (int)(pnmchar - '0');
307 if (wpng_info.pnmtype != 8)
308 wpng_info.have_bg = FALSE; /* no need for bg if opaque */
310 fgets(pnmline, 256, wpng_info.infile); /* lose any comments */
311 } while (pnmline[0] == '#');
312 sscanf(pnmline, "%ld %ld", &wpng_info.width, &wpng_info.height);
314 fgets(pnmline, 256, wpng_info.infile); /* more comment lines */
315 } while (pnmline[0] == '#');
316 sscanf(pnmline, "%d", &maxval);
317 if (wpng_info.width <= 0L || wpng_info.height <= 0L ||
320 fprintf(stderr, PROGNAME
321 ": only positive width/height, maxval == 255 allowed \n");
324 wpng_info.sample_depth = 8; /* <==> maxval 255 */
326 if (!wpng_info.filter) {
327 /* make outname from inname */
328 if ((p = strrchr(inname, '.')) == NULL ||
329 (p - inname) != (len - 4))
331 strcpy(outname, inname);
332 strcpy(outname+len, ".png");
335 strncpy(outname, inname, len);
336 strcpy(outname+len, ".png");
338 /* check if outname already exists; if not, open */
339 if ((wpng_info.outfile = fopen(outname, "rb")) != NULL) {
340 fprintf(stderr, PROGNAME ": output file exists [%s]\n",
342 fclose(wpng_info.outfile);
344 } else if (!(wpng_info.outfile = fopen(outname, "wb"))) {
345 fprintf(stderr, PROGNAME ": can't open output file [%s]\n",
352 fclose(wpng_info.infile);
353 wpng_info.infile = NULL;
354 if (wpng_info.filter) {
355 fclose(wpng_info.outfile);
356 wpng_info.outfile = NULL;
362 /* if we had any errors, print usage and die horrible death...arrr! */
365 fprintf(stderr, "\n%s %s: %s\n", PROGNAME, VERSION, APPNAME);
366 writepng_version_info();
368 "Usage: %s [-gamma exp] [-bgcolor bg] [-text] [-time] [-interlace] pnmfile\n"
369 "or: ... | %s [-gamma exp] [-bgcolor bg] [-text] [-time] [-interlace] | ...\n"
370 " exp \ttransfer-function exponent (``gamma'') of the image in\n"
371 "\t\t floating-point format (e.g., ``%.5f''); if image looks\n"
372 "\t\t correct on given display system, image gamma is equal to\n"
373 "\t\t inverse of display-system exponent, i.e., 1 / (LUT * CRT)\n"
374 "\t\t (where LUT = lookup-table exponent and CRT = CRT exponent;\n"
375 "\t\t first varies, second is usually 2.2, all are positive)\n"
376 " bg \tdesired background color for alpha-channel images, in\n"
377 "\t\t 7-character hex RGB format (e.g., ``#ff7700'' for orange:\n"
378 "\t\t same as HTML colors)\n"
379 " -text\tprompt interactively for text info (tEXt chunks)\n"
380 " -time\tinclude a tIME chunk (last modification time)\n"
381 " -interlace\twrite interlaced PNG image\n"
383 "pnmfile or stdin must be a binary PGM (`P5'), PPM (`P6') or (extremely\n"
384 "unofficial and unsupported!) PAM (`P8') file. Currently it is required\n"
385 "to have maxval == 255 (i.e., no scaling). If pnmfile is specified, it\n"
386 "is converted to the corresponding PNG file with the same base name but a\n"
387 "``.png'' extension; files read from stdin are converted and sent to stdout.\n"
388 "The conversion is progressive (low memory usage) unless interlacing is\n"
389 "requested; in that case the whole image will be buffered in memory and\n"
390 "written in one call.\n"
391 "\n", PROGNAME, PROGNAME, default_gamma);
396 /* prepare the text buffers for libpng's use; note that even though
397 * PNG's png_text struct includes a length field, we don't have to fill
402 (keybd = fdopen(fileno(stderr), "r")) != NULL &&
404 (textbuf = (char *)malloc((5 + 9)*75)) != NULL)
406 int i, valid, result;
409 "Enter text info (no more than 72 characters per line);\n");
410 fprintf(stderr, "to skip a field, hit the <Enter> key.\n");
411 /* note: just <Enter> leaves len == 1 */
415 p = textbuf + TEXT_TITLE_OFFSET;
416 fprintf(stderr, " Title: ");
418 if (FGETS(p, 74, keybd) && (len = strlen(p)) > 1) {
419 if (p[len-1] == '\n')
422 wpng_info.have_text |= TEXT_TITLE;
423 if ((result = wpng_isvalid_latin1((uch *)p, len)) >= 0) {
424 fprintf(stderr, " " PROGNAME " warning: character code"
425 " %u is %sdiscouraged by the PNG\n specification "
426 "[first occurrence was at character position #%d]\n",
427 (unsigned)p[result], (p[result] == 27)? "strongly " : "",
430 #ifdef FORBID_LATIN1_CTRL
431 wpng_info.have_text &= ~TEXT_TITLE;
434 if (p[result] == 27) { /* escape character */
435 wpng_info.have_text &= ~TEXT_TITLE;
445 p = textbuf + TEXT_AUTHOR_OFFSET;
446 fprintf(stderr, " Author: ");
448 if (FGETS(p, 74, keybd) && (len = strlen(p)) > 1) {
449 if (p[len-1] == '\n')
451 wpng_info.author = p;
452 wpng_info.have_text |= TEXT_AUTHOR;
453 if ((result = wpng_isvalid_latin1((uch *)p, len)) >= 0) {
454 fprintf(stderr, " " PROGNAME " warning: character code"
455 " %u is %sdiscouraged by the PNG\n specification "
456 "[first occurrence was at character position #%d]\n",
457 (unsigned)p[result], (p[result] == 27)? "strongly " : "",
460 #ifdef FORBID_LATIN1_CTRL
461 wpng_info.have_text &= ~TEXT_AUTHOR;
464 if (p[result] == 27) { /* escape character */
465 wpng_info.have_text &= ~TEXT_AUTHOR;
475 p = textbuf + TEXT_DESC_OFFSET;
476 fprintf(stderr, " Description (up to 9 lines):\n");
477 for (i = 1; i < 10; ++i) {
478 fprintf(stderr, " [%d] ", i);
480 if (FGETS(p, 74, keybd) && (len = strlen(p)) > 1)
481 p += len; /* now points at NULL; char before is newline */
485 if ((len = p - (textbuf + TEXT_DESC_OFFSET)) > 1) {
490 wpng_info.desc = textbuf + TEXT_DESC_OFFSET;
491 wpng_info.have_text |= TEXT_DESC;
492 p = textbuf + TEXT_DESC_OFFSET;
493 if ((result = wpng_isvalid_latin1((uch *)p, len)) >= 0) {
494 fprintf(stderr, " " PROGNAME " warning: character code"
495 " %u is %sdiscouraged by the PNG\n specification "
496 "[first occurrence was at character position #%d]\n",
497 (unsigned)p[result], (p[result] == 27)? "strongly " : "",
500 #ifdef FORBID_LATIN1_CTRL
501 wpng_info.have_text &= ~TEXT_DESC;
504 if (p[result] == 27) { /* escape character */
505 wpng_info.have_text &= ~TEXT_DESC;
515 p = textbuf + TEXT_COPY_OFFSET;
516 fprintf(stderr, " Copyright: ");
518 if (FGETS(p, 74, keybd) && (len = strlen(p)) > 1) {
519 if (p[len-1] == '\n')
521 wpng_info.copyright = p;
522 wpng_info.have_text |= TEXT_COPY;
523 if ((result = wpng_isvalid_latin1((uch *)p, len)) >= 0) {
524 fprintf(stderr, " " PROGNAME " warning: character code"
525 " %u is %sdiscouraged by the PNG\n specification "
526 "[first occurrence was at character position #%d]\n",
527 (unsigned)p[result], (p[result] == 27)? "strongly " : "",
530 #ifdef FORBID_LATIN1_CTRL
531 wpng_info.have_text &= ~TEXT_COPY;
534 if (p[result] == 27) { /* escape character */
535 wpng_info.have_text &= ~TEXT_COPY;
545 p = textbuf + TEXT_EMAIL_OFFSET;
546 fprintf(stderr, " E-mail: ");
548 if (FGETS(p, 74, keybd) && (len = strlen(p)) > 1) {
549 if (p[len-1] == '\n')
552 wpng_info.have_text |= TEXT_EMAIL;
553 if ((result = wpng_isvalid_latin1((uch *)p, len)) >= 0) {
554 fprintf(stderr, " " PROGNAME " warning: character code"
555 " %u is %sdiscouraged by the PNG\n specification "
556 "[first occurrence was at character position #%d]\n",
557 (unsigned)p[result], (p[result] == 27)? "strongly " : "",
560 #ifdef FORBID_LATIN1_CTRL
561 wpng_info.have_text &= ~TEXT_EMAIL;
564 if (p[result] == 27) { /* escape character */
565 wpng_info.have_text &= ~TEXT_EMAIL;
575 p = textbuf + TEXT_URL_OFFSET;
576 fprintf(stderr, " URL: ");
578 if (FGETS(p, 74, keybd) && (len = strlen(p)) > 1) {
579 if (p[len-1] == '\n')
582 wpng_info.have_text |= TEXT_URL;
583 if ((result = wpng_isvalid_latin1((uch *)p, len)) >= 0) {
584 fprintf(stderr, " " PROGNAME " warning: character code"
585 " %u is %sdiscouraged by the PNG\n specification "
586 "[first occurrence was at character position #%d]\n",
587 (unsigned)p[result], (p[result] == 27)? "strongly " : "",
590 #ifdef FORBID_LATIN1_CTRL
591 wpng_info.have_text &= ~TEXT_URL;
594 if (p[result] == 27) { /* escape character */
595 wpng_info.have_text &= ~TEXT_URL;
608 fprintf(stderr, PROGNAME ": unable to allocate memory for text\n");
610 wpng_info.have_text = 0;
614 /* allocate libpng stuff, initialize transformations, write pre-IDAT data */
616 if ((rc = writepng_init(&wpng_info)) != 0) {
619 fprintf(stderr, PROGNAME
620 ": libpng initialization problem (longjmp)\n");
623 fprintf(stderr, PROGNAME ": insufficient memory\n");
626 fprintf(stderr, PROGNAME
627 ": internal logic error (unexpected PNM type)\n");
630 fprintf(stderr, PROGNAME
631 ": unknown writepng_init() error\n");
638 /* free textbuf, since it's a completely local variable and all text info
639 * has just been written to the PNG file */
641 if (text && textbuf) {
647 /* calculate rowbytes on basis of image type; note that this becomes much
648 * more complicated if we choose to support PBM type, ASCII PNM types, or
649 * 16-bit-per-sample binary data [currently not an official NetPBM type] */
651 if (wpng_info.pnmtype == 5)
652 rowbytes = wpng_info.width;
653 else if (wpng_info.pnmtype == 6)
654 rowbytes = wpng_info.width * 3;
655 else /* if (wpng_info.pnmtype == 8) */
656 rowbytes = wpng_info.width * 4;
659 /* read and write the image, either in its entirety (if writing interlaced
660 * PNG) or row by row (if non-interlaced) */
662 fprintf(stderr, "Encoding image data...\n");
665 if (wpng_info.interlaced) {
668 ulg image_bytes = rowbytes * wpng_info.height; /* overflow? */
670 wpng_info.image_data = (uch *)malloc(image_bytes);
671 wpng_info.row_pointers = (uch **)malloc(wpng_info.height*sizeof(uch *));
672 if (wpng_info.image_data == NULL || wpng_info.row_pointers == NULL) {
673 fprintf(stderr, PROGNAME ": insufficient memory for image data\n");
674 writepng_cleanup(&wpng_info);
678 for (i = 0; i < wpng_info.height; ++i)
679 wpng_info.row_pointers[i] = wpng_info.image_data + i*rowbytes;
680 bytes = fread(wpng_info.image_data, 1, image_bytes, wpng_info.infile);
681 if (bytes != image_bytes) {
682 fprintf(stderr, PROGNAME ": expected %lu bytes, got %lu bytes\n",
684 fprintf(stderr, " (continuing anyway)\n");
686 if (writepng_encode_image(&wpng_info) != 0) {
687 fprintf(stderr, PROGNAME
688 ": libpng problem (longjmp) while writing image data\n");
689 writepng_cleanup(&wpng_info);
694 } else /* not interlaced: write progressively (row by row) */ {
698 wpng_info.image_data = (uch *)malloc(rowbytes);
699 if (wpng_info.image_data == NULL) {
700 fprintf(stderr, PROGNAME ": insufficient memory for row data\n");
701 writepng_cleanup(&wpng_info);
706 for (j = wpng_info.height; j > 0L; --j) {
707 bytes = fread(wpng_info.image_data, 1, rowbytes, wpng_info.infile);
708 if (bytes != rowbytes) {
709 fprintf(stderr, PROGNAME
710 ": expected %lu bytes, got %lu bytes (row %ld)\n", rowbytes,
711 bytes, wpng_info.height-j);
715 if (writepng_encode_row(&wpng_info) != 0) {
716 fprintf(stderr, PROGNAME
717 ": libpng problem (longjmp) while writing row %ld\n",
724 writepng_cleanup(&wpng_info);
728 if (writepng_encode_finish(&wpng_info) != 0) {
729 fprintf(stderr, PROGNAME ": error on final libpng call\n");
730 writepng_cleanup(&wpng_info);
737 /* OK, we're done (successfully): clean up all resources and quit */
739 fprintf(stderr, "Done.\n");
742 writepng_cleanup(&wpng_info);
752 static int wpng_isvalid_latin1(uch *p, int len)
756 for (i = 0; i < len; ++i) {
757 if (p[i] == 10 || (p[i] > 31 && p[i] < 127) || p[i] > 160)
758 continue; /* character is completely OK */
759 if (result < 0 || (p[result] != 27 && p[i] == 27))
760 result = i; /* mark location of first questionable one */
761 } /* or of first escape character (bad) */
770 static void wpng_cleanup(void)
772 if (wpng_info.outfile) {
773 fclose(wpng_info.outfile);
774 wpng_info.outfile = NULL;
777 if (wpng_info.infile) {
778 fclose(wpng_info.infile);
779 wpng_info.infile = NULL;
782 if (wpng_info.image_data) {
783 free(wpng_info.image_data);
784 wpng_info.image_data = NULL;
787 if (wpng_info.row_pointers) {
788 free(wpng_info.row_pointers);
789 wpng_info.row_pointers = NULL;
798 static char *dos_kbd_gets(char *buf, int len)
803 buf[count++] = ch = getche();
804 } while (ch != '\r' && count < len-1);
806 buf[count--] = '\0'; /* terminate string */
807 if (buf[count] == '\r') /* Enter key makes CR, so change to newline */
810 fprintf(stderr, "\n"); /* Enter key does *not* cause a newline */
816 #endif /* DOS_OS2_W32 */