1 /*---------------------------------------------------------------------------
2
3 rpng - simple PNG display program rpng-win.c
4
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).
10
11 to do:
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?
15
16 ---------------------------------------------------------------------------
17
18 Changelog:
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)
25 - 2.00: dual-licensed (added GNU GPL)
26 - 2.01: fixed improper display of usage screen on PNG error(s)
27
28 ---------------------------------------------------------------------------
29
30 Copyright (c) 1998-2008 Greg Roelofs. All rights reserved.
31
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
35 this software.
36
37 The contents of this file are DUAL-LICENSED. You may modify and/or
38 redistribute this software according to the terms of one of the
39 following two licenses (at your option):
40
41
42 LICENSE 1 ("BSD-like with advertising clause"):
43
44 Permission is granted to anyone to use this software for any purpose,
45 including commercial applications, and to alter it and redistribute
46 it freely, subject to the following restrictions:
47
48 1. Redistributions of source code must retain the above copyright
49 notice, disclaimer, and this list of conditions.
50 2. Redistributions in binary form must reproduce the above copyright
51 notice, disclaimer, and this list of conditions in the documenta-
52 tion and/or other materials provided with the distribution.
53 3. All advertising materials mentioning features or use of this
54 software must display the following acknowledgment:
55
56 This product includes software developed by Greg Roelofs
57 and contributors for the book, "PNG: The Definitive Guide,"
58 published by O'Reilly and Associates.
59
60
61 LICENSE 2 (GNU GPL v2 or later):
62
63 This program is free software; you can redistribute it and/or modify
64 it under the terms of the GNU General Public License as published by
65 the Free Software Foundation; either version 2 of the License, or
66 (at your option) any later version.
67
68 This program is distributed in the hope that it will be useful,
69 but WITHOUT ANY WARRANTY; without even the implied warranty of
70 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
71 GNU General Public License for more details.
72
73 You should have received a copy of the GNU General Public License
74 along with this program; if not, write to the Free Software Foundation,
75 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
76
77 ---------------------------------------------------------------------------*/
78
79 #define PROGNAME "rpng-win"
80 #define LONGNAME "Simple PNG Viewer for Windows"
81 #define VERSION "2.01 of 16 March 2008"
82
83 #include <stdio.h>
84 #include <stdlib.h>
85 #include <string.h>
86 #include <time.h>
87 #include <windows.h>
88 #include <conio.h> /* only for _getch() */
89
90 /* #define DEBUG : this enables the Trace() macros */
91
92 #include "readpng.h" /* typedefs, common macros, readpng prototypes */
93
94
95 /* could just include png.h, but this macro is the only thing we need
96 * (name and typedefs changed to local versions); note that side effects
97 * only happen with alpha (which could easily be avoided with
98 * "ush acopy = (alpha);") */
99
100 #define alpha_composite(composite, fg, alpha, bg) { \
101 ush temp = ((ush)(fg)*(ush)(alpha) + \
102 (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128); \
103 (composite) = (uch)((temp + (temp >> 8)) >> 8); \
104 }
105
106
107 /* local prototypes */
108 static int rpng_win_create_window(HINSTANCE hInst, int showmode);
109 static int rpng_win_display_image(void);
110 static void rpng_win_cleanup(void);
111 LRESULT CALLBACK rpng_win_wndproc(HWND, UINT, WPARAM, LPARAM);
112
113
114 static char titlebar[1024];
115 static char *progname = PROGNAME;
116 static char *appname = LONGNAME;
117 static char *filename;
118 static FILE *infile;
119
120 static char *bgstr;
121 static uch bg_red=0, bg_green=0, bg_blue=0;
122
123 static double display_exponent;
124
125 static ulg image_width, image_height, image_rowbytes;
126 static int image_channels;
127 static uch *image_data;
128
129 /* Windows-specific variables */
130 static ulg wimage_rowbytes;
131 static uch *dib;
132 static uch *wimage_data;
133 static BITMAPINFOHEADER *bmih;
134
135 static HWND global_hwnd;
136
137
138
139
WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,PSTR cmd,int showmode)140 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmd, int showmode)
141 {
142 char *args[1024]; /* arbitrary limit, but should suffice */
143 char *p, *q, **argv = args;
144 int argc = 0;
145 int rc, alen, flen;
146 int error = 0;
147 int have_bg = FALSE;
148 double LUT_exponent; /* just the lookup table */
149 double CRT_exponent = 2.2; /* just the monitor */
150 double default_display_exponent; /* whole display system */
151 MSG msg;
152
153
154 filename = (char *)NULL;
155
156
157 /* First reenable console output, which normally goes to the bit bucket
158 * for windowed apps. Closing the console window will terminate the
159 * app. Thanks to David.Geldreich@realviz.com for supplying the magical
160 * incantation. */
161
162 AllocConsole();
163 freopen("CONOUT$", "a", stderr);
164 freopen("CONOUT$", "a", stdout);
165
166
167 /* Next set the default value for our display-system exponent, i.e.,
168 * the product of the CRT exponent and the exponent corresponding to
169 * the frame-buffer's lookup table (LUT), if any. This is not an
170 * exhaustive list of LUT values (e.g., OpenStep has a lot of weird
171 * ones), but it should cover 99% of the current possibilities. And
172 * yes, these ifdefs are completely wasted in a Windows program... */
173
174 #if defined(NeXT)
175 LUT_exponent = 1.0 / 2.2;
176 /*
177 if (some_next_function_that_returns_gamma(&next_gamma))
178 LUT_exponent = 1.0 / next_gamma;
179 */
180 #elif defined(sgi)
181 LUT_exponent = 1.0 / 1.7;
182 /* there doesn't seem to be any documented function to get the
183 * "gamma" value, so we do it the hard way */
184 infile = fopen("/etc/config/system.glGammaVal", "r");
185 if (infile) {
186 double sgi_gamma;
187
188 fgets(tmpline, 80, infile);
189 fclose(infile);
190 sgi_gamma = atof(tmpline);
191 if (sgi_gamma > 0.0)
192 LUT_exponent = 1.0 / sgi_gamma;
193 }
194 #elif defined(Macintosh)
195 LUT_exponent = 1.8 / 2.61;
196 /*
197 if (some_mac_function_that_returns_gamma(&mac_gamma))
198 LUT_exponent = mac_gamma / 2.61;
199 */
200 #else
201 LUT_exponent = 1.0; /* assume no LUT: most PCs */
202 #endif
203
204 /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
205 default_display_exponent = LUT_exponent * CRT_exponent;
206
207
208 /* If the user has set the SCREEN_GAMMA environment variable as suggested
209 * (somewhat imprecisely) in the libpng documentation, use that; otherwise
210 * use the default value we just calculated. Either way, the user may
211 * override this via a command-line option. */
212
213 if ((p = getenv("SCREEN_GAMMA")) != NULL)
214 display_exponent = atof(p);
215 else
216 display_exponent = default_display_exponent;
217
218
219 /* Windows really hates command lines, so we have to set up our own argv.
220 * Note that we do NOT bother with quoted arguments here, so don't use
221 * filenames with spaces in 'em! */
222
223 argv[argc++] = PROGNAME;
224 p = cmd;
225 for (;;) {
226 if (*p == ' ')
227 while (*++p == ' ')
228 ;
229 /* now p points at the first non-space after some spaces */
230 if (*p == '\0')
231 break; /* nothing after the spaces: done */
232 argv[argc++] = q = p;
233 while (*q && *q != ' ')
234 ++q;
235 /* now q points at a space or the end of the string */
236 if (*q == '\0')
237 break; /* last argv already terminated; quit */
238 *q = '\0'; /* change space to terminator */
239 p = q + 1;
240 }
241 argv[argc] = NULL; /* terminate the argv array itself */
242
243
244 /* Now parse the command line for options and the PNG filename. */
245
246 while (*++argv && !error) {
247 if (!strncmp(*argv, "-gamma", 2)) {
248 if (!*++argv)
249 ++error;
250 else {
251 display_exponent = atof(*argv);
252 if (display_exponent <= 0.0)
253 ++error;
254 }
255 } else if (!strncmp(*argv, "-bgcolor", 2)) {
256 if (!*++argv)
257 ++error;
258 else {
259 bgstr = *argv;
260 if (strlen(bgstr) != 7 || bgstr[0] != '#')
261 ++error;
262 else
263 have_bg = TRUE;
264 }
265 } else {
266 if (**argv != '-') {
267 filename = *argv;
268 if (argv[1]) /* shouldn't be any more args after filename */
269 ++error;
270 } else
271 ++error; /* not expecting any other options */
272 }
273 }
274
275 if (!filename)
276 ++error;
277
278
279 /* print usage screen if any errors up to this point */
280
281 if (error) {
282 int ch;
283
284 fprintf(stderr, "\n%s %s: %s\n\n", PROGNAME, VERSION, appname);
285 readpng_version_info();
286 fprintf(stderr, "\n"
287 "Usage: %s [-gamma exp] [-bgcolor bg] file.png\n"
288 " exp \ttransfer-function exponent (``gamma'') of the display\n"
289 "\t\t system in floating-point format (e.g., ``%.1f''); equal\n"
290 "\t\t to the product of the lookup-table exponent (varies)\n"
291 "\t\t and the CRT exponent (usually 2.2); must be positive\n"
292 " bg \tdesired background color in 7-character hex RGB format\n"
293 "\t\t (e.g., ``#ff7700'' for orange: same as HTML colors);\n"
294 "\t\t used with transparent images\n"
295 "\nPress Q, Esc or mouse button 1 after image is displayed to quit.\n"
296 "Press Q or Esc to quit this usage screen.\n"
297 "\n", PROGNAME, default_display_exponent);
298 do
299 ch = _getch();
300 while (ch != 'q' && ch != 'Q' && ch != 0x1B);
301 exit(1);
302 }
303
304
305 if (!(infile = fopen(filename, "rb"))) {
306 fprintf(stderr, PROGNAME ": can't open PNG file [%s]\n", filename);
307 ++error;
308 } else {
309 if ((rc = readpng_init(infile, &image_width, &image_height)) != 0) {
310 switch (rc) {
311 case 1:
312 fprintf(stderr, PROGNAME
313 ": [%s] is not a PNG file: incorrect signature\n",
314 filename);
315 break;
316 case 2:
317 fprintf(stderr, PROGNAME
318 ": [%s] has bad IHDR (libpng longjmp)\n", filename);
319 break;
320 case 4:
321 fprintf(stderr, PROGNAME ": insufficient memory\n");
322 break;
323 default:
324 fprintf(stderr, PROGNAME
325 ": unknown readpng_init() error\n");
326 break;
327 }
328 ++error;
329 }
330 if (error)
331 fclose(infile);
332 }
333
334
335 if (error) {
336 int ch;
337
338 fprintf(stderr, PROGNAME ": aborting.\n");
339 do
340 ch = _getch();
341 while (ch != 'q' && ch != 'Q' && ch != 0x1B);
342 exit(2);
343 } else {
344 fprintf(stderr, "\n%s %s: %s\n", PROGNAME, VERSION, appname);
345 fprintf(stderr,
346 "\n [console window: closing this window will terminate %s]\n\n",
347 PROGNAME);
348 }
349
350
351 /* set the title-bar string, but make sure buffer doesn't overflow */
352
353 alen = strlen(appname);
354 flen = strlen(filename);
355 if (alen + flen + 3 > 1023)
356 sprintf(titlebar, "%s: ...%s", appname, filename+(alen+flen+6-1023));
357 else
358 sprintf(titlebar, "%s: %s", appname, filename);
359
360
361 /* if the user didn't specify a background color on the command line,
362 * check for one in the PNG file--if not, the initialized values of 0
363 * (black) will be used */
364
365 if (have_bg) {
366 unsigned r, g, b; /* this approach quiets compiler warnings */
367
368 sscanf(bgstr+1, "%2x%2x%2x", &r, &g, &b);
369 bg_red = (uch)r;
370 bg_green = (uch)g;
371 bg_blue = (uch)b;
372 } else if (readpng_get_bgcolor(&bg_red, &bg_green, &bg_blue) > 1) {
373 readpng_cleanup(TRUE);
374 fprintf(stderr, PROGNAME
375 ": libpng error while checking for background color\n");
376 exit(2);
377 }
378
379
380 /* do the basic Windows initialization stuff, make the window and fill it
381 * with the background color */
382
383 if (rpng_win_create_window(hInst, showmode))
384 exit(2);
385
386
387 /* decode the image, all at once */
388
389 Trace((stderr, "calling readpng_get_image()\n"))
390 image_data = readpng_get_image(display_exponent, &image_channels,
391 &image_rowbytes);
392 Trace((stderr, "done with readpng_get_image()\n"))
393
394
395 /* done with PNG file, so clean up to minimize memory usage (but do NOT
396 * nuke image_data!) */
397
398 readpng_cleanup(FALSE);
399 fclose(infile);
400
401 if (!image_data) {
402 fprintf(stderr, PROGNAME ": unable to decode PNG image\n");
403 exit(3);
404 }
405
406
407 /* display image (composite with background if requested) */
408
409 Trace((stderr, "calling rpng_win_display_image()\n"))
410 if (rpng_win_display_image()) {
411 free(image_data);
412 exit(4);
413 }
414 Trace((stderr, "done with rpng_win_display_image()\n"))
415
416
417 /* wait for the user to tell us when to quit */
418
419 printf(
420 "Done. Press Q, Esc or mouse button 1 (within image window) to quit.\n");
421 fflush(stdout);
422
423 while (GetMessage(&msg, NULL, 0, 0)) {
424 TranslateMessage(&msg);
425 DispatchMessage(&msg);
426 }
427
428
429 /* OK, we're done: clean up all image and Windows resources and go away */
430
431 rpng_win_cleanup();
432
433 return msg.wParam;
434 }
435
436
437
438
439
rpng_win_create_window(HINSTANCE hInst,int showmode)440 static int rpng_win_create_window(HINSTANCE hInst, int showmode)
441 {
442 uch *dest;
443 int extra_width, extra_height;
444 ulg i, j;
445 WNDCLASSEX wndclass;
446
447
448 /*---------------------------------------------------------------------------
449 Allocate memory for the display-specific version of the image (round up
450 to multiple of 4 for Windows DIB).
451 ---------------------------------------------------------------------------*/
452
453 wimage_rowbytes = ((3*image_width + 3L) >> 2) << 2;
454
455 if (!(dib = (uch *)malloc(sizeof(BITMAPINFOHEADER) +
456 wimage_rowbytes*image_height)))
457 {
458 return 4; /* fail */
459 }
460
461 /*---------------------------------------------------------------------------
462 Initialize the DIB. Negative height means to use top-down BMP ordering
463 (must be uncompressed, but that's what we want). Bit count of 1, 4 or 8
464 implies a colormap of RGBX quads, but 24-bit BMPs just use B,G,R values
465 directly => wimage_data begins immediately after BMP header.
466 ---------------------------------------------------------------------------*/
467
468 memset(dib, 0, sizeof(BITMAPINFOHEADER));
469 bmih = (BITMAPINFOHEADER *)dib;
470 bmih->biSize = sizeof(BITMAPINFOHEADER);
471 bmih->biWidth = image_width;
472 bmih->biHeight = -((long)image_height);
473 bmih->biPlanes = 1;
474 bmih->biBitCount = 24;
475 bmih->biCompression = 0;
476 wimage_data = dib + sizeof(BITMAPINFOHEADER);
477
478 /*---------------------------------------------------------------------------
479 Fill in background color (black by default); data are in BGR order.
480 ---------------------------------------------------------------------------*/
481
482 for (j = 0; j < image_height; ++j) {
483 dest = wimage_data + j*wimage_rowbytes;
484 for (i = image_width; i > 0; --i) {
485 *dest++ = bg_blue;
486 *dest++ = bg_green;
487 *dest++ = bg_red;
488 }
489 }
490
491 /*---------------------------------------------------------------------------
492 Set the window parameters.
493 ---------------------------------------------------------------------------*/
494
495 memset(&wndclass, 0, sizeof(wndclass));
496
497 wndclass.cbSize = sizeof(wndclass);
498 wndclass.style = CS_HREDRAW | CS_VREDRAW;
499 wndclass.lpfnWndProc = rpng_win_wndproc;
500 wndclass.hInstance = hInst;
501 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
502 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
503 wndclass.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);
504 wndclass.lpszMenuName = NULL;
505 wndclass.lpszClassName = progname;
506 wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
507
508 RegisterClassEx(&wndclass);
509
510 /*---------------------------------------------------------------------------
511 Finally, create the window.
512 ---------------------------------------------------------------------------*/
513
514 extra_width = 2*(GetSystemMetrics(SM_CXBORDER) +
515 GetSystemMetrics(SM_CXDLGFRAME));
516 extra_height = 2*(GetSystemMetrics(SM_CYBORDER) +
517 GetSystemMetrics(SM_CYDLGFRAME)) +
518 GetSystemMetrics(SM_CYCAPTION);
519
520 global_hwnd = CreateWindow(progname, titlebar, WS_OVERLAPPEDWINDOW,
521 CW_USEDEFAULT, CW_USEDEFAULT, image_width+extra_width,
522 image_height+extra_height, NULL, NULL, hInst, NULL);
523
524 ShowWindow(global_hwnd, showmode);
525 UpdateWindow(global_hwnd);
526
527 return 0;
528
529 } /* end function rpng_win_create_window() */
530
531
532
533
534
rpng_win_display_image()535 static int rpng_win_display_image()
536 {
537 uch *src, *dest;
538 uch r, g, b, a;
539 ulg i, row, lastrow;
540 RECT rect;
541
542
543 Trace((stderr, "beginning display loop (image_channels == %d)\n",
544 image_channels))
545 Trace((stderr, "(width = %ld, rowbytes = %ld, wimage_rowbytes = %d)\n",
546 image_width, image_rowbytes, wimage_rowbytes))
547
548
549 /*---------------------------------------------------------------------------
550 Blast image data to buffer. This whole routine takes place before the
551 message loop begins, so there's no real point in any pseudo-progressive
552 display...
553 ---------------------------------------------------------------------------*/
554
555 for (lastrow = row = 0; row < image_height; ++row) {
556 src = image_data + row*image_rowbytes;
557 dest = wimage_data + row*wimage_rowbytes;
558 if (image_channels == 3) {
559 for (i = image_width; i > 0; --i) {
560 r = *src++;
561 g = *src++;
562 b = *src++;
563 *dest++ = b;
564 *dest++ = g; /* note reverse order */
565 *dest++ = r;
566 }
567 } else /* if (image_channels == 4) */ {
568 for (i = image_width; i > 0; --i) {
569 r = *src++;
570 g = *src++;
571 b = *src++;
572 a = *src++;
573 if (a == 255) {
574 *dest++ = b;
575 *dest++ = g;
576 *dest++ = r;
577 } else if (a == 0) {
578 *dest++ = bg_blue;
579 *dest++ = bg_green;
580 *dest++ = bg_red;
581 } else {
582 /* this macro (copied from png.h) composites the
583 * foreground and background values and puts the
584 * result into the first argument; there are no
585 * side effects with the first argument */
586 alpha_composite(*dest++, b, a, bg_blue);
587 alpha_composite(*dest++, g, a, bg_green);
588 alpha_composite(*dest++, r, a, bg_red);
589 }
590 }
591 }
592 /* display after every 16 lines */
593 if (((row+1) & 0xf) == 0) {
594 rect.left = 0L;
595 rect.top = (LONG)lastrow;
596 rect.right = (LONG)image_width; /* possibly off by one? */
597 rect.bottom = (LONG)lastrow + 16L; /* possibly off by one? */
598 InvalidateRect(global_hwnd, &rect, FALSE);
599 UpdateWindow(global_hwnd); /* similar to XFlush() */
600 lastrow = row + 1;
601 }
602 }
603
604 Trace((stderr, "calling final image-flush routine\n"))
605 if (lastrow < image_height) {
606 rect.left = 0L;
607 rect.top = (LONG)lastrow;
608 rect.right = (LONG)image_width; /* possibly off by one? */
609 rect.bottom = (LONG)image_height; /* possibly off by one? */
610 InvalidateRect(global_hwnd, &rect, FALSE);
611 UpdateWindow(global_hwnd); /* similar to XFlush() */
612 }
613
614 /*
615 last param determines whether or not background is wiped before paint
616 InvalidateRect(global_hwnd, NULL, TRUE);
617 UpdateWindow(global_hwnd);
618 */
619
620 return 0;
621 }
622
623
624
625
626
rpng_win_cleanup()627 static void rpng_win_cleanup()
628 {
629 if (image_data) {
630 free(image_data);
631 image_data = NULL;
632 }
633
634 if (dib) {
635 free(dib);
636 dib = NULL;
637 }
638 }
639
640
641
642
643
rpng_win_wndproc(HWND hwnd,UINT iMsg,WPARAM wP,LPARAM lP)644 LRESULT CALLBACK rpng_win_wndproc(HWND hwnd, UINT iMsg, WPARAM wP, LPARAM lP)
645 {
646 HDC hdc;
647 PAINTSTRUCT ps;
648 int rc;
649
650 switch (iMsg) {
651 case WM_CREATE:
652 /* one-time processing here, if any */
653 return 0;
654
655 case WM_PAINT:
656 hdc = BeginPaint(hwnd, &ps);
657 /* dest */
658 rc = StretchDIBits(hdc, 0, 0, image_width, image_height,
659 /* source */
660 0, 0, image_width, image_height,
661 wimage_data, (BITMAPINFO *)bmih,
662 /* iUsage: no clue */
663 0, SRCCOPY);
664 EndPaint(hwnd, &ps);
665 return 0;
666
667 /* wait for the user to tell us when to quit */
668 case WM_CHAR:
669 switch (wP) { /* only need one, so ignore repeat count */
670 case 'q':
671 case 'Q':
672 case 0x1B: /* Esc key */
673 PostQuitMessage(0);
674 }
675 return 0;
676
677 case WM_LBUTTONDOWN: /* another way of quitting */
678 case WM_DESTROY:
679 PostQuitMessage(0);
680 return 0;
681 }
682
683 return DefWindowProc(hwnd, iMsg, wP, lP);
684 }
685