http://www.mirrorservice.org/sites/master.us.finkmirrors.net/distfiles/md5/fa43fdd68d...
[xscreensaver] / driver / xscreensaver-getimage.c
index 2fe88afc0fde6bf2a3604f520ae7567677e5457c..599c50cd4d10e5cab70dd98ca47c21ddeb4e1011 100644 (file)
@@ -9,8 +9,10 @@
  * implied warranty.
  */
 
-/* xscreensaver-getimage -- helper program that puts an image
-   (e.g., a snapshot of the desktop) onto the given window.
+/* xscreensaver-getimage -- helper program that puts a random image
+   onto the given window or pixmap.  That image is either a screen-grab,
+   a file loaded from disk, or a frame grabbed from the system's video
+   input.
  */
 
 #include "utils.h"
@@ -18,6 +20,8 @@
 #include <X11/Intrinsic.h>
 #include <ctype.h>
 #include <errno.h>
+#include <sys/stat.h>
+#include <sys/time.h>
 
 #ifdef HAVE_SYS_WAIT_H
 # include <sys/wait.h>         /* for waitpid() and associated macros */
 #include "vroot.h"
 
 #ifdef HAVE_GDK_PIXBUF
-
+# undef HAVE_JPEGLIB
 # ifdef HAVE_GTK2
 #  include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
 # else  /* !HAVE_GTK2 */
 #  include <gdk-pixbuf/gdk-pixbuf-xlib.h>
 # endif /* !HAVE_GTK2 */
-
-# define HAVE_BUILTIN_IMAGE_LOADER
 #endif /* HAVE_GDK_PIXBUF */
 
+#ifdef HAVE_JPEGLIB
+# undef HAVE_GDK_PIXBUF
+# include <jpeglib.h>
+#endif
+
 
 static char *defaults[] = {
 #include "../driver/XScreenSaver_ad.h"
@@ -71,7 +78,6 @@ extern void grabscreen_verbose (void);
 #define GETIMAGE_VIDEO_PROGRAM "xscreensaver-getimage-video"
 #define GETIMAGE_FILE_PROGRAM  "xscreensaver-getimage-file"
 
-
 const char *
 blurb (void)
 {
@@ -79,301 +85,925 @@ blurb (void)
 }
 
 
+static int
+x_ehandler (Display *dpy, XErrorEvent *error)
+{
+  fprintf (stderr, "\nX error in %s:\n", progname);
+  XmuPrintDefaultErrorMessage (dpy, error, stderr);
+  exit (-1);
+  return 0;
+}
+
+
+static Bool error_handler_hit_p = False;
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+  error_handler_hit_p = True;
+  return 0;
+}
+
+
+/* Returns True if the given Drawable is a Window; False if it's a Pixmap.
+ */
+static Bool
+drawable_window_p (Display *dpy, Drawable d)
+{
+  XErrorHandler old_handler;
+  XWindowAttributes xgwa;
+
+  XSync (dpy, False);
+  old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+  error_handler_hit_p = False;
+  XGetWindowAttributes (dpy, d, &xgwa);
+  XSync (dpy, False);
+  XSetErrorHandler (old_handler);
+  XSync (dpy, False);
+
+  if (!error_handler_hit_p)
+    return True;   /* It's a Window. */
+  else
+    return False;  /* It's a Pixmap, or an invalid ID. */
+}
+
+
+/* Clear the window or pixmap to black, or its background color.
+ */
+static void
+clear_drawable (Screen *screen, Drawable drawable)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XGCValues gcv;
+  GC gc;
+  Window root;
+  int x, y;
+  unsigned int w, h, bw, d;
+  XGetGeometry (dpy, drawable, &root, &x, &y, &w, &h, &bw, &d);
+
+  /* The window might have no-op background of None, so to clear it,
+     draw a black rectangle first, then do XClearWindow (in case the
+     actual background color is non-black...) */
+
+  /* #### really we should allocate "black" instead, but I'm lazy... */
+  gcv.foreground = BlackPixelOfScreen (screen);
+  gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
+  XFillRectangle (dpy, drawable, gc, 0, 0, w, h);
+  XFreeGC (dpy, gc);
+  if (drawable_window_p (dpy, drawable))
+    XClearWindow (dpy, (Window) drawable);
+  XFlush (dpy);
+}
+
+
+/* Figure out what kind of scaling/positioning we ought to do to display
+   a src-sized image in a dest-sized window/pixmap.  Returns the width
+   and height to which the image should be scaled, and the position where
+   it should be displayed to center it.
+ */
 static void
-exec_error (char **av)
+compute_image_scaling (int src_w, int src_h,
+                       int dest_w, int dest_h,
+                       Bool verbose_p,
+                       int *scaled_from_x_ret, int *scaled_from_y_ret,
+                       int *scaled_to_x_ret, int *scaled_to_y_ret,
+                       int *scaled_w_ret, int *scaled_h_ret)
 {
-  char buf [512];
-  char *token;
+  int srcx, srcy, destx, desty;
 
-  sprintf (buf, "%s: could not execute \"%s\"", progname, av[0]);
-  perror (buf);
+  Bool exact_fit_p = ((src_w == dest_w && src_h <= dest_h) ||
+                      (src_h == dest_h && src_w <= dest_w));
 
-  if (errno == ENOENT &&
-      (token = getenv("PATH")))
+  if (!exact_fit_p)  /* scale the image up or down */
     {
-# ifndef PATH_MAX
-#  ifdef MAXPATHLEN
-#   define PATH_MAX MAXPATHLEN
-#  else
-#   define PATH_MAX 2048
-#  endif
-# endif
-      char path[PATH_MAX];
-      fprintf (stderr, "\n");
-      *path = 0;
-# if defined(HAVE_GETCWD)
-      getcwd (path, sizeof(path));
-# elif defined(HAVE_GETWD)
-      getwd (path);
-# endif
-      if (*path)
-        fprintf (stderr, "    Current directory is: %s\n", path);
-      fprintf (stderr, "    PATH is:\n");
-      token = strtok (strdup(token), ":");
-      while (token)
+      float rw = (float) dest_w  / src_w;
+      float rh = (float) dest_h / src_h;
+      float r = (rw < rh ? rw : rh);
+      int tw = src_w * r;
+      int th = src_h * r;
+      int pct = (r * 100);
+
+      if (pct < 95 || pct > 105)  /* don't scale if it's close */
         {
-          fprintf (stderr, "        %s\n", token);
-          token = strtok(0, ":");
+          if (verbose_p)
+            fprintf (stderr, "%s: scaling image by %d%% (%dx%d -> %dx%d)\n",
+                     progname, pct, src_w, src_h, tw, th);
+          src_w = tw;
+          src_h = th;
         }
-      fprintf (stderr, "\n");
     }
 
-  exit (-1);
+  /* Center the image on the window/pixmap. */
+  srcx = 0;
+  srcy = 0;
+  destx = (dest_w - src_w) / 2;
+  desty = (dest_h - src_h) / 2;
+  if (destx < 0) srcx = -destx, destx = 0;
+  if (desty < 0) srcy = -desty, desty = 0;
+
+  if (dest_w < src_w) src_w = dest_w;
+  if (dest_h < src_h) src_h = dest_h;
+
+  *scaled_w_ret = src_w;
+  *scaled_h_ret = src_h;
+  *scaled_from_x_ret = srcx;
+  *scaled_from_y_ret = srcy;
+  *scaled_to_x_ret = destx;
+  *scaled_to_y_ret = desty;
+
+  if (verbose_p)
+    fprintf (stderr, "%s: displaying %dx%d image at %d,%d.\n",
+             progname, src_w, src_h, destx, desty);
 }
 
-static int
-x_ehandler (Display *dpy, XErrorEvent *error)
+
+#ifdef HAVE_GDK_PIXBUF
+
+/* Reads the given image file and renders it on the Drawable, using GDK.
+   Returns False if it fails.
+ */
+static Bool
+read_file_gdk (Screen *screen, Window window, Drawable drawable,
+               const char *filename, Bool verbose_p)
 {
-  fprintf (stderr, "\nX error in %s:\n", progname);
-  XmuPrintDefaultErrorMessage (dpy, error, stderr);
-  exit (-1);
-  return 0;
+  GdkPixbuf *pb;
+  Display *dpy = DisplayOfScreen (screen);
+  unsigned int win_width, win_height;
+# ifdef HAVE_GTK2
+  GError *gerr = 0;
+# endif /* HAVE_GTK2 */
+
+  {
+    Window root;
+    int x, y;
+    unsigned int bw, d;
+    XWindowAttributes xgwa;
+    XGetWindowAttributes (dpy, window, &xgwa);
+    screen = xgwa.screen;
+    XGetGeometry (dpy, drawable,
+                  &root, &x, &y, &win_width, &win_height, &bw, &d);
+  }
+
+  gdk_pixbuf_xlib_init (dpy, screen_number (screen));
+# ifdef HAVE_GTK2
+  g_type_init();
+# else  /* !HAVE_GTK2 */
+  xlib_rgb_init (dpy, screen);
+# endif /* !HAVE_GTK2 */
+
+  pb = gdk_pixbuf_new_from_file (filename
+# ifdef HAVE_GTK2
+                                 , &gerr
+# endif /* HAVE_GTK2 */
+                                 );
+
+  if (!pb)
+    {
+      fprintf (stderr, "%s: unable to load \"%s\"\n", progname, filename);
+#  ifdef HAVE_GTK2
+      if (gerr && gerr->message && *gerr->message)
+        fprintf (stderr, "%s: reason: %s\n", progname, gerr->message);
+#  endif /* HAVE_GTK2 */
+      return False;
+    }
+  else
+    {
+      int w = gdk_pixbuf_get_width (pb);
+      int h = gdk_pixbuf_get_height (pb);
+      int srcx, srcy, destx, desty, w2, h2;
+
+      compute_image_scaling (w, h, win_width, win_height, verbose_p,
+                             &srcx, &srcy, &destx, &desty, &w2, &h2);
+      if (w != w2 || h != h2)
+        {
+          GdkPixbuf *pb2 = gdk_pixbuf_scale_simple (pb, w2, h2,
+                                                    GDK_INTERP_BILINEAR);
+          if (pb2)
+            {
+              gdk_pixbuf_unref (pb);
+              pb = pb2;
+              w = w2;
+              h = h2;
+            }
+          else
+            fprintf (stderr, "%s: out of memory when scaling?\n", progname);
+        }
+
+      clear_drawable (screen, drawable);
+
+      /* #### Note that this always uses the default colormap!  Morons!
+         Owen says that in Gnome 2.0, I should try using
+         gdk_pixbuf_render_pixmap_and_mask_for_colormap() instead.
+         But I haven't tried.
+       */
+      gdk_pixbuf_xlib_render_to_drawable_alpha (pb, drawable,
+                                                srcx, srcy, destx, desty,
+                                                w, h,
+                                                GDK_PIXBUF_ALPHA_FULL, 127,
+                                                XLIB_RGB_DITHER_NORMAL,
+                                                0, 0);
+      XSync (dpy, False);
+    }
+
+  return True;
 }
 
+#endif /* HAVE_GDK_PIXBUF */
 
 
-#ifdef HAVE_BUILTIN_IMAGE_LOADER
-static void load_image_internal (Screen *screen, Window window,
-                                 int win_width, int win_height,
-                                 Bool verbose_p,
-                                 int ac, char **av);
-#endif /* HAVE_BUILTIN_IMAGE_LOADER */
 
+#ifdef HAVE_JPEGLIB
 
+/* Allocates a colormap that makes a PseudoColor or DirectColor
+   visual behave like a TrueColor visual of the same depth.
+ */
 static void
-get_image (Screen *screen, Window window,
-           Bool verbose_p,
-           Bool desk_p,
-           Bool video_p,
-           Bool image_p,
-           char *dir)
+allocate_cubic_colormap (Screen *screen, Visual *visual, Colormap cmap,
+                         Bool verbose_p)
 {
   Display *dpy = DisplayOfScreen (screen);
-  enum { do_desk, do_video, do_image, do_bars } which = do_bars;
-  int count = 0;
+  int nr, ng, nb, cells;
+  int r, g, b;
+  int depth;
+  XColor colors[4097];
+  int i;
 
-  XWindowAttributes xgwa;
-  XGetWindowAttributes (dpy, window, &xgwa);
-  screen = xgwa.screen;
+  depth = visual_depth (screen, visual);
 
-  if (verbose_p)
+  switch (depth)
     {
-      fprintf (stderr, "%s: grabDesktopImages:  %s\n",
-               progname, desk_p ? "True" : "False");
-      fprintf (stderr, "%s: grabVideoFrames:    %s\n",
-               progname, video_p ? "True" : "False");
-      fprintf (stderr, "%s: chooseRandomImages: %s\n",
-               progname, image_p ? "True" : "False");
-      fprintf (stderr, "%s: imageDirectory:     %s\n",
-               progname, (dir ? dir : ""));
+    case 8:  nr = 3; ng = 3; nb = 2; cells = 256;  break;
+    case 12: nr = 4; ng = 4; nb = 4; cells = 4096; break;
+    default: abort(); break;
     }
 
-  if (!dir || !*dir)
+  memset(colors, 0, sizeof(colors));
+  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) |
+                                (r <<  4) | (r <<  1));
+             colors[i].green = ((g << 13) | (g << 10) | (g << 7) |
+                                (g <<  4) | (g <<  1));
+             colors[i].blue  = ((b << 14) | (b << 12) | (b << 10) |
+                                (b <<  8) | (b <<  6) | (b <<  4) |
+                                (b <<  2) | b);
+           }
+         else
+           {
+             colors[i].red   = (r << 12) | (r << 8) | (r << 4) | r;
+             colors[i].green = (g << 12) | (g << 8) | (g << 4) | g;
+             colors[i].blue  = (b << 12) | (b << 8) | (b << 4) | b;
+           }
+       }
+
+  {
+    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 (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.)
+ */
+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++)
     {
-      if (verbose_p && image_p)
-        fprintf (stderr,
-                 "%s: no imageDirectory: turning off chooseRandomImages.\n",
-                 progname);
-      image_p = False;
+      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;
+       }
     }
 
-# ifndef _VROOT_H_
-#  error Error!  This file definitely needs vroot.h!
-# endif
+  return found;
+}
 
-  /* If the window is not the root window (real or virtual!) then the hack
-     that called this program is running in "-window" mode instead of in
-     "-root" mode.
 
-     If the window is not the root window, then it's not possible to grab
-     video or images onto it (the contract with those programs is to draw on
-     the root.)  So turn off those options in that case, and turn on desktop
-     grabbing.  (Since we're running in a window on the desktop already, we
-     know it's not a security problem to expose desktop bits.)
-   */
+/* 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.
+ */
+static void
+remap_image (Screen *screen, Colormap cmap, XImage *image, Bool verbose_p)
+{
+  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 (verbose_p)
+    fprintf(stderr, "%s: building color cube for %d bit image\n",
+            progname, image->depth);
 
-  if ((desk_p || video_p || image_p) &&
-      !top_level_window_p (screen, window))
+  for (i = 0; i < cells; i++)
     {
-      Bool changed_p = False;
-      if (desk_p)  desk_p  = False, changed_p = True;
-      if (video_p) video_p = False, changed_p = True;
-# ifndef HAVE_BUILTIN_IMAGE_LOADER
-      /* We can display images on non-top-level windows with the builtin
-         loader, but not if we're using the external (chbg-based) loader. */
-      if (image_p) image_p = False, changed_p = True;
-# endif /* !HAVE_BUILTIN_IMAGE_LOADER */
+      unsigned short r, g, b;
 
-      if (changed_p && verbose_p)
-        fprintf (stderr, "%s: not a top-level window: using colorbars.\n",
-                 progname);
+      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);
     }
-  else if (window != VirtualRootWindowOfScreen (screen))
-    {
-      /* We can display images on non-root windows with the builtin loader,
-         but not if we're using the external (chbg-based) loader.
-         We can never display video on non-root windows (since that always
-         uses the external image loader.)
-      */
-      Bool changed_p = False;
-      if (video_p) video_p = False, changed_p = True;
-# ifndef HAVE_BUILTIN_IMAGE_LOADER
-      if (!desk_p) desk_p  = True,  changed_p = True;
-      if (image_p) image_p = False, changed_p = True;
-# endif /* !HAVE_BUILTIN_IMAGE_LOADER */
-
-      if (changed_p && verbose_p)
-        fprintf (stderr,
-                 "%s: not running on root window: grabbing desktop.\n",
-                 progname);
+
+  if (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]);
+      }
+}
+
+
+/* If the file has a PPM (P6) on it, read it and return an XImage.
+   Otherwise, rewind the fd back to the beginning, and return 0.
+ */
+static XImage *
+maybe_read_ppm (Screen *screen, Visual *visual,
+                const char *filename, FILE *in, Bool verbose_p)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  int depth = visual_depth (screen, visual);
+  struct stat st;
+  char *buf = 0;
+  int bufsiz = 0;
+  char *s, dummy;
+  int i, j;
+  int x, y, w, h, maxval;
+  XImage *ximage = 0;
+
+  if (fstat (fileno (in), &st))
+    goto FAIL;
+
+  bufsiz = st.st_size;
+  buf = (char *) malloc (bufsiz + 1);
+  if (!buf)
+    {
+      fprintf (stderr, "%s: out of memory loading %d byte PPM file %s\n",
+               progname, bufsiz, filename);
+      goto FAIL;
     }
 
-  count = 0;
-  if (desk_p)  count++;
-  if (video_p) count++;
-  if (image_p) count++;
+  if (! (s = fgets (buf, bufsiz, in)))   /* line 1 */
+    goto FAIL;
 
-  if (count == 0)
-    which = do_bars;
-  else
+  if (!strncmp (buf, "\107\111", 2))
     {
-      int i = 0;
-      while (1)  /* loop until we get one that's permitted */
-        {
-          which = (random() % 3);
-          if (which == do_desk  && desk_p)  break;
-          if (which == do_video && video_p) break;
-          if (which == do_image && image_p) break;
-          if (++i > 200) abort();
-        }
+      fprintf (stderr, "%s: %s: sorry, GIF files not supported"
+               " when compiled with JPEGlib instead of GDK_Pixbuf.\n",
+               progname, filename);
+      goto FAIL;
     }
+  else if (!strncmp (buf, "\211\120", 2))
+    {
+      fprintf (stderr, "%s: %s: sorry, PNG files not supported"
+               " when compiled with JPEGlib instead of GDK_Pixbuf.\n",
+               progname, filename);
+      goto FAIL;
+    }
+
+  if (strncmp (s, "P6", 2))
+    goto FAIL;
 
-  if (which == do_desk)
+  if (! (s = fgets (buf, bufsiz, in)))   /* line 2 */
+    goto FAIL;
+  if (2 != sscanf (s, " %d %d %c", &w, &h, &dummy))
     {
-      if (verbose_p)
-        {
-          fprintf (stderr, "%s: grabbing desktop image\n", progname);
-          grabscreen_verbose();
-        }
-      grab_screen_image (screen, window);
-      XSync (dpy, False);
+      fprintf (stderr, "%s: %s: invalid PPM (line 2)\n", progname, filename);
+      goto FAIL;
     }
-  else if (which == do_bars)
+
+  if (! (s = fgets (buf, bufsiz, in)))   /* line 3 */
+    goto FAIL;
+  if (1 != sscanf (s, " %d %c", &maxval, &dummy))
     {
-      if (verbose_p)
-        fprintf (stderr, "%s: drawing colorbars\n", progname);
-      draw_colorbars (dpy, window, 0, 0, xgwa.width, xgwa.height);
-      XSync (dpy, False);
+      fprintf (stderr, "%s: %s: invalid PPM (line 3)\n", progname, filename);
+      goto FAIL;
     }
-  else
+  if (maxval != 255)
     {
-      char *av[10];
-      int ac = 0;
-      memset (av, 0, sizeof(av));
-      switch (which)
-        {
-        case do_video:
-          if (verbose_p)
-            fprintf (stderr, "%s: grabbing video\n", progname);
-          av[ac++] = GETIMAGE_VIDEO_PROGRAM;
-          break;
-        case do_image:
-          if (verbose_p)
-            fprintf (stderr, "%s: loading random image file\n", progname);
-          av[ac++] = GETIMAGE_FILE_PROGRAM;
-
-# ifdef HAVE_BUILTIN_IMAGE_LOADER
-          av[ac++] = "--name";
-# endif /* !HAVE_BUILTIN_IMAGE_LOADER */
-          av[ac++] = dir;
-          break;
-        default:
+      fprintf (stderr, "%s: %s: unparsable PPM: maxval is %d\n",
+               progname, filename, maxval);
+      goto FAIL;
+    }
+
+  ximage = XCreateImage (dpy, visual, depth, ZPixmap, 0, 0,
+                         w, h, 8, 0);
+  if (ximage)
+    ximage->data = (unsigned char *)
+      calloc (ximage->height, ximage->bytes_per_line);
+  if (!ximage || !ximage->data)
+    {
+      fprintf (stderr, "%s: out of memory loading %dx%d PPM file %s\n",
+               progname, ximage->width, ximage->height, filename);
+      goto FAIL;
+    }
+
+  s = buf;
+  j = bufsiz;
+  while ((i = fread (s, 1, j, in)) > 0)
+    s += i, j -= i;
+
+  i = 0;
+  for (y = 0; y < ximage->height; y++)
+    for (x = 0; x < ximage->width; x++)
+      {
+        unsigned char r = buf[i++];
+        unsigned char g = buf[i++];
+        unsigned char b = buf[i++];
+        unsigned long pixel;
+
+        if (depth > 16)
+          pixel = (r << 16) | (g << 8) | b;
+        else if (depth == 8)
+          pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
+        else if (depth == 12)
+          pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
+        else if (depth == 16 || depth == 15)
+          pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
+        else
           abort();
-          break;
-        }
 
-      if (verbose_p)
-        {
-          int i;
-          for (i = ac; i > 1; i--)
-            av[i] = av[i-1];
-          av[1] = strdup ("--verbose");
-          ac++;
-        }
+        XPutPixel (ximage, x, y, pixel);
+      }
 
-      if (verbose_p)
+  free (buf);
+  return ximage;
+
+ FAIL:
+  if (buf) free (buf);
+  if (ximage && ximage->data)
+    {
+      free (ximage->data);
+      ximage->data = 0;
+    }
+  if (ximage) XDestroyImage (ximage);
+  fseek (in, 0, SEEK_SET);
+  return 0;
+}
+
+
+typedef struct {
+  struct jpeg_error_mgr pub;   /* this is what passes for subclassing in C */
+  const char *filename;
+  Screen *screen;
+  Visual *visual;
+  Drawable drawable;
+  Colormap cmap;
+} getimg_jpg_error_mgr;
+
+
+static void
+jpg_output_message (j_common_ptr cinfo)
+{
+  getimg_jpg_error_mgr *err = (getimg_jpg_error_mgr *) cinfo->err;
+  char buf[JMSG_LENGTH_MAX];
+  cinfo->err->format_message (cinfo, buf);
+  fprintf (stderr, "%s: %s: %s\n", progname, err->filename, buf);
+}
+
+
+static void
+jpg_error_exit (j_common_ptr cinfo)
+{
+  getimg_jpg_error_mgr *err = (getimg_jpg_error_mgr *) cinfo->err;
+  cinfo->err->output_message (cinfo);
+  draw_colorbars (err->screen, err->visual, err->drawable, err->cmap,
+                  0, 0, 0, 0);
+  XSync (DisplayOfScreen (err->screen), False);
+  exit (1);
+}
+
+
+/* Reads a JPEG file, returns an RGB XImage of it.
+ */
+static XImage *
+read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable,
+                  Colormap cmap, const char *filename, Bool verbose_p)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  int depth = visual_depth (screen, visual);
+
+  FILE *in = 0;
+  XImage *ximage = 0;
+  struct jpeg_decompress_struct cinfo;
+  getimg_jpg_error_mgr jerr;
+  JSAMPARRAY scanbuf = 0;
+  int y;
+
+  jerr.filename = filename;
+  jerr.screen = screen;
+  jerr.visual = visual;
+  jerr.drawable = drawable;
+  jerr.cmap = cmap;
+
+  if (! (depth >= 15 || depth == 12 || depth == 8))
+    {
+      fprintf (stderr, "%s: unsupported depth: %d\n", progname, depth);
+      goto FAIL;
+    }
+
+  in = fopen (filename, "rb");
+  if (!in)
+    {
+      fprintf (stderr, "%s: %s: unreadable\n", progname, filename);
+      goto FAIL;
+    }
+
+  /* Check to see if it's a PPM, and if so, read that instead of using
+     the JPEG library.  Yeah, this is all modular and stuff.
+   */
+  if ((ximage = maybe_read_ppm (screen, visual, filename, in, verbose_p)))
+    {
+      fclose (in);
+      return ximage;
+    }
+
+  cinfo.err = jpeg_std_error (&jerr.pub);
+  jerr.pub.output_message = jpg_output_message;
+  jerr.pub.error_exit = jpg_error_exit;
+
+  jpeg_create_decompress (&cinfo);
+  jpeg_stdio_src (&cinfo, in);
+  jpeg_read_header (&cinfo, TRUE);
+
+  /* set some decode parameters */
+  cinfo.out_color_space = JCS_RGB;
+  cinfo.quantize_colors = FALSE;
+
+  jpeg_start_decompress (&cinfo);
+
+  ximage = XCreateImage (dpy, visual, depth, ZPixmap, 0, 0,
+                         cinfo.output_width, cinfo.output_height,
+                         8, 0);
+  if (ximage)
+    ximage->data = (unsigned char *)
+      calloc (ximage->height, ximage->bytes_per_line);
+
+  if (ximage && ximage->data)
+    scanbuf = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE,
+                                          cinfo.rec_outbuf_height *
+                                          cinfo.output_width *
+                                          cinfo.output_components,
+                                          1);
+  if (!ximage || !ximage->data || !scanbuf)
+    {
+      fprintf (stderr, "%s: out of memory loading %dx%d file %s\n",
+               progname, ximage->width, ximage->height, filename);
+      goto FAIL;
+    }
+
+  y = 0;
+  while (cinfo.output_scanline < cinfo.output_height)
+    {
+      int n = jpeg_read_scanlines (&cinfo, scanbuf, 1);
+      int i;
+      for (i = 0; i < n; i++)
         {
-          int i = 0;
-          fprintf (stderr, "%s: executing \"", progname);
-          while (av[i])
+          int x;
+          for (x = 0; x < ximage->width; x++)
             {
-              fprintf (stderr, "%s", av[i]);
-              if (av[++i]) fprintf (stderr, " ");
+              int j = x * cinfo.num_components;
+              unsigned char r = scanbuf[i][j];
+              unsigned char g = scanbuf[i][j+1];
+              unsigned char b = scanbuf[i][j+2];
+              unsigned long pixel;
+
+              if (depth > 16)
+                pixel = (r << 16) | (g << 8) | b;
+              else if (depth == 8)
+                pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
+              else if (depth == 12)
+                pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
+              else if (depth == 16 || 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();
+
+              XPutPixel (ximage, x, y, pixel);
             }
-          fprintf (stderr, "\"\n");
+          y++;
         }
+    }
 
-# ifdef HAVE_PUTENV
-      /* Store our "-display" argument into the $DISPLAY variable,
-         so that the subprocess gets the right display if the
-         prevailing $DISPLAY is different. */
-      {
-        const char *odpy = DisplayString (dpy);
-        char *ndpy = (char *) malloc(strlen(odpy) + 20);
-        char *s;
-        int screen_no = screen_number (screen);  /* might not be default now */
-
-        strcpy (ndpy, "DISPLAY=");
-        s = ndpy + strlen(ndpy);
-        strcpy (s, odpy);
-
-        while (*s && *s != ':') s++;           /* skip to colon */
-        while (*s == ':') s++;                 /* skip over colons */
-        while (isdigit(*s)) s++;               /* skip over dpy number */
-        while (*s == '.') s++;                 /* skip over dot */
-        if (s[-1] != '.') *s++ = '.';          /* put on a dot */
-        sprintf(s, "%d", screen_no);           /* put on screen number */
-
-        if (putenv (ndpy))
-          abort ();
-
-        /* don't free (ndpy) -- some implementations of putenv (BSD
-           4.4, glibc 2.0) copy the argument, but some (libc4,5, glibc
-           2.1.2) do not.  So we must leak it (and/or the previous
-           setting).  Yay.
-         */
-      }
-# endif /* HAVE_PUTENV */
+  if (cinfo.output_scanline < cinfo.output_height)
+    /* don't goto FAIL -- we might have viewable partial data. */
+    jpeg_abort_decompress (&cinfo);
+  else
+    jpeg_finish_decompress (&cinfo);
 
-# ifdef HAVE_BUILTIN_IMAGE_LOADER
-      if (which == do_image)
-        {
-          load_image_internal (screen, window, xgwa.width, xgwa.height,
-                               verbose_p, ac, av);
-          return;
-        }
-# endif /* HAVE_BUILTIN_IMAGE_LOADER */
+  jpeg_destroy_decompress (&cinfo);
+  fclose (in);
+  in = 0;
+
+  return ximage;
+
+ FAIL:
+  if (in) fclose (in);
+  if (ximage && ximage->data)
+    {
+      free (ximage->data);
+      ximage->data = 0;
+    }
+  if (ximage) XDestroyImage (ximage);
+  if (scanbuf) free (scanbuf);
+  return 0;
+}
+
+
+/* Scales an XImage, modifying it in place.
+   If out of memory, returns False, and the XImage will have been
+   destroyed and freed.
+ */
+static Bool
+scale_ximage (Screen *screen, Visual *visual,
+              XImage *ximage, int new_width, int new_height)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  int depth = visual_depth (screen, visual);
+  int x, y;
+  double xscale, yscale;
+
+  XImage *ximage2 = XCreateImage (dpy, visual, depth,
+                                  ZPixmap, 0, 0,
+                                  new_width, new_height, 8, 0);
+  ximage2->data = (unsigned char *)
+    calloc (ximage2->height, ximage2->bytes_per_line);
+
+  if (!ximage2->data)
+    {
+      fprintf (stderr, "%s: out of memory scaling %dx%d image to %dx%d\n",
+               progname,
+               ximage->width, ximage->height,
+               ximage2->width, ximage2->height);
+      if (ximage->data) free (ximage->data);
+      if (ximage2->data) free (ximage2->data);
+      ximage->data = 0;
+      ximage2->data = 0;
+      XDestroyImage (ximage);
+      XDestroyImage (ximage2);
+      return False;
+    }
+
+  /* Brute force scaling... */
+  xscale = (double) ximage->width  / ximage2->width;
+  yscale = (double) ximage->height / ximage2->height;
+  for (y = 0; y < ximage2->height; y++)
+    for (x = 0; x < ximage2->width; x++)
+      XPutPixel (ximage2, x, y,
+                 XGetPixel (ximage, x * xscale, y * yscale));
 
+  free (ximage->data);
+  ximage->data = 0;
 
-      close (ConnectionNumber (dpy));  /* close display fd */
+  (*ximage) = (*ximage2);
 
-      execvp (av[0], av);              /* shouldn't return */
-      exec_error (av);
+  ximage2->data = 0;
+  XDestroyImage (ximage2);
+
+  return True;
+}
+
+
+/* Reads the given image file and renders it on the Drawable, using JPEG lib.
+   Returns False if it fails.
+ */
+static Bool
+read_file_jpeglib (Screen *screen, Window window, Drawable drawable,
+                   const char *filename, Bool verbose_p)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XImage *ximage;
+  Visual *visual;
+  int class, depth;
+  Colormap cmap;
+  unsigned int win_width, win_height, win_depth;
+  int srcx, srcy, destx, desty, w2, h2;
+
+  {
+    Window root;
+    int x, y;
+    unsigned int bw;
+    XWindowAttributes xgwa;
+
+    XGetWindowAttributes (dpy, window, &xgwa);
+    screen = xgwa.screen;
+    visual = xgwa.visual;
+    cmap = xgwa.colormap;
+
+    XGetGeometry (dpy, drawable,
+                  &root, &x, &y, &win_width, &win_height, &bw, &win_depth);
+  }
+
+  /* Make sure we're not on some weirdo visual...
+   */
+  class = visual_class (screen, visual);
+  depth = visual_depth (screen, visual);
+  if ((class == PseudoColor || class == DirectColor) &&
+      (depth != 8 && depth != 12))
+    {
+      fprintf (stderr, "%s: Pseudo/DirectColor depth %d unsupported\n",
+               progname, depth);
+      return False;
     }
+
+  /* Read the file...
+   */
+  ximage = read_jpeg_ximage (screen, visual, drawable, cmap,
+                             filename, verbose_p);
+  if (!ximage) return False;
+
+  /* Scale it, if necessary...
+   */
+  compute_image_scaling (ximage->width, ximage->height,
+                         win_width, win_height, verbose_p,
+                         &srcx, &srcy, &destx, &desty, &w2, &h2);
+  if (ximage->width != w2 || ximage->height != h2)
+    if (! scale_ximage (screen, visual, ximage, w2, h2))
+      return False;
+
+  /* Allocate a colormap, if we need to...
+   */
+  if (class == PseudoColor || class == DirectColor)
+    {
+      allocate_cubic_colormap (screen, visual, cmap, verbose_p);
+      remap_image (screen, cmap, ximage, verbose_p);
+    }
+
+  /* Finally, put the resized image on the window.
+   */
+  clear_drawable (screen, drawable);
+  {
+    GC gc;
+    XGCValues gcv;
+    gc = XCreateGC (dpy, drawable, 0, &gcv);
+    XPutImage (dpy, drawable, gc, ximage,
+               srcx, srcy, destx, desty, ximage->width, ximage->height);
+    XFreeGC (dpy, gc);
+  }
+
+  free (ximage->data);
+  ximage->data = 0;
+  XDestroyImage (ximage);
+  XSync (dpy, False);
+  return True;
 }
 
+#endif /* HAVE_JPEGLIB */
 
-#ifdef HAVE_BUILTIN_IMAGE_LOADER
 
-/* Reads a filename from "GETIMAGE_FILE_PROGRAM --name /DIR"
+/* Reads the given image file and renders it on the Drawable.
+   Returns False if it fails.
+ */
+static Bool
+display_file (Screen *screen, Window window, Drawable drawable,
+              const char *filename, Bool verbose_p)
+{
+  if (verbose_p)
+    fprintf (stderr, "%s: loading \"%s\"\n", progname, filename);
+
+# if defined(HAVE_GDK_PIXBUF)
+  if (read_file_gdk (screen, window, drawable, filename, verbose_p))
+    return True;
+# elif defined(HAVE_JPEGLIB)
+  if (read_file_jpeglib (screen, window, drawable, filename, verbose_p))
+    return True;
+# else  /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
+  /* shouldn't get here if we have no image-loading methods available. */
+  abort();
+# endif /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
+
+  return False;
+}
+
+
+/* Invokes a sub-process and returns its output (presumably, a file to
+   load.)  Free the string when done.  video_p controls which program
+   to run.
  */
 static char *
-get_filename (Display *dpy, int ac, char **av)
+get_filename_1 (Screen *screen, const char *directory, Bool video_p,
+                Bool verbose_p)
 {
+  Display *dpy = DisplayOfScreen (screen);
   pid_t forked;
   int fds [2];
   int in, out;
   char buf[1024];
+  char *av[20];
+  int ac = 0;
+
+  if (!video_p)
+    {
+      av[ac++] = GETIMAGE_FILE_PROGRAM;
+      if (verbose_p)
+        av[ac++] = "--verbose";
+      av[ac++] = "--name";
+      av[ac++] = (char *) directory;
+    }
+  else
+    {
+      av[ac++] = GETIMAGE_VIDEO_PROGRAM;
+      if (verbose_p)
+        av[ac++] = "--verbose";
+      av[ac++] = "--name";
+    }
+  av[ac] = 0;
+
+  if (verbose_p)
+    {
+      int i;
+      fprintf (stderr, "%s: executing:", progname);
+      for (i = 0; i < ac; i++)
+        fprintf (stderr, " %s", av[i]);
+      fprintf (stderr, "\n");
+    }
 
   if (pipe (fds))
     {
@@ -412,6 +1042,7 @@ get_filename (Display *dpy, int ac, char **av)
       }
     default:
       {
+        struct stat st;
         int wait_status = 0;
         FILE *f = fdopen (in, "r");
         int L;
@@ -428,7 +1059,16 @@ get_filename (Display *dpy, int ac, char **av)
         while (L && buf[L-1] == '\n')
           buf[--L] = 0;
           
-        return strdup (buf);
+        if (!*buf)
+          return 0;
+        if (stat(buf, &st))
+          {
+            fprintf (stderr, "%s: file does not exist: \"%s\"\n",
+                     progname, buf);
+            return 0;
+          }
+        else
+          return strdup (buf);
       }
     }
 
@@ -436,151 +1076,234 @@ get_filename (Display *dpy, int ac, char **av)
 }
 
 
+/* Returns a pathname to an image file.  Free the string when you're done.
+ */
+static char *
+get_filename (Screen *screen, const char *directory, Bool verbose_p)
+{
+  return get_filename_1 (screen, directory, False, verbose_p);
+}
 
-static void
-load_image_internal (Screen *screen, Window window,
-                     int win_width, int win_height,
-                     Bool verbose_p,
-                     int ac, char **av)
+
+/* Grabs a video frame to a file, and returns a pathname to that file.
+   Delete that file when you are done with it (and free the string.)
+ */
+static char *
+get_video_filename (Screen *screen, Bool verbose_p)
 {
-  GdkPixbuf *pb;
-  Display *dpy = DisplayOfScreen (screen);
-  char *filename = get_filename (dpy, ac, av);
-#ifdef HAVE_GTK2
-  GError *gerr = 0;
-#endif /* HAVE_GTK2 */
+  return get_filename_1 (screen, 0, True, verbose_p);
+}
+
+
+/* Grabs a video frame, and renders it on the Drawable.
+   Returns False if it fails;
+ */
+static Bool
+display_video (Screen *screen, Window window, Drawable drawable,
+               Bool verbose_p)
+{
+  char *filename = get_video_filename (screen, verbose_p);
+  Bool status;
 
   if (!filename)
     {
-      fprintf (stderr, "%s: no file name returned by %s\n",
-               progname, av[0]);
-      goto FAIL;
+      if (verbose_p)
+        fprintf (stderr, "%s: video grab failed.\n", progname);
+      return False;
+    }
+
+  status = display_file (screen, window, drawable, filename, verbose_p);
+
+  if (unlink (filename))
+    {
+      char buf[512];
+      sprintf (buf, "%s: rm %.100s", progname, filename);
+      perror (buf);
     }
   else if (verbose_p)
-    fprintf (stderr, "%s: loading \"%s\"\n", progname, filename);
+    fprintf (stderr, "%s: rm %s\n", progname, filename);
 
-  gdk_pixbuf_xlib_init (dpy, screen_number (screen));
-#ifdef HAVE_GTK2
-  g_type_init();
-#else  /* !HAVE_GTK2 */
-  xlib_rgb_init (dpy, screen);
-#endif /* !HAVE_GTK2 */
+  if (filename) free (filename);
+  return status;
+}
 
-  pb = gdk_pixbuf_new_from_file (filename
-#ifdef HAVE_GTK2
-                                 , &gerr
-#endif /* HAVE_GTK2 */
-         );
 
-  if (pb)
+/* Grabs an image (from a file, video, or the desktop) and renders it on
+   the Drawable.  If `file' is specified, always use that file.  Otherwise,
+   select randomly, based on the other arguments.
+ */
+static void
+get_image (Screen *screen,
+           Window window, Drawable drawable,
+           Bool verbose_p,
+           Bool desk_p,
+           Bool video_p,
+           Bool image_p,
+           const char *dir,
+           const char *file)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  enum { do_desk, do_video, do_image, do_bars } which = do_bars;
+  int count = 0;
+  struct stat st;
+
+  if (! drawable_window_p (dpy, window))
     {
-      int w = gdk_pixbuf_get_width (pb);
-      int h = gdk_pixbuf_get_height (pb);
-      int srcx, srcy, destx, desty;
+      fprintf (stderr, "%s: 0x%lx is a pixmap, not a window!\n",
+               progname, (unsigned long) window);
+      exit (1);
+    }
+
+  if (file && stat (file, &st))
+    {
+      fprintf (stderr, "%s: file \"%s\" does not exist\n", progname, file);
+      file = 0;
+    }
+
+  if (verbose_p)
+    {
+      fprintf (stderr, "%s: grabDesktopImages:  %s\n",
+               progname, desk_p ? "True" : "False");
+      fprintf (stderr, "%s: grabVideoFrames:    %s\n",
+               progname, video_p ? "True" : "False");
+      fprintf (stderr, "%s: chooseRandomImages: %s\n",
+               progname, image_p ? "True" : "False");
+      fprintf (stderr, "%s: imageDirectory:     %s\n",
+               progname, (file ? file : dir ? dir : ""));
+    }
+
+# if !(defined(HAVE_GDK_PIXBUF) || defined(HAVE_JPEGLIB))
+  image_p = False;    /* can't load images from files... */
+  if (file)
+    {
+      fprintf (stderr,
+               "%s: image file loading not available at compile-time\n",
+               progname);
+      fprintf (stderr, "%s: can't load \"%s\"\n", progname, file);
+      file = 0;
+    }
+# endif /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
+
+  if (file)
+    {
+      desk_p = False;
+      video_p = False;
+      image_p = True;
+    }
+
+  if (!dir || !*dir)
+    {
+      if (verbose_p && image_p)
+        fprintf (stderr,
+                 "%s: no imageDirectory: turning off chooseRandomImages.\n",
+                 progname);
+      image_p = False;
+    }
+
 
-      Bool exact_fit_p = ((w == win_width  && h <= win_height) ||
-                          (h == win_height && w <= win_width));
+# ifndef _VROOT_H_
+#  error Error!  This file definitely needs vroot.h!
+# endif
 
-      if (!exact_fit_p)  /* scale the image up or down */
+  /* We can grab desktop images if:
+       - the window is the real root window;
+       - the window is the virtal root window;
+       - the window is a toplevel window.
+     We cannot grab desktop images if:
+       - the window is a non-top-level window.
+   */
+  if (desk_p)
+    {
+      if (!top_level_window_p (screen, window))
         {
-          float rw = (float) win_width  / w;
-          float rh = (float) win_height / h;
-          float r = (rw < rh ? rw : rh);
-          int tw = w * r;
-          int th = h * r;
-          int pct = (r * 100);
-
-          if (pct < 95 || pct > 105)  /* don't scale if it's close */
-            {
-              GdkPixbuf *pb2;
-              if (verbose_p)
-                fprintf (stderr,
-                         "%s: scaling image by %d%% (%dx%d -> %dx%d)\n",
-                         progname, pct, w, h, tw, th);
-
-              pb2 = gdk_pixbuf_scale_simple (pb, tw, th, GDK_INTERP_BILINEAR);
-              if (pb2)
-                {
-                  gdk_pixbuf_unref (pb);
-                  pb = pb2;
-                  w = tw;
-                  h = th;
-                }
-              else
-                fprintf (stderr, "%s: out of memory when scaling?\n",
-                         progname);
-            }
+          desk_p = False;
+          if (verbose_p)
+            fprintf (stderr,
+                    "%s: 0x%x not top-level: turning off grabDesktopImages.\n",
+                     progname, (unsigned int) window);
         }
+    }
 
-      /* Center the image on the window. */
-      srcx = 0;
-      srcy = 0;
-      destx = (win_width  - w) / 2;
-      desty = (win_height - h) / 2;
-      if (destx < 0) srcx = -destx, destx = 0;
-      if (desty < 0) srcy = -desty, desty = 0;
+  count = 0;
+  if (desk_p)  count++;
+  if (video_p) count++;
+  if (image_p) count++;
 
-      if (win_width  < w) w = win_width;
-      if (win_height < h) h = win_height;
+  if (count == 0)
+    which = do_bars;
+  else
+    {
+      int i = 0;
+      while (1)  /* loop until we get one that's permitted */
+        {
+          which = (random() % 3);
+          if (which == do_desk  && desk_p)  break;
+          if (which == do_video && video_p) break;
+          if (which == do_image && image_p) break;
+          if (++i > 200) abort();
+        }
+    }
 
-      /* The window might have no-op background of None, so to clear it,
-         draw a black rectangle first, then do XClearWindow (in case the
-         actual background color is non-black...) */
-      {
-        XGCValues gcv;
-        GC gc;
-        /* #### really we should allocate "black" instead, but I'm lazy... */
-        gcv.foreground = BlackPixelOfScreen (screen);
-        gc = XCreateGC (dpy, window, GCForeground, &gcv);
-        XFillRectangle (dpy, window, gc, 0, 0, win_width, win_height);
-        XFreeGC (dpy, gc);
-        XClearWindow (dpy, window);
-        XFlush (dpy);
-      }
 
-      /* #### Note that this always uses the default colormap!  Morons!
-              Owen says that in Gnome 2.0, I should try using
-              gdk_pixbuf_render_pixmap_and_mask_for_colormap() instead.
-              But I don't have Gnome 2.0 yet.
-       */
-      gdk_pixbuf_xlib_render_to_drawable_alpha (pb, window,
-                                                srcx, srcy, destx, desty, w, h,
-                                                GDK_PIXBUF_ALPHA_FULL, 127,
-                                                XLIB_RGB_DITHER_NORMAL, 0, 0);
-      XSync (dpy, False);
+  /* If we're to search a directory to find an image file, do so now.
+   */
+  if (which == do_image && !file)
+    {
+      file = get_filename (screen, dir, verbose_p);
+      if (!file)
+        {
+          which = do_bars;
+          if (verbose_p)
+            fprintf (stderr, "%s: no image files found.\n", progname);
+        }
+    }
 
+  /* Now actually render something.
+   */
+  if (which == do_bars)
+    {
+      XWindowAttributes xgwa;
+    COLORBARS:
       if (verbose_p)
-        fprintf (stderr, "%s: displayed %dx%d image at %d,%d.\n",
-                 progname, w, h, destx, desty);
+        fprintf (stderr, "%s: drawing colorbars.\n", progname);
+      XGetWindowAttributes (dpy, window, &xgwa);
+      draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
+                      0, 0, 0, 0);
+      XSync (dpy, False);
     }
-  else if (filename)
+  else if (which == do_desk)
     {
-      fprintf (stderr, "%s: unable to load %s\n", progname, filename);
-#ifdef HAVE_GTK2
-      if (gerr && gerr->message && *gerr->message)
-        fprintf (stderr, "%s: reason %s\n", progname, gerr->message);
-#endif /* HAVE_GTK2 */
+      GC gc;
+      XGCValues gcv;
+      XWindowAttributes xgwa;
 
-      goto FAIL;
+      if (verbose_p)
+        {
+          fprintf (stderr, "%s: grabbing desktop image\n", progname);
+          grabscreen_verbose();
+        }
+      gc = XCreateGC (dpy, drawable, 0, &gcv);
+      XGetWindowAttributes (dpy, window, &xgwa);
+      grab_screen_image (screen, window);
+      XCopyArea (dpy, window, drawable, gc,
+                 0, 0, xgwa.width, xgwa.height, 0, 0);
+      XFreeGC (dpy, gc);
+      XSync (dpy, False);
     }
-  else
+  else if (which == do_image)
     {
-      fprintf (stderr, "%s: unable to initialize built-in images\n", progname);
-      goto FAIL;
+      if (! display_file (screen, window, drawable, file, verbose_p))
+        goto COLORBARS;
     }
-
-  return;
-
- FAIL:
-  if (verbose_p)
-    fprintf (stderr, "%s: drawing colorbars\n", progname);
-  draw_colorbars (dpy, window, 0, 0, win_width, win_height);
-  XSync (dpy, False);
+  else if (which == do_video)
+    {
+      if (! display_video (screen, window, drawable, verbose_p))
+        goto COLORBARS;
+    }
+  else
+    abort();
 }
 
-#endif /* HAVE_BUILTIN_IMAGE_LOADER */
-
-
 
 #ifdef DEBUG
 static Bool
@@ -606,9 +1329,9 @@ mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
 #endif /* DEBUG */
 
 
-#define USAGE "usage: %s [ -options... ] window-id\n"                        \
+#define USAGE "usage: %s [ -options... ] window-id [pixmap-id]\n"            \
    "\n"                                                                              \
-   "    This program puts an image on the given window.\n"                   \
+   "    This program puts an image on the given window or pixmap.\n"         \
    "\n"                                                                              \
    "    It is used by those xscreensaver demos that operate on images.\n"     \
    "    The image may be a file loaded from disk, a frame grabbed from\n"     \
@@ -624,6 +1347,7 @@ mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
    "      -video   / -no-video        whether to allow video grabs\n"        \
    "      -desktop / -no-desktop      whether to allow desktop screen grabs\n"\
    "      -directory <path>           where to find image files to load\n"    \
+   "      -file <filename>            load this image file\n"                 \
    "\n"                                                                              \
    "    The XScreenSaver Control Panel (xscreensaver-demo) lets you set the\n"\
    "    defaults for these options in your ~/.xscreensaver file.\n"           \
@@ -637,8 +1361,12 @@ main (int argc, char **argv)
   Display *dpy;
   Screen *screen;
   char *oprogname = progname;
+  char *file = 0;
 
   Window window = (Window) 0;
+  Drawable drawable = (Drawable) 0;
+  const char *window_str = 0;
+  const char *drawable_str = 0;
   char *s;
   int i;
 
@@ -650,6 +1378,9 @@ main (int argc, char **argv)
   /* half-assed way of avoiding buffer-overrun attacks. */
   if (strlen (progname) >= 100) progname[100] = 0;
 
+# ifndef _VROOT_H_
+#  error Error!  This file definitely needs vroot.h!
+# endif
 
   /* We must read exactly the same resources as xscreensaver.
      That means we must have both the same progclass *and* progname,
@@ -684,6 +1415,9 @@ main (int argc, char **argv)
 
   for (i = 1; i < argc; i++)
     {
+      unsigned long w;
+      char dummy;
+
       /* Have to re-process these, or else the .xscreensaver file
          has priority over the command line...
        */
@@ -695,37 +1429,50 @@ main (int argc, char **argv)
       else if (!strcmp (argv[i], "-no-video"))   P.grab_video_p = False;
       else if (!strcmp (argv[i], "-images"))     P.random_image_p = True;
       else if (!strcmp (argv[i], "-no-images"))  P.random_image_p = False;
+      else if (!strcmp (argv[i], "-file"))       file = argv[++i];
       else if (!strcmp (argv[i], "-directory") || !strcmp (argv[i], "-dir"))
         P.image_directory = argv[++i];
-      else if (window == 0)
+      else if (!strcmp (argv[i], "-root") || !strcmp (argv[i], "root"))
         {
-          unsigned long w;
-          char dummy;
-
-          if (!strcmp (argv[i], "-root") ||
-              !strcmp (argv[i], "root"))
-            window = RootWindowOfScreen (screen);
-
-          else if ((1 == sscanf (argv[i], " 0x%lx %c", &w, &dummy) ||
-                    1 == sscanf (argv[i], " %ld %c",   &w, &dummy)) &&
-                   w != 0)
-            window = (Window) w;
-          else
+          if (window)
             {
-              if (argv[i][0] == '-')
-                fprintf (stderr, "\n%s: unknown option \"%s\"\n",
-                         progname, argv[i]);
-              else
-                fprintf (stderr, "\n%s: unparsable window ID: \"%s\"\n",
-                         progname, argv[i]);
+              fprintf (stderr, "%s: both %s and %s specified?\n",
+                       progname, argv[i], window_str);
               goto LOSE;
             }
+          window_str = argv[i];
+          window = (Window) RootWindowOfScreen (screen);
+        }
+      else if ((1 == sscanf (argv[i], " 0x%lx %c", &w, &dummy) ||
+                1 == sscanf (argv[i], " %ld %c",   &w, &dummy)) &&
+               w != 0)
+        {
+          if (drawable)
+            {
+              fprintf (stderr, "%s: both %s and %s specified?\n",
+                       progname, drawable_str, argv[i]);
+              goto LOSE;
+            }
+          else if (window)
+            {
+              drawable_str = argv[i];
+              drawable = (Drawable) w;
+            }
+          else
+            {
+              window_str = argv[i];
+              window = (Window) w;
+            }
         }
       else
         {
-          fprintf (stderr, "\n%s: unknown option \"%s\"\n",
-                   progname, argv[i]);
-         LOSE:
+          if (argv[i][0] == '-')
+            fprintf (stderr, "\n%s: unknown option \"%s\"\n",
+                     progname, argv[i]);
+          else
+            fprintf (stderr, "\n%s: unparsable window/pixmap ID: \"%s\"\n",
+                     progname, argv[i]);
+        LOSE:
           fprintf (stderr, USAGE, progname);
           exit (1);
         }
@@ -733,7 +1480,7 @@ main (int argc, char **argv)
 
   if (window == 0)
     {
-      fprintf (stderr, "\n%s: no window specified!\n", progname);
+      fprintf (stderr, "\n%s: no window ID specified!\n", progname);
       goto LOSE;
     }
 
@@ -749,8 +1496,11 @@ main (int argc, char **argv)
     }
 #endif /* DEBUG */
 
-  get_image (screen, window, P.verbose_p,
+  if (!window) abort();
+  if (!drawable) drawable = window;
+
+  get_image (screen, window, drawable, P.verbose_p,
              P.grab_desktop_p, P.grab_video_p, P.random_image_p,
-             P.image_directory);
+             P.image_directory, file);
   exit (0);
 }