From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / utils / grabscreen.c
index 6bf8cc8b848bdd8e6edd5ce57025a8642a6b44bb..23f40aad9b98122db39454075c5eafffd79c4106 100644 (file)
@@ -1,5 +1,4 @@
-/* xscreensaver, Copyright (c) 1992, 1993, 1994, 1997, 1998
- *  Jamie Zawinski <jwz@netscape.com>
+/* xscreensaver, Copyright (c) 1992-2013 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
    the difference between drawing on the actual root window, and on the fake
    root window used by the screensaver, since at this level the illusion 
    breaks down...
+
+   The hacks themselves use utils/grabclient.c to invoke the
+   "xscreensaver-getimage" program as a sub-process.
+
+   This code is linked only into "driver/xscreensaver-getimage".  On normal
+   X11 systems, "xscreensaver-getimage.c" invokes the code in this file.
+
+   However, under X11 on MacOS, "xscreensaver-getimage" instead runs the
+   script "driver/xscreensaver-getimage-desktop", which invokes the MacOS-
+   specific program "/usr/sbin/screencapture" to get the desktop image.
+
+   However again, for the MacOS-native (Cocoa) build of the screen savers,
+   "utils/grabclient.c" instead links against "OSX/osxgrabscreen.m", which
+   grabs screen images directly without invoking a sub-process to do it.
  */
 
 #include "utils.h"
@@ -34,7 +47,6 @@
 #include "usleep.h"
 #include "colors.h"
 #include "grabscreen.h"
-#include "sgivideo.h"
 #include "visual.h"
 #include "resources.h"
 
@@ -52,8 +64,9 @@
 
 static void copy_default_colormap_contents (Screen *, Colormap, Visual *);
 
-#if defined(HAVE_READ_DISPLAY_EXTENSION) || defined(HAVE_SGI_VIDEO)
-static void make_cubic_colormap (Screen *, Window, Visual *);
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+static void allocate_cubic_colormap (Screen *, Window, Visual *);
+void remap_image (Screen *, Window, Colormap, XImage *);
 #endif
 
 
@@ -64,19 +77,23 @@ MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
          event->xvisibility.window == (Window) window);
 }
 
-#ifdef DEBUG
 extern char *progname;
-#endif /* DEBUG */
+Bool grab_verbose_p = False;
+
+void
+grabscreen_verbose(void)
+{
+  grab_verbose_p = True;
+}
 
 
 static void
 raise_window(Display *dpy, Window window, Bool dont_wait)
 {
-#ifdef DEBUG
-  fprintf(stderr, "%s: raising window 0x%08lX (%s)\n",
-         progname, (unsigned long) window,
-         (dont_wait ? "not waiting" : "waiting"));
-#endif /* DEBUG */
+  if (grab_verbose_p)
+    fprintf(stderr, "%s: raising window 0x%08lX (%s)\n",
+            progname, (unsigned long) window,
+            (dont_wait ? "not waiting" : "waiting"));
 
   if (! dont_wait)
     {
@@ -92,6 +109,8 @@ raise_window(Display *dpy, Window window, Bool dont_wait)
       hints.height = xgwa.height;
       hints.flags |= (PPosition|USPosition|PSize|USSize);
       XSetWMNormalHints(dpy, window, &hints);
+
+      XSelectInput (dpy, window, (xgwa.your_event_mask | StructureNotifyMask));
     }
 
   XMapRaised(dpy, window);
@@ -111,12 +130,12 @@ xscreensaver_window_p (Display *dpy, Window window)
   Atom type;
   int format;
   unsigned long nitems, bytesafter;
-  char *version;
+  unsigned char *version;
   if (XGetWindowProperty (dpy, window,
                          XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
                          0, 1, False, XA_STRING,
                          &type, &format, &nitems, &bytesafter,
-                         (unsigned char **) &version)
+                         &version)
       == Success
       && type != None)
     return True;
@@ -125,14 +144,66 @@ xscreensaver_window_p (Display *dpy, Window window)
 
 
 
+/* Whether the given window is:
+   - the real root window;
+   - a direct child of the root window;
+   - a direct child of the window manager's decorations.
+ */
+Bool
+top_level_window_p (Screen *screen, Window window)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  Window root, parent, *kids;
+  unsigned int nkids;
+
+  if (!XQueryTree (dpy, window, &root, &parent, &kids, &nkids))
+    return False;
+
+  if (window == root)
+    return True;
+
+  /* If our direct parent is the real root window, then yes. */
+  if (parent == root)
+    return True;
+  else
+    {
+      Atom type = None;
+      int format;
+      unsigned long nitems, bytesafter;
+      unsigned char *data;
+
+      /* If our direct parent has the WM_STATE property, then it is a
+         window manager decoration -- yes.
+      */
+      if (XGetWindowProperty (dpy, window,
+                              XInternAtom (dpy, "WM_STATE", True),
+                              0, 0, False, AnyPropertyType,
+                              &type, &format, &nitems, &bytesafter,
+                              (unsigned char **) &data)
+          == Success
+          && type != None)
+        return True;
+    }
+
+  /* Else, no.  We're deep in a tree somewhere.
+   */
+  return False;
+}
+
+
+static Bool error_handler_hit_p = False;
 static XErrorHandler old_ehandler = 0;
 static int
 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
 {
+  error_handler_hit_p = True;
   if (error->error_code == BadWindow || error->error_code == BadDrawable)
     return 0;
   else if (!old_ehandler)
-    abort();
+    {
+      abort();
+      return 0;
+    }
   else
     return (*old_ehandler) (dpy, error);
 }
@@ -165,17 +236,18 @@ use_subwindow_mode_p(Screen *screen, Window window)
 static void
 install_screen_colormaps (Screen *screen)
 {
-  int i;
+  unsigned int i;
   Display *dpy = DisplayOfScreen (screen);
-  Window vroot, real_root;
+  Window real_root;
   Window parent, *kids = 0;
   unsigned int nkids = 0;
 
   XSync (dpy, False);
   old_ehandler = XSetErrorHandler (BadWindow_ehandler);
+  error_handler_hit_p = False;
 
-  vroot = VirtualRootWindowOfScreen (screen);
-  if (XQueryTree (dpy, vroot, &real_root, &parent, &kids, &nkids))
+  real_root = XRootWindowOfScreen (screen);  /* not vroot */
+  if (XQueryTree (dpy, real_root, &real_root, &parent, &kids, &nkids))
     for (i = 0; i < nkids; i++)
       {
        XWindowAttributes xgwa;
@@ -200,17 +272,24 @@ install_screen_colormaps (Screen *screen)
 }
 
 
-static void
-grab_screen_image_1 (Screen *screen, Window window)
+void
+grab_screen_image_internal (Screen *screen, Window window)
 {
   Display *dpy = DisplayOfScreen (screen);
   XWindowAttributes xgwa;
-  Window real_root = XRootWindowOfScreen (screen);  /* not vroot */
-  Bool root_p = (window == real_root);
-  Bool saver_p = xscreensaver_window_p (dpy, window);
+  Window real_root;
+  Bool root_p;
+  Bool saver_p;
   Bool grab_mouse_p = False;
   int unmap_time = 0;
 
+  real_root = XRootWindowOfScreen (screen);  /* not vroot */
+  root_p = (window == real_root);
+  saver_p = xscreensaver_window_p (dpy, window);
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  screen = xgwa.screen;
+
   if (saver_p)
     /* I think this is redundant, but just to be safe... */
     root_p = False;
@@ -225,30 +304,38 @@ grab_screen_image_1 (Screen *screen, Window window)
       double unmap = 0;
       if (saver_p)
        {
-         unmap = get_float_resource("grabRootDelay", "Seconds");
+         unmap = get_float_resource(dpy, "grabRootDelay", "Seconds");
          if (unmap <= 0.00001 || unmap > 20) unmap = 2.5;
        }
       else
        {
-         unmap = get_float_resource("grabWindowDelay", "Seconds");
+         unmap = get_float_resource(dpy, "grabWindowDelay", "Seconds");
          if (unmap <= 0.00001 || unmap > 20) unmap = 0.66;
        }
       unmap_time = unmap * 100000;
     }
 
-#ifdef DEBUG
-  fprintf(stderr,
-         "\n%s: window 0x%08lX root: %d saver: %d grab: %d wait: %.1f\n",
-         progname, (unsigned long) window,
-         root_p, saver_p, grab_mouse_p, ((double)unmap_time)/1000000.0);
-  {
-    XWindowAttributes xgwa2;
-    XGetWindowAttributes (dpy, window, &xgwa2);
-    fprintf(stderr, "%s: ", progname);
-    describe_visual(stderr, screen, xgwa2.visual, ####);
-    fprintf (stderr, "\n");
-  }
-#endif /* DEBUG */
+  if (grab_verbose_p)
+    {
+      fprintf(stderr,
+              "\n%s: window 0x%08lX root: %d saver: %d grab: %d wait: %.1f\n",
+              progname, (unsigned long) window,
+              root_p, saver_p, grab_mouse_p, ((double)unmap_time)/1000000.0);
+
+      fprintf(stderr, "%s: ", progname);
+      describe_visual(stderr, screen, xgwa.visual, False);
+      fprintf (stderr, "\n");
+    }
+
+
+  if (!root_p && !top_level_window_p (screen, window))
+    {
+      if (grab_verbose_p)
+        fprintf (stderr, "%s: not a top-level window: 0x%08lX: not grabbing\n",
+                 progname, (unsigned long) window);
+      return;
+    }
+
 
   if (!root_p)
     XSetWindowBackgroundPixmap (dpy, window, None);
@@ -272,39 +359,45 @@ grab_screen_image_1 (Screen *screen, Window window)
       usleep(unmap_time); /* wait for everyone to swap in and handle exposes */
     }
 
-  XGetWindowAttributes (dpy, window, &xgwa);
-
   if (!root_p)
     {
 #ifdef HAVE_READ_DISPLAY_EXTENSION
       if (! read_display(screen, window, 0, saver_p))
 #endif /* HAVE_READ_DISPLAY_EXTENSION */
        {
-#if defined(HAVE_READ_DISPLAY_EXTENSION) && defined(DEBUG)
-         fprintf(stderr, "%s: read_display() failed\n", progname);
-#endif /* DEBUG */
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: read_display() failed\n", progname);
+#endif /* HAVE_READ_DISPLAY_EXTENSION */
+
          copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
          raise_window(dpy, window, saver_p);
+
+          /* Generally it's bad news to call XInstallColormap() explicitly,
+             but this file does a lot of sleazy stuff already...  This is to
+             make sure that the window's colormap is installed, even in the
+             case where the window is OverrideRedirect. */
+          if (xgwa.colormap) XInstallColormap (dpy, xgwa.colormap);
+          XSync (dpy, False);
        }
     }
   else  /* root_p */
     {
       Pixmap pixmap;
-      XWindowAttributes xgwa;
-      XGetWindowAttributes(dpy, window, &xgwa);
       pixmap = XCreatePixmap(dpy, window, xgwa.width, xgwa.height, xgwa.depth);
 
 #ifdef HAVE_READ_DISPLAY_EXTENSION
       if (! read_display(screen, window, pixmap, True))
 #endif
        {
-         Window real_root = XRootWindowOfScreen (xgwa.screen); /* not vroot */
+         Window real_root = XRootWindowOfScreen (screen); /* not vroot */
          XGCValues gcv;
          GC gc;
 
-#if defined(HAVE_READ_DISPLAY_EXTENSION) && defined(DEBUG)
-         fprintf(stderr, "%s: read_display() failed\n", progname);
-#endif /* DEBUG */
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+          if  (grab_verbose_p)
+            fprintf(stderr, "%s: read_display() failed\n", progname);
+#endif /* HAVE_READ_DISPLAY_EXTENSION */
 
          copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
 
@@ -319,6 +412,11 @@ grab_screen_image_1 (Screen *screen, Window window)
       XFreePixmap (dpy, pixmap);
     }
 
+  if (grab_verbose_p)
+    fprintf (stderr, "%s: grabbed %d bit screen image to %swindow.\n",
+             progname, xgwa.depth,
+             (root_p ? "real root " : ""));
+
   if (grab_mouse_p)
     {
       XUngrabPointer (dpy, CurrentTime);
@@ -328,42 +426,6 @@ grab_screen_image_1 (Screen *screen, Window window)
   XSync (dpy, True);
 }
 
-void
-grab_screen_image (Screen *screen, Window window)
-{
-#ifdef HAVE_SGI_VIDEO
-  char c, *s = get_string_resource("grabVideoProbability", "Float");
-  double prob = -1;
-  if (!s ||
-      (1 != sscanf (s, " %lf %c", &prob, &c)) ||
-      prob < 0 ||
-      prob > 1)
-    prob = 0.5;
-
-  if ((random() % 100) < ((int) (100 * prob)))
-    {
-      XWindowAttributes xgwa;
-      Display *dpy = DisplayOfScreen (screen);
-      XGetWindowAttributes (dpy, window, &xgwa);
-# ifdef DEBUG
-      fprintf(stderr, "%s: trying to grab from video...\n", progname);
-# endif /* DEBUG */
-      if (grab_video_frame (screen, xgwa.visual, window))
-       {
-         if (xgwa.depth < 24)
-           {
-             int class = visual_class (screen, xgwa.visual);
-             if (class == PseudoColor || class == DirectColor)
-               make_cubic_colormap (screen, window, xgwa.visual);
-           }
-         return;
-       }
-    }
-#endif /* HAVE_SGI_VIDEO */
-
-  grab_screen_image_1 (screen, window);
-}
-
 
 /* When we are grabbing and manipulating a screen image, it's important that
    we use the same colormap it originally had.  So, if the screensaver was
@@ -415,13 +477,11 @@ copy_default_colormap_contents (Screen *screen,
   XQueryColors (dpy, from_cmap, old_colors, max_cells);
 
   got_cells = max_cells;
-  allocate_writable_colors (dpy, to_cmap, pixels, &got_cells);
+  allocate_writable_colors (screen, to_cmap, pixels, &got_cells);
 
-#ifdef DEBUG
-  if (got_cells != max_cells)
+  if (grab_verbose_p && got_cells != max_cells)
     fprintf(stderr, "%s: got only %d of %d cells\n", progname,
            got_cells, max_cells);
-#endif /* DEBUG */
 
   if (got_cells <= 0)                                   /* we're screwed */
     ;
@@ -446,9 +506,8 @@ copy_default_colormap_contents (Screen *screen,
     }
 
 
-#ifdef DEBUG
-  fprintf(stderr, "%s: installing copy of default colormap\n", progname);
-#endif /* DEBUG */
+  if (grab_verbose_p)
+    fprintf(stderr, "%s: installing copy of default colormap\n", progname);
 
   free (old_colors);
   free (new_colors);
@@ -480,33 +539,46 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
   XGCValues gcv;
   int class;
   GC gc;
-  Bool install_p = False;
+  Bool remap_p = False;
 
   /* Check to see if the server supports the extension, and bug out if not.
    */
   if (! XReadDisplayQueryExtension (dpy, &rd_event_base, &rd_error_base))
-    return False;
+    {
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: no XReadDisplay extension\n", progname);
+      return False;
+    }
 
   /* If this isn't a visual we know how to handle, bug out.  We handle:
-      = TrueColor in depths 8, 12, 16, and 32;
+      = TrueColor in depths 8, 12, 15, 16, and 32;
       = PseudoColor and DirectColor in depths 8 and 12.
    */
   XGetWindowAttributes(dpy, window, &xgwa);
   class = visual_class (screen, xgwa.visual);
   if (class == TrueColor)
     {
-      if (xgwa.depth != 8  && xgwa.depth != 12 && xgwa.depth != 16 &&
-         xgwa.depth != 24 && xgwa.depth != 32)
-       return False;
+      if (xgwa.depth != 8  && xgwa.depth != 12 && xgwa.depth != 15 &&
+          xgwa.depth != 16 && xgwa.depth != 24 && xgwa.depth != 32)
+        {
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: TrueColor depth %d unsupported\n",
+                    progname, xgwa.depth);
+          return False;
+        }
     }
   else if (class == PseudoColor || class == DirectColor)
     {
       if (xgwa.depth != 8 && xgwa.depth != 12)
-       return False;
+        {
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: Pseudo/DirectColor depth %d unsupported\n",
+                    progname, xgwa.depth);
+          return False;
+        }
       else
-       /* Install a colormap that makes this visual behave like
-          a TrueColor visual of the same depth. */
-       install_p = True;
+       /* Allocate a TrueColor-like spread of colors for the image. */
+       remap_p = True;
     }
 
 
@@ -516,9 +588,15 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
   image = XReadDisplay (dpy, window, xgwa.x, xgwa.y, xgwa.width, xgwa.height,
                        hints, &hints);
   if (!image)
-    return False;
+    {
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: XReadDisplay() failed\n", progname);
+      return False;
+    }
   if (!image->data)
     {
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: XReadDisplay() returned no data\n", progname);
       XDestroyImage(image);
       return False;
     }
@@ -559,12 +637,15 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
                                     xgwa.width, xgwa.height,
                                     8, 0);
       if (!image2)
-       return False;
+        {
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: out of memory?\n", progname);
+          return False;
+        }
 
-#ifdef DEBUG
-      fprintf(stderr, "%s: converting from depth %d to depth %d\n",
-             progname, image->depth, xgwa.depth);
-#endif /* DEBUG */
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: converting from depth %d to depth %d\n",
+                progname, image->depth, xgwa.depth);
 
       for (y = 0; y < image->height; y++)
        for (x = 0; x < image->width; x++)
@@ -582,8 +663,9 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
              pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
            else if (xgwa.depth == 12)
              pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
-           else if (xgwa.depth == 16)
-             pixel = ((r >> 3) | ((g >> 3) << 5) | ((b >> 3) << 10));
+           else if (xgwa.depth == 16 || xgwa.depth == 15)
+              /* Gah! I don't understand why these are in the other order. */
+             pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
            else
              abort();
 
@@ -594,6 +676,11 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
       image = image2;
     }
 
+  if (remap_p)
+    {
+      allocate_cubic_colormap (screen, window, xgwa.visual);
+      remap_image (screen, window, xgwa.colormap, image);
+    }
 
   /* Now actually put the bits into the window or pixmap -- note the design
      bogosity of this extension, where we've been forced to take 24 bit data
@@ -634,31 +721,34 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
     }
   XDestroyImage(image);
 
-  if (install_p)
-    make_cubic_colormap (screen, window, xgwa.visual);
-
   return True;
 }
 #endif /* HAVE_READ_DISPLAY_EXTENSION */
 
 
-#if defined(HAVE_READ_DISPLAY_EXTENSION) || defined(HAVE_SGI_VIDEO)
+#ifdef HAVE_READ_DISPLAY_EXTENSION
 
 /* Makes and installs a colormap that makes a PseudoColor or DirectColor
    visual behave like a TrueColor visual of the same depth.
+
+   #### Duplicated in driver/xscreensaver-getimage.c
  */
 static void
-make_cubic_colormap (Screen *screen, Window window, Visual *visual)
+allocate_cubic_colormap (Screen *screen, Window window, Visual *visual)
 {
   Display *dpy = DisplayOfScreen (screen);
-  Colormap cmap = XCreateColormap(dpy, window, visual, AllocAll);
+  XWindowAttributes xgwa;
+  Colormap cmap;
   int nr, ng, nb, cells;
   int r, g, b;
   int depth;
   XColor colors[4097];
   int i;
 
-  depth = visual_depth(screen, visual);
+  XGetWindowAttributes (dpy, window, &xgwa);
+  cmap = xgwa.colormap;
+  depth = visual_depth (screen, visual);
+
   switch (depth)
     {
     case 8:  nr = 3; ng = 3; nb = 2; cells = 256;  break;
@@ -667,18 +757,13 @@ make_cubic_colormap (Screen *screen, Window window, Visual *visual)
     }
 
   memset(colors, 0, sizeof(colors));
-  for (i = 0; i < cells; i++)
-    {
-      colors[i].flags = DoRed|DoGreen|DoBlue;
-      colors[i].red = colors[i].green = colors[i].blue = 0;
-    }
-
   for (r = 0; r < (1 << nr); r++)
     for (g = 0; g < (1 << ng); g++)
       for (b = 0; b < (1 << nb); b++)
        {
          i = (r | (g << nr) | (b << (nr + ng)));
          colors[i].pixel = i;
+          colors[i].flags = DoRed|DoGreen|DoBlue;
          if (depth == 8)
            {
              colors[i].red   = ((r << 13) | (r << 10) | (r << 7) |
@@ -697,27 +782,144 @@ make_cubic_colormap (Screen *screen, Window window, Visual *visual)
            }
        }
 
-#ifdef DEBUG
-  fprintf(stderr, "%s: installing cubic colormap\n", progname);
-#endif /* DEBUG */
-
-  XStoreColors (dpy, cmap, colors, cells);
-  XSetWindowColormap (dpy, window, cmap);
-
-  /* Gag, install the colormap.
-     This is definitely right in the `if xscreensaver_window_p' case, since
-     it will never get installed otherwise.  But, if we don't do it
-     unconditionally, then the new colormap won't get installed until the
-     window (re-)gains focus.  It's generally very antisocial to install
-     the colormap of a non-OverrideRedirect window (that task belongs to
-     the WM) and if we were being kosher, we would only install this cmap
-     if the old cmap was already installed (or perhaps, if the window had
-     focus.)  But, since this extension only exists on SGIs, and since SGIs
-     can handle four colormaps at once, let's go ahead and install it all
-     the time, so that even if the window pops up and has never had focus,
-     it will still display in the proper colors.
-   */
-  XInstallColormap (dpy, cmap);
+  {
+    int j;
+    int allocated = 0;
+    int interleave = cells / 8;  /* skip around, rather than allocating in
+                                    order, so that we get better coverage if
+                                    we can't allocated all of them. */
+    for (j = 0; j < interleave; j++)
+      for (i = 0; i < cells; i += interleave)
+        if (XAllocColor (dpy, cmap, &colors[i + j]))
+          allocated++;
+
+    if (grab_verbose_p)
+      fprintf (stderr, "%s: allocated %d of %d colors for cubic map\n",
+               progname, allocated, cells);
+  }
+}
+
+/* Find the pixel index that is closest to the given color
+   (using linear distance in RGB space -- which is far from the best way.)
+
+   #### Duplicated in driver/xscreensaver-getimage.c
+ */
+static unsigned long
+find_closest_pixel (XColor *colors, int ncolors,
+                    unsigned long r, unsigned long g, unsigned long b)
+{
+  unsigned long distance = ~0;
+  int i, found = 0;
+
+  if (ncolors == 0)
+    abort();
+  for (i = 0; i < ncolors; i++)
+    {
+      unsigned long d;
+      int rd, gd, bd;
+
+      rd = r - colors[i].red;
+      gd = g - colors[i].green;
+      bd = b - colors[i].blue;
+      if (rd < 0) rd = -rd;
+      if (gd < 0) gd = -gd;
+      if (bd < 0) bd = -bd;
+      d = (rd << 1) + (gd << 2) + bd;
+      
+      if (d < distance)
+       {
+         distance = d;
+         found = i;
+          if (distance == 0)
+              break;
+       }
+    }
+
+  return found;
 }
 
-#endif /* HAVE_READ_DISPLAY_EXTENSION || HAVE_SGI_VIDEO */
+
+/* Given an XImage with 8-bit or 12-bit RGB data, convert it to be 
+   displayable with the given X colormap.  The farther from a perfect
+   color cube the contents of the colormap are, the lossier the 
+   transformation will be.  No dithering is done.
+
+   #### Duplicated in driver/xscreensaver-getimage.c
+ */
+void
+remap_image (Screen *screen, Window window, Colormap cmap, XImage *image)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  unsigned long map[4097];
+  int x, y, i;
+  int cells;
+  XColor colors[4097];
+
+  if (image->depth == 8)
+    cells = 256;
+  else if (image->depth == 12)
+    cells = 4096;
+  else
+    abort();
+
+  memset(map,    -1, sizeof(*map));
+  memset(colors, -1, sizeof(*colors));
+
+  for (i = 0; i < cells; i++)
+    colors[i].pixel = i;
+  XQueryColors (dpy, cmap, colors, cells);
+
+  if (grab_verbose_p)
+    fprintf(stderr, "%s: building table for %d bit image\n",
+            progname, image->depth);
+
+  for (i = 0; i < cells; i++)
+    {
+      unsigned short r, g, b;
+
+      if (cells == 256)
+        {
+          /* "RRR GGG BB" In an 8 bit map.  Convert that to
+             "RRR RRR RR" "GGG GGG GG" "BB BB BB BB" to give
+             an even spread. */
+          r = (i & 0x07);
+          g = (i & 0x38) >> 3;
+          b = (i & 0xC0) >> 6;
+
+          r = ((r << 13) | (r << 10) | (r << 7) | (r <<  4) | (r <<  1));
+          g = ((g << 13) | (g << 10) | (g << 7) | (g <<  4) | (g <<  1));
+          b = ((b << 14) | (b << 12) | (b << 10) | (b <<  8) |
+               (b <<  6) | (b <<  4) | (b <<  2) | b);
+        }
+      else
+        {
+          /* "RRRR GGGG BBBB" In a 12 bit map.  Convert that to
+             "RRRR RRRR" "GGGG GGGG" "BBBB BBBB" to give an even
+             spread. */
+          r = (i & 0x00F);
+          g = (i & 0x0F0) >> 4;
+          b = (i & 0xF00) >> 8;
+
+          r = (r << 12) | (r << 8) | (r << 4) | r;
+          g = (g << 12) | (g << 8) | (g << 4) | g;
+          b = (b << 12) | (b << 8) | (b << 4) | b;
+        }
+
+      map[i] = find_closest_pixel (colors, cells, r, g, b);
+    }
+
+  if (grab_verbose_p)
+    fprintf(stderr, "%s: remapping colors in %d bit image\n",
+            progname, image->depth);
+
+  for (y = 0; y < image->height; y++)
+    for (x = 0; x < image->width; x++)
+      {
+        unsigned long pixel = XGetPixel(image, x, y);
+        if (pixel >= cells) abort();
+        XPutPixel(image, x, y, map[pixel]);
+      }
+}
+
+
+#endif /* HAVE_READ_DISPLAY_EXTENSION */