http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.32.tar.gz
[xscreensaver] / utils / grabscreen.c
1 /* xscreensaver, Copyright (c) 1992, 1993, 1994, 1997, 1998
2  *  Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /* This file contains code for grabbing an image of the screen to hack its
14    bits.  This is a little tricky, since doing this involves the need to tell
15    the difference between drawing on the actual root window, and on the fake
16    root window used by the screensaver, since at this level the illusion 
17    breaks down...
18  */
19
20 #include "utils.h"
21 #include "yarandom.h"
22
23 #include <X11/Xatom.h>
24 #include <X11/Xutil.h>
25
26 #ifdef HAVE_XMU
27 # ifndef VMS
28 #  include <X11/Xmu/WinUtil.h>
29 # else  /* VMS */
30 #  include <Xmu/WinUtil.h>
31 # endif /* VMS */
32 #endif
33
34 #include "usleep.h"
35 #include "colors.h"
36 #include "grabscreen.h"
37 #include "visual.h"
38 #include "resources.h"
39
40 #include "vroot.h"
41 #undef RootWindowOfScreen
42 #undef RootWindow
43 #undef DefaultRootWindow
44
45
46 #ifdef HAVE_READ_DISPLAY_EXTENSION
47 # include <X11/extensions/readdisplay.h>
48   static Bool read_display (Screen *, Window, Pixmap, Bool);
49 #endif /* HAVE_READ_DISPLAY_EXTENSION */
50
51
52 static void copy_default_colormap_contents (Screen *, Colormap, Visual *);
53
54 #ifdef HAVE_READ_DISPLAY_EXTENSION
55 static void allocate_cubic_colormap (Screen *, Window, Visual *);
56 void remap_image (Screen *, Window, Colormap, XImage *);
57 #endif
58
59
60 static Bool
61 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
62 {
63   return (event->xany.type == MapNotify &&
64           event->xvisibility.window == (Window) window);
65 }
66
67 extern char *progname;
68 Bool grab_verbose_p = False;
69
70 void
71 grabscreen_verbose(void)
72 {
73   grab_verbose_p = True;
74 }
75
76
77 static void
78 raise_window(Display *dpy, Window window, Bool dont_wait)
79 {
80   if (grab_verbose_p)
81     fprintf(stderr, "%s: raising window 0x%08lX (%s)\n",
82             progname, (unsigned long) window,
83             (dont_wait ? "not waiting" : "waiting"));
84
85   if (! dont_wait)
86     {
87       XWindowAttributes xgwa;
88       XSizeHints hints;
89       long supplied = 0;
90       memset(&hints, 0, sizeof(hints));
91       XGetWMNormalHints(dpy, window, &hints, &supplied);
92       XGetWindowAttributes (dpy, window, &xgwa);
93       hints.x = xgwa.x;
94       hints.y = xgwa.y;
95       hints.width = xgwa.width;
96       hints.height = xgwa.height;
97       hints.flags |= (PPosition|USPosition|PSize|USSize);
98       XSetWMNormalHints(dpy, window, &hints);
99
100       XSelectInput (dpy, window, (xgwa.your_event_mask | StructureNotifyMask));
101     }
102
103   XMapRaised(dpy, window);
104
105   if (! dont_wait)
106     {
107       XEvent event;
108       XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
109       XSync (dpy, True);
110     }
111 }
112
113
114 static Bool
115 xscreensaver_window_p (Display *dpy, Window window)
116 {
117   Atom type;
118   int format;
119   unsigned long nitems, bytesafter;
120   char *version;
121   if (XGetWindowProperty (dpy, window,
122                           XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
123                           0, 1, False, XA_STRING,
124                           &type, &format, &nitems, &bytesafter,
125                           (unsigned char **) &version)
126       == Success
127       && type != None)
128     return True;
129   return False;
130 }
131
132
133
134 static XErrorHandler old_ehandler = 0;
135 static int
136 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
137 {
138   if (error->error_code == BadWindow || error->error_code == BadDrawable)
139     return 0;
140   else if (!old_ehandler)
141     {
142       abort();
143       return 0;
144     }
145   else
146     return (*old_ehandler) (dpy, error);
147 }
148
149
150 /* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
151    on a window whose depth is not the maximal depth of the screen?  Or
152    something.  Anyway, things don't work unless we: use SubwindowMode for
153    the real root window (or a legitimate virtual root window); but do not
154    use SubwindowMode for the xscreensaver window.  I make no attempt to
155    explain.
156  */
157 Bool
158 use_subwindow_mode_p(Screen *screen, Window window)
159 {
160   if (window != VirtualRootWindowOfScreen(screen))
161     return False;
162   else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
163     return False;
164   else
165     return True;
166 }
167
168
169 /* Install the colormaps of all visible windows, deepest first.
170    This should leave the colormaps of the topmost windows installed
171    (if only N colormaps can be installed at a time, then only the
172    topmost N windows will be shown in the right colors.)
173  */
174 static void
175 install_screen_colormaps (Screen *screen)
176 {
177   int i;
178   Display *dpy = DisplayOfScreen (screen);
179   Window vroot, real_root;
180   Window parent, *kids = 0;
181   unsigned int nkids = 0;
182
183   XSync (dpy, False);
184   old_ehandler = XSetErrorHandler (BadWindow_ehandler);
185
186   vroot = VirtualRootWindowOfScreen (screen);
187   if (XQueryTree (dpy, vroot, &real_root, &parent, &kids, &nkids))
188     for (i = 0; i < nkids; i++)
189       {
190         XWindowAttributes xgwa;
191         Window client;
192 #ifdef HAVE_XMU
193         /* #### need to put XmuClientWindow() in xmu.c, sigh... */
194         if (! (client = XmuClientWindow (dpy, kids[i])))
195 #endif
196           client = kids[i];
197         xgwa.colormap = 0;
198         XGetWindowAttributes (dpy, client, &xgwa);
199         if (xgwa.colormap && xgwa.map_state == IsViewable)
200           XInstallColormap (dpy, xgwa.colormap);
201       }
202   XInstallColormap (dpy, DefaultColormapOfScreen (screen));
203   XSync (dpy, False);
204   XSetErrorHandler (old_ehandler);
205   XSync (dpy, False);
206
207   if (kids)
208     XFree ((char *) kids);
209 }
210
211
212 void
213 grab_screen_image (Screen *screen, Window window)
214 {
215   Display *dpy = DisplayOfScreen (screen);
216   XWindowAttributes xgwa;
217   Window real_root = XRootWindowOfScreen (screen);  /* not vroot */
218   Bool root_p = (window == real_root);
219   Bool saver_p = xscreensaver_window_p (dpy, window);
220   Bool grab_mouse_p = False;
221   int unmap_time = 0;
222
223   if (saver_p)
224     /* I think this is redundant, but just to be safe... */
225     root_p = False;
226
227   if (saver_p)
228     /* The only time grabbing the mouse is important is if this program
229        is being run while the saver is locking the screen. */
230     grab_mouse_p = True;
231
232   if (!root_p)
233     {
234       double unmap = 0;
235       if (saver_p)
236         {
237           unmap = get_float_resource("grabRootDelay", "Seconds");
238           if (unmap <= 0.00001 || unmap > 20) unmap = 2.5;
239         }
240       else
241         {
242           unmap = get_float_resource("grabWindowDelay", "Seconds");
243           if (unmap <= 0.00001 || unmap > 20) unmap = 0.66;
244         }
245       unmap_time = unmap * 100000;
246     }
247
248   if (grab_verbose_p)
249     {
250       XWindowAttributes xgwa2;
251       fprintf(stderr,
252               "\n%s: window 0x%08lX root: %d saver: %d grab: %d wait: %.1f\n",
253               progname, (unsigned long) window,
254               root_p, saver_p, grab_mouse_p, ((double)unmap_time)/1000000.0);
255
256       XGetWindowAttributes (dpy, window, &xgwa2);
257       fprintf(stderr, "%s: ", progname);
258       describe_visual(stderr, screen, xgwa2.visual, False);
259       fprintf (stderr, "\n");
260     }
261
262   if (!root_p)
263     XSetWindowBackgroundPixmap (dpy, window, None);
264
265   if (grab_mouse_p)
266     {
267       /* prevent random viewer of the screen saver (locker) from messing
268          with windows.   We don't check whether it succeeded, because what
269          are our options, really... */
270       XGrabPointer (dpy, real_root, True, ButtonPressMask|ButtonReleaseMask,
271                     GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
272       XGrabKeyboard (dpy, real_root, True, GrabModeSync, GrabModeAsync,
273                      CurrentTime);
274     }
275
276   if (unmap_time > 0)
277     {
278       XUnmapWindow (dpy, window);
279       install_screen_colormaps (screen);
280       XSync (dpy, True);
281       usleep(unmap_time); /* wait for everyone to swap in and handle exposes */
282     }
283
284   XGetWindowAttributes (dpy, window, &xgwa);
285
286   if (!root_p)
287     {
288 #ifdef HAVE_READ_DISPLAY_EXTENSION
289       if (! read_display(screen, window, 0, saver_p))
290 #endif /* HAVE_READ_DISPLAY_EXTENSION */
291         {
292 #ifdef HAVE_READ_DISPLAY_EXTENSION
293           if (grab_verbose_p)
294             fprintf(stderr, "%s: read_display() failed\n", progname);
295 #endif /* HAVE_READ_DISPLAY_EXTENSION */
296
297           copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
298           raise_window(dpy, window, saver_p);
299
300           /* Generally it's bad news to call XInstallColormap() explicitly,
301              but this file does a lot of sleazy stuff already...  This is to
302              make sure that the window's colormap is installed, even in the
303              case where the window is OverrideRedirect. */
304           if (xgwa.colormap) XInstallColormap (dpy, xgwa.colormap);
305           XSync (dpy, False);
306         }
307     }
308   else  /* root_p */
309     {
310       Pixmap pixmap;
311       XWindowAttributes xgwa;
312       XGetWindowAttributes(dpy, window, &xgwa);
313       pixmap = XCreatePixmap(dpy, window, xgwa.width, xgwa.height, xgwa.depth);
314
315 #ifdef HAVE_READ_DISPLAY_EXTENSION
316       if (! read_display(screen, window, pixmap, True))
317 #endif
318         {
319           Window real_root = XRootWindowOfScreen (xgwa.screen); /* not vroot */
320           XGCValues gcv;
321           GC gc;
322
323 #ifdef HAVE_READ_DISPLAY_EXTENSION
324           if  (grab_verbose_p)
325             fprintf(stderr, "%s: read_display() failed\n", progname);
326 #endif /* HAVE_READ_DISPLAY_EXTENSION */
327
328           copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
329
330           gcv.function = GXcopy;
331           gcv.subwindow_mode = IncludeInferiors;
332           gc = XCreateGC (dpy, window, GCFunction | GCSubwindowMode, &gcv);
333           XCopyArea (dpy, real_root, pixmap, gc,
334                      xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0);
335           XFreeGC (dpy, gc);
336         }
337       XSetWindowBackgroundPixmap (dpy, window, pixmap);
338       XFreePixmap (dpy, pixmap);
339     }
340
341   if (grab_verbose_p)
342     fprintf (stderr, "%s: grabbed %d bit screen image to %swindow.\n",
343              progname, xgwa.depth,
344              (root_p ? "real root " : ""));
345
346   if (grab_mouse_p)
347     {
348       XUngrabPointer (dpy, CurrentTime);
349       XUngrabKeyboard (dpy, CurrentTime);
350     }
351
352   XSync (dpy, True);
353 }
354
355
356 /* When we are grabbing and manipulating a screen image, it's important that
357    we use the same colormap it originally had.  So, if the screensaver was
358    started with -install, we need to copy the contents of the default colormap
359    into the screensaver's colormap.
360  */
361 static void
362 copy_default_colormap_contents (Screen *screen,
363                                 Colormap to_cmap,
364                                 Visual *to_visual)
365 {
366   Display *dpy = DisplayOfScreen (screen);
367   Visual *from_visual = DefaultVisualOfScreen (screen);
368   Colormap from_cmap = XDefaultColormapOfScreen (screen);
369
370   XColor *old_colors, *new_colors;
371   unsigned long *pixels;
372   XVisualInfo vi_in, *vi_out;
373   int out_count;
374   int from_cells, to_cells, max_cells, got_cells;
375   int i;
376
377   if (from_cmap == to_cmap)
378     return;
379
380   vi_in.screen = XScreenNumberOfScreen (screen);
381   vi_in.visualid = XVisualIDFromVisual (from_visual);
382   vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
383                            &vi_in, &out_count);
384   if (! vi_out) abort ();
385   from_cells = vi_out [0].colormap_size;
386   XFree ((char *) vi_out);
387
388   vi_in.screen = XScreenNumberOfScreen (screen);
389   vi_in.visualid = XVisualIDFromVisual (to_visual);
390   vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
391                            &vi_in, &out_count);
392   if (! vi_out) abort ();
393   to_cells = vi_out [0].colormap_size;
394   XFree ((char *) vi_out);
395
396   max_cells = (from_cells > to_cells ? to_cells : from_cells);
397
398   old_colors = (XColor *) calloc (sizeof (XColor), max_cells);
399   new_colors = (XColor *) calloc (sizeof (XColor), max_cells);
400   pixels = (unsigned long *) calloc (sizeof (unsigned long), max_cells);
401   for (i = 0; i < max_cells; i++)
402     old_colors[i].pixel = i;
403   XQueryColors (dpy, from_cmap, old_colors, max_cells);
404
405   got_cells = max_cells;
406   allocate_writable_colors (dpy, to_cmap, pixels, &got_cells);
407
408   if (grab_verbose_p && got_cells != max_cells)
409     fprintf(stderr, "%s: got only %d of %d cells\n", progname,
410             got_cells, max_cells);
411
412   if (got_cells <= 0)                                    /* we're screwed */
413     ;
414   else if (got_cells == max_cells &&                     /* we're golden */
415            from_cells == to_cells)
416     XStoreColors (dpy, to_cmap, old_colors, got_cells);
417   else                                                   /* try to cope... */
418     {
419       for (i = 0; i < got_cells; i++)
420         {
421           XColor *c = old_colors + i;
422           int j;
423           for (j = 0; j < got_cells; j++)
424             if (pixels[j] == c->pixel)
425               {
426                 /* only store this color value if this is one of the pixels
427                    we were able to allocate. */
428                 XStoreColors (dpy, to_cmap, c, 1);
429                 break;
430               }
431         }
432     }
433
434
435   if (grab_verbose_p)
436     fprintf(stderr, "%s: installing copy of default colormap\n", progname);
437
438   free (old_colors);
439   free (new_colors);
440   free (pixels);
441 }
442
443
444 \f
445 /* The SGI ReadDisplay extension.
446    This extension lets you get back a 24-bit image of the screen, taking into
447    account the colors with which all windows are *currently* displayed, even
448    if those windows have different visuals.  Without this extension, presence
449    of windows with different visuals or colormaps will result in technicolor
450    when one tries to grab the screen image.
451  */
452
453 #ifdef HAVE_READ_DISPLAY_EXTENSION
454
455 static Bool
456 read_display (Screen *screen, Window window, Pixmap into_pixmap,
457               Bool dont_wait)
458 {
459   Display *dpy = DisplayOfScreen (screen);
460   XWindowAttributes xgwa;
461   int rd_event_base = 0;
462   int rd_error_base = 0;
463   unsigned long hints = 0;
464   XImage *image = 0;
465   XGCValues gcv;
466   int class;
467   GC gc;
468   Bool remap_p = False;
469
470   /* Check to see if the server supports the extension, and bug out if not.
471    */
472   if (! XReadDisplayQueryExtension (dpy, &rd_event_base, &rd_error_base))
473     return False;
474
475   /* If this isn't a visual we know how to handle, bug out.  We handle:
476       = TrueColor in depths 8, 12, 16, and 32;
477       = PseudoColor and DirectColor in depths 8 and 12.
478    */
479   XGetWindowAttributes(dpy, window, &xgwa);
480   class = visual_class (screen, xgwa.visual);
481   if (class == TrueColor)
482     {
483       if (xgwa.depth != 8  && xgwa.depth != 12 && xgwa.depth != 16 &&
484           xgwa.depth != 24 && xgwa.depth != 32)
485         return False;
486     }
487   else if (class == PseudoColor || class == DirectColor)
488     {
489       if (xgwa.depth != 8 && xgwa.depth != 12)
490         return False;
491       else
492         /* Allocate a TrueColor-like spread of colors for the image. */
493         remap_p = True;
494     }
495
496
497   /* Try and read the screen.
498    */
499   hints = (XRD_TRANSPARENT | XRD_READ_POINTER);
500   image = XReadDisplay (dpy, window, xgwa.x, xgwa.y, xgwa.width, xgwa.height,
501                         hints, &hints);
502   if (!image)
503     return False;
504   if (!image->data)
505     {
506       XDestroyImage(image);
507       return False;
508     }
509
510   /* XReadDisplay tends to LIE about the depth of the image it read.
511      It is returning an XImage which has `depth' and `bits_per_pixel'
512      confused!
513
514      That is, on a 24-bit display, where all visuals claim depth 24, and
515      where XGetImage would return an XImage with depth 24, and where
516      XPutImage will get a BadMatch with images that are not depth 24,
517      XReadDisplay is returning images with depth 32!  Fuckwits!
518
519      So if the visual is of depth 24, but the image came back as depth 32,
520      hack it to be 24 lest we get a BadMatch from XPutImage.
521
522      I wonder what happens on an 8-bit SGI...  Probably it still returns
523      an image claiming depth 32?  Certainly it can't be 8.  So, let's just
524      smash it to 32...
525    */
526   if (image->depth == 32 /* && xgwa.depth == 24 */ )
527     image->depth = 24;
528
529   /* If the visual of the window/pixmap into which we're going to draw is
530      less deep than the screen itself, then we need to convert the grabbed bits
531      to match the depth by clipping off the less significant bit-planes of each
532      color component.
533    */
534   if (image->depth > xgwa.depth)
535     {
536       int x, y;
537       /* We use the same image->data in both images -- that's ok, because
538          since we're reading from B and writing to A, and B uses more bytes
539          per pixel than A, the write pointer won't overrun the read pointer.
540        */
541       XImage *image2 = XCreateImage (dpy, xgwa.visual, xgwa.depth,
542                                      ZPixmap, 0, image->data,
543                                      xgwa.width, xgwa.height,
544                                      8, 0);
545       if (!image2)
546         return False;
547
548       if (grab_verbose_p)
549         fprintf(stderr, "%s: converting from depth %d to depth %d\n",
550                 progname, image->depth, xgwa.depth);
551
552       for (y = 0; y < image->height; y++)
553         for (x = 0; x < image->width; x++)
554           {
555             /* #### really these shift values should be determined from the
556                mask values -- but that's a pain in the ass, and anyway,
557                this is an SGI-specific extension so hardcoding assumptions
558                about the SGI server's behavior isn't *too* heinous... */
559             unsigned long pixel = XGetPixel(image, x, y);
560             unsigned int r = (pixel & image->red_mask);
561             unsigned int g = (pixel & image->green_mask) >> 8;
562             unsigned int b = (pixel & image->blue_mask) >> 16;
563
564             if (xgwa.depth == 8)
565               pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
566             else if (xgwa.depth == 12)
567               pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
568             else if (xgwa.depth == 16)
569               pixel = ((r >> 3) | ((g >> 3) << 5) | ((b >> 3) << 10));
570             else
571               abort();
572
573             XPutPixel(image2, x, y, pixel);
574           }
575       image->data = 0;
576       XDestroyImage(image);
577       image = image2;
578     }
579
580   if (remap_p)
581     {
582       allocate_cubic_colormap (screen, window, xgwa.visual);
583       remap_image (screen, window, xgwa.colormap, image);
584     }
585
586   /* Now actually put the bits into the window or pixmap -- note the design
587      bogosity of this extension, where we've been forced to take 24 bit data
588      from the server to the client, and then push it back from the client to
589      the server, *without alteration*.  We should have just been able to tell
590      the server, "put a screen image in this drawable", instead of having to
591      go through the intermediate step of converting it to an Image.  Geez.
592      (Assuming that the window is of screen depth; we happen to handle less
593      deep windows, but that's beside the point.)
594    */
595   gcv.function = GXcopy;
596   gc = XCreateGC (dpy, window, GCFunction, &gcv);
597
598   if (into_pixmap)
599     {
600       gcv.function = GXcopy;
601       gc = XCreateGC (dpy, into_pixmap, GCFunction, &gcv);
602       XPutImage (dpy, into_pixmap, gc, image, 0, 0, 0, 0,
603                  xgwa.width, xgwa.height);
604     }
605   else
606     {
607       gcv.function = GXcopy;
608       gc = XCreateGC (dpy, window, GCFunction, &gcv);
609
610       /* Ok, now we'll be needing that window on the screen... */
611       raise_window(dpy, window, dont_wait);
612
613       /* Plop down the bits... */
614       XPutImage (dpy, window, gc, image, 0, 0, 0, 0, xgwa.width, xgwa.height);
615     }
616   XFreeGC (dpy, gc);
617
618   if (image->data)
619     {
620       free(image->data);
621       image->data = 0;
622     }
623   XDestroyImage(image);
624
625   return True;
626 }
627 #endif /* HAVE_READ_DISPLAY_EXTENSION */
628
629
630 #ifdef HAVE_READ_DISPLAY_EXTENSION
631
632 /* Makes and installs a colormap that makes a PseudoColor or DirectColor
633    visual behave like a TrueColor visual of the same depth.
634  */
635 static void
636 allocate_cubic_colormap (Screen *screen, Window window, Visual *visual)
637 {
638   Display *dpy = DisplayOfScreen (screen);
639   XWindowAttributes xgwa;
640   Colormap cmap;
641   int nr, ng, nb, cells;
642   int r, g, b;
643   int depth;
644   XColor colors[4097];
645   int i;
646
647   XGetWindowAttributes (dpy, window, &xgwa);
648   cmap = xgwa.colormap;
649   depth = visual_depth(screen, visual);
650
651   switch (depth)
652     {
653     case 8:  nr = 3; ng = 3; nb = 2; cells = 256;  break;
654     case 12: nr = 4; ng = 4; nb = 4; cells = 4096; break;
655     default: abort(); break;
656     }
657
658   memset(colors, 0, sizeof(colors));
659   for (r = 0; r < (1 << nr); r++)
660     for (g = 0; g < (1 << ng); g++)
661       for (b = 0; b < (1 << nb); b++)
662         {
663           i = (r | (g << nr) | (b << (nr + ng)));
664           colors[i].pixel = i;
665           colors[i].flags = DoRed|DoGreen|DoBlue;
666           if (depth == 8)
667             {
668               colors[i].red   = ((r << 13) | (r << 10) | (r << 7) |
669                                  (r <<  4) | (r <<  1));
670               colors[i].green = ((g << 13) | (g << 10) | (g << 7) |
671                                  (g <<  4) | (g <<  1));
672               colors[i].blue  = ((b << 14) | (b << 12) | (b << 10) |
673                                  (b <<  8) | (b <<  6) | (b <<  4) |
674                                  (b <<  2) | b);
675             }
676           else
677             {
678               colors[i].red   = (r << 12) | (r << 8) | (r << 4) | r;
679               colors[i].green = (g << 12) | (g << 8) | (g << 4) | g;
680               colors[i].blue  = (b << 12) | (b << 8) | (b << 4) | b;
681             }
682         }
683
684   {
685     int j;
686     int allocated = 0;
687     int interleave = cells / 8;  /* skip around, rather than allocating in
688                                     order, so that we get better coverage if
689                                     we can't allocated all of them. */
690     for (j = 0; j < interleave; j++)
691       for (i = 0; i < cells; i += interleave)
692         if (XAllocColor (dpy, cmap, &colors[i + j]))
693           allocated++;
694
695     if (grab_verbose_p)
696       fprintf (stderr, "%s: allocated %d of %d colors for cubic map\n",
697                progname, allocated, cells);
698   }
699 }
700
701 static unsigned long
702 find_closest_pixel (XColor *colors, int ncolors,
703                     unsigned long r, unsigned long g, unsigned long b)
704 {
705   unsigned long distance = ~0;
706   int i, found = 0;
707
708   if (ncolors == 0)
709     abort();
710   for (i = 0; i < ncolors; i++)
711     {
712       unsigned long d;
713       int rd, gd, bd;
714
715       rd = r - colors[i].red;
716       gd = g - colors[i].green;
717       bd = b - colors[i].blue;
718       if (rd < 0) rd = -rd;
719       if (gd < 0) gd = -gd;
720       if (bd < 0) bd = -bd;
721       d = (rd << 1) + (gd << 2) + bd;
722       
723       if (d < distance)
724         {
725           distance = d;
726           found = i;
727           if (distance == 0)
728               break;
729         }
730     }
731
732   return found;
733 }
734
735
736 void
737 remap_image (Screen *screen, Window window, Colormap cmap, XImage *image)
738 {
739   Display *dpy = DisplayOfScreen (screen);
740   unsigned long map[4097];
741   int x, y, i;
742   int cells;
743   XColor colors[4097];
744
745   if (image->depth == 8)
746     cells = 256;
747   else if (image->depth == 12)
748     cells = 4096;
749   else
750     abort();
751
752   memset(map,    -1, sizeof(*map));
753   memset(colors, -1, sizeof(*colors));
754
755   for (i = 0; i < cells; i++)
756     colors[i].pixel = i;
757   XQueryColors (dpy, cmap, colors, cells);
758
759   if (grab_verbose_p)
760     fprintf(stderr, "%s: building table for %d bit image\n",
761             progname, image->depth);
762
763   for (i = 0; i < cells; i++)
764     {
765       unsigned short r, g, b;
766
767       if (cells == 256)
768         {
769           /* "RRR GGG BB" In an 8 bit map.  Convert that to
770              "RRR RRR RR" "GGG GGG GG" "BB BB BB BB" to give
771              an even spread. */
772           r = (i & 0x07);
773           g = (i & 0x38) >> 3;
774           b = (i & 0xC0) >> 6;
775
776           r = ((r << 13) | (r << 10) | (r << 7) | (r <<  4) | (r <<  1));
777           g = ((g << 13) | (g << 10) | (g << 7) | (g <<  4) | (g <<  1));
778           b = ((b << 14) | (b << 12) | (b << 10) | (b <<  8) |
779                (b <<  6) | (b <<  4) | (b <<  2) | b);
780         }
781       else
782         {
783           /* "RRRR GGGG BBBB" In a 12 bit map.  Convert that to
784              "RRRR RRRR" "GGGG GGGG" "BBBB BBBB" to give an even
785              spread. */
786           r = (i & 0x00F);
787           g = (i & 0x0F0) >> 4;
788           b = (i & 0xF00) >> 8;
789
790           r = (r << 12) | (r << 8) | (r << 4) | r;
791           g = (g << 12) | (g << 8) | (g << 4) | g;
792           b = (b << 12) | (b << 8) | (b << 4) | b;
793         }
794
795       map[i] = find_closest_pixel (colors, cells, r, g, b);
796     }
797
798   if (grab_verbose_p)
799     fprintf(stderr, "%s: remapping colors in %d bit image\n",
800             progname, image->depth);
801
802   for (y = 0; y < image->height; y++)
803     for (x = 0; x < image->width; x++)
804       {
805         unsigned long pixel = XGetPixel(image, x, y);
806         if (pixel >= cells) abort();
807         XPutPixel(image, x, y, map[pixel]);
808       }
809 }
810
811
812 #endif /* HAVE_READ_DISPLAY_EXTENSION */