1/*--------------------------------------------------------------------------- 2 3 rpng - simple PNG display program rpng-win.c 4 5 This program decodes and displays PNG images, with gamma correction and 6 optionally with a user-specified background color (in case the image has 7 transparency). It is very nearly the most basic PNG viewer possible. 8 This version is for 32-bit Windows; it may compile under 16-bit Windows 9 with a little tweaking (or maybe not). 10 11 to do: 12 - handle quoted command-line args (especially filenames with spaces) 13 - have minimum window width: oh well 14 - use %.1023s to simplify truncation of title-bar string? 15 16 --------------------------------------------------------------------------- 17 18 Changelog: 19 - 1.00: initial public release 20 - 1.01: modified to allow abbreviated options; fixed long/ulong mis- 21 match; switched to png_jmpbuf() macro 22 - 1.02: added extra set of parentheses to png_jmpbuf() macro; fixed 23 command-line parsing bug 24 - 1.10: enabled "message window"/console (thanks to David Geldreich) 25 - 2.00: dual-licensed (added GNU GPL) 26 - 2.01: fixed improper display of usage screen on PNG error(s) 27 28 --------------------------------------------------------------------------- 29 30 Copyright (c) 1998-2008 Greg Roelofs. All rights reserved. 31 32 This software is provided "as is," without warranty of any kind, 33 express or implied. In no event shall the author or contributors 34 be held liable for any damages arising in any way from the use of 35 this software. 36 37 The contents of this file are DUAL-LICENSED. You may modify and/or 38 redistribute this software according to the terms of one of the 39 following two licenses (at your option): 40 41 42 LICENSE 1 ("BSD-like with advertising clause"): 43 44 Permission is granted to anyone to use this software for any purpose, 45 including commercial applications, and to alter it and redistribute 46 it freely, subject to the following restrictions: 47 48 1. Redistributions of source code must retain the above copyright 49 notice, disclaimer, and this list of conditions. 50 2. Redistributions in binary form must reproduce the above copyright 51 notice, disclaimer, and this list of conditions in the documenta- 52 tion and/or other materials provided with the distribution. 53 3. All advertising materials mentioning features or use of this 54 software must display the following acknowledgment: 55 56 This product includes software developed by Greg Roelofs 57 and contributors for the book, "PNG: The Definitive Guide," 58 published by O'Reilly and Associates. 59 60 61 LICENSE 2 (GNU GPL v2 or later): 62 63 This program is free software; you can redistribute it and/or modify 64 it under the terms of the GNU General Public License as published by 65 the Free Software Foundation; either version 2 of the License, or 66 (at your option) any later version. 67 68 This program is distributed in the hope that it will be useful, 69 but WITHOUT ANY WARRANTY; without even the implied warranty of 70 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 71 GNU General Public License for more details. 72 73 You should have received a copy of the GNU General Public License 74 along with this program; if not, write to the Free Software Foundation, 75 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 76 77 ---------------------------------------------------------------------------*/ 78 79#define PROGNAME "rpng-win" 80#define LONGNAME "Simple PNG Viewer for Windows" 81#define VERSION "2.01 of 16 March 2008" 82 83#include <stdio.h> 84#include <stdlib.h> 85#include <string.h> 86#include <time.h> 87#include <windows.h> 88#ifdef __CYGWIN__ 89/* getch replacement. Turns out, we don't really need this, 90 * but leave it here if we ever enable any of the uses of 91 * _getch in the main code 92 */ 93#include <unistd.h> 94#include <termio.h> 95#include <sys/ioctl.h> 96int repl_getch( void ) 97{ 98 char ch; 99 int fd = fileno(stdin); 100 struct termio old_tty, new_tty; 101 102 ioctl(fd, TCGETA, &old_tty); 103 new_tty = old_tty; 104 new_tty.c_lflag &= ~(ICANON | ECHO | ISIG); 105 ioctl(fd, TCSETA, &new_tty); 106 fread(&ch, 1, sizeof(ch), stdin); 107 ioctl(fd, TCSETA, &old_tty); 108 109 return ch; 110} 111#define _getch repl_getch 112#else 113#include <conio.h> /* only for _getch() */ 114#endif 115 116/* #define DEBUG : this enables the Trace() macros */ 117 118#include "readpng.h" /* typedefs, common macros, readpng prototypes */ 119 120 121/* could just include png.h, but this macro is the only thing we need 122 * (name and typedefs changed to local versions); note that side effects 123 * only happen with alpha (which could easily be avoided with 124 * "ush acopy = (alpha);") */ 125 126#define alpha_composite(composite, fg, alpha, bg) { \ 127 ush temp = ((ush)(fg)*(ush)(alpha) + \ 128 (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128); \ 129 (composite) = (uch)((temp + (temp >> 8)) >> 8); \ 130} 131 132 133/* local prototypes */ 134static int rpng_win_create_window(HINSTANCE hInst, int showmode); 135static int rpng_win_display_image(void); 136static void rpng_win_cleanup(void); 137LRESULT CALLBACK rpng_win_wndproc(HWND, UINT, WPARAM, LPARAM); 138 139 140static char titlebar[1024]; 141static char *progname = PROGNAME; 142static char *appname = LONGNAME; 143static char *filename; 144static FILE *infile; 145 146static char *bgstr; 147static uch bg_red=0, bg_green=0, bg_blue=0; 148 149static double display_exponent; 150 151static ulg image_width, image_height, image_rowbytes; 152static int image_channels; 153static uch *image_data; 154 155/* Windows-specific variables */ 156static ulg wimage_rowbytes; 157static uch *dib; 158static uch *wimage_data; 159static BITMAPINFOHEADER *bmih; 160 161static HWND global_hwnd; 162 163 164 165 166int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmd, int showmode) 167{ 168 char *args[1024]; /* arbitrary limit, but should suffice */ 169 char *p, *q, **argv = args; 170 int argc = 0; 171 int rc, alen, flen; 172 int error = 0; 173 int have_bg = FALSE; 174 double LUT_exponent; /* just the lookup table */ 175 double CRT_exponent = 2.2; /* just the monitor */ 176 double default_display_exponent; /* whole display system */ 177 MSG msg; 178 179 180 filename = (char *)NULL; 181 182#ifndef __CYGWIN__ 183 /* First reenable console output, which normally goes to the bit bucket 184 * for windowed apps. Closing the console window will terminate the 185 * app. Thanks to David.Geldreich@realviz.com for supplying the magical 186 * incantation. */ 187 188 AllocConsole(); 189 freopen("CONOUT$", "a", stderr); 190 freopen("CONOUT$", "a", stdout); 191#endif 192 193 194 /* Next set the default value for our display-system exponent, i.e., 195 * the product of the CRT exponent and the exponent corresponding to 196 * the frame-buffer's lookup table (LUT), if any. This is not an 197 * exhaustive list of LUT values (e.g., OpenStep has a lot of weird 198 * ones), but it should cover 99% of the current possibilities. And 199 * yes, these ifdefs are completely wasted in a Windows program... */ 200 201#if defined(NeXT) 202 LUT_exponent = 1.0 / 2.2; 203 /* 204 if (some_next_function_that_returns_gamma(&next_gamma)) 205 LUT_exponent = 1.0 / next_gamma; 206 */ 207#elif defined(sgi) 208 LUT_exponent = 1.0 / 1.7; 209 /* there doesn't seem to be any documented function to get the 210 * "gamma" value, so we do it the hard way */ 211 infile = fopen("/etc/config/system.glGammaVal", "r"); 212 if (infile) { 213 double sgi_gamma; 214 215 fgets(tmpline, 80, infile); 216 fclose(infile); 217 sgi_gamma = atof(tmpline); 218 if (sgi_gamma > 0.0) 219 LUT_exponent = 1.0 / sgi_gamma; 220 } 221#elif defined(Macintosh) 222 LUT_exponent = 1.8 / 2.61; 223 /* 224 if (some_mac_function_that_returns_gamma(&mac_gamma)) 225 LUT_exponent = mac_gamma / 2.61; 226 */ 227#else 228 LUT_exponent = 1.0; /* assume no LUT: most PCs */ 229#endif 230 231 /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */ 232 default_display_exponent = LUT_exponent * CRT_exponent; 233 234 235 /* If the user has set the SCREEN_GAMMA environment variable as suggested 236 * (somewhat imprecisely) in the libpng documentation, use that; otherwise 237 * use the default value we just calculated. Either way, the user may 238 * override this via a command-line option. */ 239 240 if ((p = getenv("SCREEN_GAMMA")) != NULL) 241 display_exponent = atof(p); 242 else 243 display_exponent = default_display_exponent; 244 245 246 /* Windows really hates command lines, so we have to set up our own argv. 247 * Note that we do NOT bother with quoted arguments here, so don't use 248 * filenames with spaces in 'em! */ 249 250 argv[argc++] = PROGNAME; 251 p = cmd; 252 for (;;) { 253 if (*p == ' ') 254 while (*++p == ' ') 255 ; 256 /* now p points at the first non-space after some spaces */ 257 if (*p == '\0') 258 break; /* nothing after the spaces: done */ 259 argv[argc++] = q = p; 260 while (*q && *q != ' ') 261 ++q; 262 /* now q points at a space or the end of the string */ 263 if (*q == '\0') 264 break; /* last argv already terminated; quit */ 265 *q = '\0'; /* change space to terminator */ 266 p = q + 1; 267 } 268 argv[argc] = NULL; /* terminate the argv array itself */ 269 270 271 /* Now parse the command line for options and the PNG filename. */ 272 273 while (*++argv && !error) { 274 if (!strncmp(*argv, "-gamma", 2)) { 275 if (!*++argv) 276 ++error; 277 else { 278 display_exponent = atof(*argv); 279 if (display_exponent <= 0.0) 280 ++error; 281 } 282 } else if (!strncmp(*argv, "-bgcolor", 2)) { 283 if (!*++argv) 284 ++error; 285 else { 286 bgstr = *argv; 287 if (strlen(bgstr) != 7 || bgstr[0] != '#') 288 ++error; 289 else 290 have_bg = TRUE; 291 } 292 } else { 293 if (**argv != '-') { 294 filename = *argv; 295 if (argv[1]) /* shouldn't be any more args after filename */ 296 ++error; 297 } else 298 ++error; /* not expecting any other options */ 299 } 300 } 301 302 if (!filename) 303 ++error; 304 305 306 /* print usage screen if any errors up to this point */ 307 308 if (error) { 309#ifndef __CYGWIN__ 310 int ch; 311#endif 312 313 fprintf(stderr, "\n%s %s: %s\n\n", PROGNAME, VERSION, appname); 314 readpng_version_info(); 315 fprintf(stderr, "\n" 316 "Usage: %s [-gamma exp] [-bgcolor bg] file.png\n" 317 " exp \ttransfer-function exponent (``gamma'') of the display\n" 318 "\t\t system in floating-point format (e.g., ``%.1f''); equal\n" 319 "\t\t to the product of the lookup-table exponent (varies)\n" 320 "\t\t and the CRT exponent (usually 2.2); must be positive\n" 321 " bg \tdesired background color in 7-character hex RGB format\n" 322 "\t\t (e.g., ``#ff7700'' for orange: same as HTML colors);\n" 323 "\t\t used with transparent images\n" 324 "\nPress Q, Esc or mouse button 1 after image is displayed to quit.\n" 325#ifndef __CYGWIN__ 326 "Press Q or Esc to quit this usage screen.\n" 327#endif 328 "\n", PROGNAME, default_display_exponent); 329#ifndef __CYGWIN__ 330 do 331 ch = _getch(); 332 while (ch != 'q' && ch != 'Q' && ch != 0x1B); 333#endif 334 exit(1); 335 } 336 337 338 if (!(infile = fopen(filename, "rb"))) { 339 fprintf(stderr, PROGNAME ": can't open PNG file [%s]\n", filename); 340 ++error; 341 } else { 342 if ((rc = readpng_init(infile, &image_width, &image_height)) != 0) { 343 switch (rc) { 344 case 1: 345 fprintf(stderr, PROGNAME 346 ": [%s] is not a PNG file: incorrect signature\n", 347 filename); 348 break; 349 case 2: 350 fprintf(stderr, PROGNAME 351 ": [%s] has bad IHDR (libpng longjmp)\n", filename); 352 break; 353 case 4: 354 fprintf(stderr, PROGNAME ": insufficient memory\n"); 355 break; 356 default: 357 fprintf(stderr, PROGNAME 358 ": unknown readpng_init() error\n"); 359 break; 360 } 361 ++error; 362 } 363 if (error) 364 fclose(infile); 365 } 366 367 368 if (error) { 369#ifndef __CYGWIN__ 370 int ch; 371#endif 372 373 fprintf(stderr, PROGNAME ": aborting.\n"); 374#ifndef __CYGWIN__ 375 do 376 ch = _getch(); 377 while (ch != 'q' && ch != 'Q' && ch != 0x1B); 378#endif 379 exit(2); 380 } else { 381 fprintf(stderr, "\n%s %s: %s\n", PROGNAME, VERSION, appname); 382#ifndef __CYGWIN__ 383 fprintf(stderr, 384 "\n [console window: closing this window will terminate %s]\n\n", 385 PROGNAME); 386#endif 387 } 388 389 390 /* set the title-bar string, but make sure buffer doesn't overflow */ 391 392 alen = strlen(appname); 393 flen = strlen(filename); 394 if (alen + flen + 3 > 1023) 395 sprintf(titlebar, "%s: ...%s", appname, filename+(alen+flen+6-1023)); 396 else 397 sprintf(titlebar, "%s: %s", appname, filename); 398 399 400 /* if the user didn't specify a background color on the command line, 401 * check for one in the PNG file--if not, the initialized values of 0 402 * (black) will be used */ 403 404 if (have_bg) { 405 unsigned r, g, b; /* this approach quiets compiler warnings */ 406 407 sscanf(bgstr+1, "%2x%2x%2x", &r, &g, &b); 408 bg_red = (uch)r; 409 bg_green = (uch)g; 410 bg_blue = (uch)b; 411 } else if (readpng_get_bgcolor(&bg_red, &bg_green, &bg_blue) > 1) { 412 readpng_cleanup(TRUE); 413 fprintf(stderr, PROGNAME 414 ": libpng error while checking for background color\n"); 415 exit(2); 416 } 417 418 419 /* do the basic Windows initialization stuff, make the window and fill it 420 * with the background color */ 421 422 if (rpng_win_create_window(hInst, showmode)) 423 exit(2); 424 425 426 /* decode the image, all at once */ 427 428 Trace((stderr, "calling readpng_get_image()\n")) 429 image_data = readpng_get_image(display_exponent, &image_channels, 430 &image_rowbytes); 431 Trace((stderr, "done with readpng_get_image()\n")) 432 433 434 /* done with PNG file, so clean up to minimize memory usage (but do NOT 435 * nuke image_data!) */ 436 437 readpng_cleanup(FALSE); 438 fclose(infile); 439 440 if (!image_data) { 441 fprintf(stderr, PROGNAME ": unable to decode PNG image\n"); 442 exit(3); 443 } 444 445 446 /* display image (composite with background if requested) */ 447 448 Trace((stderr, "calling rpng_win_display_image()\n")) 449 if (rpng_win_display_image()) { 450 free(image_data); 451 exit(4); 452 } 453 Trace((stderr, "done with rpng_win_display_image()\n")) 454 455 456 /* wait for the user to tell us when to quit */ 457 458 printf( 459#ifndef __CYGWIN__ 460 "Done. Press Q, Esc or mouse button 1 (within image window) to quit.\n" 461#else 462 "Done. Press mouse button 1 (within image window) to quit.\n" 463#endif 464 ); 465 fflush(stdout); 466 467 while (GetMessage(&msg, NULL, 0, 0)) { 468 TranslateMessage(&msg); 469 DispatchMessage(&msg); 470 } 471 472 473 /* OK, we're done: clean up all image and Windows resources and go away */ 474 475 rpng_win_cleanup(); 476 477 return msg.wParam; 478} 479 480 481 482 483 484static int rpng_win_create_window(HINSTANCE hInst, int showmode) 485{ 486 uch *dest; 487 int extra_width, extra_height; 488 ulg i, j; 489 WNDCLASSEX wndclass; 490 491 492/*--------------------------------------------------------------------------- 493 Allocate memory for the display-specific version of the image (round up 494 to multiple of 4 for Windows DIB). 495 ---------------------------------------------------------------------------*/ 496 497 wimage_rowbytes = ((3*image_width + 3L) >> 2) << 2; 498 499 if (!(dib = (uch *)malloc(sizeof(BITMAPINFOHEADER) + 500 wimage_rowbytes*image_height))) 501 { 502 return 4; /* fail */ 503 } 504 505/*--------------------------------------------------------------------------- 506 Initialize the DIB. Negative height means to use top-down BMP ordering 507 (must be uncompressed, but that's what we want). Bit count of 1, 4 or 8 508 implies a colormap of RGBX quads, but 24-bit BMPs just use B,G,R values 509 directly => wimage_data begins immediately after BMP header. 510 ---------------------------------------------------------------------------*/ 511 512 memset(dib, 0, sizeof(BITMAPINFOHEADER)); 513 bmih = (BITMAPINFOHEADER *)dib; 514 bmih->biSize = sizeof(BITMAPINFOHEADER); 515 bmih->biWidth = image_width; 516 bmih->biHeight = -((long)image_height); 517 bmih->biPlanes = 1; 518 bmih->biBitCount = 24; 519 bmih->biCompression = 0; 520 wimage_data = dib + sizeof(BITMAPINFOHEADER); 521 522/*--------------------------------------------------------------------------- 523 Fill in background color (black by default); data are in BGR order. 524 ---------------------------------------------------------------------------*/ 525 526 for (j = 0; j < image_height; ++j) { 527 dest = wimage_data + j*wimage_rowbytes; 528 for (i = image_width; i > 0; --i) { 529 *dest++ = bg_blue; 530 *dest++ = bg_green; 531 *dest++ = bg_red; 532 } 533 } 534 535/*--------------------------------------------------------------------------- 536 Set the window parameters. 537 ---------------------------------------------------------------------------*/ 538 539 memset(&wndclass, 0, sizeof(wndclass)); 540 541 wndclass.cbSize = sizeof(wndclass); 542 wndclass.style = CS_HREDRAW | CS_VREDRAW; 543 wndclass.lpfnWndProc = rpng_win_wndproc; 544 wndclass.hInstance = hInst; 545 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); 546 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); 547 wndclass.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH); 548 wndclass.lpszMenuName = NULL; 549 wndclass.lpszClassName = progname; 550 wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); 551 552 RegisterClassEx(&wndclass); 553 554/*--------------------------------------------------------------------------- 555 Finally, create the window. 556 ---------------------------------------------------------------------------*/ 557 558 extra_width = 2*(GetSystemMetrics(SM_CXBORDER) + 559 GetSystemMetrics(SM_CXDLGFRAME)); 560 extra_height = 2*(GetSystemMetrics(SM_CYBORDER) + 561 GetSystemMetrics(SM_CYDLGFRAME)) + 562 GetSystemMetrics(SM_CYCAPTION); 563 564 global_hwnd = CreateWindow(progname, titlebar, WS_OVERLAPPEDWINDOW, 565 CW_USEDEFAULT, CW_USEDEFAULT, image_width+extra_width, 566 image_height+extra_height, NULL, NULL, hInst, NULL); 567 568 ShowWindow(global_hwnd, showmode); 569 UpdateWindow(global_hwnd); 570 571 return 0; 572 573} /* end function rpng_win_create_window() */ 574 575 576 577 578 579static int rpng_win_display_image() 580{ 581 uch *src, *dest; 582 uch r, g, b, a; 583 ulg i, row, lastrow; 584 RECT rect; 585 586 587 Trace((stderr, "beginning display loop (image_channels == %d)\n", 588 image_channels)) 589 Trace((stderr, "(width = %ld, rowbytes = %ld, wimage_rowbytes = %d)\n", 590 image_width, image_rowbytes, wimage_rowbytes)) 591 592 593/*--------------------------------------------------------------------------- 594 Blast image data to buffer. This whole routine takes place before the 595 message loop begins, so there's no real point in any pseudo-progressive 596 display... 597 ---------------------------------------------------------------------------*/ 598 599 for (lastrow = row = 0; row < image_height; ++row) { 600 src = image_data + row*image_rowbytes; 601 dest = wimage_data + row*wimage_rowbytes; 602 if (image_channels == 3) { 603 for (i = image_width; i > 0; --i) { 604 r = *src++; 605 g = *src++; 606 b = *src++; 607 *dest++ = b; 608 *dest++ = g; /* note reverse order */ 609 *dest++ = r; 610 } 611 } else /* if (image_channels == 4) */ { 612 for (i = image_width; i > 0; --i) { 613 r = *src++; 614 g = *src++; 615 b = *src++; 616 a = *src++; 617 if (a == 255) { 618 *dest++ = b; 619 *dest++ = g; 620 *dest++ = r; 621 } else if (a == 0) { 622 *dest++ = bg_blue; 623 *dest++ = bg_green; 624 *dest++ = bg_red; 625 } else { 626 /* this macro (copied from png.h) composites the 627 * foreground and background values and puts the 628 * result into the first argument; there are no 629 * side effects with the first argument */ 630 alpha_composite(*dest++, b, a, bg_blue); 631 alpha_composite(*dest++, g, a, bg_green); 632 alpha_composite(*dest++, r, a, bg_red); 633 } 634 } 635 } 636 /* display after every 16 lines */ 637 if (((row+1) & 0xf) == 0) { 638 rect.left = 0L; 639 rect.top = (LONG)lastrow; 640 rect.right = (LONG)image_width; /* possibly off by one? */ 641 rect.bottom = (LONG)lastrow + 16L; /* possibly off by one? */ 642 InvalidateRect(global_hwnd, &rect, FALSE); 643 UpdateWindow(global_hwnd); /* similar to XFlush() */ 644 lastrow = row + 1; 645 } 646 } 647 648 Trace((stderr, "calling final image-flush routine\n")) 649 if (lastrow < image_height) { 650 rect.left = 0L; 651 rect.top = (LONG)lastrow; 652 rect.right = (LONG)image_width; /* possibly off by one? */ 653 rect.bottom = (LONG)image_height; /* possibly off by one? */ 654 InvalidateRect(global_hwnd, &rect, FALSE); 655 UpdateWindow(global_hwnd); /* similar to XFlush() */ 656 } 657 658/* 659 last param determines whether or not background is wiped before paint 660 InvalidateRect(global_hwnd, NULL, TRUE); 661 UpdateWindow(global_hwnd); 662 */ 663 664 return 0; 665} 666 667 668 669 670 671static void rpng_win_cleanup() 672{ 673 if (image_data) { 674 free(image_data); 675 image_data = NULL; 676 } 677 678 if (dib) { 679 free(dib); 680 dib = NULL; 681 } 682} 683 684 685 686 687 688LRESULT CALLBACK rpng_win_wndproc(HWND hwnd, UINT iMsg, WPARAM wP, LPARAM lP) 689{ 690 HDC hdc; 691 PAINTSTRUCT ps; 692 int rc; 693 694 switch (iMsg) { 695 case WM_CREATE: 696 /* one-time processing here, if any */ 697 return 0; 698 699 case WM_PAINT: 700 hdc = BeginPaint(hwnd, &ps); 701 /* dest */ 702 rc = StretchDIBits(hdc, 0, 0, image_width, image_height, 703 /* source */ 704 0, 0, image_width, image_height, 705 wimage_data, (BITMAPINFO *)bmih, 706 /* iUsage: no clue */ 707 0, SRCCOPY); 708 EndPaint(hwnd, &ps); 709 return 0; 710 711 /* wait for the user to tell us when to quit */ 712 case WM_CHAR: 713 switch (wP) { /* only need one, so ignore repeat count */ 714 case 'q': 715 case 'Q': 716 case 0x1B: /* Esc key */ 717 PostQuitMessage(0); 718 } 719 return 0; 720 721 case WM_LBUTTONDOWN: /* another way of quitting */ 722 case WM_DESTROY: 723 PostQuitMessage(0); 724 return 0; 725 } 726 727 return DefWindowProc(hwnd, iMsg, wP, lP); 728} 729