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#include <conio.h>      /* only for _getch() */
89
90/* #define DEBUG  :  this enables the Trace() macros */
91
92#include "readpng.h"    /* typedefs, common macros, readpng prototypes */
93
94
95/* could just include png.h, but this macro is the only thing we need
96 * (name and typedefs changed to local versions); note that side effects
97 * only happen with alpha (which could easily be avoided with
98 * "ush acopy = (alpha);") */
99
100#define alpha_composite(composite, fg, alpha, bg) {               \
101    ush temp = ((ush)(fg)*(ush)(alpha) +                          \
102                (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128);  \
103    (composite) = (uch)((temp + (temp >> 8)) >> 8);               \
104}
105
106
107/* local prototypes */
108static int        rpng_win_create_window(HINSTANCE hInst, int showmode);
109static int        rpng_win_display_image(void);
110static void       rpng_win_cleanup(void);
111LRESULT CALLBACK  rpng_win_wndproc(HWND, UINT, WPARAM, LPARAM);
112
113
114static char titlebar[1024];
115static char *progname = PROGNAME;
116static char *appname = LONGNAME;
117static char *filename;
118static FILE *infile;
119
120static char *bgstr;
121static uch bg_red=0, bg_green=0, bg_blue=0;
122
123static double display_exponent;
124
125static ulg image_width, image_height, image_rowbytes;
126static int image_channels;
127static uch *image_data;
128
129/* Windows-specific variables */
130static ulg wimage_rowbytes;
131static uch *dib;
132static uch *wimage_data;
133static BITMAPINFOHEADER *bmih;
134
135static HWND global_hwnd;
136
137
138
139
140int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmd, int showmode)
141{
142    char *args[1024];                 /* arbitrary limit, but should suffice */
143    char *p, *q, **argv = args;
144    int argc = 0;
145    int rc, alen, flen;
146    int error = 0;
147    int have_bg = FALSE;
148    double LUT_exponent;              /* just the lookup table */
149    double CRT_exponent = 2.2;        /* just the monitor */
150    double default_display_exponent;  /* whole display system */
151    MSG msg;
152
153
154    filename = (char *)NULL;
155
156
157    /* First reenable console output, which normally goes to the bit bucket
158     * for windowed apps.  Closing the console window will terminate the
159     * app.  Thanks to David.Geldreich@realviz.com for supplying the magical
160     * incantation. */
161
162    AllocConsole();
163    freopen("CONOUT$", "a", stderr);
164    freopen("CONOUT$", "a", stdout);
165
166
167    /* Next set the default value for our display-system exponent, i.e.,
168     * the product of the CRT exponent and the exponent corresponding to
169     * the frame-buffer's lookup table (LUT), if any.  This is not an
170     * exhaustive list of LUT values (e.g., OpenStep has a lot of weird
171     * ones), but it should cover 99% of the current possibilities.  And
172     * yes, these ifdefs are completely wasted in a Windows program... */
173
174#if defined(NeXT)
175    LUT_exponent = 1.0 / 2.2;
176    /*
177    if (some_next_function_that_returns_gamma(&next_gamma))
178        LUT_exponent = 1.0 / next_gamma;
179     */
180#elif defined(sgi)
181    LUT_exponent = 1.0 / 1.7;
182    /* there doesn't seem to be any documented function to get the
183     * "gamma" value, so we do it the hard way */
184    infile = fopen("/etc/config/system.glGammaVal", "r");
185    if (infile) {
186        double sgi_gamma;
187
188        fgets(tmpline, 80, infile);
189        fclose(infile);
190        sgi_gamma = atof(tmpline);
191        if (sgi_gamma > 0.0)
192            LUT_exponent = 1.0 / sgi_gamma;
193    }
194#elif defined(Macintosh)
195    LUT_exponent = 1.8 / 2.61;
196    /*
197    if (some_mac_function_that_returns_gamma(&mac_gamma))
198        LUT_exponent = mac_gamma / 2.61;
199     */
200#else
201    LUT_exponent = 1.0;   /* assume no LUT:  most PCs */
202#endif
203
204    /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
205    default_display_exponent = LUT_exponent * CRT_exponent;
206
207
208    /* If the user has set the SCREEN_GAMMA environment variable as suggested
209     * (somewhat imprecisely) in the libpng documentation, use that; otherwise
210     * use the default value we just calculated.  Either way, the user may
211     * override this via a command-line option. */
212
213    if ((p = getenv("SCREEN_GAMMA")) != NULL)
214        display_exponent = atof(p);
215    else
216        display_exponent = default_display_exponent;
217
218
219    /* Windows really hates command lines, so we have to set up our own argv.
220     * Note that we do NOT bother with quoted arguments here, so don't use
221     * filenames with spaces in 'em! */
222
223    argv[argc++] = PROGNAME;
224    p = cmd;
225    for (;;) {
226        if (*p == ' ')
227            while (*++p == ' ')
228                ;
229        /* now p points at the first non-space after some spaces */
230        if (*p == '\0')
231            break;    /* nothing after the spaces:  done */
232        argv[argc++] = q = p;
233        while (*q && *q != ' ')
234            ++q;
235        /* now q points at a space or the end of the string */
236        if (*q == '\0')
237            break;    /* last argv already terminated; quit */
238        *q = '\0';    /* change space to terminator */
239        p = q + 1;
240    }
241    argv[argc] = NULL;   /* terminate the argv array itself */
242
243
244    /* Now parse the command line for options and the PNG filename. */
245
246    while (*++argv && !error) {
247        if (!strncmp(*argv, "-gamma", 2)) {
248            if (!*++argv)
249                ++error;
250            else {
251                display_exponent = atof(*argv);
252                if (display_exponent <= 0.0)
253                    ++error;
254            }
255        } else if (!strncmp(*argv, "-bgcolor", 2)) {
256            if (!*++argv)
257                ++error;
258            else {
259                bgstr = *argv;
260                if (strlen(bgstr) != 7 || bgstr[0] != '#')
261                    ++error;
262                else
263                    have_bg = TRUE;
264            }
265        } else {
266            if (**argv != '-') {
267                filename = *argv;
268                if (argv[1])   /* shouldn't be any more args after filename */
269                    ++error;
270            } else
271                ++error;   /* not expecting any other options */
272        }
273    }
274
275    if (!filename)
276        ++error;
277
278
279    /* print usage screen if any errors up to this point */
280
281    if (error) {
282        int ch;
283
284        fprintf(stderr, "\n%s %s:  %s\n\n", PROGNAME, VERSION, appname);
285        readpng_version_info();
286        fprintf(stderr, "\n"
287          "Usage:  %s [-gamma exp] [-bgcolor bg] file.png\n"
288          "    exp \ttransfer-function exponent (``gamma'') of the display\n"
289          "\t\t  system in floating-point format (e.g., ``%.1f''); equal\n"
290          "\t\t  to the product of the lookup-table exponent (varies)\n"
291          "\t\t  and the CRT exponent (usually 2.2); must be positive\n"
292          "    bg  \tdesired background color in 7-character hex RGB format\n"
293          "\t\t  (e.g., ``#ff7700'' for orange:  same as HTML colors);\n"
294          "\t\t  used with transparent images\n"
295          "\nPress Q, Esc or mouse button 1 after image is displayed to quit.\n"
296          "Press Q or Esc to quit this usage screen.\n"
297          "\n", PROGNAME, default_display_exponent);
298        do
299            ch = _getch();
300        while (ch != 'q' && ch != 'Q' && ch != 0x1B);
301        exit(1);
302    }
303
304
305    if (!(infile = fopen(filename, "rb"))) {
306        fprintf(stderr, PROGNAME ":  can't open PNG file [%s]\n", filename);
307        ++error;
308    } else {
309        if ((rc = readpng_init(infile, &image_width, &image_height)) != 0) {
310            switch (rc) {
311                case 1:
312                    fprintf(stderr, PROGNAME
313                      ":  [%s] is not a PNG file: incorrect signature\n",
314                      filename);
315                    break;
316                case 2:
317                    fprintf(stderr, PROGNAME
318                      ":  [%s] has bad IHDR (libpng longjmp)\n", filename);
319                    break;
320                case 4:
321                    fprintf(stderr, PROGNAME ":  insufficient memory\n");
322                    break;
323                default:
324                    fprintf(stderr, PROGNAME
325                      ":  unknown readpng_init() error\n");
326                    break;
327            }
328            ++error;
329        }
330        if (error)
331            fclose(infile);
332    }
333
334
335    if (error) {
336        int ch;
337
338        fprintf(stderr, PROGNAME ":  aborting.\n");
339        do
340            ch = _getch();
341        while (ch != 'q' && ch != 'Q' && ch != 0x1B);
342        exit(2);
343    } else {
344        fprintf(stderr, "\n%s %s:  %s\n", PROGNAME, VERSION, appname);
345        fprintf(stderr,
346          "\n   [console window:  closing this window will terminate %s]\n\n",
347          PROGNAME);
348    }
349
350
351    /* set the title-bar string, but make sure buffer doesn't overflow */
352
353    alen = strlen(appname);
354    flen = strlen(filename);
355    if (alen + flen + 3 > 1023)
356        sprintf(titlebar, "%s:  ...%s", appname, filename+(alen+flen+6-1023));
357    else
358        sprintf(titlebar, "%s:  %s", appname, filename);
359
360
361    /* if the user didn't specify a background color on the command line,
362     * check for one in the PNG file--if not, the initialized values of 0
363     * (black) will be used */
364
365    if (have_bg) {
366        unsigned r, g, b;   /* this approach quiets compiler warnings */
367
368        sscanf(bgstr+1, "%2x%2x%2x", &r, &g, &b);
369        bg_red   = (uch)r;
370        bg_green = (uch)g;
371        bg_blue  = (uch)b;
372    } else if (readpng_get_bgcolor(&bg_red, &bg_green, &bg_blue) > 1) {
373        readpng_cleanup(TRUE);
374        fprintf(stderr, PROGNAME
375          ":  libpng error while checking for background color\n");
376        exit(2);
377    }
378
379
380    /* do the basic Windows initialization stuff, make the window and fill it
381     * with the background color */
382
383    if (rpng_win_create_window(hInst, showmode))
384        exit(2);
385
386
387    /* decode the image, all at once */
388
389    Trace((stderr, "calling readpng_get_image()\n"))
390    image_data = readpng_get_image(display_exponent, &image_channels,
391      &image_rowbytes);
392    Trace((stderr, "done with readpng_get_image()\n"))
393
394
395    /* done with PNG file, so clean up to minimize memory usage (but do NOT
396     * nuke image_data!) */
397
398    readpng_cleanup(FALSE);
399    fclose(infile);
400
401    if (!image_data) {
402        fprintf(stderr, PROGNAME ":  unable to decode PNG image\n");
403        exit(3);
404    }
405
406
407    /* display image (composite with background if requested) */
408
409    Trace((stderr, "calling rpng_win_display_image()\n"))
410    if (rpng_win_display_image()) {
411        free(image_data);
412        exit(4);
413    }
414    Trace((stderr, "done with rpng_win_display_image()\n"))
415
416
417    /* wait for the user to tell us when to quit */
418
419    printf(
420      "Done.  Press Q, Esc or mouse button 1 (within image window) to quit.\n");
421    fflush(stdout);
422
423    while (GetMessage(&msg, NULL, 0, 0)) {
424        TranslateMessage(&msg);
425        DispatchMessage(&msg);
426    }
427
428
429    /* OK, we're done:  clean up all image and Windows resources and go away */
430
431    rpng_win_cleanup();
432
433    return msg.wParam;
434}
435
436
437
438
439
440static int rpng_win_create_window(HINSTANCE hInst, int showmode)
441{
442    uch *dest;
443    int extra_width, extra_height;
444    ulg i, j;
445    WNDCLASSEX wndclass;
446
447
448/*---------------------------------------------------------------------------
449    Allocate memory for the display-specific version of the image (round up
450    to multiple of 4 for Windows DIB).
451  ---------------------------------------------------------------------------*/
452
453    wimage_rowbytes = ((3*image_width + 3L) >> 2) << 2;
454
455    if (!(dib = (uch *)malloc(sizeof(BITMAPINFOHEADER) +
456                              wimage_rowbytes*image_height)))
457    {
458        return 4;   /* fail */
459    }
460
461/*---------------------------------------------------------------------------
462    Initialize the DIB.  Negative height means to use top-down BMP ordering
463    (must be uncompressed, but that's what we want).  Bit count of 1, 4 or 8
464    implies a colormap of RGBX quads, but 24-bit BMPs just use B,G,R values
465    directly => wimage_data begins immediately after BMP header.
466  ---------------------------------------------------------------------------*/
467
468    memset(dib, 0, sizeof(BITMAPINFOHEADER));
469    bmih = (BITMAPINFOHEADER *)dib;
470    bmih->biSize = sizeof(BITMAPINFOHEADER);
471    bmih->biWidth = image_width;
472    bmih->biHeight = -((long)image_height);
473    bmih->biPlanes = 1;
474    bmih->biBitCount = 24;
475    bmih->biCompression = 0;
476    wimage_data = dib + sizeof(BITMAPINFOHEADER);
477
478/*---------------------------------------------------------------------------
479    Fill in background color (black by default); data are in BGR order.
480  ---------------------------------------------------------------------------*/
481
482    for (j = 0;  j < image_height;  ++j) {
483        dest = wimage_data + j*wimage_rowbytes;
484        for (i = image_width;  i > 0;  --i) {
485            *dest++ = bg_blue;
486            *dest++ = bg_green;
487            *dest++ = bg_red;
488        }
489    }
490
491/*---------------------------------------------------------------------------
492    Set the window parameters.
493  ---------------------------------------------------------------------------*/
494
495    memset(&wndclass, 0, sizeof(wndclass));
496
497    wndclass.cbSize = sizeof(wndclass);
498    wndclass.style = CS_HREDRAW | CS_VREDRAW;
499    wndclass.lpfnWndProc = rpng_win_wndproc;
500    wndclass.hInstance = hInst;
501    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
502    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
503    wndclass.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);
504    wndclass.lpszMenuName = NULL;
505    wndclass.lpszClassName = progname;
506    wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
507
508    RegisterClassEx(&wndclass);
509
510/*---------------------------------------------------------------------------
511    Finally, create the window.
512  ---------------------------------------------------------------------------*/
513
514    extra_width  = 2*(GetSystemMetrics(SM_CXBORDER) +
515                      GetSystemMetrics(SM_CXDLGFRAME));
516    extra_height = 2*(GetSystemMetrics(SM_CYBORDER) +
517                      GetSystemMetrics(SM_CYDLGFRAME)) +
518                      GetSystemMetrics(SM_CYCAPTION);
519
520    global_hwnd = CreateWindow(progname, titlebar, WS_OVERLAPPEDWINDOW,
521      CW_USEDEFAULT, CW_USEDEFAULT, image_width+extra_width,
522      image_height+extra_height, NULL, NULL, hInst, NULL);
523
524    ShowWindow(global_hwnd, showmode);
525    UpdateWindow(global_hwnd);
526
527    return 0;
528
529} /* end function rpng_win_create_window() */
530
531
532
533
534
535static int rpng_win_display_image()
536{
537    uch *src, *dest;
538    uch r, g, b, a;
539    ulg i, row, lastrow;
540    RECT rect;
541
542
543    Trace((stderr, "beginning display loop (image_channels == %d)\n",
544      image_channels))
545    Trace((stderr, "(width = %ld, rowbytes = %ld, wimage_rowbytes = %d)\n",
546      image_width, image_rowbytes, wimage_rowbytes))
547
548
549/*---------------------------------------------------------------------------
550    Blast image data to buffer.  This whole routine takes place before the
551    message loop begins, so there's no real point in any pseudo-progressive
552    display...
553  ---------------------------------------------------------------------------*/
554
555    for (lastrow = row = 0;  row < image_height;  ++row) {
556        src = image_data + row*image_rowbytes;
557        dest = wimage_data + row*wimage_rowbytes;
558        if (image_channels == 3) {
559            for (i = image_width;  i > 0;  --i) {
560                r = *src++;
561                g = *src++;
562                b = *src++;
563                *dest++ = b;
564                *dest++ = g;   /* note reverse order */
565                *dest++ = r;
566            }
567        } else /* if (image_channels == 4) */ {
568            for (i = image_width;  i > 0;  --i) {
569                r = *src++;
570                g = *src++;
571                b = *src++;
572                a = *src++;
573                if (a == 255) {
574                    *dest++ = b;
575                    *dest++ = g;
576                    *dest++ = r;
577                } else if (a == 0) {
578                    *dest++ = bg_blue;
579                    *dest++ = bg_green;
580                    *dest++ = bg_red;
581                } else {
582                    /* this macro (copied from png.h) composites the
583                     * foreground and background values and puts the
584                     * result into the first argument; there are no
585                     * side effects with the first argument */
586                    alpha_composite(*dest++, b, a, bg_blue);
587                    alpha_composite(*dest++, g, a, bg_green);
588                    alpha_composite(*dest++, r, a, bg_red);
589                }
590            }
591        }
592        /* display after every 16 lines */
593        if (((row+1) & 0xf) == 0) {
594            rect.left = 0L;
595            rect.top = (LONG)lastrow;
596            rect.right = (LONG)image_width;      /* possibly off by one? */
597            rect.bottom = (LONG)lastrow + 16L;   /* possibly off by one? */
598            InvalidateRect(global_hwnd, &rect, FALSE);
599            UpdateWindow(global_hwnd);     /* similar to XFlush() */
600            lastrow = row + 1;
601        }
602    }
603
604    Trace((stderr, "calling final image-flush routine\n"))
605    if (lastrow < image_height) {
606        rect.left = 0L;
607        rect.top = (LONG)lastrow;
608        rect.right = (LONG)image_width;      /* possibly off by one? */
609        rect.bottom = (LONG)image_height;    /* possibly off by one? */
610        InvalidateRect(global_hwnd, &rect, FALSE);
611        UpdateWindow(global_hwnd);     /* similar to XFlush() */
612    }
613
614/*
615    last param determines whether or not background is wiped before paint
616    InvalidateRect(global_hwnd, NULL, TRUE);
617    UpdateWindow(global_hwnd);
618 */
619
620    return 0;
621}
622
623
624
625
626
627static void rpng_win_cleanup()
628{
629    if (image_data) {
630        free(image_data);
631        image_data = NULL;
632    }
633
634    if (dib) {
635        free(dib);
636        dib = NULL;
637    }
638}
639
640
641
642
643
644LRESULT CALLBACK rpng_win_wndproc(HWND hwnd, UINT iMsg, WPARAM wP, LPARAM lP)
645{
646    HDC         hdc;
647    PAINTSTRUCT ps;
648    int rc;
649
650    switch (iMsg) {
651        case WM_CREATE:
652            /* one-time processing here, if any */
653            return 0;
654
655        case WM_PAINT:
656            hdc = BeginPaint(hwnd, &ps);
657                    /*                    dest                          */
658            rc = StretchDIBits(hdc, 0, 0, image_width, image_height,
659                    /*                    source                        */
660                                    0, 0, image_width, image_height,
661                                    wimage_data, (BITMAPINFO *)bmih,
662                    /*              iUsage: no clue                     */
663                                    0, SRCCOPY);
664            EndPaint(hwnd, &ps);
665            return 0;
666
667        /* wait for the user to tell us when to quit */
668        case WM_CHAR:
669            switch (wP) {      /* only need one, so ignore repeat count */
670                case 'q':
671                case 'Q':
672                case 0x1B:     /* Esc key */
673                    PostQuitMessage(0);
674            }
675            return 0;
676
677        case WM_LBUTTONDOWN:   /* another way of quitting */
678        case WM_DESTROY:
679            PostQuitMessage(0);
680            return 0;
681    }
682
683    return DefWindowProc(hwnd, iMsg, wP, lP);
684}
685