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