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