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