Screenshot bug

One often needs to make screenshots - e.g. to illustrate how to do something, or to show what goes wrong. Several utilities work fine, but ImageMagick's import is (and has always been) broken (with X11 and Linux).


The xwd utility allows one to dump the contents of a window or the entire screen. A window dump will have black rectangles if the window is partially obscured. Most useful seems to dump the entire screen. Since xwd produces output in some private format, it is easiest to convert it immediately:
 % xwd -root | convert - screenshot.png
Some post-processing is needed if one wants an image of part of the visible screen.


An easy-to-use utility is scrot. I tried
 % scrot -s
and selected a rectangle. This gave correct output:


Another option is to use gimp (maybe File->Acquire->Screenshot, or File->Create->Screenshot, depending on the gimp version). That works, but is inconvenient: one has to start gimp, and save the image afterwards. I selected roughly the same rectangle and got


The ImageMagick suite has a special purpose utility import. Unfortunately, it is buggy in several ways. (The below is for ImageMagick 6.6.9-7 and ImageMagick 6.7.9-6.)

Import bug1

Grabbing approximately the same rectangle using
% import screenshot.png

with a black rectangle. The problem seems to be that import tries to get this part of the image from an obscured window, instead of from the window that lies on top in the stacking order. If one has many windows, as I always have, then import almost never gives a correct screenshot. One can fix the problem by moving away (or closing) all other windows, so that there is nothing below the part that is screenshotted.

Others have reported the same bug. E.g. Big black square, Taken screenshot is all black, Black box on import, Import returning mostly black images, Black rectangles appearing in screenshots. Slackware documents this as a known ImageMagick problem:

Import bug2

There is a different problem with the output of import if it has PNG format. If one views it with gimp one gets the error message "The PNG file specifies an offset that caused the layer to be positioned outside the image."

This problem is also well-known and has existed for years (I saw a 2004 bug report). See also, e.g., Screen capture problem, Offset issue, PNG offset problem. It helps to add the +repage option to the invocation of import. (Sometimes this option has to be given twice.) If you have to deal with such a broken image produced by import, zoom out in gimp until the part with bad offset appears, and then move it back into place. There are various utilities to repair broken import output. An ImageMagick solution is

% convert +repage screenshot_bad.png screenshot_ok.png

X: source

The two commands
% import ss0.png
% convert X: ss0a.png
do approximately the same, and are equally broken:


(with similar hand-selected screenshot areas - roughly the same visible screen as above, but a different stacking order of the windows).


The recursive descent of the window tree that the routine xwindow.c:XGetWindowImage() does, is broken. A 1-line patch that fixes most cases of the "black rectangles" bug is given below. It changes the default "always descend" into "only descend when necessary", so that usually the descent is avoided.

diff -ur ImageMagick-6.8.0-6/wand/import.c ImageMagick-6.8.0-6a/wand/import.c
--- ImageMagick-6.8.0-6/wand/import.c	2011-12-19 02:54:26.000000000 +0100
+++ ImageMagick-6.8.0-6a/wand/import.c	2013-01-10 00:21:19.000000000 +0100
@@ -381,7 +381,7 @@
     "density",(char *) NULL);
-    "descend","True");
+    "descend","False");

A larger patch fixes the main problems with the recursive descent, both in import and in the X: source for convert. It also applies to ImageMagick-6.8.1-9.

diff -ur ImageMagick-6.8.0-6/magick/xwindow.c ImageMagick-6.8.0-6a/magick/xwindow.c
--- ImageMagick-6.8.0-6/magick/xwindow.c	2012-10-14 15:40:22.000000000 +0200
+++ ImageMagick-6.8.0-6a/magick/xwindow.c	2013-01-10 22:17:09.000000000 +0100
@@ -4114,10 +4114,10 @@
             (window_info[i].visual == window_info[id].visual) &&
             (window_info[i].colormap == window_info[id].colormap))
-            if ((window_info[id].bounds.x1 <= window_info[i].bounds.x1) ||
-                (window_info[id].bounds.x1 >= window_info[i].bounds.x2) ||
-                (window_info[id].bounds.y1 <= window_info[i].bounds.y1) ||
-                (window_info[id].bounds.y1 >= window_info[i].bounds.y2))
+            if ((window_info[id].bounds.x1 < window_info[i].bounds.x1) ||
+                (window_info[id].bounds.x2 > window_info[i].bounds.x2) ||
+                (window_info[id].bounds.y1 < window_info[i].bounds.y1) ||
+                (window_info[id].bounds.y2 > window_info[i].bounds.y2))
                   Eliminate windows not circumscribed by their parent.
@@ -4195,22 +4195,12 @@
           if ((window_info[id].visual == window_info[j].visual) &&
               (window_info[id].colormap == window_info[j].colormap))
-              if ((window_info[id].bounds.x1 <= window_info[j].bounds.x1) ||
-                  (window_info[id].bounds.x1 >= window_info[j].bounds.x2) ||
-                  (window_info[id].bounds.y1 <= window_info[j].bounds.y1) ||
-                  (window_info[id].bounds.y1 >= window_info[j].bounds.y2))
+              if ((window_info[id].bounds.x1 >= window_info[j].bounds.x1) &&
+                  (window_info[id].bounds.x2 <= window_info[j].bounds.x2) &&
+                  (window_info[id].bounds.y1 >= window_info[j].bounds.y1) &&
+                  (window_info[id].bounds.y2 <= window_info[j].bounds.y2))
-          else
-            if ((window_info[id].visual != window_info[j].visual) ||
-                (window_info[id].colormap != window_info[j].colormap))
-              {
-                if ((window_info[id].bounds.x2 > window_info[j].bounds.x1) &&
-                    (window_info[id].bounds.x1 < window_info[j].bounds.x2) &&
-                    (window_info[id].bounds.y2 > window_info[j].bounds.y1) &&
-                    (window_info[id].bounds.y1 < window_info[j].bounds.y2))
-                  import=MagickTrue;
-              }
         if (import == MagickFalse)

The offset information that made GIMP unhappy is removed by the following.

diff -ur ImageMagick-6.8.1-9/magick/xwindow.c ImageMagick-6.8.1-9a/magick/xwindow.c
--- ImageMagick-6.8.1-9/magick/xwindow.c        2012-12-07 14:14:16.000000000 +0100
+++ ImageMagick-6.8.1-9a/magick/xwindow.c       2013-01-11 15:39:45.000000000 +0100
@@ -5071,6 +5061,12 @@
               (size_t) window_name.nitems+1);
           (void) XFree((void *) window_name.value);
+      if (image != (Image *) NULL)
+        {
+          /* forget that this was part of a larger image */
+          image->page.width = image->page.height = 0;
+          image->page.x = image->page.y = 0;
+        }
   if (ximage_info->silent == MagickFalse)
I don't know whether there are also reasons to keep it.