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)
27 - 2.02: check for integer overflow (Glenn R-P)
29 ---------------------------------------------------------------------------
31 Copyright (c) 1998-2008, 2017 Greg Roelofs. All rights reserved.
33 This software is provided "as is," without warranty of any kind,
34 express or implied. In no event shall the author or contributors
35 be held liable for any damages arising in any way from the use of
38 The contents of this file are DUAL-LICENSED. You may modify and/or
39 redistribute this software according to the terms of one of the
40 following two licenses (at your option):
43 LICENSE 1 ("BSD-like with advertising clause"):
45 Permission is granted to anyone to use this software for any purpose,
46 including commercial applications, and to alter it and redistribute
47 it freely, subject to the following restrictions:
49 1. Redistributions of source code must retain the above copyright
50 notice, disclaimer, and this list of conditions.
51 2. Redistributions in binary form must reproduce the above copyright
52 notice, disclaimer, and this list of conditions in the documenta-
53 tion and/or other materials provided with the distribution.
54 3. All advertising materials mentioning features or use of this
55 software must display the following acknowledgment:
57 This product includes software developed by Greg Roelofs
58 and contributors for the book, "PNG: The Definitive Guide,"
59 published by O'Reilly and Associates.
62 LICENSE 2 (GNU GPL v2 or later):
64 This program is free software; you can redistribute it and/or modify
65 it under the terms of the GNU General Public License as published by
66 the Free Software Foundation; either version 2 of the License, or
67 (at your option) any later version.
69 This program is distributed in the hope that it will be useful,
70 but WITHOUT ANY WARRANTY; without even the implied warranty of
71 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
72 GNU General Public License for more details.
74 You should have received a copy of the GNU General Public License
75 along with this program; if not, write to the Free Software Foundation,
76 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
78 ---------------------------------------------------------------------------*/
80 #define PROGNAME "rpng-win"
81 #define LONGNAME "Simple PNG Viewer for Windows"
82 #define VERSION "2.01 of 16 March 2008"
90 /* getch replacement. Turns out, we don't really need this,
91 * but leave it here if we ever enable any of the uses of
92 * _getch in the main code
96 #include <sys/ioctl.h>
97 int repl_getch( void )
100 int fd
= fileno(stdin
);
101 struct termio old_tty
, new_tty
;
103 ioctl(fd
, TCGETA
, &old_tty
);
105 new_tty
.c_lflag
&= ~(ICANON
| ECHO
| ISIG
);
106 ioctl(fd
, TCSETA
, &new_tty
);
107 fread(&ch
, 1, sizeof(ch
), stdin
);
108 ioctl(fd
, TCSETA
, &old_tty
);
112 #define _getch repl_getch
114 #include <conio.h> /* only for _getch() */
117 /* #define DEBUG : this enables the Trace() macros */
119 #include "readpng.h" /* typedefs, common macros, readpng prototypes */
122 /* could just include png.h, but this macro is the only thing we need
123 * (name and typedefs changed to local versions); note that side effects
124 * only happen with alpha (which could easily be avoided with
125 * "ush acopy = (alpha);") */
127 #define alpha_composite(composite, fg, alpha, bg) { \
128 ush temp = ((ush)(fg)*(ush)(alpha) + \
129 (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128); \
130 (composite) = (uch)((temp + (temp >> 8)) >> 8); \
134 /* local prototypes */
135 static int rpng_win_create_window(HINSTANCE hInst
, int showmode
);
136 static int rpng_win_display_image(void);
137 static void rpng_win_cleanup(void);
138 LRESULT CALLBACK
rpng_win_wndproc(HWND
, UINT
, WPARAM
, LPARAM
);
141 static char titlebar
[1024];
142 static char *progname
= PROGNAME
;
143 static char *appname
= LONGNAME
;
144 static char *filename
;
148 static uch bg_red
=0, bg_green
=0, bg_blue
=0;
150 static double display_exponent
;
152 static ulg image_width
, image_height
, image_rowbytes
;
153 static int image_channels
;
154 static uch
*image_data
;
156 /* Windows-specific variables */
157 static ulg wimage_rowbytes
;
159 static uch
*wimage_data
;
160 static BITMAPINFOHEADER
*bmih
;
162 static HWND global_hwnd
;
167 int WINAPI
WinMain(HINSTANCE hInst
, HINSTANCE hPrevInst
, PSTR cmd
, int showmode
)
169 char *args
[1024]; /* arbitrary limit, but should suffice */
170 char *p
, *q
, **argv
= args
;
175 double LUT_exponent
; /* just the lookup table */
176 double CRT_exponent
= 2.2; /* just the monitor */
177 double default_display_exponent
; /* whole display system */
181 filename
= (char *)NULL
;
184 /* First reenable console output, which normally goes to the bit bucket
185 * for windowed apps. Closing the console window will terminate the
186 * app. Thanks to David.Geldreich at realviz.com for supplying the magical
190 freopen("CONOUT$", "a", stderr
);
191 freopen("CONOUT$", "a", stdout
);
195 /* Next set the default value for our display-system exponent, i.e.,
196 * the product of the CRT exponent and the exponent corresponding to
197 * the frame-buffer's lookup table (LUT), if any. This is not an
198 * exhaustive list of LUT values (e.g., OpenStep has a lot of weird
199 * ones), but it should cover 99% of the current possibilities. And
200 * yes, these ifdefs are completely wasted in a Windows program... */
203 LUT_exponent
= 1.0 / 2.2;
205 if (some_next_function_that_returns_gamma(&next_gamma))
206 LUT_exponent = 1.0 / next_gamma;
209 LUT_exponent
= 1.0 / 1.7;
210 /* there doesn't seem to be any documented function to get the
211 * "gamma" value, so we do it the hard way */
212 infile
= fopen("/etc/config/system.glGammaVal", "r");
216 fgets(tmpline
, 80, infile
);
218 sgi_gamma
= atof(tmpline
);
220 LUT_exponent
= 1.0 / sgi_gamma
;
222 #elif defined(Macintosh)
223 LUT_exponent
= 1.8 / 2.61;
225 if (some_mac_function_that_returns_gamma(&mac_gamma))
226 LUT_exponent = mac_gamma / 2.61;
229 LUT_exponent
= 1.0; /* assume no LUT: most PCs */
232 /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
233 default_display_exponent
= LUT_exponent
* CRT_exponent
;
236 /* If the user has set the SCREEN_GAMMA environment variable as suggested
237 * (somewhat imprecisely) in the libpng documentation, use that; otherwise
238 * use the default value we just calculated. Either way, the user may
239 * override this via a command-line option. */
241 if ((p
= getenv("SCREEN_GAMMA")) != NULL
)
242 display_exponent
= atof(p
);
244 display_exponent
= default_display_exponent
;
247 /* Windows really hates command lines, so we have to set up our own argv.
248 * Note that we do NOT bother with quoted arguments here, so don't use
249 * filenames with spaces in 'em! */
251 argv
[argc
++] = PROGNAME
;
257 /* now p points at the first non-space after some spaces */
259 break; /* nothing after the spaces: done */
260 argv
[argc
++] = q
= p
;
261 while (*q
&& *q
!= ' ')
263 /* now q points at a space or the end of the string */
265 break; /* last argv already terminated; quit */
266 *q
= '\0'; /* change space to terminator */
269 argv
[argc
] = NULL
; /* terminate the argv array itself */
272 /* Now parse the command line for options and the PNG filename. */
274 while (*++argv
&& !error
) {
275 if (!strncmp(*argv
, "-gamma", 2)) {
279 display_exponent
= atof(*argv
);
280 if (display_exponent
<= 0.0)
283 } else if (!strncmp(*argv
, "-bgcolor", 2)) {
288 if (strlen(bgstr
) != 7 || bgstr
[0] != '#')
296 if (argv
[1]) /* shouldn't be any more args after filename */
299 ++error
; /* not expecting any other options */
307 /* print usage screen if any errors up to this point */
314 fprintf(stderr
, "\n%s %s: %s\n\n", PROGNAME
, VERSION
, appname
);
315 readpng_version_info();
317 "Usage: %s [-gamma exp] [-bgcolor bg] file.png\n"
318 " exp \ttransfer-function exponent (``gamma'') of the display\n"
319 "\t\t system in floating-point format (e.g., ``%.1f''); equal\n"
320 "\t\t to the product of the lookup-table exponent (varies)\n"
321 "\t\t and the CRT exponent (usually 2.2); must be positive\n"
322 " bg \tdesired background color in 7-character hex RGB format\n"
323 "\t\t (e.g., ``#ff7700'' for orange: same as HTML colors);\n"
324 "\t\t used with transparent images\n"
325 "\nPress Q, Esc or mouse button 1 after image is displayed to quit.\n"
327 "Press Q or Esc to quit this usage screen.\n"
329 "\n", PROGNAME
, default_display_exponent
);
333 while (ch
!= 'q' && ch
!= 'Q' && ch
!= 0x1B);
339 if (!(infile
= fopen(filename
, "rb"))) {
340 fprintf(stderr
, PROGNAME
": can't open PNG file [%s]\n", filename
);
343 if ((rc
= readpng_init(infile
, &image_width
, &image_height
)) != 0) {
346 fprintf(stderr
, PROGNAME
347 ": [%s] is not a PNG file: incorrect signature\n",
351 fprintf(stderr
, PROGNAME
352 ": [%s] has bad IHDR (libpng longjmp)\n", filename
);
355 fprintf(stderr
, PROGNAME
": insufficient memory\n");
358 fprintf(stderr
, PROGNAME
359 ": unknown readpng_init() error\n");
374 fprintf(stderr
, PROGNAME
": aborting.\n");
378 while (ch
!= 'q' && ch
!= 'Q' && ch
!= 0x1B);
382 fprintf(stderr
, "\n%s %s: %s\n", PROGNAME
, VERSION
, appname
);
385 "\n [console window: closing this window will terminate %s]\n\n",
391 /* set the title-bar string, but make sure buffer doesn't overflow */
393 alen
= strlen(appname
);
394 flen
= strlen(filename
);
395 if (alen
+ flen
+ 3 > 1023)
396 sprintf(titlebar
, "%s: ...%s", appname
, filename
+(alen
+flen
+6-1023));
398 sprintf(titlebar
, "%s: %s", appname
, filename
);
401 /* if the user didn't specify a background color on the command line,
402 * check for one in the PNG file--if not, the initialized values of 0
403 * (black) will be used */
406 unsigned r
, g
, b
; /* this approach quiets compiler warnings */
408 sscanf(bgstr
+1, "%2x%2x%2x", &r
, &g
, &b
);
412 } else if (readpng_get_bgcolor(&bg_red
, &bg_green
, &bg_blue
) > 1) {
413 readpng_cleanup(TRUE
);
414 fprintf(stderr
, PROGNAME
415 ": libpng error while checking for background color\n");
420 /* do the basic Windows initialization stuff, make the window and fill it
421 * with the background color */
423 if (rpng_win_create_window(hInst
, showmode
))
427 /* decode the image, all at once */
429 Trace((stderr
, "calling readpng_get_image()\n"))
430 image_data
= readpng_get_image(display_exponent
, &image_channels
,
432 Trace((stderr
, "done with readpng_get_image()\n"))
435 /* done with PNG file, so clean up to minimize memory usage (but do NOT
436 * nuke image_data!) */
438 readpng_cleanup(FALSE
);
442 fprintf(stderr
, PROGNAME
": unable to decode PNG image\n");
447 /* display image (composite with background if requested) */
449 Trace((stderr
, "calling rpng_win_display_image()\n"))
450 if (rpng_win_display_image()) {
454 Trace((stderr
, "done with rpng_win_display_image()\n"))
457 /* wait for the user to tell us when to quit */
461 "Done. Press Q, Esc or mouse button 1 (within image window) to quit.\n"
463 "Done. Press mouse button 1 (within image window) to quit.\n"
468 while (GetMessage(&msg
, NULL
, 0, 0)) {
469 TranslateMessage(&msg
);
470 DispatchMessage(&msg
);
474 /* OK, we're done: clean up all image and Windows resources and go away */
485 static int rpng_win_create_window(HINSTANCE hInst
, int showmode
)
488 int extra_width
, extra_height
;
493 /*---------------------------------------------------------------------------
494 Allocate memory for the display-specific version of the image (round up
495 to multiple of 4 for Windows DIB).
496 ---------------------------------------------------------------------------*/
498 wimage_rowbytes
= ((3*image_width
+ 3L) >> 2) << 2;
500 /* Guard against integer overflow */
501 if (image_height
> ((size_t)(-1))/wimage_rowbytes
) {
502 fprintf(stderr
, PROGNAME
": image_data buffer would be too large\n");
506 if (!(dib
= (uch
*)malloc(sizeof(BITMAPINFOHEADER
) +
507 wimage_rowbytes
*image_height
)))
512 /*---------------------------------------------------------------------------
513 Initialize the DIB. Negative height means to use top-down BMP ordering
514 (must be uncompressed, but that's what we want). Bit count of 1, 4 or 8
515 implies a colormap of RGBX quads, but 24-bit BMPs just use B,G,R values
516 directly => wimage_data begins immediately after BMP header.
517 ---------------------------------------------------------------------------*/
519 memset(dib
, 0, sizeof(BITMAPINFOHEADER
));
520 bmih
= (BITMAPINFOHEADER
*)dib
;
521 bmih
->biSize
= sizeof(BITMAPINFOHEADER
);
522 bmih
->biWidth
= image_width
;
523 bmih
->biHeight
= -((long)image_height
);
525 bmih
->biBitCount
= 24;
526 bmih
->biCompression
= 0;
527 wimage_data
= dib
+ sizeof(BITMAPINFOHEADER
);
529 /*---------------------------------------------------------------------------
530 Fill in background color (black by default); data are in BGR order.
531 ---------------------------------------------------------------------------*/
533 for (j
= 0; j
< image_height
; ++j
) {
534 dest
= wimage_data
+ j
*wimage_rowbytes
;
535 for (i
= image_width
; i
> 0; --i
) {
542 /*---------------------------------------------------------------------------
543 Set the window parameters.
544 ---------------------------------------------------------------------------*/
546 memset(&wndclass
, 0, sizeof(wndclass
));
548 wndclass
.cbSize
= sizeof(wndclass
);
549 wndclass
.style
= CS_HREDRAW
| CS_VREDRAW
;
550 wndclass
.lpfnWndProc
= rpng_win_wndproc
;
551 wndclass
.hInstance
= hInst
;
552 wndclass
.hIcon
= LoadIcon(NULL
, IDI_APPLICATION
);
553 wndclass
.hCursor
= LoadCursor(NULL
, IDC_ARROW
);
554 wndclass
.hbrBackground
= (HBRUSH
)GetStockObject(DKGRAY_BRUSH
);
555 wndclass
.lpszMenuName
= NULL
;
556 wndclass
.lpszClassName
= progname
;
557 wndclass
.hIconSm
= LoadIcon(NULL
, IDI_APPLICATION
);
559 RegisterClassEx(&wndclass
);
561 /*---------------------------------------------------------------------------
562 Finally, create the window.
563 ---------------------------------------------------------------------------*/
565 extra_width
= 2*(GetSystemMetrics(SM_CXBORDER
) +
566 GetSystemMetrics(SM_CXDLGFRAME
));
567 extra_height
= 2*(GetSystemMetrics(SM_CYBORDER
) +
568 GetSystemMetrics(SM_CYDLGFRAME
)) +
569 GetSystemMetrics(SM_CYCAPTION
);
571 global_hwnd
= CreateWindow(progname
, titlebar
, WS_OVERLAPPEDWINDOW
,
572 CW_USEDEFAULT
, CW_USEDEFAULT
, image_width
+extra_width
,
573 image_height
+extra_height
, NULL
, NULL
, hInst
, NULL
);
575 ShowWindow(global_hwnd
, showmode
);
576 UpdateWindow(global_hwnd
);
580 } /* end function rpng_win_create_window() */
586 static int rpng_win_display_image()
594 Trace((stderr
, "beginning display loop (image_channels == %d)\n",
596 Trace((stderr
, "(width = %ld, rowbytes = %ld, wimage_rowbytes = %d)\n",
597 image_width
, image_rowbytes
, wimage_rowbytes
))
600 /*---------------------------------------------------------------------------
601 Blast image data to buffer. This whole routine takes place before the
602 message loop begins, so there's no real point in any pseudo-progressive
604 ---------------------------------------------------------------------------*/
606 for (lastrow
= row
= 0; row
< image_height
; ++row
) {
607 src
= image_data
+ row
*image_rowbytes
;
608 dest
= wimage_data
+ row
*wimage_rowbytes
;
609 if (image_channels
== 3) {
610 for (i
= image_width
; i
> 0; --i
) {
615 *dest
++ = g
; /* note reverse order */
618 } else /* if (image_channels == 4) */ {
619 for (i
= image_width
; i
> 0; --i
) {
633 /* this macro (copied from png.h) composites the
634 * foreground and background values and puts the
635 * result into the first argument; there are no
636 * side effects with the first argument */
637 alpha_composite(*dest
++, b
, a
, bg_blue
);
638 alpha_composite(*dest
++, g
, a
, bg_green
);
639 alpha_composite(*dest
++, r
, a
, bg_red
);
643 /* display after every 16 lines */
644 if (((row
+1) & 0xf) == 0) {
646 rect
.top
= (LONG
)lastrow
;
647 rect
.right
= (LONG
)image_width
; /* possibly off by one? */
648 rect
.bottom
= (LONG
)lastrow
+ 16L; /* possibly off by one? */
649 InvalidateRect(global_hwnd
, &rect
, FALSE
);
650 UpdateWindow(global_hwnd
); /* similar to XFlush() */
655 Trace((stderr
, "calling final image-flush routine\n"))
656 if (lastrow
< image_height
) {
658 rect
.top
= (LONG
)lastrow
;
659 rect
.right
= (LONG
)image_width
; /* possibly off by one? */
660 rect
.bottom
= (LONG
)image_height
; /* possibly off by one? */
661 InvalidateRect(global_hwnd
, &rect
, FALSE
);
662 UpdateWindow(global_hwnd
); /* similar to XFlush() */
666 last param determines whether or not background is wiped before paint
667 InvalidateRect(global_hwnd, NULL, TRUE);
668 UpdateWindow(global_hwnd);
678 static void rpng_win_cleanup()
695 LRESULT CALLBACK
rpng_win_wndproc(HWND hwnd
, UINT iMsg
, WPARAM wP
, LPARAM lP
)
703 /* one-time processing here, if any */
707 hdc
= BeginPaint(hwnd
, &ps
);
709 rc
= StretchDIBits(hdc
, 0, 0, image_width
, image_height
,
711 0, 0, image_width
, image_height
,
712 wimage_data
, (BITMAPINFO
*)bmih
,
713 /* iUsage: no clue */
718 /* wait for the user to tell us when to quit */
720 switch (wP
) { /* only need one, so ignore repeat count */
723 case 0x1B: /* Esc key */
728 case WM_LBUTTONDOWN
: /* another way of quitting */
734 return DefWindowProc(hwnd
, iMsg
, wP
, lP
);