How do take a screenshot correctly with xlib?

Lalaland picture Lalaland · Nov 23, 2011 · Viewed 14.7k times · Source

I am trying to capture an image of the screen for use in screencasting. Thus I need a fast solution, and cannot rely on shell programs such as import or xwd.

This is the code I have written so far, but it fails and gives me a junk image, which just seems to show fragments of several images with odd colors tossed together.

enter image description here

Any ideas on what I am doing wrong?

#include <X11/Xlib.h>
#include <X11/X.h>

#include <cstdio>
#include <CImg.h>
using namespace cimg_library;

int main()
{
   Display *display = XOpenDisplay(NULL);
   Window root = DefaultRootWindow(display);

   XWindowAttributes gwa;

   XGetWindowAttributes(display, root, &gwa);
   int width = gwa.width;
   int height = gwa.height;


   XImage *image = XGetImage(display,root, 0,0 , width,height,AllPlanes, ZPixmap);

   unsigned char *array = new unsigned char[width * height * 3];

   unsigned long red_mask = image->red_mask;
   unsigned long green_mask = image->green_mask;
   unsigned long blue_mask = image->blue_mask;

   for (int x = 0; x < width; x++)
      for (int y = 0; y < height ; y++)
      {
         unsigned long pixel = XGetPixel(image,x,y);

         unsigned char blue = pixel & blue_mask;
         unsigned char green = (pixel & green_mask) >> 8;
         unsigned char red = (pixel & red_mask) >> 16;

         array[(x + width * y) * 3] = red;
         array[(x + width * y) * 3+1] = green;
         array[(x + width * y) * 3+2] = blue;
      }

   CImg<unsigned char> pic(array,width,height,1,3);
   pic.save_png("blah.png");

   printf("%ld %ld %ld\n",red_mask>> 16, green_mask>>8, blue_mask);

   return 0;
}

Answer

themel picture themel · Nov 23, 2011

You are mistaken about the way array is laid out in memory, as you can find out by declaring img before the loop and adding this printf to your inner loop:

printf("%ld %ld %u %u %u\n",x,y,pic.offset(x,y,0),pic.offset(x,y,1),pic.offset(x,y,2));

This yields (on my 1920x1200 screen):

0 0 0 2304000 4608000
0 1 1920 2305920 4609920
0 2 3840 2307840 4611840

and so on, indicating that the red/green/blue subimages are kept "together" instead of the three color components of a single pixel being adjacent to each other.

The builtin CImg accessors will make your code work:

pic(x,y,0) = red;
pic(x,y,1) = green;
pic(x,y,2) = blue;