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)
25 - 2.00: dual-licensed (added GNU GPL)
26 - 2.01: fixed improper display of usage screen on PNG error(s)
28 ---------------------------------------------------------------------------
30 Copyright (c) 1998-2008 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 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):
42 LICENSE 1 ("BSD-like with advertising clause"):
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:
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:
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.
61 LICENSE 2 (GNU GPL v2 or later):
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.
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.
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
77 ---------------------------------------------------------------------------*/
79 #define PROGNAME "rpng-win"
80 #define LONGNAME "Simple PNG Viewer for Windows"
81 #define VERSION "2.01 of 16 March 2008"
89 /* getch replacement. Turns out, we don't really need this,
90 * but leave it here if we ever enable any of the uses of
91 * _getch in the main code
95 #include <sys/ioctl.h>
96 int repl_getch( void )
99 int fd
= fileno(stdin
);
100 struct termio old_tty
, new_tty
;
102 ioctl(fd
, TCGETA
, &old_tty
);
104 new_tty
.c_lflag
&= ~(ICANON
| ECHO
| ISIG
);
105 ioctl(fd
, TCSETA
, &new_tty
);
106 fread(&ch
, 1, sizeof(ch
), stdin
);
107 ioctl(fd
, TCSETA
, &old_tty
);
111 #define _getch repl_getch
113 #include <conio.h> /* only for _getch() */
116 /* #define DEBUG : this enables the Trace() macros */
118 #include "readpng.h" /* typedefs, common macros, readpng prototypes */
121 /* could just include png.h, but this macro is the only thing we need
122 * (name and typedefs changed to local versions); note that side effects
123 * only happen with alpha (which could easily be avoided with
124 * "ush acopy = (alpha);") */
126 #define alpha_composite(composite, fg, alpha, bg) { \
127 ush temp = ((ush)(fg)*(ush)(alpha) + \
128 (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128); \
129 (composite) = (uch)((temp + (temp >> 8)) >> 8); \
133 /* local prototypes */
134 static int rpng_win_create_window(HINSTANCE hInst
, int showmode
);
135 static int rpng_win_display_image(void);
136 static void rpng_win_cleanup(void);
137 LRESULT CALLBACK
rpng_win_wndproc(HWND
, UINT
, WPARAM
, LPARAM
);
140 static char titlebar
[1024];
141 static char *progname
= PROGNAME
;
142 static char *appname
= LONGNAME
;
143 static char *filename
;
147 static uch bg_red
=0, bg_green
=0, bg_blue
=0;
149 static double display_exponent
;
151 static ulg image_width
, image_height
, image_rowbytes
;
152 static int image_channels
;
153 static uch
*image_data
;
155 /* Windows-specific variables */
156 static ulg wimage_rowbytes
;
158 static uch
*wimage_data
;
159 static BITMAPINFOHEADER
*bmih
;
161 static HWND global_hwnd
;
166 int WINAPI
WinMain(HINSTANCE hInst
, HINSTANCE hPrevInst
, PSTR cmd
, int showmode
)
168 char *args
[1024]; /* arbitrary limit, but should suffice */
169 char *p
, *q
, **argv
= args
;
174 double LUT_exponent
; /* just the lookup table */
175 double CRT_exponent
= 2.2; /* just the monitor */
176 double default_display_exponent
; /* whole display system */
180 filename
= (char *)NULL
;
183 /* First reenable console output, which normally goes to the bit bucket
184 * for windowed apps. Closing the console window will terminate the
185 * app. Thanks to David.Geldreich@realviz.com for supplying the magical
189 freopen("CONOUT$", "a", stderr
);
190 freopen("CONOUT$", "a", stdout
);
194 /* Next set the default value for our display-system exponent, i.e.,
195 * the product of the CRT exponent and the exponent corresponding to
196 * the frame-buffer's lookup table (LUT), if any. This is not an
197 * exhaustive list of LUT values (e.g., OpenStep has a lot of weird
198 * ones), but it should cover 99% of the current possibilities. And
199 * yes, these ifdefs are completely wasted in a Windows program... */
202 LUT_exponent
= 1.0 / 2.2;
204 if (some_next_function_that_returns_gamma(&next_gamma))
205 LUT_exponent = 1.0 / next_gamma;
208 LUT_exponent
= 1.0 / 1.7;
209 /* there doesn't seem to be any documented function to get the
210 * "gamma" value, so we do it the hard way */
211 infile
= fopen("/etc/config/system.glGammaVal", "r");
215 fgets(tmpline
, 80, infile
);
217 sgi_gamma
= atof(tmpline
);
219 LUT_exponent
= 1.0 / sgi_gamma
;
221 #elif defined(Macintosh)
222 LUT_exponent
= 1.8 / 2.61;
224 if (some_mac_function_that_returns_gamma(&mac_gamma))
225 LUT_exponent = mac_gamma / 2.61;
228 LUT_exponent
= 1.0; /* assume no LUT: most PCs */
231 /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
232 default_display_exponent
= LUT_exponent
* CRT_exponent
;
235 /* If the user has set the SCREEN_GAMMA environment variable as suggested
236 * (somewhat imprecisely) in the libpng documentation, use that; otherwise
237 * use the default value we just calculated. Either way, the user may
238 * override this via a command-line option. */
240 if ((p
= getenv("SCREEN_GAMMA")) != NULL
)
241 display_exponent
= atof(p
);
243 display_exponent
= default_display_exponent
;
246 /* Windows really hates command lines, so we have to set up our own argv.
247 * Note that we do NOT bother with quoted arguments here, so don't use
248 * filenames with spaces in 'em! */
250 argv
[argc
++] = PROGNAME
;
256 /* now p points at the first non-space after some spaces */
258 break; /* nothing after the spaces: done */
259 argv
[argc
++] = q
= p
;
260 while (*q
&& *q
!= ' ')
262 /* now q points at a space or the end of the string */
264 break; /* last argv already terminated; quit */
265 *q
= '\0'; /* change space to terminator */
268 argv
[argc
] = NULL
; /* terminate the argv array itself */
271 /* Now parse the command line for options and the PNG filename. */
273 while (*++argv
&& !error
) {
274 if (!strncmp(*argv
, "-gamma", 2)) {
278 display_exponent
= atof(*argv
);
279 if (display_exponent
<= 0.0)
282 } else if (!strncmp(*argv
, "-bgcolor", 2)) {
287 if (strlen(bgstr
) != 7 || bgstr
[0] != '#')
295 if (argv
[1]) /* shouldn't be any more args after filename */
298 ++error
; /* not expecting any other options */
306 /* print usage screen if any errors up to this point */
313 fprintf(stderr
, "\n%s %s: %s\n\n", PROGNAME
, VERSION
, appname
);
314 readpng_version_info();
316 "Usage: %s [-gamma exp] [-bgcolor bg] file.png\n"
317 " exp \ttransfer-function exponent (``gamma'') of the display\n"
318 "\t\t system in floating-point format (e.g., ``%.1f''); equal\n"
319 "\t\t to the product of the lookup-table exponent (varies)\n"
320 "\t\t and the CRT exponent (usually 2.2); must be positive\n"
321 " bg \tdesired background color in 7-character hex RGB format\n"
322 "\t\t (e.g., ``#ff7700'' for orange: same as HTML colors);\n"
323 "\t\t used with transparent images\n"
324 "\nPress Q, Esc or mouse button 1 after image is displayed to quit.\n"
326 "Press Q or Esc to quit this usage screen.\n"
328 "\n", PROGNAME
, default_display_exponent
);
332 while (ch
!= 'q' && ch
!= 'Q' && ch
!= 0x1B);
338 if (!(infile
= fopen(filename
, "rb"))) {
339 fprintf(stderr
, PROGNAME
": can't open PNG file [%s]\n", filename
);
342 if ((rc
= readpng_init(infile
, &image_width
, &image_height
)) != 0) {
345 fprintf(stderr
, PROGNAME
346 ": [%s] is not a PNG file: incorrect signature\n",
350 fprintf(stderr
, PROGNAME
351 ": [%s] has bad IHDR (libpng longjmp)\n", filename
);
354 fprintf(stderr
, PROGNAME
": insufficient memory\n");
357 fprintf(stderr
, PROGNAME
358 ": unknown readpng_init() error\n");
373 fprintf(stderr
, PROGNAME
": aborting.\n");
377 while (ch
!= 'q' && ch
!= 'Q' && ch
!= 0x1B);
381 fprintf(stderr
, "\n%s %s: %s\n", PROGNAME
, VERSION
, appname
);
384 "\n [console window: closing this window will terminate %s]\n\n",
390 /* set the title-bar string, but make sure buffer doesn't overflow */
392 alen
= strlen(appname
);
393 flen
= strlen(filename
);
394 if (alen
+ flen
+ 3 > 1023)
395 sprintf(titlebar
, "%s: ...%s", appname
, filename
+(alen
+flen
+6-1023));
397 sprintf(titlebar
, "%s: %s", appname
, filename
);
400 /* if the user didn't specify a background color on the command line,
401 * check for one in the PNG file--if not, the initialized values of 0
402 * (black) will be used */
405 unsigned r
, g
, b
; /* this approach quiets compiler warnings */
407 sscanf(bgstr
+1, "%2x%2x%2x", &r
, &g
, &b
);
411 } else if (readpng_get_bgcolor(&bg_red
, &bg_green
, &bg_blue
) > 1) {
412 readpng_cleanup(TRUE
);
413 fprintf(stderr
, PROGNAME
414 ": libpng error while checking for background color\n");
419 /* do the basic Windows initialization stuff, make the window and fill it
420 * with the background color */
422 if (rpng_win_create_window(hInst
, showmode
))
426 /* decode the image, all at once */
428 Trace((stderr
, "calling readpng_get_image()\n"))
429 image_data
= readpng_get_image(display_exponent
, &image_channels
,
431 Trace((stderr
, "done with readpng_get_image()\n"))
434 /* done with PNG file, so clean up to minimize memory usage (but do NOT
435 * nuke image_data!) */
437 readpng_cleanup(FALSE
);
441 fprintf(stderr
, PROGNAME
": unable to decode PNG image\n");
446 /* display image (composite with background if requested) */
448 Trace((stderr
, "calling rpng_win_display_image()\n"))
449 if (rpng_win_display_image()) {
453 Trace((stderr
, "done with rpng_win_display_image()\n"))
456 /* wait for the user to tell us when to quit */
460 "Done. Press Q, Esc or mouse button 1 (within image window) to quit.\n"
462 "Done. Press mouse button 1 (within image window) to quit.\n"
467 while (GetMessage(&msg
, NULL
, 0, 0)) {
468 TranslateMessage(&msg
);
469 DispatchMessage(&msg
);
473 /* OK, we're done: clean up all image and Windows resources and go away */
484 static int rpng_win_create_window(HINSTANCE hInst
, int showmode
)
487 int extra_width
, extra_height
;
492 /*---------------------------------------------------------------------------
493 Allocate memory for the display-specific version of the image (round up
494 to multiple of 4 for Windows DIB).
495 ---------------------------------------------------------------------------*/
497 wimage_rowbytes
= ((3*image_width
+ 3L) >> 2) << 2;
499 if (!(dib
= (uch
*)malloc(sizeof(BITMAPINFOHEADER
) +
500 wimage_rowbytes
*image_height
)))
505 /*---------------------------------------------------------------------------
506 Initialize the DIB. Negative height means to use top-down BMP ordering
507 (must be uncompressed, but that's what we want). Bit count of 1, 4 or 8
508 implies a colormap of RGBX quads, but 24-bit BMPs just use B,G,R values
509 directly => wimage_data begins immediately after BMP header.
510 ---------------------------------------------------------------------------*/
512 memset(dib
, 0, sizeof(BITMAPINFOHEADER
));
513 bmih
= (BITMAPINFOHEADER
*)dib
;
514 bmih
->biSize
= sizeof(BITMAPINFOHEADER
);
515 bmih
->biWidth
= image_width
;
516 bmih
->biHeight
= -((long)image_height
);
518 bmih
->biBitCount
= 24;
519 bmih
->biCompression
= 0;
520 wimage_data
= dib
+ sizeof(BITMAPINFOHEADER
);
522 /*---------------------------------------------------------------------------
523 Fill in background color (black by default); data are in BGR order.
524 ---------------------------------------------------------------------------*/
526 for (j
= 0; j
< image_height
; ++j
) {
527 dest
= wimage_data
+ j
*wimage_rowbytes
;
528 for (i
= image_width
; i
> 0; --i
) {
535 /*---------------------------------------------------------------------------
536 Set the window parameters.
537 ---------------------------------------------------------------------------*/
539 memset(&wndclass
, 0, sizeof(wndclass
));
541 wndclass
.cbSize
= sizeof(wndclass
);
542 wndclass
.style
= CS_HREDRAW
| CS_VREDRAW
;
543 wndclass
.lpfnWndProc
= rpng_win_wndproc
;
544 wndclass
.hInstance
= hInst
;
545 wndclass
.hIcon
= LoadIcon(NULL
, IDI_APPLICATION
);
546 wndclass
.hCursor
= LoadCursor(NULL
, IDC_ARROW
);
547 wndclass
.hbrBackground
= (HBRUSH
)GetStockObject(DKGRAY_BRUSH
);
548 wndclass
.lpszMenuName
= NULL
;
549 wndclass
.lpszClassName
= progname
;
550 wndclass
.hIconSm
= LoadIcon(NULL
, IDI_APPLICATION
);
552 RegisterClassEx(&wndclass
);
554 /*---------------------------------------------------------------------------
555 Finally, create the window.
556 ---------------------------------------------------------------------------*/
558 extra_width
= 2*(GetSystemMetrics(SM_CXBORDER
) +
559 GetSystemMetrics(SM_CXDLGFRAME
));
560 extra_height
= 2*(GetSystemMetrics(SM_CYBORDER
) +
561 GetSystemMetrics(SM_CYDLGFRAME
)) +
562 GetSystemMetrics(SM_CYCAPTION
);
564 global_hwnd
= CreateWindow(progname
, titlebar
, WS_OVERLAPPEDWINDOW
,
565 CW_USEDEFAULT
, CW_USEDEFAULT
, image_width
+extra_width
,
566 image_height
+extra_height
, NULL
, NULL
, hInst
, NULL
);
568 ShowWindow(global_hwnd
, showmode
);
569 UpdateWindow(global_hwnd
);
573 } /* end function rpng_win_create_window() */
579 static int rpng_win_display_image()
587 Trace((stderr
, "beginning display loop (image_channels == %d)\n",
589 Trace((stderr
, "(width = %ld, rowbytes = %ld, wimage_rowbytes = %d)\n",
590 image_width
, image_rowbytes
, wimage_rowbytes
))
593 /*---------------------------------------------------------------------------
594 Blast image data to buffer. This whole routine takes place before the
595 message loop begins, so there's no real point in any pseudo-progressive
597 ---------------------------------------------------------------------------*/
599 for (lastrow
= row
= 0; row
< image_height
; ++row
) {
600 src
= image_data
+ row
*image_rowbytes
;
601 dest
= wimage_data
+ row
*wimage_rowbytes
;
602 if (image_channels
== 3) {
603 for (i
= image_width
; i
> 0; --i
) {
608 *dest
++ = g
; /* note reverse order */
611 } else /* if (image_channels == 4) */ {
612 for (i
= image_width
; i
> 0; --i
) {
626 /* this macro (copied from png.h) composites the
627 * foreground and background values and puts the
628 * result into the first argument; there are no
629 * side effects with the first argument */
630 alpha_composite(*dest
++, b
, a
, bg_blue
);
631 alpha_composite(*dest
++, g
, a
, bg_green
);
632 alpha_composite(*dest
++, r
, a
, bg_red
);
636 /* display after every 16 lines */
637 if (((row
+1) & 0xf) == 0) {
639 rect
.top
= (LONG
)lastrow
;
640 rect
.right
= (LONG
)image_width
; /* possibly off by one? */
641 rect
.bottom
= (LONG
)lastrow
+ 16L; /* possibly off by one? */
642 InvalidateRect(global_hwnd
, &rect
, FALSE
);
643 UpdateWindow(global_hwnd
); /* similar to XFlush() */
648 Trace((stderr
, "calling final image-flush routine\n"))
649 if (lastrow
< image_height
) {
651 rect
.top
= (LONG
)lastrow
;
652 rect
.right
= (LONG
)image_width
; /* possibly off by one? */
653 rect
.bottom
= (LONG
)image_height
; /* possibly off by one? */
654 InvalidateRect(global_hwnd
, &rect
, FALSE
);
655 UpdateWindow(global_hwnd
); /* similar to XFlush() */
659 last param determines whether or not background is wiped before paint
660 InvalidateRect(global_hwnd, NULL, TRUE);
661 UpdateWindow(global_hwnd);
671 static void rpng_win_cleanup()
688 LRESULT CALLBACK
rpng_win_wndproc(HWND hwnd
, UINT iMsg
, WPARAM wP
, LPARAM lP
)
696 /* one-time processing here, if any */
700 hdc
= BeginPaint(hwnd
, &ps
);
702 rc
= StretchDIBits(hdc
, 0, 0, image_width
, image_height
,
704 0, 0, image_width
, image_height
,
705 wimage_data
, (BITMAPINFO
*)bmih
,
706 /* iUsage: no clue */
711 /* wait for the user to tell us when to quit */
713 switch (wP
) { /* only need one, so ignore repeat count */
716 case 0x1B: /* Esc key */
721 case WM_LBUTTONDOWN
: /* another way of quitting */
727 return DefWindowProc(hwnd
, iMsg
, wP
, lP
);