【问题标题】:Why is an XImage's data pointer null when capturing using XShmGetImage?为什么使用 XShmGetImage 捕获时 XImage 的数据指针为空?
【发布时间】:2018-07-19 20:32:27
【问题描述】:

我正在编写一个程序来快速从一个窗口捕获图像,修改它们,然后使用带有 C++ 的 Xlib 将它们输出到另一个窗口。我有这个通过 XGetImage 工作:

#include <iostream>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

const int TEST_SIZE = 512;

int main()
{
  // Open default display
  Display *display = XOpenDisplay(nullptr);
  int screen = DefaultScreen(display);
  Window rootWin = RootWindow(display, screen);
  GC graphicsContext = DefaultGC(display, screen);

  // Create new window and subscribe to events
  long blackPixel = BlackPixel(display, screen);
  long whitePixel = WhitePixel(display, screen);
  Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
  XMapWindow(display, newWin);
  XSelectInput(display, newWin, ExposureMask | KeyPressMask);

  // Main event loop for new window
  XImage *image;
  XEvent event;
  bool exposed = false;
  bool killWindow = false;
  while (!killWindow)
  {
    // Handle pending events
    if (XPending(display) > 0)
    {
      XNextEvent(display, &event);
      if (event.type == Expose)
      {
        exposed = true;
      } else if (event.type == NoExpose)
      {
        exposed = false;
      } else if (event.type == KeyPress)
      {
        killWindow = true;
      }
    }

    // Capture the original image
    image = XGetImage(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, AllPlanes, ZPixmap);

    // Modify the image
    if (image->data != nullptr)
    {
      long pixel = 0;
      for (int x = 0; x < image->width; x++)
      {
        for (int y = 0; y < image->height; y++)
        {
          // Invert the color of each pixel
          pixel = XGetPixel(image, x, y);
          XPutPixel(image, x, y, ~pixel);
        }
      }
    }

    // Output the modified image
    if (exposed && killWindow == false)
    {
      XPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, TEST_SIZE, TEST_SIZE);
    }
    XDestroyImage(image);
  }

  // Goodbye
  XCloseDisplay(display);
}

它生成如下输出:https://streamable.com/hovg9

我很高兴能走到这一步,但据我所知,性能并不能很好地扩展,因为它必须在每一帧为新图像分配空间。事实上,如果没有在循环结束时调用 XDestroyImage,这个程序会在几秒钟内填满我机器上的所有 16GB 内存!

似乎这里推荐的方法是设置一个共享内存空间,X 可以在其中写入每个帧的内容,我的程序随后可以读取和修改它们,而无需任何额外的分配。由于对 XShmGetImage 的调用会阻塞并等待 IPC,我相信这意味着我不必担心共享空间中的任何并发问题。

我尝试使用以下代码实现 XShmGetImage 方法:

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
#include <X11/Xutil.h>

const int TEST_SIZE = 512;

int main()
{
  // Open default display
  Display *display = XOpenDisplay(nullptr);
  int screen = DefaultScreen(display);
  Window rootWin = RootWindow(display, screen);
  GC graphicsContext = DefaultGC(display, screen);

  // Create new window and subscribe to events
  long blackPixel = BlackPixel(display, screen);
  long whitePixel = WhitePixel(display, screen);
  Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
  XMapWindow(display, newWin);
  XSelectInput(display, newWin, ExposureMask | KeyPressMask);

  // Allocate shared memory for image capturing
  Visual *visual = DefaultVisual(display, 0);
  XShmSegmentInfo shminfo;
  int depth = DefaultDepth(display, screen);
  XImage *image = XShmCreateImage(display, visual, depth, ZPixmap, nullptr, &shminfo, TEST_SIZE, TEST_SIZE);
  shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0666);
  shmat(shminfo.shmid, nullptr, 0);
  shminfo.shmaddr = image->data;
  shminfo.readOnly = False;
  XShmAttach(display, &shminfo);

  // Main event loop for new window
  XEvent event;
  bool exposed = false;
  bool killWindow = false;
  while (!killWindow)
  {
    // Handle pending events
    if (XPending(display) > 0)
    {
      XNextEvent(display, &event);
      if (event.type == Expose)
      {
        exposed = true;
      } else if (event.type == NoExpose)
      {
        exposed = false;
      } else if (event.type == KeyPress)
      {
        killWindow = true;
      }
    }

    // Capture the original image
    XShmGetImage(display, rootWin, image, 0, 0, AllPlanes);

    // Modify the image
    if (image->data != nullptr) // NEVER TRUE.  DATA IS ALWAYS NULL!
    {
      long pixel = 0;
      for (int x = 0; x < image->width; x++)
      {
        for (int y = 0; y < image->height; y++)
        {
          // Invert the color of each pixel
          pixel = XGetPixel(image, x, y);
          XPutPixel(image, x, y, ~pixel);
        }
      }
    }

    // Output the modified image
    if (exposed && killWindow == false)
    {
      XShmPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, 512, 512, false);
    }
  }

  // Goodbye
  XFree(image);
  XCloseDisplay(display);
}

不知何故,图像仍在被捕获并发送到新窗口,但 XImage 中的数据指针始终为空。我无法修改任何内容,并且 XGetPixel 和 XPutPixel 宏的任何使用都将失败。请注意,在此视频中,没有像以前那样反转颜色:https://streamable.com/dckyv

这对我来说没有任何意义。显然数据仍在窗口之间传输,但它在 XImage 结构中的什么位置?如何通过代码访问它?

【问题讨论】:

    标签: c++ x11 shared-memory xlib


    【解决方案1】:

    原来我没有正确读取 shmat 的签名。它没有 void 返回类型,它返回一个 void 指针。这需要直接分配给 XImage 的数据指针,以便数据指针执行任何操作。

    在对 XShmPutImage 的调用中,共享内存位置是在内部引用的,而不是 XImage 的数据指针,这就是为什么即使不使用 shmat 的返回值,它仍然可以工作的原因。

    这是工作代码,以及我为错误处理和拆卸而添加的一些其他内容:

    #include <iostream>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <X11/Xlib.h>
    #include <X11/extensions/XShm.h>
    #include <X11/Xutil.h>
    
    const int TEST_SIZE = 512;
    
    static int handleXError(Display *display, XErrorEvent *event)
    {
      printf("XErrorEvent triggered!\n");
      printf("error_code: %d", event->error_code);
      printf("minor_code: %d", event->minor_code);
      printf("request_code: %d", event->request_code);
      printf("resourceid: %lu", event->resourceid);
      printf("serial: %d", event->error_code);
      printf("type: %d", event->type);
      return 0;
    }
    
    int main()
    {
      // Open default display
      XSetErrorHandler(handleXError);
      Display *display = XOpenDisplay(nullptr);
      int screen = DefaultScreen(display);
      Window rootWin = RootWindow(display, screen);
      GC graphicsContext = DefaultGC(display, screen);
    
      // Create new window and subscribe to events
      long blackPixel = BlackPixel(display, screen);
      long whitePixel = WhitePixel(display, screen);
      Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
      XMapWindow(display, newWin);
      XSelectInput(display, newWin, ExposureMask | KeyPressMask);
    
      // Allocate shared memory for image capturing
      XShmSegmentInfo shminfo;
      Visual *visual = DefaultVisual(display, screen);
      int depth = DefaultDepth(display, screen);
      XImage *image = XShmCreateImage(display, visual, depth, ZPixmap, nullptr, &shminfo, TEST_SIZE, TEST_SIZE);
      shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0777);
      image->data = (char*)shmat(shminfo.shmid, 0, 0);
      shminfo.shmaddr = image->data;
      shminfo.readOnly = False;
      XShmAttach(display, &shminfo);
      XSync(display, false);
      shmctl(shminfo.shmid, IPC_RMID, 0);
    
      // Main event loop for new window
      XEvent event;
      bool exposed = false;
      bool killWindow = false;
      while (!killWindow)
      {
        // Handle pending events
        if (XPending(display) > 0)
        {
          XNextEvent(display, &event);
          if (event.type == Expose)
          {
            exposed = true;
          } else if (event.type == NoExpose)
          {
            exposed = false;
          } else if (event.type == KeyPress)
          {
            killWindow = true;
          }
        }
    
        // Capture the original image
        XShmGetImage(display, rootWin, image, 0, 0, AllPlanes);
    
        // Modify the image
        if(image->data != nullptr) // NEVER TRUE.  DATA IS ALWAYS NULL!
        {
          long pixel = 0;
          for (int x = 0; x < image->width; x++)
          {
            for (int y = 0; y < image->height; y++)
            {
              // Invert the color of each pixel
              pixel = XGetPixel(image, x, y);
              XPutPixel(image, x, y, ~pixel);
            }
          }
        }
    
        // Output the modified image
        if (exposed && killWindow == false)
        {
          XShmPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, 512, 512, false);
        }
      }
    
      // Goodbye
      XShmDetach(display, &shminfo);
      XDestroyImage(image);
      shmdt(shminfo.shmaddr);
      XCloseDisplay(display);
    }
    

    【讨论】:

      猜你喜欢
      • 2013-10-03
      • 1970-01-01
      • 2021-01-16
      • 2013-12-31
      • 1970-01-01
      • 2012-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多