1 /*---------------------------------------------------------------------------
3 rpng2 - progressive-model PNG display program rpng2-x.c
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 the X Window System (tested by the author under Unix and by Martin
10 Zinser under OpenVMS; may work under OS/2 with a little tweaking).
12 Thanks to Adam Costello and Pieter S. van der Meulen for the "diamond"
13 and "radial waves" patterns, respectively.
17 - finish resizable checkerboard-gradient (sizes 4-128?)
18 - use %.1023s to simplify truncation of title-bar string?
20 ---------------------------------------------------------------------------
23 - 1.01: initial public release
24 - 1.02: modified to allow abbreviated options; fixed char/uchar mismatch
25 - 1.10: added support for non-default visuals; fixed X pixel-conversion
26 - 1.11: added -usleep option for demos; fixed command-line parsing bug
27 - 1.12: added -pause option for demos and testing
28 - 1.20: added runtime MMX-enabling/disabling and new -mmx* options
29 - 1.21: fixed small X memory leak (thanks to Francois Petitjean)
30 - 1.22: fixed XFreeGC() crash bug
32 ---------------------------------------------------------------------------
34 Copyright (c) 1998-2001 Greg Roelofs. All rights reserved.
36 This software is provided "as is," without warranty of any kind,
37 express or implied. In no event shall the author or contributors
38 be held liable for any damages arising in any way from the use of
41 Permission is granted to anyone to use this software for any purpose,
42 including commercial applications, and to alter it and redistribute
43 it freely, subject to the following restrictions:
45 1. Redistributions of source code must retain the above copyright
46 notice, disclaimer, and this list of conditions.
47 2. Redistributions in binary form must reproduce the above copyright
48 notice, disclaimer, and this list of conditions in the documenta-
49 tion and/or other materials provided with the distribution.
50 3. All advertising materials mentioning features or use of this
51 software must display the following acknowledgment:
53 This product includes software developed by Greg Roelofs
54 and contributors for the book, "PNG: The Definitive Guide,"
55 published by O'Reilly and Associates.
57 ---------------------------------------------------------------------------*/
59 #define PROGNAME "rpng2-x"
60 #define LONGNAME "Progressive PNG Viewer for X"
61 #define VERSION "1.22 of 16 August 2001"
66 #include <setjmp.h> /* for jmpbuf declaration in readpng2.h */
68 #include <math.h> /* only for PvdM background code */
70 #include <X11/Xutil.h>
72 #include <X11/keysym.h> /* defines XK_* macros */
78 /* all for PvdM background code: */
80 # define PI 3.141592653589793238
83 #define INV_PI_360 (360.0 / PI)
84 #define MAX(a,b) (a>b?a:b)
85 #define MIN(a,b) (a<b?a:b)
86 #define CLIP(a,min,max) MAX(min,MIN((a),max))
87 #define ABS(a) ((a)<0?-(a):(a))
88 #define CLIP8P(c) MAX(0,(MIN((c),255))) /* 8-bit pos. integer (uch) */
89 #define ROUNDF(f) ((int)(f + 0.5))
91 #define rgb1_max bg_freq
92 #define rgb1_min bg_gray
93 #define rgb2_max bg_bsat
94 #define rgb2_min bg_brot
96 /* #define DEBUG */ /* this enables the Trace() macros */
98 #include "readpng2.h" /* typedefs, common macros, readpng2 prototypes */
101 /* could just include png.h, but this macro is the only thing we need
102 * (name and typedefs changed to local versions); note that side effects
103 * only happen with alpha (which could easily be avoided with
104 * "ush acopy = (alpha);") */
106 #define alpha_composite(composite, fg, alpha, bg) { \
107 ush temp = ((ush)(fg)*(ush)(alpha) + \
108 (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128); \
109 (composite) = (uch)((temp + (temp >> 8)) >> 8); \
113 #define INBUFSIZE 4096 /* with pseudo-timing on (1 sec delay/block), this
114 * block size corresponds roughly to a download
115 * speed 10% faster than theoretical 33.6K maximum
116 * (assuming 8 data bits, 1 stop bit and no other
119 /* local prototypes */
120 static void rpng2_x_init(void);
121 static int rpng2_x_create_window(void);
122 static int rpng2_x_load_bg_image(void);
123 static void rpng2_x_display_row(ulg row);
124 static void rpng2_x_finish_display(void);
125 static void rpng2_x_cleanup(void);
126 static int rpng2_x_msb(ulg u32val);
129 static char titlebar[1024], *window_name = titlebar;
130 static char *appname = LONGNAME;
131 static char *icon_name = PROGNAME;
132 static char *filename;
135 static mainprog_info rpng2_info;
137 static uch inbuf[INBUFSIZE];
140 static int pat = 6; /* must be less than num_bgpat */
141 static int bg_image = 0;
142 static int bgscale = 16;
143 static ulg bg_rowbytes;
146 int pause_after_pass = FALSE;
147 int demo_timing = FALSE;
148 ulg usleep_duration = 0L;
150 static struct rgb_color {
153 { 0, 0, 0}, /* 0: black */
154 {255, 255, 255}, /* 1: white */
155 {173, 132, 57}, /* 2: tan */
156 { 64, 132, 0}, /* 3: medium green */
157 {189, 117, 1}, /* 4: gold */
158 {253, 249, 1}, /* 5: yellow */
159 { 0, 0, 255}, /* 6: blue */
160 { 0, 0, 120}, /* 7: medium blue */
161 {255, 0, 255}, /* 8: magenta */
162 { 64, 0, 64}, /* 9: dark magenta */
163 {255, 0, 0}, /* 10: red */
164 { 64, 0, 0}, /* 11: dark red */
165 {255, 127, 0}, /* 12: orange */
166 {192, 96, 0}, /* 13: darker orange */
167 { 24, 60, 0}, /* 14: dark green-yellow */
168 { 85, 125, 200} /* 15: ice blue */
170 /* not used for now, but should be for error-checking:
171 static int num_rgb = sizeof(rgb) / sizeof(struct rgb_color);
175 This whole struct is a fairly cheesy way to keep the number of
176 command-line options to a minimum. The radial-waves background
177 type is a particularly poor fit to the integer elements of the
178 struct...but a few macros and a little fixed-point math will do
182 F E D C B A 9 8 7 6 5 4 3 2 1 0
184 | | +-+-+-- 0 = sharp-edged checkerboard
185 | | 1 = soft diamonds
188 | +-- gradient #2 inverted?
189 +-- alternating columns inverted?
191 static struct background_pattern {
193 int rgb1_max, rgb1_min; /* or bg_freq, bg_gray */
194 int rgb2_max, rgb2_min; /* or bg_bsat, bg_brot (both scaled by 10)*/
196 {0+8, 2,0, 1,15}, /* checkered: tan/black vs. white/ice blue */
197 {0+24, 2,0, 1,0}, /* checkered: tan/black vs. white/black */
198 {0+8, 4,5, 0,2}, /* checkered: gold/yellow vs. black/tan */
199 {0+8, 4,5, 0,6}, /* checkered: gold/yellow vs. black/blue */
200 {0, 7,0, 8,9}, /* checkered: deep blue/black vs. magenta */
201 {0+8, 13,0, 5,14}, /* checkered: orange/black vs. yellow */
202 {0+8, 12,0, 10,11}, /* checkered: orange/black vs. red */
203 {1, 7,0, 8,0}, /* diamonds: deep blue/black vs. magenta */
204 {1, 12,0, 11,0}, /* diamonds: orange vs. dark red */
205 {1, 10,0, 7,0}, /* diamonds: red vs. medium blue */
206 {1, 4,0, 5,0}, /* diamonds: gold vs. yellow */
207 {1, 3,0, 0,0}, /* diamonds: medium green vs. black */
208 {2, 16, 100, 20, 0}, /* radial: ~hard radial color-beams */
209 {2, 18, 100, 10, 2}, /* radial: soft, curved radial color-beams */
210 {2, 16, 256, 100, 250}, /* radial: very tight spiral */
211 {2, 10000, 256, 11, 0} /* radial: dipole-moire' (almost fractal) */
213 static int num_bgpat = sizeof(bg) / sizeof(struct background_pattern);
216 /* X-specific variables */
217 static char *displayname;
218 static XImage *ximage;
219 static Display *display;
221 static Visual *visual;
222 static XVisualInfo *visual_list;
223 static int RShift, GShift, BShift;
224 static ulg RMask, GMask, BMask;
225 static Window window;
227 static Colormap colormap;
229 static int have_nondefault_visual = FALSE;
230 static int have_colormap = FALSE;
231 static int have_window = FALSE;
232 static int have_gc = FALSE;
237 int main(int argc, char **argv)
242 char *p, *bgstr = NULL;
247 double LUT_exponent; /* just the lookup table */
248 double CRT_exponent = 2.2; /* just the monitor */
249 double default_display_exponent; /* whole display system */
254 /* First initialize a few things, just to be sure--memset takes care of
255 * default background color (black), booleans (FALSE), pointers (NULL),
258 displayname = (char *)NULL;
259 filename = (char *)NULL;
260 memset(&rpng2_info, 0, sizeof(mainprog_info));
263 /* Set the default value for our display-system exponent, i.e., the
264 * product of the CRT exponent and the exponent corresponding to
265 * the frame-buffer's lookup table (LUT), if any. This is not an
266 * exhaustive list of LUT values (e.g., OpenStep has a lot of weird
267 * ones), but it should cover 99% of the current possibilities. */
270 /* third-party utilities can modify the default LUT exponent */
271 LUT_exponent = 1.0 / 2.2;
273 if (some_next_function_that_returns_gamma(&next_gamma))
274 LUT_exponent = 1.0 / next_gamma;
277 LUT_exponent = 1.0 / 1.7;
278 /* there doesn't seem to be any documented function to
279 * get the "gamma" value, so we do it the hard way */
280 infile = fopen("/etc/config/system.glGammaVal", "r");
284 fgets(tmpline, 80, infile);
286 sgi_gamma = atof(tmpline);
288 LUT_exponent = 1.0 / sgi_gamma;
290 #elif defined(Macintosh)
291 LUT_exponent = 1.8 / 2.61;
293 if (some_mac_function_that_returns_gamma(&mac_gamma))
294 LUT_exponent = mac_gamma / 2.61;
297 LUT_exponent = 1.0; /* assume no LUT: most PCs */
300 /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
301 default_display_exponent = LUT_exponent * CRT_exponent;
304 /* If the user has set the SCREEN_GAMMA environment variable as suggested
305 * (somewhat imprecisely) in the libpng documentation, use that; otherwise
306 * use the default value we just calculated. Either way, the user may
307 * override this via a command-line option. */
309 if ((p = getenv("SCREEN_GAMMA")) != NULL)
310 rpng2_info.display_exponent = atof(p);
312 rpng2_info.display_exponent = default_display_exponent;
315 /* Now parse the command line for options and the PNG filename. */
317 while (*++argv && !error) {
318 if (!strncmp(*argv, "-display", 2)) {
323 } else if (!strncmp(*argv, "-gamma", 2)) {
327 rpng2_info.display_exponent = atof(*argv);
328 if (rpng2_info.display_exponent <= 0.0)
331 } else if (!strncmp(*argv, "-bgcolor", 4)) {
336 if (strlen(bgstr) != 7 || bgstr[0] != '#')
343 } else if (!strncmp(*argv, "-bgpat", 4)) {
347 pat = atoi(*argv) - 1;
348 if (pat < 0 || pat >= num_bgpat)
355 } else if (!strncmp(*argv, "-usleep", 2)) {
359 usleep_duration = (ulg)atol(*argv);
362 } else if (!strncmp(*argv, "-pause", 2)) {
363 pause_after_pass = TRUE;
364 } else if (!strncmp(*argv, "-timing", 2)) {
366 #if (defined(__i386__) || defined(_M_IX86))
367 } else if (!strncmp(*argv, "-nommxfilters", 7)) {
368 rpng2_info.nommxfilters = TRUE;
369 } else if (!strncmp(*argv, "-nommxcombine", 7)) {
370 rpng2_info.nommxcombine = TRUE;
371 } else if (!strncmp(*argv, "-nommxinterlace", 7)) {
372 rpng2_info.nommxinterlace = TRUE;
373 } else if (!strcmp(*argv, "-nommx")) {
374 rpng2_info.nommxfilters = TRUE;
375 rpng2_info.nommxcombine = TRUE;
376 rpng2_info.nommxinterlace = TRUE;
381 if (argv[1]) /* shouldn't be any more args after filename */
384 ++error; /* not expecting any other options */
390 } else if (!(infile = fopen(filename, "rb"))) {
391 fprintf(stderr, PROGNAME ": can't open PNG file [%s]\n", filename);
394 incount = fread(inbuf, 1, INBUFSIZE, infile);
395 if (incount < 8 || !readpng2_check_sig(inbuf, 8)) {
396 fprintf(stderr, PROGNAME
397 ": [%s] is not a PNG file: incorrect signature\n",
400 } else if ((rc = readpng2_init(&rpng2_info)) != 0) {
403 fprintf(stderr, PROGNAME
404 ": [%s] has bad IHDR (libpng longjmp)\n",
408 fprintf(stderr, PROGNAME ": insufficient memory\n");
411 fprintf(stderr, PROGNAME
412 ": unknown readpng2_init() error\n");
417 display = XOpenDisplay(displayname);
419 readpng2_cleanup(&rpng2_info);
420 fprintf(stderr, PROGNAME ": can't open X display [%s]\n",
421 displayname? displayname : "default");
433 fprintf(stderr, "\n%s %s: %s\n\n", PROGNAME, VERSION, appname);
434 readpng2_version_info();
436 "Usage: %s [-display xdpy] [-gamma exp] [-bgcolor bg | -bgpat pat]\n"
437 #if (defined(__i386__) || defined(_M_IX86))
438 " %*s [[-nommxfilters] [-nommxcombine] [-nommxinterlace] | -nommx]\n"
440 " %*s [-usleep dur | -timing] [-pause] file.png\n\n"
441 " xdpy\tname of the target X display (e.g., ``hostname:0'')\n"
442 " exp \ttransfer-function exponent (``gamma'') of the display\n"
443 "\t\t system in floating-point format (e.g., ``%.1f''); equal\n"
444 "\t\t to the product of the lookup-table exponent (varies)\n"
445 "\t\t and the CRT exponent (usually 2.2); must be positive\n"
446 " bg \tdesired background color in 7-character hex RGB format\n"
447 "\t\t (e.g., ``#ff7700'' for orange: same as HTML colors);\n"
448 "\t\t used with transparent images; overrides -bgpat\n"
449 " pat \tdesired background pattern number (1-%d); used with\n"
450 "\t\t transparent images; overrides -bgcolor\n"
451 #if (defined(__i386__) || defined(_M_IX86))
452 " -nommx*\tdisable optimized MMX routines for decoding row filters,\n"
453 "\t\t combining rows, and expanding interlacing, respectively\n"
455 " dur \tduration in microseconds to wait after displaying each\n"
456 "\t\t row (for demo purposes)\n"
457 " -timing\tenables delay for every block read, to simulate modem\n"
458 "\t\t download of image (~36 Kbps)\n"
459 " -pause\tpauses after displaying each pass until key pressed\n"
460 "\nPress Q, Esc or mouse button 1 (within image window, after image\n"
461 "is displayed) to quit.\n"
463 #if (defined(__i386__) || defined(_M_IX86))
464 strlen(PROGNAME), " ",
466 strlen(PROGNAME), " ", default_display_exponent, num_bgpat);
471 /* set the title-bar string, but make sure buffer doesn't overflow */
473 alen = strlen(appname);
474 flen = strlen(filename);
475 if (alen + flen + 3 > 1023)
476 sprintf(titlebar, "%s: ...%s", appname, filename+(alen+flen+6-1023));
478 sprintf(titlebar, "%s: %s", appname, filename);
481 /* set some final rpng2_info variables before entering main data loop */
484 unsigned r, g, b; /* this approach quiets compiler warnings */
486 sscanf(bgstr+1, "%2x%2x%2x", &r, &g, &b);
487 rpng2_info.bg_red = (uch)r;
488 rpng2_info.bg_green = (uch)g;
489 rpng2_info.bg_blue = (uch)b;
491 rpng2_info.need_bgcolor = TRUE;
493 rpng2_info.done = FALSE;
494 rpng2_info.mainprog_init = rpng2_x_init;
495 rpng2_info.mainprog_display_row = rpng2_x_display_row;
496 rpng2_info.mainprog_finish_display = rpng2_x_finish_display;
499 /* OK, this is the fun part: call readpng2_decode_data() at the start of
500 * the loop to deal with our first buffer of data (read in above to verify
501 * that the file is a PNG image), then loop through the file and continue
502 * calling the same routine to handle each chunk of data. It in turn
503 * passes the data to libpng, which will invoke one or more of our call-
504 * backs as decoded data become available. We optionally call sleep() for
505 * one second per iteration to simulate downloading the image via an analog
509 Trace((stderr, "about to call readpng2_decode_data()\n"))
510 if (readpng2_decode_data(&rpng2_info, inbuf, incount))
512 Trace((stderr, "done with readpng2_decode_data()\n"))
513 if (error || feof(infile) || rpng2_info.done)
517 incount = fread(inbuf, 1, INBUFSIZE, infile);
521 /* clean up PNG stuff and report any decoding errors */
524 Trace((stderr, "about to call readpng2_cleanup()\n"))
525 readpng2_cleanup(&rpng2_info);
528 fprintf(stderr, PROGNAME ": libpng error while decoding PNG image\n");
533 /* wait for the user to tell us when to quit */
536 XNextEvent(display, &e);
537 while (!(e.type == ButtonPress && e.xbutton.button == Button1) &&
538 !(e.type == KeyPress && /* v--- or 1 for shifted keys */
539 ((k = XLookupKeysym(&e.xkey, 0)) == XK_q || k == XK_Escape) ));
542 /* we're done: clean up all image and X resources and go away */
544 Trace((stderr, "about to call rpng2_x_cleanup()\n"))
554 /* this function is called by readpng2_info_callback() in readpng2.c, which
555 * in turn is called by libpng after all of the pre-IDAT chunks have been
556 * read and processed--i.e., we now have enough info to finish initializing */
558 static void rpng2_x_init(void)
561 ulg rowbytes = rpng2_info.rowbytes;
563 Trace((stderr, "beginning rpng2_x_init()\n"))
564 Trace((stderr, " rowbytes = %ld\n", rpng2_info.rowbytes))
565 Trace((stderr, " width = %ld\n", rpng2_info.width))
566 Trace((stderr, " height = %ld\n", rpng2_info.height))
568 rpng2_info.image_data = (uch *)malloc(rowbytes * rpng2_info.height);
569 if (!rpng2_info.image_data) {
570 readpng2_cleanup(&rpng2_info);
574 rpng2_info.row_pointers = (uch **)malloc(rpng2_info.height * sizeof(uch *));
575 if (!rpng2_info.row_pointers) {
576 free(rpng2_info.image_data);
577 rpng2_info.image_data = NULL;
578 readpng2_cleanup(&rpng2_info);
582 for (i = 0; i < rpng2_info.height; ++i)
583 rpng2_info.row_pointers[i] = rpng2_info.image_data + i*rowbytes;
586 /* do the basic X initialization stuff, make the window, and fill it with
587 * the user-specified, file-specified or default background color or
590 if (rpng2_x_create_window()) {
592 /* GRR TEMPORARY HACK: this is fundamentally no different from cases
593 * above; libpng should longjmp() back to us when png_ptr goes away.
594 * If we/it segfault instead, seems like a libpng bug... */
596 /* we're here via libpng callback, so if window fails, clean and bail */
597 printf("readpng2_cleanup.\n");
598 readpng2_cleanup(&rpng2_info);
608 static int rpng2_x_create_window(void)
610 ulg bg_red = rpng2_info.bg_red;
611 ulg bg_green = rpng2_info.bg_green;
612 ulg bg_blue = rpng2_info.bg_blue;
615 int need_colormap = FALSE;
621 XSetWindowAttributes attr;
622 XSizeHints *size_hints;
623 XTextProperty windowName, *pWindowName = &windowName;
624 XTextProperty iconName, *pIconName = &iconName;
625 XVisualInfo visual_info;
629 Trace((stderr, "beginning rpng2_x_create_window()\n"))
631 screen = DefaultScreen(display);
632 depth = DisplayPlanes(display, screen);
633 root = RootWindow(display, screen);
636 XSynchronize(display, True);
639 if (depth != 16 && depth != 24 && depth != 32) {
640 int visuals_matched = 0;
642 Trace((stderr, "default depth is %d: checking other visuals\n",
646 visual_info.screen = screen;
647 visual_info.depth = 24;
648 visual_list = XGetVisualInfo(display,
649 VisualScreenMask | VisualDepthMask, &visual_info, &visuals_matched);
650 if (visuals_matched == 0) {
651 /* GRR: add 15-, 16- and 32-bit TrueColor visuals (also DirectColor?) */
652 fprintf(stderr, "default screen depth %d not supported, and no"
653 " 24-bit visuals found\n", depth);
656 Trace((stderr, "XGetVisualInfo() returned %d 24-bit visuals\n",
658 visual = visual_list[0].visual;
659 depth = visual_list[0].depth;
661 colormap_size = visual_list[0].colormap_size;
662 visual_class = visual->class;
663 visualID = XVisualIDFromVisual(visual);
665 have_nondefault_visual = TRUE;
666 need_colormap = TRUE;
668 XMatchVisualInfo(display, screen, depth, TrueColor, &visual_info);
669 visual = visual_info.visual;
672 RMask = visual->red_mask;
673 GMask = visual->green_mask;
674 BMask = visual->blue_mask;
676 /* GRR: add/check 8-bit support */
677 if (depth == 8 || need_colormap) {
678 colormap = XCreateColormap(display, root, visual, AllocNone);
680 fprintf(stderr, "XCreateColormap() failed\n");
683 have_colormap = TRUE;
685 bg_image = FALSE; /* gradient just wastes palette entries */
687 if (depth == 15 || depth == 16) {
688 RShift = 15 - rpng2_x_msb(RMask); /* these are right-shifts */
689 GShift = 15 - rpng2_x_msb(GMask);
690 BShift = 15 - rpng2_x_msb(BMask);
691 } else if (depth > 16) {
692 RShift = rpng2_x_msb(RMask) - 7; /* these are left-shifts */
693 GShift = rpng2_x_msb(GMask) - 7;
694 BShift = rpng2_x_msb(BMask) - 7;
696 if (depth >= 15 && (RShift < 0 || GShift < 0 || BShift < 0)) {
697 fprintf(stderr, "rpng2 internal logic error: negative X shift(s)!\n");
701 /*---------------------------------------------------------------------------
702 Finally, create the window.
703 ---------------------------------------------------------------------------*/
705 attr.backing_store = Always;
706 attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask;
707 attrmask = CWBackingStore | CWEventMask;
708 if (have_nondefault_visual) {
709 attr.colormap = colormap;
710 attr.background_pixel = 0;
711 attr.border_pixel = 1;
712 attrmask |= CWColormap | CWBackPixel | CWBorderPixel;
715 window = XCreateWindow(display, root, 0, 0, rpng2_info.width,
716 rpng2_info.height, 0, depth, InputOutput, visual, attrmask, &attr);
718 if (window == None) {
719 fprintf(stderr, "XCreateWindow() failed\n");
725 XSetWindowColormap(display, window, colormap);
727 if (!XStringListToTextProperty(&window_name, 1, pWindowName))
729 if (!XStringListToTextProperty(&icon_name, 1, pIconName))
732 /* OK if either hints allocation fails; XSetWMProperties() allows NULLs */
734 if ((size_hints = XAllocSizeHints()) != NULL) {
735 /* window will not be resizable */
736 size_hints->flags = PMinSize | PMaxSize;
737 size_hints->min_width = size_hints->max_width = (int)rpng2_info.width;
738 size_hints->min_height = size_hints->max_height =
739 (int)rpng2_info.height;
742 if ((wm_hints = XAllocWMHints()) != NULL) {
743 wm_hints->initial_state = NormalState;
744 wm_hints->input = True;
745 /* wm_hints->icon_pixmap = icon_pixmap; */
746 wm_hints->flags = StateHint | InputHint /* | IconPixmapHint */ ;
749 XSetWMProperties(display, window, pWindowName, pIconName, NULL, 0,
750 size_hints, wm_hints, NULL);
752 /* various properties and hints no longer needed; free memory */
754 XFree(pWindowName->value);
756 XFree(pIconName->value);
762 XMapWindow(display, window);
764 gc = XCreateGC(display, window, 0, &gcvalues);
767 /*---------------------------------------------------------------------------
768 Allocate memory for the X- and display-specific version of the image.
769 ---------------------------------------------------------------------------*/
771 if (depth == 24 || depth == 32) {
772 xdata = (uch *)malloc(4*rpng2_info.width*rpng2_info.height);
774 } else if (depth == 16) {
775 xdata = (uch *)malloc(2*rpng2_info.width*rpng2_info.height);
777 } else /* depth == 8 */ {
778 xdata = (uch *)malloc(rpng2_info.width*rpng2_info.height);
783 fprintf(stderr, PROGNAME ": unable to allocate image memory\n");
787 ximage = XCreateImage(display, visual, depth, ZPixmap, 0,
788 (char *)xdata, rpng2_info.width, rpng2_info.height, pad, 0);
791 fprintf(stderr, PROGNAME ": XCreateImage() failed\n");
796 /* to avoid testing the byte order every pixel (or doubling the size of
797 * the drawing routine with a giant if-test), we arbitrarily set the byte
798 * order to MSBFirst and let Xlib worry about inverting things on little-
799 * endian machines (e.g., Linux/x86, old VAXen, etc.)--this is not the
800 * most efficient approach (the giant if-test would be better), but in
801 * the interest of clarity, we'll take the easy way out... */
803 ximage->byte_order = MSBFirst;
805 /*---------------------------------------------------------------------------
806 Fill window with the specified background color (default is black) or
807 faked "background image" (but latter is disabled if 8-bit; gradients
808 just waste palette entries).
809 ---------------------------------------------------------------------------*/
812 rpng2_x_load_bg_image(); /* resets bg_image if fails */
815 if (depth == 24 || depth == 32) {
816 bg_pixel = (bg_red << RShift) |
817 (bg_green << GShift) |
819 } else if (depth == 16) {
820 bg_pixel = (((bg_red << 8) >> RShift) & RMask) |
821 (((bg_green << 8) >> GShift) & GMask) |
822 (((bg_blue << 8) >> BShift) & BMask);
823 } else /* depth == 8 */ {
825 /* GRR: add 8-bit support */
828 XSetForeground(display, gc, bg_pixel);
829 XFillRectangle(display, window, gc, 0, 0, rpng2_info.width,
833 /*---------------------------------------------------------------------------
834 Wait for first Expose event to do any drawing, then flush and return.
835 ---------------------------------------------------------------------------*/
838 XNextEvent(display, &e);
839 while (e.type != Expose || e.xexpose.count);
845 } /* end function rpng2_x_create_window() */
851 static int rpng2_x_load_bg_image(void)
855 uch r1, r2, g1, g2, b1, b2;
856 uch r1_inv, r2_inv, g1_inv, g2_inv, b1_inv, b2_inv;
858 int xidx, yidx, yidx_max = (bgscale-1);
859 int even_odd_vert, even_odd_horiz, even_odd;
860 int invert_gradient2 = (bg[pat].type & 0x08);
862 int ximage_rowbytes = ximage->bytes_per_line;
866 /*---------------------------------------------------------------------------
867 Allocate buffer for fake background image to be used with transparent
868 images; if this fails, revert to plain background color.
869 ---------------------------------------------------------------------------*/
871 bg_rowbytes = 3 * rpng2_info.width;
872 bg_data = (uch *)malloc(bg_rowbytes * rpng2_info.height);
874 fprintf(stderr, PROGNAME
875 ": unable to allocate memory for background image\n");
880 /*---------------------------------------------------------------------------
881 Vertical gradients (ramps) in NxN squares, alternating direction and
882 colors (N == bgscale).
883 ---------------------------------------------------------------------------*/
885 if ((bg[pat].type & 0x07) == 0) {
886 uch r1_min = rgb[bg[pat].rgb1_min].r;
887 uch g1_min = rgb[bg[pat].rgb1_min].g;
888 uch b1_min = rgb[bg[pat].rgb1_min].b;
889 uch r2_min = rgb[bg[pat].rgb2_min].r;
890 uch g2_min = rgb[bg[pat].rgb2_min].g;
891 uch b2_min = rgb[bg[pat].rgb2_min].b;
892 int r1_diff = rgb[bg[pat].rgb1_max].r - r1_min;
893 int g1_diff = rgb[bg[pat].rgb1_max].g - g1_min;
894 int b1_diff = rgb[bg[pat].rgb1_max].b - b1_min;
895 int r2_diff = rgb[bg[pat].rgb2_max].r - r2_min;
896 int g2_diff = rgb[bg[pat].rgb2_max].g - g2_min;
897 int b2_diff = rgb[bg[pat].rgb2_max].b - b2_min;
899 for (row = 0; row < rpng2_info.height; ++row) {
900 yidx = (int)(row % bgscale);
901 even_odd_vert = (int)((row / bgscale) & 1);
903 r1 = r1_min + (r1_diff * yidx) / yidx_max;
904 g1 = g1_min + (g1_diff * yidx) / yidx_max;
905 b1 = b1_min + (b1_diff * yidx) / yidx_max;
906 r1_inv = r1_min + (r1_diff * (yidx_max-yidx)) / yidx_max;
907 g1_inv = g1_min + (g1_diff * (yidx_max-yidx)) / yidx_max;
908 b1_inv = b1_min + (b1_diff * (yidx_max-yidx)) / yidx_max;
910 r2 = r2_min + (r2_diff * yidx) / yidx_max;
911 g2 = g2_min + (g2_diff * yidx) / yidx_max;
912 b2 = b2_min + (b2_diff * yidx) / yidx_max;
913 r2_inv = r2_min + (r2_diff * (yidx_max-yidx)) / yidx_max;
914 g2_inv = g2_min + (g2_diff * (yidx_max-yidx)) / yidx_max;
915 b2_inv = b2_min + (b2_diff * (yidx_max-yidx)) / yidx_max;
917 dest = (char *)bg_data + row*bg_rowbytes;
918 for (i = 0; i < rpng2_info.width; ++i) {
919 even_odd_horiz = (int)((i / bgscale) & 1);
920 even_odd = even_odd_vert ^ even_odd_horiz;
922 (even_odd_horiz && (bg[pat].type & 0x10));
923 if (even_odd == 0) { /* gradient #1 */
933 } else { /* gradient #2 */
934 if ((invert_column && invert_gradient2) ||
935 (!invert_column && !invert_gradient2))
937 *dest++ = r2; /* not inverted or */
938 *dest++ = g2; /* doubly inverted */
942 *dest++ = g2_inv; /* singly inverted */
949 /*---------------------------------------------------------------------------
950 Soft gradient-diamonds with scale = bgscale. Code contributed by Adam
952 ---------------------------------------------------------------------------*/
954 } else if ((bg[pat].type & 0x07) == 1) {
956 hmax = (bgscale-1)/2; /* half the max weight of a color */
957 max = 2*hmax; /* the max weight of a color */
959 r1 = rgb[bg[pat].rgb1_max].r;
960 g1 = rgb[bg[pat].rgb1_max].g;
961 b1 = rgb[bg[pat].rgb1_max].b;
962 r2 = rgb[bg[pat].rgb2_max].r;
963 g2 = rgb[bg[pat].rgb2_max].g;
964 b2 = rgb[bg[pat].rgb2_max].b;
966 for (row = 0; row < rpng2_info.height; ++row) {
967 yidx = (int)(row % bgscale);
969 yidx = bgscale-1 - yidx;
970 dest = (char *)bg_data + row*bg_rowbytes;
971 for (i = 0; i < rpng2_info.width; ++i) {
972 xidx = (int)(i % bgscale);
974 xidx = bgscale-1 - xidx;
976 *dest++ = (k*r1 + (max-k)*r2) / max;
977 *dest++ = (k*g1 + (max-k)*g2) / max;
978 *dest++ = (k*b1 + (max-k)*b2) / max;
982 /*---------------------------------------------------------------------------
983 Radial "starburst" with azimuthal sinusoids; [eventually number of sinu-
984 soids will equal bgscale?]. This one is slow but very cool. Code con-
985 tributed by Pieter S. van der Meulen (originally in Smalltalk).
986 ---------------------------------------------------------------------------*/
988 } else if ((bg[pat].type & 0x07) == 2) {
990 int ii, x, y, hw, hh, grayspot;
991 double freq, rotate, saturate, gray, intensity;
992 double angle=0.0, aoffset=0.0, maxDist, dist;
993 double red=0.0, green=0.0, blue=0.0, hue, s, v, f, p, q, t;
995 fprintf(stderr, "%s: computing radial background...",
999 hh = (int)(rpng2_info.height / 2);
1000 hw = (int)(rpng2_info.width / 2);
1002 /* variables for radial waves:
1003 * aoffset: number of degrees to rotate hue [CURRENTLY NOT USED]
1004 * freq: number of color beams originating from the center
1005 * grayspot: size of the graying center area (anti-alias)
1006 * rotate: rotation of the beams as a function of radius
1007 * saturate: saturation of beams' shape azimuthally
1009 angle = CLIP(angle, 0.0, 360.0);
1010 grayspot = CLIP(bg[pat].bg_gray, 1, (hh + hw));
1011 freq = MAX((double)bg[pat].bg_freq, 0.0);
1012 saturate = (double)bg[pat].bg_bsat * 0.1;
1013 rotate = (double)bg[pat].bg_brot * 0.1;
1016 maxDist = (double)((hw*hw) + (hh*hh));
1018 for (row = 0; row < rpng2_info.height; ++row) {
1019 y = (int)(row - hh);
1020 dest = (char *)bg_data + row*bg_rowbytes;
1021 for (i = 0; i < rpng2_info.width; ++i) {
1023 angle = (x == 0)? PI_2 : atan((double)y / (double)x);
1024 gray = (double)MAX(ABS(y), ABS(x)) / grayspot;
1025 gray = MIN(1.0, gray);
1026 dist = (double)((x*x) + (y*y)) / maxDist;
1027 intensity = cos((angle+(rotate*dist*PI)) * freq) *
1029 intensity = (MAX(MIN(intensity,1.0),-1.0) + 1.0) * 0.5;
1030 hue = (angle + PI) * INV_PI_360 + aoffset;
1031 s = gray * ((double)(ABS(x)+ABS(y)) / (double)(hw + hh));
1032 s = MIN(MAX(s,0.0), 1.0);
1033 v = MIN(MAX(intensity,0.0), 1.0);
1036 ch = (uch)(v * 255.0);
1041 if ((hue < 0.0) || (hue >= 360.0))
1042 hue -= (((int)(hue / 360.0)) * 360.0);
1045 f = hue - (double)ii;
1047 q = (1.0 - (s * f)) * v;
1048 t = (1.0 - (s * (1.0 - f))) * v;
1049 if (ii == 0) { red = v; green = t; blue = p; }
1050 else if (ii == 1) { red = q; green = v; blue = p; }
1051 else if (ii == 2) { red = p; green = v; blue = t; }
1052 else if (ii == 3) { red = p; green = q; blue = v; }
1053 else if (ii == 4) { red = t; green = p; blue = v; }
1054 else if (ii == 5) { red = v; green = p; blue = q; }
1055 *dest++ = (uch)(red * 255.0);
1056 *dest++ = (uch)(green * 255.0);
1057 *dest++ = (uch)(blue * 255.0);
1061 fprintf(stderr, "done.\n");
1065 /*---------------------------------------------------------------------------
1066 Blast background image to display buffer before beginning PNG decode.
1067 ---------------------------------------------------------------------------*/
1069 if (depth == 24 || depth == 32) {
1070 ulg red, green, blue;
1072 for (row = 0; row < rpng2_info.height; ++row) {
1073 src = bg_data + row*bg_rowbytes;
1074 dest = ximage->data + row*ximage_rowbytes;
1075 for (i = rpng2_info.width; i > 0; --i) {
1079 pixel = (red << RShift) |
1082 /* recall that we set ximage->byte_order = MSBFirst above */
1083 /* GRR BUG: this assumes bpp == 32, but may be 24: */
1084 *dest++ = (char)((pixel >> 24) & 0xff);
1085 *dest++ = (char)((pixel >> 16) & 0xff);
1086 *dest++ = (char)((pixel >> 8) & 0xff);
1087 *dest++ = (char)( pixel & 0xff);
1091 } else if (depth == 16) {
1092 ush red, green, blue;
1094 for (row = 0; row < rpng2_info.height; ++row) {
1095 src = bg_data + row*bg_rowbytes;
1096 dest = ximage->data + row*ximage_rowbytes;
1097 for (i = rpng2_info.width; i > 0; --i) {
1098 red = ((ush)(*src) << 8); ++src;
1099 green = ((ush)(*src) << 8); ++src;
1100 blue = ((ush)(*src) << 8); ++src;
1101 pixel = ((red >> RShift) & RMask) |
1102 ((green >> GShift) & GMask) |
1103 ((blue >> BShift) & BMask);
1104 /* recall that we set ximage->byte_order = MSBFirst above */
1105 *dest++ = (char)((pixel >> 8) & 0xff);
1106 *dest++ = (char)( pixel & 0xff);
1110 } else /* depth == 8 */ {
1112 /* GRR: add 8-bit support */
1116 XPutImage(display, window, gc, ximage, 0, 0, 0, 0, rpng2_info.width,
1121 } /* end function rpng2_x_load_bg_image() */
1127 static void rpng2_x_display_row(ulg row)
1129 uch bg_red = rpng2_info.bg_red;
1130 uch bg_green = rpng2_info.bg_green;
1131 uch bg_blue = rpng2_info.bg_blue;
1132 uch *src, *src2=NULL;
1135 int ximage_rowbytes = ximage->bytes_per_line;
1137 static int rows=0, prevpass=(-1);
1138 static ulg firstrow;
1140 /*---------------------------------------------------------------------------
1141 rows and firstrow simply track how many rows (and which ones) have not
1142 yet been displayed; alternatively, we could call XPutImage() for every
1143 row and not bother with the records-keeping.
1144 ---------------------------------------------------------------------------*/
1146 Trace((stderr, "beginning rpng2_x_display_row()\n"))
1148 if (rpng2_info.pass != prevpass) {
1149 if (pause_after_pass && rpng2_info.pass > 0) {
1154 "%s: end of pass %d of 7; click in image window to continue\n",
1155 PROGNAME, prevpass + 1);
1157 XNextEvent(display, &e);
1158 while (!(e.type == ButtonPress && e.xbutton.button == Button1)
1159 && !(e.type == KeyPress &&
1160 ((k = XLookupKeysym(&e.xkey, 0)) == XK_q
1161 || k == XK_Escape) )) ;
1163 fprintf(stderr, "%s: pass %d of 7\r", PROGNAME, rpng2_info.pass + 1);
1165 prevpass = rpng2_info.pass;
1169 firstrow = row; /* first row that is not yet displayed */
1171 ++rows; /* count of rows received but not yet displayed */
1173 /*---------------------------------------------------------------------------
1174 Aside from the use of the rpng2_info struct, the lack of an outer loop
1175 (over rows) and moving the XPutImage() call outside the "if (depth)"
1176 tests, this routine is identical to rpng_x_display_image() in the non-
1177 progressive version of the program.
1178 ---------------------------------------------------------------------------*/
1180 if (depth == 24 || depth == 32) {
1181 ulg red, green, blue;
1183 src = rpng2_info.image_data + row*rpng2_info.rowbytes;
1185 src2 = bg_data + row*bg_rowbytes;
1186 dest = ximage->data + row*ximage_rowbytes;
1187 if (rpng2_info.channels == 3) {
1188 for (i = rpng2_info.width; i > 0; --i) {
1192 pixel = (red << RShift) |
1195 /* recall that we set ximage->byte_order = MSBFirst above */
1196 /* GRR BUG: this assumes bpp == 32, but may be 24: */
1197 *dest++ = (char)((pixel >> 24) & 0xff);
1198 *dest++ = (char)((pixel >> 16) & 0xff);
1199 *dest++ = (char)((pixel >> 8) & 0xff);
1200 *dest++ = (char)( pixel & 0xff);
1202 } else /* if (rpng2_info.channels == 4) */ {
1203 for (i = rpng2_info.width; i > 0; --i) {
1217 } else if (a == 0) {
1222 /* this macro (from png.h) composites the foreground
1223 * and background values and puts the result into the
1225 alpha_composite(red, r, a, bg_red);
1226 alpha_composite(green, g, a, bg_green);
1227 alpha_composite(blue, b, a, bg_blue);
1229 pixel = (red << RShift) |
1232 /* recall that we set ximage->byte_order = MSBFirst above */
1233 /* GRR BUG: this assumes bpp == 32, but may be 24: */
1234 *dest++ = (char)((pixel >> 24) & 0xff);
1235 *dest++ = (char)((pixel >> 16) & 0xff);
1236 *dest++ = (char)((pixel >> 8) & 0xff);
1237 *dest++ = (char)( pixel & 0xff);
1241 } else if (depth == 16) {
1242 ush red, green, blue;
1244 src = rpng2_info.row_pointers[row];
1246 src2 = bg_data + row*bg_rowbytes;
1247 dest = ximage->data + row*ximage_rowbytes;
1248 if (rpng2_info.channels == 3) {
1249 for (i = rpng2_info.width; i > 0; --i) {
1250 red = ((ush)(*src) << 8);
1252 green = ((ush)(*src) << 8);
1254 blue = ((ush)(*src) << 8);
1256 pixel = ((red >> RShift) & RMask) |
1257 ((green >> GShift) & GMask) |
1258 ((blue >> BShift) & BMask);
1259 /* recall that we set ximage->byte_order = MSBFirst above */
1260 *dest++ = (char)((pixel >> 8) & 0xff);
1261 *dest++ = (char)( pixel & 0xff);
1263 } else /* if (rpng2_info.channels == 4) */ {
1264 for (i = rpng2_info.width; i > 0; --i) {
1275 red = ((ush)r << 8);
1276 green = ((ush)g << 8);
1277 blue = ((ush)b << 8);
1278 } else if (a == 0) {
1279 red = ((ush)bg_red << 8);
1280 green = ((ush)bg_green << 8);
1281 blue = ((ush)bg_blue << 8);
1283 /* this macro (from png.h) composites the foreground
1284 * and background values and puts the result back into
1285 * the first argument (== fg byte here: safe) */
1286 alpha_composite(r, r, a, bg_red);
1287 alpha_composite(g, g, a, bg_green);
1288 alpha_composite(b, b, a, bg_blue);
1289 red = ((ush)r << 8);
1290 green = ((ush)g << 8);
1291 blue = ((ush)b << 8);
1293 pixel = ((red >> RShift) & RMask) |
1294 ((green >> GShift) & GMask) |
1295 ((blue >> BShift) & BMask);
1296 /* recall that we set ximage->byte_order = MSBFirst above */
1297 *dest++ = (char)((pixel >> 8) & 0xff);
1298 *dest++ = (char)( pixel & 0xff);
1302 } else /* depth == 8 */ {
1304 /* GRR: add 8-bit support */
1309 /*---------------------------------------------------------------------------
1310 Display after every 16 rows or when on one of last two rows. (Region
1311 may include previously displayed lines due to interlacing--i.e., not
1312 contiguous. Also, second-to-last row is final one in interlaced images
1313 with odd number of rows.) For demos, flush (and delay) after every 16th
1314 row so "sparse" passes don't go twice as fast.
1315 ---------------------------------------------------------------------------*/
1317 if (demo_timing && (row - firstrow >= 16 || row >= rpng2_info.height-2)) {
1318 XPutImage(display, window, gc, ximage, 0, (int)firstrow, 0,
1319 (int)firstrow, rpng2_info.width, row - firstrow + 1);
1322 usleep(usleep_duration);
1324 if (!demo_timing && ((rows & 0xf) == 0 || row >= rpng2_info.height-2)) {
1325 XPutImage(display, window, gc, ximage, 0, (int)firstrow, 0,
1326 (int)firstrow, rpng2_info.width, row - firstrow + 1);
1337 static void rpng2_x_finish_display(void)
1339 Trace((stderr, "beginning rpng2_x_finish_display()\n"))
1341 /* last row has already been displayed by rpng2_x_display_row(), so we
1342 * have nothing to do here except set a flag and let the user know that
1343 * the image is done */
1345 rpng2_info.done = TRUE;
1347 "Done. Press Q, Esc or mouse button 1 (within image window) to quit.\n");
1355 static void rpng2_x_cleanup(void)
1357 if (bg_image && bg_data) {
1362 if (rpng2_info.image_data) {
1363 free(rpng2_info.image_data);
1364 rpng2_info.image_data = NULL;
1367 if (rpng2_info.row_pointers) {
1368 free(rpng2_info.row_pointers);
1369 rpng2_info.row_pointers = NULL;
1374 free(ximage->data); /* we allocated it, so we free it */
1375 ximage->data = (char *)NULL; /* instead of XDestroyImage() */
1377 XDestroyImage(ximage);
1382 XFreeGC(display, gc);
1385 XDestroyWindow(display, window);
1388 XFreeColormap(display, colormap);
1390 if (have_nondefault_visual)
1398 static int rpng2_x_msb(ulg u32val)
1402 for (i = 31; i >= 0; --i) {
1403 if (u32val & 0x80000000L)