How do take a screenshot correctly with xlib?
Asked Answered
C

3

31

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;
}
Cricoid answered 23/11, 2011 at 22:1 Comment(6)
Hi @lalaland can you please share your final code, does this support multi monitor?Weichsel
@Weichsel Yes, I supported multiple monitors. I think github.com/Lalaland/ScreenCap/blob/master/src/… might be the right file. Haven't touched the code in years though. The code is poorly written, but if you want to use it, I'll throw a BSD license on there.Cricoid
Thanks very much @lalaland for reply so fast! If you have time is it ok if i posted on your issues page so i can understand how it works with multi monitorsWeichsel
@Weichsel Truthfully, I have no idea how the code works anymore. I don't even know if the code even runs at all on modern Linux systems.Cricoid
Ah haha thanks for that note, I think it doesnt because I cant find some of the functions used like XFixesGetCursorImageWeichsel
but what about alpha channelSevere
E
19

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;
Enate answered 23/11, 2011 at 22:34 Comment(0)
L
5

You can use libpng

int code = 0;
FILE *fp;
png_structp png_ptr;
png_infop png_info_ptr;
png_bytep png_row;

// Open file
fp = fopen ("test.png", "wb");
if (fp == NULL){
    fprintf (stderr, "Could not open file for writing\n");
    code = 1;
}

// Initialize write structure
png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr == NULL){
    fprintf (stderr, "Could not allocate write struct\n");
    code = 1;
}

// Initialize info structure
png_info_ptr = png_create_info_struct (png_ptr);
if (png_info_ptr == NULL){
    fprintf (stderr, "Could not allocate info struct\n");
    code = 1;
 }

// Setup Exception handling
if (setjmp (png_jmpbuf (png_ptr))){
    fprintf(stderr, "Error during png creation\n");
   code = 1;
}

png_init_io (png_ptr, fp);

// Write header (8 bit colour depth)
png_set_IHDR (png_ptr, png_info_ptr, width, height,
     8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
     PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

// Set title
char *title = "Screenshot";
if (title != NULL){
    png_text title_text;
    title_text.compression = PNG_TEXT_COMPRESSION_NONE;
    title_text.key = "Title";
    title_text.text = title;
    png_set_text (png_ptr, png_info_ptr, &title_text, 1);
}

png_write_info (png_ptr, png_info_ptr);

// Allocate memory for one row (3 bytes per pixel - RGB)
png_row = (png_bytep) malloc (3 * width * sizeof (png_byte));

// Write image data
int x, y;
for (y = 0; y < height; y++){
    for (x = 0; x < width; x++){
        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;
        png_byte *ptr = &(png_row[x*3]);
        ptr[0] = red;
        ptr[1] = green;
        ptr[2] = blue;
    }
    png_write_row (png_ptr, png_row);
}

// End write
png_write_end (png_ptr, NULL);

// Free
fclose (fp);
if (png_info_ptr != NULL) png_free_data (png_ptr, png_info_ptr, PNG_FREE_ALL, -1);
if (png_ptr != NULL) png_destroy_write_struct (&png_ptr, (png_infopp)NULL);
if (png_row != NULL) free (png_row);
Loge answered 1/2, 2014 at 16:4 Comment(0)
B
2

image has to stored in memory as R1R2R3R4R5R6......G1G2G3G4G5G6.......B1B2B3B4B5B6. cimg storage

Boggers answered 9/1, 2018 at 18:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.