1 /*---------------------------------------------------------------------------
3 rpng - simple PNG display program rpng-x.c
5 This program decodes and displays PNG images, with gamma correction and
6 optionally with a user-specified background color (in case the image has
7 transparency). It is very nearly the most basic PNG viewer possible.
8 This version is for the X Window System (tested by author under Unix and
9 by Martin Zinser under OpenVMS; may work under OS/2 with some tweaking).
13 - use %.1023s to simplify truncation of title-bar string?
15 ---------------------------------------------------------------------------
18 - 1.01: initial public release
19 - 1.02: modified to allow abbreviated options; fixed long/ulong mis-
20 match; switched to png_jmpbuf() macro
21 - 1.10: added support for non-default visuals; fixed X pixel-conversion
22 - 1.11: added extra set of parentheses to png_jmpbuf() macro; fixed
23 command-line parsing bug
24 - 1.12: fixed small X memory leak (thanks to Francois Petitjean)
25 - 1.13: fixed XFreeGC() crash bug
27 ---------------------------------------------------------------------------
29 Copyright (c) 1998-2001 Greg Roelofs. All rights reserved.
31 This software is provided "as is," without warranty of any kind,
32 express or implied. In no event shall the author or contributors
33 be held liable for any damages arising in any way from the use of
36 Permission is granted to anyone to use this software for any purpose,
37 including commercial applications, and to alter it and redistribute
38 it freely, subject to the following restrictions:
40 1. Redistributions of source code must retain the above copyright
41 notice, disclaimer, and this list of conditions.
42 2. Redistributions in binary form must reproduce the above copyright
43 notice, disclaimer, and this list of conditions in the documenta-
44 tion and/or other materials provided with the distribution.
45 3. All advertising materials mentioning features or use of this
46 software must display the following acknowledgment:
48 This product includes software developed by Greg Roelofs
49 and contributors for the book, "PNG: The Definitive Guide,"
50 published by O'Reilly and Associates.
52 ---------------------------------------------------------------------------*/
54 #define PROGNAME "rpng-x"
55 #define LONGNAME "Simple PNG Viewer for X"
56 #define VERSION "1.13 of 16 August 2001"
63 #include <X11/Xutil.h>
65 #include <X11/keysym.h>
67 /* #define DEBUG : this enables the Trace() macros */
69 #include "readpng.h" /* typedefs, common macros, readpng prototypes */
72 /* could just include png.h, but this macro is the only thing we need
73 * (name and typedefs changed to local versions); note that side effects
74 * only happen with alpha (which could easily be avoided with
75 * "ush acopy = (alpha);") */
77 #define alpha_composite(composite, fg, alpha, bg) { \
78 ush temp = ((ush)(fg)*(ush)(alpha) + \
79 (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128); \
80 (composite) = (uch)((temp + (temp >> 8)) >> 8); \
84 /* local prototypes */
85 static int rpng_x_create_window(void);
86 static int rpng_x_display_image(void);
87 static void rpng_x_cleanup(void);
88 static int rpng_x_msb(ulg u32val);
91 static char titlebar[1024], *window_name = titlebar;
92 static char *appname = LONGNAME;
93 static char *icon_name = PROGNAME;
94 static char *filename;
98 static uch bg_red=0, bg_green=0, bg_blue=0;
100 static double display_exponent;
102 static ulg image_width, image_height, image_rowbytes;
103 static int image_channels;
104 static uch *image_data;
106 /* X-specific variables */
107 static char *displayname;
108 static XImage *ximage;
109 static Display *display;
111 static Visual *visual;
112 static XVisualInfo *visual_list;
113 static int RShift, GShift, BShift;
114 static ulg RMask, GMask, BMask;
115 static Window window;
117 static Colormap colormap;
119 static int have_nondefault_visual = FALSE;
120 static int have_colormap = FALSE;
121 static int have_window = FALSE;
122 static int have_gc = FALSE;
124 ulg numcolors=0, pixels[256];
125 ush reds[256], greens[256], blues[256];
131 int main(int argc, char **argv)
140 double LUT_exponent; /* just the lookup table */
141 double CRT_exponent = 2.2; /* just the monitor */
142 double default_display_exponent; /* whole display system */
147 displayname = (char *)NULL;
148 filename = (char *)NULL;
151 /* First set the default value for our display-system exponent, i.e.,
152 * the product of the CRT exponent and the exponent corresponding to
153 * the frame-buffer's lookup table (LUT), if any. This is not an
154 * exhaustive list of LUT values (e.g., OpenStep has a lot of weird
155 * ones), but it should cover 99% of the current possibilities. */
158 LUT_exponent = 1.0 / 2.2;
160 if (some_next_function_that_returns_gamma(&next_gamma))
161 LUT_exponent = 1.0 / next_gamma;
164 LUT_exponent = 1.0 / 1.7;
165 /* there doesn't seem to be any documented function to get the
166 * "gamma" value, so we do it the hard way */
167 infile = fopen("/etc/config/system.glGammaVal", "r");
171 fgets(tmpline, 80, infile);
173 sgi_gamma = atof(tmpline);
175 LUT_exponent = 1.0 / sgi_gamma;
177 #elif defined(Macintosh)
178 LUT_exponent = 1.8 / 2.61;
180 if (some_mac_function_that_returns_gamma(&mac_gamma))
181 LUT_exponent = mac_gamma / 2.61;
184 LUT_exponent = 1.0; /* assume no LUT: most PCs */
187 /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
188 default_display_exponent = LUT_exponent * CRT_exponent;
191 /* If the user has set the SCREEN_GAMMA environment variable as suggested
192 * (somewhat imprecisely) in the libpng documentation, use that; otherwise
193 * use the default value we just calculated. Either way, the user may
194 * override this via a command-line option. */
196 if ((p = getenv("SCREEN_GAMMA")) != NULL)
197 display_exponent = atof(p);
199 display_exponent = default_display_exponent;
202 /* Now parse the command line for options and the PNG filename. */
204 while (*++argv && !error) {
205 if (!strncmp(*argv, "-display", 2)) {
210 } else if (!strncmp(*argv, "-gamma", 2)) {
214 display_exponent = atof(*argv);
215 if (display_exponent <= 0.0)
218 } else if (!strncmp(*argv, "-bgcolor", 2)) {
223 if (strlen(bgstr) != 7 || bgstr[0] != '#')
231 if (argv[1]) /* shouldn't be any more args after filename */
234 ++error; /* not expecting any other options */
240 } else if (!(infile = fopen(filename, "rb"))) {
241 fprintf(stderr, PROGNAME ": can't open PNG file [%s]\n", filename);
244 if ((rc = readpng_init(infile, &image_width, &image_height)) != 0) {
247 fprintf(stderr, PROGNAME
248 ": [%s] is not a PNG file: incorrect signature\n",
252 fprintf(stderr, PROGNAME
253 ": [%s] has bad IHDR (libpng longjmp)\n",
257 fprintf(stderr, PROGNAME ": insufficient memory\n");
260 fprintf(stderr, PROGNAME
261 ": unknown readpng_init() error\n");
266 display = XOpenDisplay(displayname);
268 readpng_cleanup(TRUE);
269 fprintf(stderr, PROGNAME ": can't open X display [%s]\n",
270 displayname? displayname : "default");
282 fprintf(stderr, "\n%s %s: %s\n", PROGNAME, VERSION, appname);
283 readpng_version_info();
285 "Usage: %s [-display xdpy] [-gamma exp] [-bgcolor bg] file.png\n"
286 " xdpy\tname of the target X display (e.g., ``hostname:0'')\n"
287 " exp \ttransfer-function exponent (``gamma'') of the display\n"
288 "\t\t system in floating-point format (e.g., ``%.1f''); equal\n"
289 "\t\t to the product of the lookup-table exponent (varies)\n"
290 "\t\t and the CRT exponent (usually 2.2); must be positive\n"
291 " bg \tdesired background color in 7-character hex RGB format\n"
292 "\t\t (e.g., ``#ff7700'' for orange: same as HTML colors);\n"
293 "\t\t used with transparent images\n"
294 "\nPress Q, Esc or mouse button 1 (within image window, after image\n"
295 "is displayed) to quit.\n"
296 "\n", PROGNAME, default_display_exponent);
301 /* set the title-bar string, but make sure buffer doesn't overflow */
303 alen = strlen(appname);
304 flen = strlen(filename);
305 if (alen + flen + 3 > 1023)
306 sprintf(titlebar, "%s: ...%s", appname, filename+(alen+flen+6-1023));
308 sprintf(titlebar, "%s: %s", appname, filename);
311 /* if the user didn't specify a background color on the command line,
312 * check for one in the PNG file--if not, the initialized values of 0
313 * (black) will be used */
316 unsigned r, g, b; /* this approach quiets compiler warnings */
318 sscanf(bgstr+1, "%2x%2x%2x", &r, &g, &b);
322 } else if (readpng_get_bgcolor(&bg_red, &bg_green, &bg_blue) > 1) {
323 readpng_cleanup(TRUE);
324 fprintf(stderr, PROGNAME
325 ": libpng error while checking for background color\n");
330 /* do the basic X initialization stuff, make the window and fill it
331 * with the background color */
333 if (rpng_x_create_window())
337 /* decode the image, all at once */
339 Trace((stderr, "calling readpng_get_image()\n"))
340 image_data = readpng_get_image(display_exponent, &image_channels,
342 Trace((stderr, "done with readpng_get_image()\n"))
345 /* done with PNG file, so clean up to minimize memory usage (but do NOT
346 * nuke image_data!) */
348 readpng_cleanup(FALSE);
352 fprintf(stderr, PROGNAME ": unable to decode PNG image\n");
357 /* display image (composite with background if requested) */
359 Trace((stderr, "calling rpng_x_display_image()\n"))
360 if (rpng_x_display_image()) {
364 Trace((stderr, "done with rpng_x_display_image()\n"))
367 /* wait for the user to tell us when to quit */
370 "Done. Press Q, Esc or mouse button 1 (within image window) to quit.\n");
374 XNextEvent(display, &e);
375 while (!(e.type == ButtonPress && e.xbutton.button == Button1) &&
376 !(e.type == KeyPress && /* v--- or 1 for shifted keys */
377 ((k = XLookupKeysym(&e.xkey, 0)) == XK_q || k == XK_Escape) ));
380 /* OK, we're done: clean up all image and X resources and go away */
391 static int rpng_x_create_window(void)
394 int need_colormap = FALSE;
401 XSetWindowAttributes attr;
402 XSizeHints *size_hints;
403 XTextProperty windowName, *pWindowName = &windowName;
404 XTextProperty iconName, *pIconName = &iconName;
405 XVisualInfo visual_info;
409 screen = DefaultScreen(display);
410 depth = DisplayPlanes(display, screen);
411 root = RootWindow(display, screen);
414 XSynchronize(display, True);
418 /* GRR: add 8-bit support */
419 if (/* depth != 8 && */ depth != 16 && depth != 24 && depth != 32) {
421 "screen depth %d not supported (only 16-, 24- or 32-bit TrueColor)\n",
426 XMatchVisualInfo(display, screen, depth,
427 (depth == 8)? PseudoColor : TrueColor, &visual_info);
428 visual = visual_info.visual;
430 if (depth != 16 && depth != 24 && depth != 32) {
431 int visuals_matched = 0;
433 Trace((stderr, "default depth is %d: checking other visuals\n",
437 visual_info.screen = screen;
438 visual_info.depth = 24;
439 visual_list = XGetVisualInfo(display,
440 VisualScreenMask | VisualDepthMask, &visual_info, &visuals_matched);
441 if (visuals_matched == 0) {
442 /* GRR: add 15-, 16- and 32-bit TrueColor visuals (also DirectColor?) */
443 fprintf(stderr, "default screen depth %d not supported, and no"
444 " 24-bit visuals found\n", depth);
447 Trace((stderr, "XGetVisualInfo() returned %d 24-bit visuals\n",
449 visual = visual_list[0].visual;
450 depth = visual_list[0].depth;
452 colormap_size = visual_list[0].colormap_size;
453 visual_class = visual->class;
454 visualID = XVisualIDFromVisual(visual);
456 have_nondefault_visual = TRUE;
457 need_colormap = TRUE;
459 XMatchVisualInfo(display, screen, depth, TrueColor, &visual_info);
460 visual = visual_info.visual;
464 RMask = visual->red_mask;
465 GMask = visual->green_mask;
466 BMask = visual->blue_mask;
468 /* GRR: add/check 8-bit support */
469 if (depth == 8 || need_colormap) {
470 colormap = XCreateColormap(display, root, visual, AllocNone);
472 fprintf(stderr, "XCreateColormap() failed\n");
475 have_colormap = TRUE;
477 if (depth == 15 || depth == 16) {
478 RShift = 15 - rpng_x_msb(RMask); /* these are right-shifts */
479 GShift = 15 - rpng_x_msb(GMask);
480 BShift = 15 - rpng_x_msb(BMask);
481 } else if (depth > 16) {
482 #define NO_24BIT_MASKS
483 #ifdef NO_24BIT_MASKS
484 RShift = rpng_x_msb(RMask) - 7; /* these are left-shifts */
485 GShift = rpng_x_msb(GMask) - 7;
486 BShift = rpng_x_msb(BMask) - 7;
488 RShift = 7 - rpng_x_msb(RMask); /* these are right-shifts, too */
489 GShift = 7 - rpng_x_msb(GMask);
490 BShift = 7 - rpng_x_msb(BMask);
493 if (depth >= 15 && (RShift < 0 || GShift < 0 || BShift < 0)) {
494 fprintf(stderr, "rpng internal logic error: negative X shift(s)!\n");
498 /*---------------------------------------------------------------------------
499 Finally, create the window.
500 ---------------------------------------------------------------------------*/
502 attr.backing_store = Always;
503 attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask;
504 attrmask = CWBackingStore | CWEventMask;
505 if (have_nondefault_visual) {
506 attr.colormap = colormap;
507 attr.background_pixel = 0;
508 attr.border_pixel = 1;
509 attrmask |= CWColormap | CWBackPixel | CWBorderPixel;
512 window = XCreateWindow(display, root, 0, 0, image_width, image_height, 0,
513 depth, InputOutput, visual, attrmask, &attr);
515 if (window == None) {
516 fprintf(stderr, "XCreateWindow() failed\n");
522 XSetWindowColormap(display, window, colormap);
524 if (!XStringListToTextProperty(&window_name, 1, pWindowName))
526 if (!XStringListToTextProperty(&icon_name, 1, pIconName))
529 /* OK if either hints allocation fails; XSetWMProperties() allows NULLs */
531 if ((size_hints = XAllocSizeHints()) != NULL) {
532 /* window will not be resizable */
533 size_hints->flags = PMinSize | PMaxSize;
534 size_hints->min_width = size_hints->max_width = (int)image_width;
535 size_hints->min_height = size_hints->max_height = (int)image_height;
538 if ((wm_hints = XAllocWMHints()) != NULL) {
539 wm_hints->initial_state = NormalState;
540 wm_hints->input = True;
541 /* wm_hints->icon_pixmap = icon_pixmap; */
542 wm_hints->flags = StateHint | InputHint /* | IconPixmapHint */ ;
545 XSetWMProperties(display, window, pWindowName, pIconName, NULL, 0,
546 size_hints, wm_hints, NULL);
548 /* various properties and hints no longer needed; free memory */
550 XFree(pWindowName->value);
552 XFree(pIconName->value);
558 XMapWindow(display, window);
560 gc = XCreateGC(display, window, 0, &gcvalues);
563 /*---------------------------------------------------------------------------
564 Fill window with the specified background color.
565 ---------------------------------------------------------------------------*/
567 if (depth == 24 || depth == 32) {
568 bg_pixel = ((ulg)bg_red << RShift) |
569 ((ulg)bg_green << GShift) |
570 ((ulg)bg_blue << BShift);
571 } else if (depth == 16) {
572 bg_pixel = ((((ulg)bg_red << 8) >> RShift) & RMask) |
573 ((((ulg)bg_green << 8) >> GShift) & GMask) |
574 ((((ulg)bg_blue << 8) >> BShift) & BMask);
575 } else /* depth == 8 */ {
577 /* GRR: add 8-bit support */
581 XSetForeground(display, gc, bg_pixel);
582 XFillRectangle(display, window, gc, 0, 0, image_width, image_height);
584 /*---------------------------------------------------------------------------
585 Wait for first Expose event to do any drawing, then flush.
586 ---------------------------------------------------------------------------*/
589 XNextEvent(display, &e);
590 while (e.type != Expose || e.xexpose.count);
594 /*---------------------------------------------------------------------------
595 Allocate memory for the X- and display-specific version of the image.
596 ---------------------------------------------------------------------------*/
598 if (depth == 24 || depth == 32) {
599 xdata = (uch *)malloc(4*image_width*image_height);
601 } else if (depth == 16) {
602 xdata = (uch *)malloc(2*image_width*image_height);
604 } else /* depth == 8 */ {
605 xdata = (uch *)malloc(image_width*image_height);
610 fprintf(stderr, PROGNAME ": unable to allocate image memory\n");
614 ximage = XCreateImage(display, visual, depth, ZPixmap, 0,
615 (char *)xdata, image_width, image_height, pad, 0);
618 fprintf(stderr, PROGNAME ": XCreateImage() failed\n");
623 /* to avoid testing the byte order every pixel (or doubling the size of
624 * the drawing routine with a giant if-test), we arbitrarily set the byte
625 * order to MSBFirst and let Xlib worry about inverting things on little-
626 * endian machines (like Linux/x86, old VAXen, etc.)--this is not the most
627 * efficient approach (the giant if-test would be better), but in the
628 * interest of clarity, we take the easy way out... */
630 ximage->byte_order = MSBFirst;
634 } /* end function rpng_x_create_window() */
640 static int rpng_x_display_image(void)
645 ulg i, row, lastrow = 0;
647 int ximage_rowbytes = ximage->bytes_per_line;
648 /* int bpp = ximage->bits_per_pixel; */
651 Trace((stderr, "beginning display loop (image_channels == %d)\n",
653 Trace((stderr, " (width = %ld, rowbytes = %ld, ximage_rowbytes = %d)\n",
654 image_width, image_rowbytes, ximage_rowbytes))
655 Trace((stderr, " (bpp = %d)\n", ximage->bits_per_pixel))
656 Trace((stderr, " (byte_order = %s)\n", ximage->byte_order == MSBFirst?
657 "MSBFirst" : (ximage->byte_order == LSBFirst? "LSBFirst" : "unknown")))
659 if (depth == 24 || depth == 32) {
660 ulg red, green, blue;
662 for (lastrow = row = 0; row < image_height; ++row) {
663 src = image_data + row*image_rowbytes;
664 dest = ximage->data + row*ximage_rowbytes;
665 if (image_channels == 3) {
666 for (i = image_width; i > 0; --i) {
670 #ifdef NO_24BIT_MASKS
671 pixel = (red << RShift) |
674 /* recall that we set ximage->byte_order = MSBFirst above */
675 /* GRR BUG: this assumes bpp == 32, but may be 24: */
676 *dest++ = (char)((pixel >> 24) & 0xff);
677 *dest++ = (char)((pixel >> 16) & 0xff);
678 *dest++ = (char)((pixel >> 8) & 0xff);
679 *dest++ = (char)( pixel & 0xff);
681 red = (RShift < 0)? red << (-RShift) : red >> RShift;
682 green = (GShift < 0)? green << (-GShift) : green >> GShift;
683 blue = (BShift < 0)? blue << (-BShift) : blue >> BShift;
684 pixel = (red & RMask) | (green & GMask) | (blue & BMask);
685 /* recall that we set ximage->byte_order = MSBFirst above */
686 *dest++ = (char)((pixel >> 24) & 0xff);
687 *dest++ = (char)((pixel >> 16) & 0xff);
688 *dest++ = (char)((pixel >> 8) & 0xff);
689 *dest++ = (char)( pixel & 0xff);
692 } else /* if (image_channels == 4) */ {
693 for (i = image_width; i > 0; --i) {
707 /* this macro (from png.h) composites the foreground
708 * and background values and puts the result into the
710 alpha_composite(red, r, a, bg_red);
711 alpha_composite(green, g, a, bg_green);
712 alpha_composite(blue, b, a, bg_blue);
714 pixel = (red << RShift) |
717 /* recall that we set ximage->byte_order = MSBFirst above */
718 *dest++ = (char)((pixel >> 24) & 0xff);
719 *dest++ = (char)((pixel >> 16) & 0xff);
720 *dest++ = (char)((pixel >> 8) & 0xff);
721 *dest++ = (char)( pixel & 0xff);
724 /* display after every 16 lines */
725 if (((row+1) & 0xf) == 0) {
726 XPutImage(display, window, gc, ximage, 0, (int)lastrow, 0,
727 (int)lastrow, image_width, 16);
733 } else if (depth == 16) {
734 ush red, green, blue;
736 for (lastrow = row = 0; row < image_height; ++row) {
737 src = image_data + row*image_rowbytes;
738 dest = ximage->data + row*ximage_rowbytes;
739 if (image_channels == 3) {
740 for (i = image_width; i > 0; --i) {
741 red = ((ush)(*src) << 8);
743 green = ((ush)(*src) << 8);
745 blue = ((ush)(*src) << 8);
747 pixel = ((red >> RShift) & RMask) |
748 ((green >> GShift) & GMask) |
749 ((blue >> BShift) & BMask);
750 /* recall that we set ximage->byte_order = MSBFirst above */
751 *dest++ = (char)((pixel >> 8) & 0xff);
752 *dest++ = (char)( pixel & 0xff);
754 } else /* if (image_channels == 4) */ {
755 for (i = image_width; i > 0; --i) {
762 green = ((ush)g << 8);
763 blue = ((ush)b << 8);
765 red = ((ush)bg_red << 8);
766 green = ((ush)bg_green << 8);
767 blue = ((ush)bg_blue << 8);
769 /* this macro (from png.h) composites the foreground
770 * and background values and puts the result back into
771 * the first argument (== fg byte here: safe) */
772 alpha_composite(r, r, a, bg_red);
773 alpha_composite(g, g, a, bg_green);
774 alpha_composite(b, b, a, bg_blue);
776 green = ((ush)g << 8);
777 blue = ((ush)b << 8);
779 pixel = ((red >> RShift) & RMask) |
780 ((green >> GShift) & GMask) |
781 ((blue >> BShift) & BMask);
782 /* recall that we set ximage->byte_order = MSBFirst above */
783 *dest++ = (char)((pixel >> 8) & 0xff);
784 *dest++ = (char)( pixel & 0xff);
787 /* display after every 16 lines */
788 if (((row+1) & 0xf) == 0) {
789 XPutImage(display, window, gc, ximage, 0, (int)lastrow, 0,
790 (int)lastrow, image_width, 16);
796 } else /* depth == 8 */ {
798 /* GRR: add 8-bit support */
802 Trace((stderr, "calling final XPutImage()\n"))
803 if (lastrow < image_height) {
804 XPutImage(display, window, gc, ximage, 0, (int)lastrow, 0,
805 (int)lastrow, image_width, image_height-lastrow);
815 static void rpng_x_cleanup(void)
824 free(ximage->data); /* we allocated it, so we free it */
825 ximage->data = (char *)NULL; /* instead of XDestroyImage() */
827 XDestroyImage(ximage);
832 XFreeGC(display, gc);
835 XDestroyWindow(display, window);
838 XFreeColormap(display, colormap);
840 if (have_nondefault_visual)
848 static int rpng_x_msb(ulg u32val)
852 for (i = 31; i >= 0; --i) {
853 if (u32val & 0x80000000L)