1 /*---------------------------------------------------------------------------
3 rpng - simple PNG display program rpng-win.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 32-bit Windows; it may compile under 16-bit Windows
9 with a little tweaking (or maybe not).
12 - handle quoted command-line args (especially filenames with spaces)
13 - have minimum window width: oh well
14 - use %.1023s to simplify truncation of title-bar string?
16 ---------------------------------------------------------------------------
19 - 1.00: initial public release
20 - 1.01: modified to allow abbreviated options; fixed long/ulong mis-
21 match; switched to png_jmpbuf() macro
22 - 1.02: added extra set of parentheses to png_jmpbuf() macro; fixed
23 command-line parsing bug
24 - 1.10: enabled "message window"/console (thanks to David Geldreich)
26 ---------------------------------------------------------------------------
28 Copyright (c) 1998-2001 Greg Roelofs. All rights reserved.
30 This software is provided "as is," without warranty of any kind,
31 express or implied. In no event shall the author or contributors
32 be held liable for any damages arising in any way from the use of
35 Permission is granted to anyone to use this software for any purpose,
36 including commercial applications, and to alter it and redistribute
37 it freely, subject to the following restrictions:
39 1. Redistributions of source code must retain the above copyright
40 notice, disclaimer, and this list of conditions.
41 2. Redistributions in binary form must reproduce the above copyright
42 notice, disclaimer, and this list of conditions in the documenta-
43 tion and/or other materials provided with the distribution.
44 3. All advertising materials mentioning features or use of this
45 software must display the following acknowledgment:
47 This product includes software developed by Greg Roelofs
48 and contributors for the book, "PNG: The Definitive Guide,"
49 published by O'Reilly and Associates.
51 ---------------------------------------------------------------------------*/
53 #define PROGNAME "rpng-win"
54 #define LONGNAME "Simple PNG Viewer for Windows"
55 #define VERSION "1.20 of 28 May 2001"
62 #include <conio.h> /* only for _getch() */
64 /* #define DEBUG : this enables the Trace() macros */
66 #include "readpng.h" /* typedefs, common macros, readpng prototypes */
69 /* could just include png.h, but this macro is the only thing we need
70 * (name and typedefs changed to local versions); note that side effects
71 * only happen with alpha (which could easily be avoided with
72 * "ush acopy = (alpha);") */
74 #define alpha_composite(composite, fg, alpha, bg) { \
75 ush temp = ((ush)(fg)*(ush)(alpha) + \
76 (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128); \
77 (composite) = (uch)((temp + (temp >> 8)) >> 8); \
81 /* local prototypes */
82 static int rpng_win_create_window(HINSTANCE hInst, int showmode);
83 static int rpng_win_display_image(void);
84 static void rpng_win_cleanup(void);
85 LRESULT CALLBACK rpng_win_wndproc(HWND, UINT, WPARAM, LPARAM);
88 static char titlebar[1024], *window_name = titlebar;
89 static char *progname = PROGNAME;
90 static char *appname = LONGNAME;
91 static char *icon_name = PROGNAME; /* GRR: not (yet) used */
92 static char *filename;
96 static uch bg_red=0, bg_green=0, bg_blue=0;
98 static double display_exponent;
100 static ulg image_width, image_height, image_rowbytes;
101 static int image_channels;
102 static uch *image_data;
104 /* Windows-specific variables */
105 static ulg wimage_rowbytes;
107 static uch *wimage_data;
108 static BITMAPINFOHEADER *bmih;
110 static HWND global_hwnd;
115 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmd, int showmode)
117 char *args[1024]; /* arbitrary limit, but should suffice */
118 char *p, *q, **argv = args;
123 double LUT_exponent; /* just the lookup table */
124 double CRT_exponent = 2.2; /* just the monitor */
125 double default_display_exponent; /* whole display system */
129 filename = (char *)NULL;
132 /* First reenable console output, which normally goes to the bit bucket
133 * for windowed apps. Closing the console window will terminate the
134 * app. Thanks to David.Geldreich@realviz.com for supplying the magical
138 freopen("CONOUT$", "a", stderr);
139 freopen("CONOUT$", "a", stdout);
142 /* Next set the default value for our display-system exponent, i.e.,
143 * the product of the CRT exponent and the exponent corresponding to
144 * the frame-buffer's lookup table (LUT), if any. This is not an
145 * exhaustive list of LUT values (e.g., OpenStep has a lot of weird
146 * ones), but it should cover 99% of the current possibilities. And
147 * yes, these ifdefs are completely wasted in a Windows program... */
150 LUT_exponent = 1.0 / 2.2;
152 if (some_next_function_that_returns_gamma(&next_gamma))
153 LUT_exponent = 1.0 / next_gamma;
156 LUT_exponent = 1.0 / 1.7;
157 /* there doesn't seem to be any documented function to get the
158 * "gamma" value, so we do it the hard way */
159 infile = fopen("/etc/config/system.glGammaVal", "r");
163 fgets(tmpline, 80, infile);
165 sgi_gamma = atof(tmpline);
167 LUT_exponent = 1.0 / sgi_gamma;
169 #elif defined(Macintosh)
170 LUT_exponent = 1.8 / 2.61;
172 if (some_mac_function_that_returns_gamma(&mac_gamma))
173 LUT_exponent = mac_gamma / 2.61;
176 LUT_exponent = 1.0; /* assume no LUT: most PCs */
179 /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
180 default_display_exponent = LUT_exponent * CRT_exponent;
183 /* If the user has set the SCREEN_GAMMA environment variable as suggested
184 * (somewhat imprecisely) in the libpng documentation, use that; otherwise
185 * use the default value we just calculated. Either way, the user may
186 * override this via a command-line option. */
188 if ((p = getenv("SCREEN_GAMMA")) != NULL)
189 display_exponent = atof(p);
191 display_exponent = default_display_exponent;
194 /* Windows really hates command lines, so we have to set up our own argv.
195 * Note that we do NOT bother with quoted arguments here, so don't use
196 * filenames with spaces in 'em! */
198 argv[argc++] = PROGNAME;
204 /* now p points at the first non-space after some spaces */
206 break; /* nothing after the spaces: done */
207 argv[argc++] = q = p;
208 while (*q && *q != ' ')
210 /* now q points at a space or the end of the string */
212 break; /* last argv already terminated; quit */
213 *q = '\0'; /* change space to terminator */
216 argv[argc] = NULL; /* terminate the argv array itself */
219 /* Now parse the command line for options and the PNG filename. */
221 while (*++argv && !error) {
222 if (!strncmp(*argv, "-gamma", 2)) {
226 display_exponent = atof(*argv);
227 if (display_exponent <= 0.0)
230 } else if (!strncmp(*argv, "-bgcolor", 2)) {
235 if (strlen(bgstr) != 7 || bgstr[0] != '#')
243 if (argv[1]) /* shouldn't be any more args after filename */
246 ++error; /* not expecting any other options */
252 } else if (!(infile = fopen(filename, "rb"))) {
253 fprintf(stderr, PROGNAME ": can't open PNG file [%s]\n", filename);
256 if ((rc = readpng_init(infile, &image_width, &image_height)) != 0) {
259 fprintf(stderr, PROGNAME
260 ": [%s] is not a PNG file: incorrect signature\n",
264 fprintf(stderr, PROGNAME
265 ": [%s] has bad IHDR (libpng longjmp)\n",
269 fprintf(stderr, PROGNAME ": insufficient memory\n");
272 fprintf(stderr, PROGNAME
273 ": unknown readpng_init() error\n");
288 fprintf(stderr, "\n%s %s: %s\n\n", PROGNAME, VERSION, appname);
289 readpng_version_info();
291 "Usage: %s [-gamma exp] [-bgcolor bg] file.png\n"
292 " exp \ttransfer-function exponent (``gamma'') of the display\n"
293 "\t\t system in floating-point format (e.g., ``%.1f''); equal\n"
294 "\t\t to the product of the lookup-table exponent (varies)\n"
295 "\t\t and the CRT exponent (usually 2.2); must be positive\n"
296 " bg \tdesired background color in 7-character hex RGB format\n"
297 "\t\t (e.g., ``#ff7700'' for orange: same as HTML colors);\n"
298 "\t\t used with transparent images\n"
299 "\nPress Q, Esc or mouse button 1 after image is displayed to quit.\n"
300 "Press Q or Esc to quit this usage screen.\n"
301 "\n", PROGNAME, default_display_exponent);
304 while (ch != 'q' && ch != 'Q' && ch != 0x1B);
307 fprintf(stderr, "\n%s %s: %s\n", PROGNAME, VERSION, appname);
309 "\n [console window: closing this window will terminate %s]\n\n",
314 /* set the title-bar string, but make sure buffer doesn't overflow */
316 alen = strlen(appname);
317 flen = strlen(filename);
318 if (alen + flen + 3 > 1023)
319 sprintf(titlebar, "%s: ...%s", appname, filename+(alen+flen+6-1023));
321 sprintf(titlebar, "%s: %s", appname, filename);
324 /* if the user didn't specify a background color on the command line,
325 * check for one in the PNG file--if not, the initialized values of 0
326 * (black) will be used */
329 sscanf(bgstr+1, "%2x%2x%2x", &bg_red, &bg_green, &bg_blue);
330 else if (readpng_get_bgcolor(&bg_red, &bg_green, &bg_blue) > 1) {
331 readpng_cleanup(TRUE);
332 fprintf(stderr, PROGNAME
333 ": libpng error while checking for background color\n");
338 /* do the basic Windows initialization stuff, make the window and fill it
339 * with the background color */
341 if (rpng_win_create_window(hInst, showmode))
345 /* decode the image, all at once */
347 Trace((stderr, "calling readpng_get_image()\n"))
348 image_data = readpng_get_image(display_exponent, &image_channels,
350 Trace((stderr, "done with readpng_get_image()\n"))
353 /* done with PNG file, so clean up to minimize memory usage (but do NOT
354 * nuke image_data!) */
356 readpng_cleanup(FALSE);
360 fprintf(stderr, PROGNAME ": unable to decode PNG image\n");
365 /* display image (composite with background if requested) */
367 Trace((stderr, "calling rpng_win_display_image()\n"))
368 if (rpng_win_display_image()) {
372 Trace((stderr, "done with rpng_win_display_image()\n"))
375 /* wait for the user to tell us when to quit */
378 "Done. Press Q, Esc or mouse button 1 (within image window) to quit.\n");
381 while (GetMessage(&msg, NULL, 0, 0)) {
382 TranslateMessage(&msg);
383 DispatchMessage(&msg);
387 /* OK, we're done: clean up all image and Windows resources and go away */
398 static int rpng_win_create_window(HINSTANCE hInst, int showmode)
401 int extra_width, extra_height;
406 /*---------------------------------------------------------------------------
407 Allocate memory for the display-specific version of the image (round up
408 to multiple of 4 for Windows DIB).
409 ---------------------------------------------------------------------------*/
411 wimage_rowbytes = ((3*image_width + 3L) >> 2) << 2;
413 if (!(dib = (uch *)malloc(sizeof(BITMAPINFOHEADER) +
414 wimage_rowbytes*image_height)))
419 /*---------------------------------------------------------------------------
420 Initialize the DIB. Negative height means to use top-down BMP ordering
421 (must be uncompressed, but that's what we want). Bit count of 1, 4 or 8
422 implies a colormap of RGBX quads, but 24-bit BMPs just use B,G,R values
423 directly => wimage_data begins immediately after BMP header.
424 ---------------------------------------------------------------------------*/
426 memset(dib, 0, sizeof(BITMAPINFOHEADER));
427 bmih = (BITMAPINFOHEADER *)dib;
428 bmih->biSize = sizeof(BITMAPINFOHEADER);
429 bmih->biWidth = image_width;
430 bmih->biHeight = -((long)image_height);
432 bmih->biBitCount = 24;
433 bmih->biCompression = 0;
434 wimage_data = dib + sizeof(BITMAPINFOHEADER);
436 /*---------------------------------------------------------------------------
437 Fill in background color (black by default); data are in BGR order.
438 ---------------------------------------------------------------------------*/
440 for (j = 0; j < image_height; ++j) {
441 dest = wimage_data + j*wimage_rowbytes;
442 for (i = image_width; i > 0; --i) {
449 /*---------------------------------------------------------------------------
450 Set the window parameters.
451 ---------------------------------------------------------------------------*/
453 memset(&wndclass, 0, sizeof(wndclass));
455 wndclass.cbSize = sizeof(wndclass);
456 wndclass.style = CS_HREDRAW | CS_VREDRAW;
457 wndclass.lpfnWndProc = rpng_win_wndproc;
458 wndclass.hInstance = hInst;
459 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
460 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
461 wndclass.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);
462 wndclass.lpszMenuName = NULL;
463 wndclass.lpszClassName = progname;
464 wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
466 RegisterClassEx(&wndclass);
468 /*---------------------------------------------------------------------------
469 Finally, create the window.
470 ---------------------------------------------------------------------------*/
472 extra_width = 2*(GetSystemMetrics(SM_CXBORDER) +
473 GetSystemMetrics(SM_CXDLGFRAME));
474 extra_height = 2*(GetSystemMetrics(SM_CYBORDER) +
475 GetSystemMetrics(SM_CYDLGFRAME)) +
476 GetSystemMetrics(SM_CYCAPTION);
478 global_hwnd = CreateWindow(progname, titlebar, WS_OVERLAPPEDWINDOW,
479 CW_USEDEFAULT, CW_USEDEFAULT, image_width+extra_width,
480 image_height+extra_height, NULL, NULL, hInst, NULL);
482 ShowWindow(global_hwnd, showmode);
483 UpdateWindow(global_hwnd);
487 } /* end function rpng_win_create_window() */
493 static int rpng_win_display_image()
501 Trace((stderr, "beginning display loop (image_channels == %d)\n",
503 Trace((stderr, "(width = %ld, rowbytes = %ld, wimage_rowbytes = %d)\n",
504 image_width, image_rowbytes, wimage_rowbytes))
507 /*---------------------------------------------------------------------------
508 Blast image data to buffer. This whole routine takes place before the
509 message loop begins, so there's no real point in any pseudo-progressive
511 ---------------------------------------------------------------------------*/
513 for (lastrow = row = 0; row < image_height; ++row) {
514 src = image_data + row*image_rowbytes;
515 dest = wimage_data + row*wimage_rowbytes;
516 if (image_channels == 3) {
517 for (i = image_width; i > 0; --i) {
522 *dest++ = g; /* note reverse order */
525 } else /* if (image_channels == 4) */ {
526 for (i = image_width; i > 0; --i) {
540 /* this macro (copied from png.h) composites the
541 * foreground and background values and puts the
542 * result into the first argument; there are no
543 * side effects with the first argument */
544 alpha_composite(*dest++, b, a, bg_blue);
545 alpha_composite(*dest++, g, a, bg_green);
546 alpha_composite(*dest++, r, a, bg_red);
550 /* display after every 16 lines */
551 if (((row+1) & 0xf) == 0) {
553 rect.top = (LONG)lastrow;
554 rect.right = (LONG)image_width; /* possibly off by one? */
555 rect.bottom = (LONG)lastrow + 16L; /* possibly off by one? */
556 InvalidateRect(global_hwnd, &rect, FALSE);
557 UpdateWindow(global_hwnd); /* similar to XFlush() */
562 Trace((stderr, "calling final image-flush routine\n"))
563 if (lastrow < image_height) {
565 rect.top = (LONG)lastrow;
566 rect.right = (LONG)image_width; /* possibly off by one? */
567 rect.bottom = (LONG)image_height; /* possibly off by one? */
568 InvalidateRect(global_hwnd, &rect, FALSE);
569 UpdateWindow(global_hwnd); /* similar to XFlush() */
573 last param determines whether or not background is wiped before paint
574 InvalidateRect(global_hwnd, NULL, TRUE);
575 UpdateWindow(global_hwnd);
585 static void rpng_win_cleanup()
602 LRESULT CALLBACK rpng_win_wndproc(HWND hwnd, UINT iMsg, WPARAM wP, LPARAM lP)
610 /* one-time processing here, if any */
614 hdc = BeginPaint(hwnd, &ps);
616 rc = StretchDIBits(hdc, 0, 0, image_width, image_height,
618 0, 0, image_width, image_height,
619 wimage_data, (BITMAPINFO *)bmih,
620 /* iUsage: no clue */
625 /* wait for the user to tell us when to quit */
627 switch (wP) { /* only need one, so ignore repeat count */
630 case 0x1B: /* Esc key */
635 case WM_LBUTTONDOWN: /* another way of quitting */
641 return DefWindowProc(hwnd, iMsg, wP, lP);