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 
26   ---------------------------------------------------------------------------
27 
28       Copyright (c) 1998-2001 Greg Roelofs.  All rights reserved.
29 
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
33       this software.
34 
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:
38 
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:
46 
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.
50 
51   ---------------------------------------------------------------------------*/
52 
53 #define PROGNAME  "rpng-win"
54 #define LONGNAME  "Simple PNG Viewer for Windows"
55 #define VERSION   "1.20 of 28 May 2001"
56 
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <time.h>
61 #include <windows.h>
62 #include <conio.h>      /* only for _getch() */
63 
64 /* #define DEBUG  :  this enables the Trace() macros */
65 
66 #include "readpng.h"    /* typedefs, common macros, readpng prototypes */
67 
68 
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);") */
73 
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);               \
78 }
79 
80 
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);
86 
87 
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;
93 static FILE *infile;
94 
95 static char *bgstr;
96 static uch bg_red=0, bg_green=0, bg_blue=0;
97 
98 static double display_exponent;
99 
100 static ulg image_width, image_height, image_rowbytes;
101 static int image_channels;
102 static uch *image_data;
103 
104 /* Windows-specific variables */
105 static ulg wimage_rowbytes;
106 static uch *dib;
107 static uch *wimage_data;
108 static BITMAPINFOHEADER *bmih;
109 
110 static HWND global_hwnd;
111 
112 
113 
114 
WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,PSTR cmd,int showmode)115 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmd, int showmode)
116 {
117     char *args[1024];                 /* arbitrary limit, but should suffice */
118     char *p, *q, **argv = args;
119     int argc = 0;
120     int rc, alen, flen;
121     int error = 0;
122     int have_bg = FALSE;
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 */
126     MSG msg;
127 
128 
129     filename = (char *)NULL;
130 
131 
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
135      * incantation. */
136 
137     AllocConsole();
138     freopen("CONOUT$", "a", stderr);
139     freopen("CONOUT$", "a", stdout);
140 
141 
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... */
148 
149 #if defined(NeXT)
150     LUT_exponent = 1.0 / 2.2;
151     /*
152     if (some_next_function_that_returns_gamma(&next_gamma))
153         LUT_exponent = 1.0 / next_gamma;
154      */
155 #elif defined(sgi)
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");
160     if (infile) {
161         double sgi_gamma;
162 
163         fgets(tmpline, 80, infile);
164         fclose(infile);
165         sgi_gamma = atof(tmpline);
166         if (sgi_gamma > 0.0)
167             LUT_exponent = 1.0 / sgi_gamma;
168     }
169 #elif defined(Macintosh)
170     LUT_exponent = 1.8 / 2.61;
171     /*
172     if (some_mac_function_that_returns_gamma(&mac_gamma))
173         LUT_exponent = mac_gamma / 2.61;
174      */
175 #else
176     LUT_exponent = 1.0;   /* assume no LUT:  most PCs */
177 #endif
178 
179     /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
180     default_display_exponent = LUT_exponent * CRT_exponent;
181 
182 
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. */
187 
188     if ((p = getenv("SCREEN_GAMMA")) != NULL)
189         display_exponent = atof(p);
190     else
191         display_exponent = default_display_exponent;
192 
193 
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! */
197 
198     argv[argc++] = PROGNAME;
199     p = cmd;
200     for (;;) {
201         if (*p == ' ')
202             while (*++p == ' ')
203                 ;
204         /* now p points at the first non-space after some spaces */
205         if (*p == '\0')
206             break;    /* nothing after the spaces:  done */
207         argv[argc++] = q = p;
208         while (*q && *q != ' ')
209             ++q;
210         /* now q points at a space or the end of the string */
211         if (*q == '\0')
212             break;    /* last argv already terminated; quit */
213         *q = '\0';    /* change space to terminator */
214         p = q + 1;
215     }
216     argv[argc] = NULL;   /* terminate the argv array itself */
217 
218 
219     /* Now parse the command line for options and the PNG filename. */
220 
221     while (*++argv && !error) {
222         if (!strncmp(*argv, "-gamma", 2)) {
223             if (!*++argv)
224                 ++error;
225             else {
226                 display_exponent = atof(*argv);
227                 if (display_exponent <= 0.0)
228                     ++error;
229             }
230         } else if (!strncmp(*argv, "-bgcolor", 2)) {
231             if (!*++argv)
232                 ++error;
233             else {
234                 bgstr = *argv;
235                 if (strlen(bgstr) != 7 || bgstr[0] != '#')
236                     ++error;
237                 else
238                     have_bg = TRUE;
239             }
240         } else {
241             if (**argv != '-') {
242                 filename = *argv;
243                 if (argv[1])   /* shouldn't be any more args after filename */
244                     ++error;
245             } else
246                 ++error;   /* not expecting any other options */
247         }
248     }
249 
250     if (!filename) {
251         ++error;
252     } else if (!(infile = fopen(filename, "rb"))) {
253         fprintf(stderr, PROGNAME ":  can't open PNG file [%s]\n", filename);
254         ++error;
255     } else {
256         if ((rc = readpng_init(infile, &image_width, &image_height)) != 0) {
257             switch (rc) {
258                 case 1:
259                     fprintf(stderr, PROGNAME
260                       ":  [%s] is not a PNG file: incorrect signature\n",
261                       filename);
262                     break;
263                 case 2:
264                     fprintf(stderr, PROGNAME
265                       ":  [%s] has bad IHDR (libpng longjmp)\n",
266                       filename);
267                     break;
268                 case 4:
269                     fprintf(stderr, PROGNAME ":  insufficient memory\n");
270                     break;
271                 default:
272                     fprintf(stderr, PROGNAME
273                       ":  unknown readpng_init() error\n");
274                     break;
275             }
276             ++error;
277         }
278         if (error)
279             fclose(infile);
280     }
281 
282 
283     /* usage screen */
284 
285     if (error) {
286         int ch;
287 
288         fprintf(stderr, "\n%s %s:  %s\n\n", PROGNAME, VERSION, appname);
289         readpng_version_info();
290         fprintf(stderr, "\n"
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);
302         do
303             ch = _getch();
304         while (ch != 'q' && ch != 'Q' && ch != 0x1B);
305         exit(1);
306     } else {
307         fprintf(stderr, "\n%s %s:  %s\n", PROGNAME, VERSION, appname);
308         fprintf(stderr,
309           "\n   [console window:  closing this window will terminate %s]\n\n",
310           PROGNAME);
311     }
312 
313 
314     /* set the title-bar string, but make sure buffer doesn't overflow */
315 
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));
320     else
321         sprintf(titlebar, "%s:  %s", appname, filename);
322 
323 
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 */
327 
328     if (have_bg)
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");
334         exit(2);
335     }
336 
337 
338     /* do the basic Windows initialization stuff, make the window and fill it
339      * with the background color */
340 
341     if (rpng_win_create_window(hInst, showmode))
342         exit(2);
343 
344 
345     /* decode the image, all at once */
346 
347     Trace((stderr, "calling readpng_get_image()\n"))
348     image_data = readpng_get_image(display_exponent, &image_channels,
349       &image_rowbytes);
350     Trace((stderr, "done with readpng_get_image()\n"))
351 
352 
353     /* done with PNG file, so clean up to minimize memory usage (but do NOT
354      * nuke image_data!) */
355 
356     readpng_cleanup(FALSE);
357     fclose(infile);
358 
359     if (!image_data) {
360         fprintf(stderr, PROGNAME ":  unable to decode PNG image\n");
361         exit(3);
362     }
363 
364 
365     /* display image (composite with background if requested) */
366 
367     Trace((stderr, "calling rpng_win_display_image()\n"))
368     if (rpng_win_display_image()) {
369         free(image_data);
370         exit(4);
371     }
372     Trace((stderr, "done with rpng_win_display_image()\n"))
373 
374 
375     /* wait for the user to tell us when to quit */
376 
377     printf(
378       "Done.  Press Q, Esc or mouse button 1 (within image window) to quit.\n");
379     fflush(stdout);
380 
381     while (GetMessage(&msg, NULL, 0, 0)) {
382         TranslateMessage(&msg);
383         DispatchMessage(&msg);
384     }
385 
386 
387     /* OK, we're done:  clean up all image and Windows resources and go away */
388 
389     rpng_win_cleanup();
390 
391     return msg.wParam;
392 }
393 
394 
395 
396 
397 
rpng_win_create_window(HINSTANCE hInst,int showmode)398 static int rpng_win_create_window(HINSTANCE hInst, int showmode)
399 {
400     uch *dest;
401     int extra_width, extra_height;
402     ulg i, j;
403     WNDCLASSEX wndclass;
404 
405 
406 /*---------------------------------------------------------------------------
407     Allocate memory for the display-specific version of the image (round up
408     to multiple of 4 for Windows DIB).
409   ---------------------------------------------------------------------------*/
410 
411     wimage_rowbytes = ((3*image_width + 3L) >> 2) << 2;
412 
413     if (!(dib = (uch *)malloc(sizeof(BITMAPINFOHEADER) +
414                               wimage_rowbytes*image_height)))
415     {
416         return 4;   /* fail */
417     }
418 
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   ---------------------------------------------------------------------------*/
425 
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);
431     bmih->biPlanes = 1;
432     bmih->biBitCount = 24;
433     bmih->biCompression = 0;
434     wimage_data = dib + sizeof(BITMAPINFOHEADER);
435 
436 /*---------------------------------------------------------------------------
437     Fill in background color (black by default); data are in BGR order.
438   ---------------------------------------------------------------------------*/
439 
440     for (j = 0;  j < image_height;  ++j) {
441         dest = wimage_data + j*wimage_rowbytes;
442         for (i = image_width;  i > 0;  --i) {
443             *dest++ = bg_blue;
444             *dest++ = bg_green;
445             *dest++ = bg_red;
446         }
447     }
448 
449 /*---------------------------------------------------------------------------
450     Set the window parameters.
451   ---------------------------------------------------------------------------*/
452 
453     memset(&wndclass, 0, sizeof(wndclass));
454 
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);
465 
466     RegisterClassEx(&wndclass);
467 
468 /*---------------------------------------------------------------------------
469     Finally, create the window.
470   ---------------------------------------------------------------------------*/
471 
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);
477 
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);
481 
482     ShowWindow(global_hwnd, showmode);
483     UpdateWindow(global_hwnd);
484 
485     return 0;
486 
487 } /* end function rpng_win_create_window() */
488 
489 
490 
491 
492 
rpng_win_display_image()493 static int rpng_win_display_image()
494 {
495     uch *src, *dest;
496     uch r, g, b, a;
497     ulg i, row, lastrow;
498     RECT rect;
499 
500 
501     Trace((stderr, "beginning display loop (image_channels == %d)\n",
502       image_channels))
503     Trace((stderr, "(width = %ld, rowbytes = %ld, wimage_rowbytes = %d)\n",
504       image_width, image_rowbytes, wimage_rowbytes))
505 
506 
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
510     display...
511   ---------------------------------------------------------------------------*/
512 
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) {
518                 r = *src++;
519                 g = *src++;
520                 b = *src++;
521                 *dest++ = b;
522                 *dest++ = g;   /* note reverse order */
523                 *dest++ = r;
524             }
525         } else /* if (image_channels == 4) */ {
526             for (i = image_width;  i > 0;  --i) {
527                 r = *src++;
528                 g = *src++;
529                 b = *src++;
530                 a = *src++;
531                 if (a == 255) {
532                     *dest++ = b;
533                     *dest++ = g;
534                     *dest++ = r;
535                 } else if (a == 0) {
536                     *dest++ = bg_blue;
537                     *dest++ = bg_green;
538                     *dest++ = bg_red;
539                 } else {
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);
547                 }
548             }
549         }
550         /* display after every 16 lines */
551         if (((row+1) & 0xf) == 0) {
552             rect.left = 0L;
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() */
558             lastrow = row + 1;
559         }
560     }
561 
562     Trace((stderr, "calling final image-flush routine\n"))
563     if (lastrow < image_height) {
564         rect.left = 0L;
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() */
570     }
571 
572 /*
573     last param determines whether or not background is wiped before paint
574     InvalidateRect(global_hwnd, NULL, TRUE);
575     UpdateWindow(global_hwnd);
576  */
577 
578     return 0;
579 }
580 
581 
582 
583 
584 
rpng_win_cleanup()585 static void rpng_win_cleanup()
586 {
587     if (image_data) {
588         free(image_data);
589         image_data = NULL;
590     }
591 
592     if (dib) {
593         free(dib);
594         dib = NULL;
595     }
596 }
597 
598 
599 
600 
601 
rpng_win_wndproc(HWND hwnd,UINT iMsg,WPARAM wP,LPARAM lP)602 LRESULT CALLBACK rpng_win_wndproc(HWND hwnd, UINT iMsg, WPARAM wP, LPARAM lP)
603 {
604     HDC         hdc;
605     PAINTSTRUCT ps;
606     int rc;
607 
608     switch (iMsg) {
609         case WM_CREATE:
610             /* one-time processing here, if any */
611             return 0;
612 
613         case WM_PAINT:
614             hdc = BeginPaint(hwnd, &ps);
615                     /*                    dest                          */
616             rc = StretchDIBits(hdc, 0, 0, image_width, image_height,
617                     /*                    source                        */
618                                     0, 0, image_width, image_height,
619                                     wimage_data, (BITMAPINFO *)bmih,
620                     /*              iUsage: no clue                     */
621                                     0, SRCCOPY);
622             EndPaint(hwnd, &ps);
623             return 0;
624 
625         /* wait for the user to tell us when to quit */
626         case WM_CHAR:
627             switch (wP) {      /* only need one, so ignore repeat count */
628                 case 'q':
629                 case 'Q':
630                 case 0x1B:     /* Esc key */
631                     PostQuitMessage(0);
632             }
633             return 0;
634 
635         case WM_LBUTTONDOWN:   /* another way of quitting */
636         case WM_DESTROY:
637             PostQuitMessage(0);
638             return 0;
639     }
640 
641     return DefWindowProc(hwnd, iMsg, wP, lP);
642 }
643