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