1 /*---------------------------------------------------------------------------
2 
3    rpng2 - progressive-model PNG display program                rpng2-win.c
4 
5    This program decodes and displays PNG files progressively, as if it were
6    a web browser (though the front end is only set up to read from files).
7    It supports gamma correction, user-specified background colors, and user-
8    specified background patterns (for transparent images).  This version is
9    for 32-bit Windows; it may compile under 16-bit Windows with a little
10    tweaking (or maybe not).  Thanks to Adam Costello and Pieter S. van der
11    Meulen for the "diamond" and "radial waves" patterns, respectively.
12 
13    to do (someday, maybe):
14     - handle quoted command-line args (especially filenames with spaces)
15     - finish resizable checkerboard-gradient (sizes 4-128?)
16     - use %.1023s to simplify truncation of title-bar string?
17     - have minimum window width:  oh well
18 
19   ---------------------------------------------------------------------------
20 
21    Changelog:
22     - 1.01:  initial public release
23     - 1.02:  fixed cut-and-paste error in usage screen (oops...)
24     - 1.03:  modified to allow abbreviated options
25     - 1.04:  removed bogus extra argument from usage fprintf() [Glenn R-P?];
26               fixed command-line parsing bug
27     - 1.10:  enabled "message window"/console (thanks to David Geldreich)
28     - 1.20:  added runtime MMX-enabling/disabling and new -mmx* options
29     - 1.21:  made minor tweak to usage screen to fit within 25-line console
30     - 1.22:  added AMD64/EM64T support (__x86_64__)
31     - 2.00:  dual-licensed (added GNU GPL)
32     - 2.01:  fixed 64-bit typo in readpng2.c
33     - 2.02:  fixed improper display of usage screen on PNG error(s); fixed
34               unexpected-EOF and file-read-error cases
35     - 2.03:  removed runtime MMX-enabling/disabling and obsolete -mmx* options
36 
37   ---------------------------------------------------------------------------
38 
39       Copyright (c) 1998-2008 Greg Roelofs.  All rights reserved.
40 
41       This software is provided "as is," without warranty of any kind,
42       express or implied.  In no event shall the author or contributors
43       be held liable for any damages arising in any way from the use of
44       this software.
45 
46       The contents of this file are DUAL-LICENSED.  You may modify and/or
47       redistribute this software according to the terms of one of the
48       following two licenses (at your option):
49 
50 
51       LICENSE 1 ("BSD-like with advertising clause"):
52 
53       Permission is granted to anyone to use this software for any purpose,
54       including commercial applications, and to alter it and redistribute
55       it freely, subject to the following restrictions:
56 
57       1. Redistributions of source code must retain the above copyright
58          notice, disclaimer, and this list of conditions.
59       2. Redistributions in binary form must reproduce the above copyright
60          notice, disclaimer, and this list of conditions in the documenta-
61          tion and/or other materials provided with the distribution.
62       3. All advertising materials mentioning features or use of this
63          software must display the following acknowledgment:
64 
65             This product includes software developed by Greg Roelofs
66             and contributors for the book, "PNG: The Definitive Guide,"
67             published by O'Reilly and Associates.
68 
69 
70       LICENSE 2 (GNU GPL v2 or later):
71 
72       This program is free software; you can redistribute it and/or modify
73       it under the terms of the GNU General Public License as published by
74       the Free Software Foundation; either version 2 of the License, or
75       (at your option) any later version.
76 
77       This program is distributed in the hope that it will be useful,
78       but WITHOUT ANY WARRANTY; without even the implied warranty of
79       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
80       GNU General Public License for more details.
81 
82       You should have received a copy of the GNU General Public License
83       along with this program; if not, write to the Free Software Foundation,
84       Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
85 
86   ---------------------------------------------------------------------------*/
87 
88 #define PROGNAME  "rpng2-win"
89 #define LONGNAME  "Progressive PNG Viewer for Windows"
90 #define VERSION   "2.02 of 16 March 2008"
91 
92 #include <stdio.h>
93 #include <stdlib.h>
94 #include <string.h>
95 #include <setjmp.h>    /* for jmpbuf declaration in readpng2.h */
96 #include <time.h>
97 #include <math.h>      /* only for PvdM background code */
98 #include <windows.h>
99 #ifdef __CYGWIN__
100 /* getch replacement. Turns out, we don't really need this,
101  * but leave it here if we ever enable any of the uses of
102  * _getch in the main code
103  */
104 #include <unistd.h>
105 #include <termio.h>
106 #include <sys/ioctl.h>
repl_getch(void)107 int repl_getch( void )
108 {
109   char ch;
110   int fd = fileno(stdin);
111   struct termio old_tty, new_tty;
112 
113   ioctl(fd, TCGETA, &old_tty);
114   new_tty = old_tty;
115   new_tty.c_lflag &= ~(ICANON | ECHO | ISIG);
116   ioctl(fd, TCSETA, &new_tty);
117   fread(&ch, 1, sizeof(ch), stdin);
118   ioctl(fd, TCSETA, &old_tty);
119 
120   return ch;
121 }
122 #define _getch repl_getch
123 #else
124 #include <conio.h>     /* only for _getch() */
125 #endif
126 
127 /* all for PvdM background code: */
128 #ifndef PI
129 #  define PI             3.141592653589793238
130 #endif
131 #define PI_2             (PI*0.5)
132 #define INV_PI_360       (360.0 / PI)
133 #define MAX(a,b)         (a>b?a:b)
134 #define MIN(a,b)         (a<b?a:b)
135 #define CLIP(a,min,max)  MAX(min,MIN((a),max))
136 #define ABS(a)           ((a)<0?-(a):(a))
137 #define CLIP8P(c)        MAX(0,(MIN((c),255)))   /* 8-bit pos. integer (uch) */
138 #define ROUNDF(f)        ((int)(f + 0.5))
139 
140 #define rgb1_max   bg_freq
141 #define rgb1_min   bg_gray
142 #define rgb2_max   bg_bsat
143 #define rgb2_min   bg_brot
144 
145 /* #define DEBUG */     /* this enables the Trace() macros */
146 
147 #include "readpng2.h"   /* typedefs, common macros, readpng2 prototypes */
148 
149 
150 /* could just include png.h, but this macro is the only thing we need
151  * (name and typedefs changed to local versions); note that side effects
152  * only happen with alpha (which could easily be avoided with
153  * "ush acopy = (alpha);") */
154 
155 #define alpha_composite(composite, fg, alpha, bg) {               \
156     ush temp = ((ush)(fg)*(ush)(alpha) +                          \
157                 (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128);  \
158     (composite) = (uch)((temp + (temp >> 8)) >> 8);               \
159 }
160 
161 
162 #define INBUFSIZE 4096   /* with pseudo-timing on (1 sec delay/block), this
163                           *  block size corresponds roughly to a download
164                           *  speed 10% faster than theoretical 33.6K maximum
165                           *  (assuming 8 data bits, 1 stop bit and no other
166                           *  overhead) */
167 
168 /* local prototypes */
169 static void       rpng2_win_init(void);
170 static int        rpng2_win_create_window(void);
171 static int        rpng2_win_load_bg_image(void);
172 static void       rpng2_win_display_row(ulg row);
173 static void       rpng2_win_finish_display(void);
174 static void       rpng2_win_cleanup(void);
175 LRESULT CALLBACK  rpng2_win_wndproc(HWND, UINT, WPARAM, LPARAM);
176 
177 
178 static char titlebar[1024];
179 static char *progname = PROGNAME;
180 static char *appname = LONGNAME;
181 static char *filename;
182 static FILE *infile;
183 
184 static mainprog_info rpng2_info;
185 
186 static uch inbuf[INBUFSIZE];
187 static int incount;
188 
189 static int pat = 6;         /* must be less than num_bgpat */
190 static int bg_image = 0;
191 static int bgscale = 16;
192 static ulg bg_rowbytes;
193 static uch *bg_data;
194 
195 static struct rgb_color {
196     uch r, g, b;
197 } rgb[] = {
198     {  0,   0,   0},    /*  0:  black */
199     {255, 255, 255},    /*  1:  white */
200     {173, 132,  57},    /*  2:  tan */
201     { 64, 132,   0},    /*  3:  medium green */
202     {189, 117,   1},    /*  4:  gold */
203     {253, 249,   1},    /*  5:  yellow */
204     {  0,   0, 255},    /*  6:  blue */
205     {  0,   0, 120},    /*  7:  medium blue */
206     {255,   0, 255},    /*  8:  magenta */
207     { 64,   0,  64},    /*  9:  dark magenta */
208     {255,   0,   0},    /* 10:  red */
209     { 64,   0,   0},    /* 11:  dark red */
210     {255, 127,   0},    /* 12:  orange */
211     {192,  96,   0},    /* 13:  darker orange */
212     { 24,  60,   0},    /* 14:  dark green-yellow */
213     { 85, 125, 200}     /* 15:  ice blue */
214 };
215 /* not used for now, but should be for error-checking:
216 static int num_rgb = sizeof(rgb) / sizeof(struct rgb_color);
217  */
218 
219 /*
220     This whole struct is a fairly cheesy way to keep the number of
221     command-line options to a minimum.  The radial-waves background
222     type is a particularly poor fit to the integer elements of the
223     struct...but a few macros and a little fixed-point math will do
224     wonders for ya.
225 
226     type bits:
227        F E D C B A 9 8 7 6 5 4 3 2 1 0
228                              | | | | |
229                              | | +-+-+-- 0 = sharp-edged checkerboard
230                              | |         1 = soft diamonds
231                              | |         2 = radial waves
232                              | |       3-7 = undefined
233                              | +-- gradient #2 inverted?
234                              +-- alternating columns inverted?
235  */
236 static struct background_pattern {
237     ush type;
238     int rgb1_max, rgb1_min;     /* or bg_freq, bg_gray */
239     int rgb2_max, rgb2_min;     /* or bg_bsat, bg_brot (both scaled by 10)*/
240 } bg[] = {
241     {0+8,   2,0,  1,15},        /* checkered:  tan/black vs. white/ice blue */
242     {0+24,  2,0,  1,0},         /* checkered:  tan/black vs. white/black */
243     {0+8,   4,5,  0,2},         /* checkered:  gold/yellow vs. black/tan */
244     {0+8,   4,5,  0,6},         /* checkered:  gold/yellow vs. black/blue */
245     {0,     7,0,  8,9},         /* checkered:  deep blue/black vs. magenta */
246     {0+8,  13,0,  5,14},        /* checkered:  orange/black vs. yellow */
247     {0+8,  12,0, 10,11},        /* checkered:  orange/black vs. red */
248     {1,     7,0,  8,0},         /* diamonds:  deep blue/black vs. magenta */
249     {1,    12,0, 11,0},         /* diamonds:  orange vs. dark red */
250     {1,    10,0,  7,0},         /* diamonds:  red vs. medium blue */
251     {1,     4,0,  5,0},         /* diamonds:  gold vs. yellow */
252     {1,     3,0,  0,0},         /* diamonds:  medium green vs. black */
253     {2,    16, 100,  20,   0},  /* radial:  ~hard radial color-beams */
254     {2,    18, 100,  10,   2},  /* radial:  soft, curved radial color-beams */
255     {2,    16, 256, 100, 250},  /* radial:  very tight spiral */
256     {2, 10000, 256,  11,   0}   /* radial:  dipole-moire' (almost fractal) */
257 };
258 static int num_bgpat = sizeof(bg) / sizeof(struct background_pattern);
259 
260 
261 /* Windows-specific global variables (could go in struct, but messy...) */
262 static ulg wimage_rowbytes;
263 static uch *dib;
264 static uch *wimage_data;
265 static BITMAPINFOHEADER *bmih;
266 
267 static HWND global_hwnd;
268 static HINSTANCE global_hInst;
269 static int global_showmode;
270 
271 
272 
273 
WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,PSTR cmd,int showmode)274 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmd, int showmode)
275 {
276     char *args[1024];                 /* arbitrary limit, but should suffice */
277     char **argv = args;
278     char *p, *q, *bgstr = NULL;
279     int argc = 0;
280     int rc, alen, flen;
281     int error = 0;
282     int timing = FALSE;
283     int have_bg = FALSE;
284     double LUT_exponent;              /* just the lookup table */
285     double CRT_exponent = 2.2;        /* just the monitor */
286     double default_display_exponent;  /* whole display system */
287     MSG msg;
288 
289 
290     /* First initialize a few things, just to be sure--memset takes care of
291      * default background color (black), booleans (FALSE), pointers (NULL),
292      * etc. */
293 
294     global_hInst = hInst;
295     global_showmode = showmode;
296     filename = (char *)NULL;
297     memset(&rpng2_info, 0, sizeof(mainprog_info));
298 
299 #ifndef __CYGWIN__
300     /* Next reenable console output, which normally goes to the bit bucket
301      * for windowed apps.  Closing the console window will terminate the
302      * app.  Thanks to David.Geldreich@realviz.com for supplying the magical
303      * incantation. */
304 
305     AllocConsole();
306     freopen("CONOUT$", "a", stderr);
307     freopen("CONOUT$", "a", stdout);
308 #endif
309 
310     /* Set the default value for our display-system exponent, i.e., the
311      * product of the CRT exponent and the exponent corresponding to
312      * the frame-buffer's lookup table (LUT), if any.  This is not an
313      * exhaustive list of LUT values (e.g., OpenStep has a lot of weird
314      * ones), but it should cover 99% of the current possibilities.  And
315      * yes, these ifdefs are completely wasted in a Windows program... */
316 
317 #if defined(NeXT)
318     /* third-party utilities can modify the default LUT exponent */
319     LUT_exponent = 1.0 / 2.2;
320     /*
321     if (some_next_function_that_returns_gamma(&next_gamma))
322         LUT_exponent = 1.0 / next_gamma;
323      */
324 #elif defined(sgi)
325     LUT_exponent = 1.0 / 1.7;
326     /* there doesn't seem to be any documented function to
327      * get the "gamma" value, so we do it the hard way */
328     infile = fopen("/etc/config/system.glGammaVal", "r");
329     if (infile) {
330         double sgi_gamma;
331 
332         fgets(tmpline, 80, infile);
333         fclose(infile);
334         sgi_gamma = atof(tmpline);
335         if (sgi_gamma > 0.0)
336             LUT_exponent = 1.0 / sgi_gamma;
337     }
338 #elif defined(Macintosh)
339     LUT_exponent = 1.8 / 2.61;
340     /*
341     if (some_mac_function_that_returns_gamma(&mac_gamma))
342         LUT_exponent = mac_gamma / 2.61;
343      */
344 #else
345     LUT_exponent = 1.0;   /* assume no LUT:  most PCs */
346 #endif
347 
348     /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
349     default_display_exponent = LUT_exponent * CRT_exponent;
350 
351 
352     /* If the user has set the SCREEN_GAMMA environment variable as suggested
353      * (somewhat imprecisely) in the libpng documentation, use that; otherwise
354      * use the default value we just calculated.  Either way, the user may
355      * override this via a command-line option. */
356 
357     if ((p = getenv("SCREEN_GAMMA")) != NULL)
358         rpng2_info.display_exponent = atof(p);
359     else
360         rpng2_info.display_exponent = default_display_exponent;
361 
362 
363     /* Windows really hates command lines, so we have to set up our own argv.
364      * Note that we do NOT bother with quoted arguments here, so don't use
365      * filenames with spaces in 'em! */
366 
367     argv[argc++] = PROGNAME;
368     p = cmd;
369     for (;;) {
370         if (*p == ' ')
371             while (*++p == ' ')
372                 ;
373         /* now p points at the first non-space after some spaces */
374         if (*p == '\0')
375             break;    /* nothing after the spaces:  done */
376         argv[argc++] = q = p;
377         while (*q && *q != ' ')
378             ++q;
379         /* now q points at a space or the end of the string */
380         if (*q == '\0')
381             break;    /* last argv already terminated; quit */
382         *q = '\0';    /* change space to terminator */
383         p = q + 1;
384     }
385     argv[argc] = NULL;   /* terminate the argv array itself */
386 
387 
388     /* Now parse the command line for options and the PNG filename. */
389 
390     while (*++argv && !error) {
391         if (!strncmp(*argv, "-gamma", 2)) {
392             if (!*++argv)
393                 ++error;
394             else {
395                 rpng2_info.display_exponent = atof(*argv);
396                 if (rpng2_info.display_exponent <= 0.0)
397                     ++error;
398             }
399         } else if (!strncmp(*argv, "-bgcolor", 4)) {
400             if (!*++argv)
401                 ++error;
402             else {
403                 bgstr = *argv;
404                 if (strlen(bgstr) != 7 || bgstr[0] != '#')
405                     ++error;
406                 else {
407                     have_bg = TRUE;
408                     bg_image = FALSE;
409                 }
410             }
411         } else if (!strncmp(*argv, "-bgpat", 4)) {
412             if (!*++argv)
413                 ++error;
414             else {
415                 pat = atoi(*argv) - 1;
416                 if (pat < 0 || pat >= num_bgpat)
417                     ++error;
418                 else {
419                     bg_image = TRUE;
420                     have_bg = FALSE;
421                 }
422             }
423         } else if (!strncmp(*argv, "-timing", 2)) {
424             timing = TRUE;
425         } else {
426             if (**argv != '-') {
427                 filename = *argv;
428                 if (argv[1])   /* shouldn't be any more args after filename */
429                     ++error;
430             } else
431                 ++error;   /* not expecting any other options */
432         }
433     }
434 
435     if (!filename)
436         ++error;
437 
438 
439     /* print usage screen if any errors up to this point */
440 
441     if (error) {
442 #ifndef __CYGWIN__
443         int ch;
444 #endif
445 
446         fprintf(stderr, "\n%s %s:  %s\n\n", PROGNAME, VERSION, appname);
447         readpng2_version_info();
448         fprintf(stderr, "\n"
449           "Usage:  %s [-gamma exp] [-bgcolor bg | -bgpat pat] [-timing]\n"
450           "        %*s file.png\n\n"
451           "    exp \ttransfer-function exponent (``gamma'') of the display\n"
452           "\t\t  system in floating-point format (e.g., ``%.1f''); equal\n"
453           "\t\t  to the product of the lookup-table exponent (varies)\n"
454           "\t\t  and the CRT exponent (usually 2.2); must be positive\n"
455           "    bg  \tdesired background color in 7-character hex RGB format\n"
456           "\t\t  (e.g., ``#ff7700'' for orange:  same as HTML colors);\n"
457           "\t\t  used with transparent images; overrides -bgpat option\n"
458           "    pat \tdesired background pattern number (1-%d); used with\n"
459           "\t\t  transparent images; overrides -bgcolor option\n"
460           "    -timing\tenables delay for every block read, to simulate modem\n"
461           "\t\t  download of image (~36 Kbps)\n"
462           "\nPress Q, Esc or mouse button 1 after image is displayed to quit.\n"
463 #ifndef __CYGWIN__
464           "Press Q or Esc to quit this usage screen. ",
465 #else
466           ,
467 #endif
468           PROGNAME,
469 #if (defined(__i386__) || defined(_M_IX86) || defined(__x86_64__)) && \
470     !(defined(__CYGWIN__) || defined(__MINGW32__))
471           (int)strlen(PROGNAME), " ",
472 #endif
473           (int)strlen(PROGNAME), " ", default_display_exponent, num_bgpat);
474         fflush(stderr);
475 #ifndef __CYGWIN__
476         do
477             ch = _getch();
478         while (ch != 'q' && ch != 'Q' && ch != 0x1B);
479 #endif
480         exit(1);
481     }
482 
483 
484     if (!(infile = fopen(filename, "rb"))) {
485         fprintf(stderr, PROGNAME ":  can't open PNG file [%s]\n", filename);
486         ++error;
487     } else {
488         incount = fread(inbuf, 1, INBUFSIZE, infile);
489         if (incount < 8 || !readpng2_check_sig(inbuf, 8)) {
490             fprintf(stderr, PROGNAME
491               ":  [%s] is not a PNG file: incorrect signature\n",
492               filename);
493             ++error;
494         } else if ((rc = readpng2_init(&rpng2_info)) != 0) {
495             switch (rc) {
496                 case 2:
497                     fprintf(stderr, PROGNAME
498                       ":  [%s] has bad IHDR (libpng longjmp)\n", filename);
499                     break;
500                 case 4:
501                     fprintf(stderr, PROGNAME ":  insufficient memory\n");
502                     break;
503                 default:
504                     fprintf(stderr, PROGNAME
505                       ":  unknown readpng2_init() error\n");
506                     break;
507             }
508             ++error;
509         }
510         if (error)
511             fclose(infile);
512     }
513 
514 
515     if (error) {
516 #ifndef __CYGWIN__
517         int ch;
518 #endif
519 
520         fprintf(stderr, PROGNAME ":  aborting.\n");
521 #ifndef __CYGWIN__
522         do
523             ch = _getch();
524         while (ch != 'q' && ch != 'Q' && ch != 0x1B);
525 #endif
526         exit(2);
527     } else {
528         fprintf(stderr, "\n%s %s:  %s\n", PROGNAME, VERSION, appname);
529 #ifndef __CYGWIN__
530         fprintf(stderr,
531           "\n   [console window:  closing this window will terminate %s]\n\n",
532           PROGNAME);
533 #endif
534         fflush(stderr);
535     }
536 
537 
538     /* set the title-bar string, but make sure buffer doesn't overflow */
539 
540     alen = strlen(appname);
541     flen = strlen(filename);
542     if (alen + flen + 3 > 1023)
543         sprintf(titlebar, "%s:  ...%s", appname, filename+(alen+flen+6-1023));
544     else
545         sprintf(titlebar, "%s:  %s", appname, filename);
546 
547 
548     /* set some final rpng2_info variables before entering main data loop */
549 
550     if (have_bg) {
551         unsigned r, g, b;   /* this approach quiets compiler warnings */
552 
553         sscanf(bgstr+1, "%2x%2x%2x", &r, &g, &b);
554         rpng2_info.bg_red   = (uch)r;
555         rpng2_info.bg_green = (uch)g;
556         rpng2_info.bg_blue  = (uch)b;
557     } else
558         rpng2_info.need_bgcolor = TRUE;
559 
560     rpng2_info.state = kPreInit;
561     rpng2_info.mainprog_init = rpng2_win_init;
562     rpng2_info.mainprog_display_row = rpng2_win_display_row;
563     rpng2_info.mainprog_finish_display = rpng2_win_finish_display;
564 
565 
566     /* OK, this is the fun part:  call readpng2_decode_data() at the start of
567      * the loop to deal with our first buffer of data (read in above to verify
568      * that the file is a PNG image), then loop through the file and continue
569      * calling the same routine to handle each chunk of data.  It in turn
570      * passes the data to libpng, which will invoke one or more of our call-
571      * backs as decoded data become available.  We optionally call Sleep() for
572      * one second per iteration to simulate downloading the image via an analog
573      * modem. */
574 
575     for (;;) {
576         Trace((stderr, "about to call readpng2_decode_data()\n"))
577         if (readpng2_decode_data(&rpng2_info, inbuf, incount))
578             ++error;
579         Trace((stderr, "done with readpng2_decode_data()\n"))
580 
581         if (error || incount != INBUFSIZE || rpng2_info.state == kDone) {
582             if (rpng2_info.state == kDone) {
583                 Trace((stderr, "done decoding PNG image\n"))
584             } else if (ferror(infile)) {
585                 fprintf(stderr, PROGNAME
586                   ":  error while reading PNG image file\n");
587                 exit(3);
588             } else if (feof(infile)) {
589                 fprintf(stderr, PROGNAME ":  end of file reached "
590                   "(unexpectedly) while reading PNG image file\n");
591                 exit(3);
592             } else /* if (error) */ {
593                 /* will print error message below */
594             }
595             break;
596         }
597 
598         if (timing)
599             Sleep(1000L);
600 
601         incount = fread(inbuf, 1, INBUFSIZE, infile);
602     }
603 
604 
605     /* clean up PNG stuff and report any decoding errors */
606 
607     fclose(infile);
608     Trace((stderr, "about to call readpng2_cleanup()\n"))
609     readpng2_cleanup(&rpng2_info);
610 
611     if (error) {
612         fprintf(stderr, PROGNAME ":  libpng error while decoding PNG image\n");
613         exit(3);
614     }
615 
616 
617     /* wait for the user to tell us when to quit */
618 
619     while (GetMessage(&msg, NULL, 0, 0)) {
620         TranslateMessage(&msg);
621         DispatchMessage(&msg);
622     }
623 
624 
625     /* we're done:  clean up all image and Windows resources and go away */
626 
627     Trace((stderr, "about to call rpng2_win_cleanup()\n"))
628     rpng2_win_cleanup();
629 
630     return msg.wParam;
631 }
632 
633 
634 
635 
636 
637 /* this function is called by readpng2_info_callback() in readpng2.c, which
638  * in turn is called by libpng after all of the pre-IDAT chunks have been
639  * read and processed--i.e., we now have enough info to finish initializing */
640 
rpng2_win_init()641 static void rpng2_win_init()
642 {
643     ulg i;
644     ulg rowbytes = rpng2_info.rowbytes;
645 
646     Trace((stderr, "beginning rpng2_win_init()\n"))
647     Trace((stderr, "  rowbytes = %d\n", rpng2_info.rowbytes))
648     Trace((stderr, "  width  = %ld\n", rpng2_info.width))
649     Trace((stderr, "  height = %ld\n", rpng2_info.height))
650 
651     rpng2_info.image_data = (uch *)malloc(rowbytes * rpng2_info.height);
652     if (!rpng2_info.image_data) {
653         readpng2_cleanup(&rpng2_info);
654         return;
655     }
656 
657     rpng2_info.row_pointers = (uch **)malloc(rpng2_info.height * sizeof(uch *));
658     if (!rpng2_info.row_pointers) {
659         free(rpng2_info.image_data);
660         rpng2_info.image_data = NULL;
661         readpng2_cleanup(&rpng2_info);
662         return;
663     }
664 
665     for (i = 0;  i < rpng2_info.height;  ++i)
666         rpng2_info.row_pointers[i] = rpng2_info.image_data + i*rowbytes;
667 
668 /*---------------------------------------------------------------------------
669     Do the basic Windows initialization stuff, make the window, and fill it
670     with the user-specified, file-specified or default background color.
671   ---------------------------------------------------------------------------*/
672 
673     if (rpng2_win_create_window()) {
674         readpng2_cleanup(&rpng2_info);
675         return;
676     }
677 
678     rpng2_info.state = kWindowInit;
679 }
680 
681 
682 
683 
684 
rpng2_win_create_window()685 static int rpng2_win_create_window()
686 {
687     uch bg_red   = rpng2_info.bg_red;
688     uch bg_green = rpng2_info.bg_green;
689     uch bg_blue  = rpng2_info.bg_blue;
690     uch *dest;
691     int extra_width, extra_height;
692     ulg i, j;
693     WNDCLASSEX wndclass;
694     RECT rect;
695 
696 
697 /*---------------------------------------------------------------------------
698     Allocate memory for the display-specific version of the image (round up
699     to multiple of 4 for Windows DIB).
700   ---------------------------------------------------------------------------*/
701 
702     wimage_rowbytes = ((3*rpng2_info.width + 3L) >> 2) << 2;
703 
704     if (!(dib = (uch *)malloc(sizeof(BITMAPINFOHEADER) +
705                               wimage_rowbytes*rpng2_info.height)))
706     {
707         return 4;   /* fail */
708     }
709 
710 /*---------------------------------------------------------------------------
711     Initialize the DIB.  Negative height means to use top-down BMP ordering
712     (must be uncompressed, but that's what we want).  Bit count of 1, 4 or 8
713     implies a colormap of RGBX quads, but 24-bit BMPs just use B,G,R values
714     directly => wimage_data begins immediately after BMP header.
715   ---------------------------------------------------------------------------*/
716 
717     memset(dib, 0, sizeof(BITMAPINFOHEADER));
718     bmih = (BITMAPINFOHEADER *)dib;
719     bmih->biSize = sizeof(BITMAPINFOHEADER);
720     bmih->biWidth = rpng2_info.width;
721     bmih->biHeight = -((long)rpng2_info.height);
722     bmih->biPlanes = 1;
723     bmih->biBitCount = 24;
724     bmih->biCompression = 0;
725     wimage_data = dib + sizeof(BITMAPINFOHEADER);
726 
727 /*---------------------------------------------------------------------------
728     Fill window with the specified background color (default is black), but
729     defer loading faked "background image" until window is displayed (may be
730     slow to compute).  Data are in BGR order.
731   ---------------------------------------------------------------------------*/
732 
733     if (bg_image) {   /* just fill with black for now */
734         memset(wimage_data, 0, wimage_rowbytes*rpng2_info.height);
735     } else {
736         for (j = 0;  j < rpng2_info.height;  ++j) {
737             dest = wimage_data + j*wimage_rowbytes;
738             for (i = rpng2_info.width;  i > 0;  --i) {
739                 *dest++ = bg_blue;
740                 *dest++ = bg_green;
741                 *dest++ = bg_red;
742             }
743         }
744     }
745 
746 /*---------------------------------------------------------------------------
747     Set the window parameters.
748   ---------------------------------------------------------------------------*/
749 
750     memset(&wndclass, 0, sizeof(wndclass));
751 
752     wndclass.cbSize = sizeof(wndclass);
753     wndclass.style = CS_HREDRAW | CS_VREDRAW;
754     wndclass.lpfnWndProc = rpng2_win_wndproc;
755     wndclass.hInstance = global_hInst;
756     wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
757     wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
758     wndclass.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);
759     wndclass.lpszMenuName = NULL;
760     wndclass.lpszClassName = progname;
761     wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
762 
763     RegisterClassEx(&wndclass);
764 
765 /*---------------------------------------------------------------------------
766     Finally, create the window.
767   ---------------------------------------------------------------------------*/
768 
769     extra_width  = 2*(GetSystemMetrics(SM_CXBORDER) +
770                       GetSystemMetrics(SM_CXDLGFRAME));
771     extra_height = 2*(GetSystemMetrics(SM_CYBORDER) +
772                       GetSystemMetrics(SM_CYDLGFRAME)) +
773                       GetSystemMetrics(SM_CYCAPTION);
774 
775     global_hwnd = CreateWindow(progname, titlebar, WS_OVERLAPPEDWINDOW,
776       CW_USEDEFAULT, CW_USEDEFAULT, rpng2_info.width+extra_width,
777       rpng2_info.height+extra_height, NULL, NULL, global_hInst, NULL);
778 
779     ShowWindow(global_hwnd, global_showmode);
780     UpdateWindow(global_hwnd);
781 
782 /*---------------------------------------------------------------------------
783     Now compute the background image and display it.  If it fails (memory
784     allocation), revert to a plain background color.
785   ---------------------------------------------------------------------------*/
786 
787     if (bg_image) {
788         static const char *msg = "Computing background image...";
789         int x, y, len = strlen(msg);
790         HDC hdc = GetDC(global_hwnd);
791         TEXTMETRIC tm;
792 
793         GetTextMetrics(hdc, &tm);
794         x = (rpng2_info.width - len*tm.tmAveCharWidth)/2;
795         y = (rpng2_info.height - tm.tmHeight)/2;
796         SetBkMode(hdc, TRANSPARENT);
797         SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
798         /* this can still begin out of bounds even if x is positive (???): */
799         TextOut(hdc, ((x < 0)? 0 : x), ((y < 0)? 0 : y), msg, len);
800         ReleaseDC(global_hwnd, hdc);
801 
802         rpng2_win_load_bg_image();   /* resets bg_image if fails */
803     }
804 
805     if (!bg_image) {
806         for (j = 0;  j < rpng2_info.height;  ++j) {
807             dest = wimage_data + j*wimage_rowbytes;
808             for (i = rpng2_info.width;  i > 0;  --i) {
809                 *dest++ = bg_blue;
810                 *dest++ = bg_green;
811                 *dest++ = bg_red;
812             }
813         }
814     }
815 
816     rect.left = 0L;
817     rect.top = 0L;
818     rect.right = (LONG)rpng2_info.width;       /* possibly off by one? */
819     rect.bottom = (LONG)rpng2_info.height;     /* possibly off by one? */
820     InvalidateRect(global_hwnd, &rect, FALSE);
821     UpdateWindow(global_hwnd);                 /* similar to XFlush() */
822 
823     return 0;
824 
825 } /* end function rpng2_win_create_window() */
826 
827 
828 
829 
830 
rpng2_win_load_bg_image()831 static int rpng2_win_load_bg_image()
832 {
833     uch *src, *dest;
834     uch r1, r2, g1, g2, b1, b2;
835     uch r1_inv, r2_inv, g1_inv, g2_inv, b1_inv, b2_inv;
836     int k, hmax, max;
837     int xidx, yidx, yidx_max = (bgscale-1);
838     int even_odd_vert, even_odd_horiz, even_odd;
839     int invert_gradient2 = (bg[pat].type & 0x08);
840     int invert_column;
841     ulg i, row;
842 
843 /*---------------------------------------------------------------------------
844     Allocate buffer for fake background image to be used with transparent
845     images; if this fails, revert to plain background color.
846   ---------------------------------------------------------------------------*/
847 
848     bg_rowbytes = 3 * rpng2_info.width;
849     bg_data = (uch *)malloc(bg_rowbytes * rpng2_info.height);
850     if (!bg_data) {
851         fprintf(stderr, PROGNAME
852           ":  unable to allocate memory for background image\n");
853         bg_image = 0;
854         return 1;
855     }
856 
857 /*---------------------------------------------------------------------------
858     Vertical gradients (ramps) in NxN squares, alternating direction and
859     colors (N == bgscale).
860   ---------------------------------------------------------------------------*/
861 
862     if ((bg[pat].type & 0x07) == 0) {
863         uch r1_min  = rgb[bg[pat].rgb1_min].r;
864         uch g1_min  = rgb[bg[pat].rgb1_min].g;
865         uch b1_min  = rgb[bg[pat].rgb1_min].b;
866         uch r2_min  = rgb[bg[pat].rgb2_min].r;
867         uch g2_min  = rgb[bg[pat].rgb2_min].g;
868         uch b2_min  = rgb[bg[pat].rgb2_min].b;
869         int r1_diff = rgb[bg[pat].rgb1_max].r - r1_min;
870         int g1_diff = rgb[bg[pat].rgb1_max].g - g1_min;
871         int b1_diff = rgb[bg[pat].rgb1_max].b - b1_min;
872         int r2_diff = rgb[bg[pat].rgb2_max].r - r2_min;
873         int g2_diff = rgb[bg[pat].rgb2_max].g - g2_min;
874         int b2_diff = rgb[bg[pat].rgb2_max].b - b2_min;
875 
876         for (row = 0;  row < rpng2_info.height;  ++row) {
877             yidx = row % bgscale;
878             even_odd_vert = (row / bgscale) & 1;
879 
880             r1 = r1_min + (r1_diff * yidx) / yidx_max;
881             g1 = g1_min + (g1_diff * yidx) / yidx_max;
882             b1 = b1_min + (b1_diff * yidx) / yidx_max;
883             r1_inv = r1_min + (r1_diff * (yidx_max-yidx)) / yidx_max;
884             g1_inv = g1_min + (g1_diff * (yidx_max-yidx)) / yidx_max;
885             b1_inv = b1_min + (b1_diff * (yidx_max-yidx)) / yidx_max;
886 
887             r2 = r2_min + (r2_diff * yidx) / yidx_max;
888             g2 = g2_min + (g2_diff * yidx) / yidx_max;
889             b2 = b2_min + (b2_diff * yidx) / yidx_max;
890             r2_inv = r2_min + (r2_diff * (yidx_max-yidx)) / yidx_max;
891             g2_inv = g2_min + (g2_diff * (yidx_max-yidx)) / yidx_max;
892             b2_inv = b2_min + (b2_diff * (yidx_max-yidx)) / yidx_max;
893 
894             dest = bg_data + row*bg_rowbytes;
895             for (i = 0;  i < rpng2_info.width;  ++i) {
896                 even_odd_horiz = (i / bgscale) & 1;
897                 even_odd = even_odd_vert ^ even_odd_horiz;
898                 invert_column =
899                   (even_odd_horiz && (bg[pat].type & 0x10));
900                 if (even_odd == 0) {         /* gradient #1 */
901                     if (invert_column) {
902                         *dest++ = r1_inv;
903                         *dest++ = g1_inv;
904                         *dest++ = b1_inv;
905                     } else {
906                         *dest++ = r1;
907                         *dest++ = g1;
908                         *dest++ = b1;
909                     }
910                 } else {                     /* gradient #2 */
911                     if ((invert_column && invert_gradient2) ||
912                         (!invert_column && !invert_gradient2))
913                     {
914                         *dest++ = r2;        /* not inverted or */
915                         *dest++ = g2;        /*  doubly inverted */
916                         *dest++ = b2;
917                     } else {
918                         *dest++ = r2_inv;
919                         *dest++ = g2_inv;    /* singly inverted */
920                         *dest++ = b2_inv;
921                     }
922                 }
923             }
924         }
925 
926 /*---------------------------------------------------------------------------
927     Soft gradient-diamonds with scale = bgscale.  Code contributed by Adam
928     M. Costello.
929   ---------------------------------------------------------------------------*/
930 
931     } else if ((bg[pat].type & 0x07) == 1) {
932 
933         hmax = (bgscale-1)/2;   /* half the max weight of a color */
934         max = 2*hmax;           /* the max weight of a color */
935 
936         r1 = rgb[bg[pat].rgb1_max].r;
937         g1 = rgb[bg[pat].rgb1_max].g;
938         b1 = rgb[bg[pat].rgb1_max].b;
939         r2 = rgb[bg[pat].rgb2_max].r;
940         g2 = rgb[bg[pat].rgb2_max].g;
941         b2 = rgb[bg[pat].rgb2_max].b;
942 
943         for (row = 0;  row < rpng2_info.height;  ++row) {
944             yidx = row % bgscale;
945             if (yidx > hmax)
946                 yidx = bgscale-1 - yidx;
947             dest = bg_data + row*bg_rowbytes;
948             for (i = 0;  i < rpng2_info.width;  ++i) {
949                 xidx = i % bgscale;
950                 if (xidx > hmax)
951                     xidx = bgscale-1 - xidx;
952                 k = xidx + yidx;
953                 *dest++ = (k*r1 + (max-k)*r2) / max;
954                 *dest++ = (k*g1 + (max-k)*g2) / max;
955                 *dest++ = (k*b1 + (max-k)*b2) / max;
956             }
957         }
958 
959 /*---------------------------------------------------------------------------
960     Radial "starburst" with azimuthal sinusoids; [eventually number of sinu-
961     soids will equal bgscale?].  This one is slow but very cool.  Code con-
962     tributed by Pieter S. van der Meulen (originally in Smalltalk).
963   ---------------------------------------------------------------------------*/
964 
965     } else if ((bg[pat].type & 0x07) == 2) {
966         uch ch;
967         int ii, x, y, hw, hh, grayspot;
968         double freq, rotate, saturate, gray, intensity;
969         double angle=0.0, aoffset=0.0, maxDist, dist;
970         double red=0.0, green=0.0, blue=0.0, hue, s, v, f, p, q, t;
971 
972         fprintf(stderr, "%s:  computing radial background...",
973           PROGNAME);
974         fflush(stderr);
975 
976         hh = rpng2_info.height / 2;
977         hw = rpng2_info.width / 2;
978 
979         /* variables for radial waves:
980          *   aoffset:  number of degrees to rotate hue [CURRENTLY NOT USED]
981          *   freq:  number of color beams originating from the center
982          *   grayspot:  size of the graying center area (anti-alias)
983          *   rotate:  rotation of the beams as a function of radius
984          *   saturate:  saturation of beams' shape azimuthally
985          */
986         angle = CLIP(angle, 0.0, 360.0);
987         grayspot = CLIP(bg[pat].bg_gray, 1, (hh + hw));
988         freq = MAX((double)bg[pat].bg_freq, 0.0);
989         saturate = (double)bg[pat].bg_bsat * 0.1;
990         rotate = (double)bg[pat].bg_brot * 0.1;
991         gray = 0.0;
992         intensity = 0.0;
993         maxDist = (double)((hw*hw) + (hh*hh));
994 
995         for (row = 0;  row < rpng2_info.height;  ++row) {
996             y = row - hh;
997             dest = bg_data + row*bg_rowbytes;
998             for (i = 0;  i < rpng2_info.width;  ++i) {
999                 x = i - hw;
1000                 angle = (x == 0)? PI_2 : atan((double)y / (double)x);
1001                 gray = (double)MAX(ABS(y), ABS(x)) / grayspot;
1002                 gray = MIN(1.0, gray);
1003                 dist = (double)((x*x) + (y*y)) / maxDist;
1004                 intensity = cos((angle+(rotate*dist*PI)) * freq) *
1005                   gray * saturate;
1006                 intensity = (MAX(MIN(intensity,1.0),-1.0) + 1.0) * 0.5;
1007                 hue = (angle + PI) * INV_PI_360 + aoffset;
1008                 s = gray * ((double)(ABS(x)+ABS(y)) / (double)(hw + hh));
1009                 s = MIN(MAX(s,0.0), 1.0);
1010                 v = MIN(MAX(intensity,0.0), 1.0);
1011 
1012                 if (s == 0.0) {
1013                     ch = (uch)(v * 255.0);
1014                     *dest++ = ch;
1015                     *dest++ = ch;
1016                     *dest++ = ch;
1017                 } else {
1018                     if ((hue < 0.0) || (hue >= 360.0))
1019                         hue -= (((int)(hue / 360.0)) * 360.0);
1020                     hue /= 60.0;
1021                     ii = (int)hue;
1022                     f = hue - (double)ii;
1023                     p = (1.0 - s) * v;
1024                     q = (1.0 - (s * f)) * v;
1025                     t = (1.0 - (s * (1.0 - f))) * v;
1026                     if      (ii == 0) { red = v; green = t; blue = p; }
1027                     else if (ii == 1) { red = q; green = v; blue = p; }
1028                     else if (ii == 2) { red = p; green = v; blue = t; }
1029                     else if (ii == 3) { red = p; green = q; blue = v; }
1030                     else if (ii == 4) { red = t; green = p; blue = v; }
1031                     else if (ii == 5) { red = v; green = p; blue = q; }
1032                     *dest++ = (uch)(red * 255.0);
1033                     *dest++ = (uch)(green * 255.0);
1034                     *dest++ = (uch)(blue * 255.0);
1035                 }
1036             }
1037         }
1038         fprintf(stderr, "done.\n");
1039         fflush(stderr);
1040     }
1041 
1042 /*---------------------------------------------------------------------------
1043     Blast background image to display buffer before beginning PNG decode;
1044     calling function will handle invalidation and UpdateWindow() call.
1045   ---------------------------------------------------------------------------*/
1046 
1047     for (row = 0;  row < rpng2_info.height;  ++row) {
1048         src = bg_data + row*bg_rowbytes;
1049         dest = wimage_data + row*wimage_rowbytes;
1050         for (i = rpng2_info.width;  i > 0;  --i) {
1051             r1 = *src++;
1052             g1 = *src++;
1053             b1 = *src++;
1054             *dest++ = b1;
1055             *dest++ = g1;   /* note reverse order */
1056             *dest++ = r1;
1057         }
1058     }
1059 
1060     return 0;
1061 
1062 } /* end function rpng2_win_load_bg_image() */
1063 
1064 
1065 
1066 
1067 
rpng2_win_display_row(ulg row)1068 static void rpng2_win_display_row(ulg row)
1069 {
1070     uch bg_red   = rpng2_info.bg_red;
1071     uch bg_green = rpng2_info.bg_green;
1072     uch bg_blue  = rpng2_info.bg_blue;
1073     uch *src, *src2=NULL, *dest;
1074     uch r, g, b, a;
1075     ulg i;
1076     static int rows=0;
1077     static ulg firstrow;
1078 
1079 /*---------------------------------------------------------------------------
1080     rows and firstrow simply track how many rows (and which ones) have not
1081     yet been displayed; alternatively, we could call InvalidateRect() for
1082     every row and not bother with the records-keeping.
1083   ---------------------------------------------------------------------------*/
1084 
1085     Trace((stderr, "beginning rpng2_win_display_row()\n"))
1086 
1087     if (rows == 0)
1088         firstrow = row;   /* first row not yet displayed */
1089 
1090     ++rows;   /* count of rows received but not yet displayed */
1091 
1092 /*---------------------------------------------------------------------------
1093     Aside from the use of the rpng2_info struct and the lack of an outer
1094     loop (over rows), this routine is identical to rpng_win_display_image()
1095     in the non-progressive version of the program.
1096   ---------------------------------------------------------------------------*/
1097 
1098     src = rpng2_info.image_data + row*rpng2_info.rowbytes;
1099     if (bg_image)
1100         src2 = bg_data + row*bg_rowbytes;
1101     dest = wimage_data + row*wimage_rowbytes;
1102 
1103     if (rpng2_info.channels == 3) {
1104         for (i = rpng2_info.width;  i > 0;  --i) {
1105             r = *src++;
1106             g = *src++;
1107             b = *src++;
1108             *dest++ = b;
1109             *dest++ = g;   /* note reverse order */
1110             *dest++ = r;
1111         }
1112     } else /* if (rpng2_info.channels == 4) */ {
1113         for (i = rpng2_info.width;  i > 0;  --i) {
1114             r = *src++;
1115             g = *src++;
1116             b = *src++;
1117             a = *src++;
1118             if (bg_image) {
1119                 bg_red   = *src2++;
1120                 bg_green = *src2++;
1121                 bg_blue  = *src2++;
1122             }
1123             if (a == 255) {
1124                 *dest++ = b;
1125                 *dest++ = g;
1126                 *dest++ = r;
1127             } else if (a == 0) {
1128                 *dest++ = bg_blue;
1129                 *dest++ = bg_green;
1130                 *dest++ = bg_red;
1131             } else {
1132                 /* this macro (copied from png.h) composites the
1133                  * foreground and background values and puts the
1134                  * result into the first argument; there are no
1135                  * side effects with the first argument */
1136                 alpha_composite(*dest++, b, a, bg_blue);
1137                 alpha_composite(*dest++, g, a, bg_green);
1138                 alpha_composite(*dest++, r, a, bg_red);
1139             }
1140         }
1141     }
1142 
1143 /*---------------------------------------------------------------------------
1144     Display after every 16 rows or when on last row.  (Region may include
1145     previously displayed lines due to interlacing--i.e., not contiguous.)
1146   ---------------------------------------------------------------------------*/
1147 
1148     if ((rows & 0xf) == 0 || row == rpng2_info.height-1) {
1149         RECT rect;
1150 
1151         rect.left = 0L;
1152         rect.top = (LONG)firstrow;
1153         rect.right = (LONG)rpng2_info.width;       /* possibly off by one? */
1154         rect.bottom = (LONG)row + 1L;              /* possibly off by one? */
1155         InvalidateRect(global_hwnd, &rect, FALSE);
1156         UpdateWindow(global_hwnd);                 /* similar to XFlush() */
1157         rows = 0;
1158     }
1159 
1160 } /* end function rpng2_win_display_row() */
1161 
1162 
1163 
1164 
1165 
rpng2_win_finish_display()1166 static void rpng2_win_finish_display()
1167 {
1168     Trace((stderr, "beginning rpng2_win_finish_display()\n"))
1169 
1170     /* last row has already been displayed by rpng2_win_display_row(), so
1171      * we have nothing to do here except set a flag and let the user know
1172      * that the image is done */
1173 
1174     rpng2_info.state = kDone;
1175     printf(
1176 #ifndef __CYGWIN__
1177       "Done.  Press Q, Esc or mouse button 1 (within image window) to quit.\n"
1178 #else
1179       "Done.  Press mouse button 1 (within image window) to quit.\n"
1180 #endif
1181     );
1182     fflush(stdout);
1183 }
1184 
1185 
1186 
1187 
1188 
rpng2_win_cleanup()1189 static void rpng2_win_cleanup()
1190 {
1191     if (bg_image && bg_data) {
1192         free(bg_data);
1193         bg_data = NULL;
1194     }
1195 
1196     if (rpng2_info.image_data) {
1197         free(rpng2_info.image_data);
1198         rpng2_info.image_data = NULL;
1199     }
1200 
1201     if (rpng2_info.row_pointers) {
1202         free(rpng2_info.row_pointers);
1203         rpng2_info.row_pointers = NULL;
1204     }
1205 
1206     if (dib) {
1207         free(dib);
1208         dib = NULL;
1209     }
1210 }
1211 
1212 
1213 
1214 
1215 
rpng2_win_wndproc(HWND hwnd,UINT iMsg,WPARAM wP,LPARAM lP)1216 LRESULT CALLBACK rpng2_win_wndproc(HWND hwnd, UINT iMsg, WPARAM wP, LPARAM lP)
1217 {
1218     HDC         hdc;
1219     PAINTSTRUCT ps;
1220     int rc;
1221 
1222     switch (iMsg) {
1223         case WM_CREATE:
1224             /* one-time processing here, if any */
1225             return 0;
1226 
1227         case WM_PAINT:
1228             hdc = BeginPaint(hwnd, &ps);
1229             rc = StretchDIBits(hdc, 0, 0, rpng2_info.width, rpng2_info.height,
1230                                     0, 0, rpng2_info.width, rpng2_info.height,
1231                                     wimage_data, (BITMAPINFO *)bmih,
1232                                     0, SRCCOPY);
1233             EndPaint(hwnd, &ps);
1234             return 0;
1235 
1236         /* wait for the user to tell us when to quit */
1237         case WM_CHAR:
1238             switch (wP) {       /* only need one, so ignore repeat count */
1239                 case 'q':
1240                 case 'Q':
1241                 case 0x1B:      /* Esc key */
1242                     PostQuitMessage(0);
1243             }
1244             return 0;
1245 
1246         case WM_LBUTTONDOWN:    /* another way of quitting */
1247         case WM_DESTROY:
1248             PostQuitMessage(0);
1249             return 0;
1250     }
1251 
1252     return DefWindowProc(hwnd, iMsg, wP, lP);
1253 }
1254