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