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