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