How to display an image into an XCB window?
Asked Answered
R

1

15

I'm having trouble displaying an image (PNG extracted with libpng) into an XCB window, it is always entirely empty/white. I'm pretty sure the PNG extraction is correct since I can perfectly re-write it into another file.

I've tried everything I found (explanations, guides, documentation) and I'm running out of ideas:

  • Creating an xcb_pixmap_t calling xcb_create_pixmap_from_bitmap_data() with the data taken from the PNG, then calling xcb_copy_area() into the EXPOSE part of the event loop.
  • Creating an xcb_image_t* calling xcb_image_create_from_bitmap_data() then trying to map it to the window with xcb_image_put(). I've even tried to display each pixel with xcb_image_put_pixel(), but without success.

Code sample:

  xcb_pixmap_t pixmap = xcb_create_pixmap_from_bitmap_data(
                            connection, // xcb_connect(0, 0)    (type: xcb_connection_t*)
                            window, // xcb_generate_id(connection)    (type: xcb_window_t)
                            img.getData(), // uint8_t*
                            img.getWidth(), // 128
                            img.getHeight(), // 128
                            img.getBitDepth(), // 8
                            screen->black_pixel, // screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data    (type: xcb_screen_t*)
                            screen->white_pixel,
                            nullptr);

   // "img" is an instance of my own custom class, result of PNG reading
  xcb_image_t* image = xcb_image_create_from_bitmap_data(
                              img.getData(),
                              img.getWidth(),
                              img.getHeight()); // image->data seems fine

  xcb_image_put(connection,
                window,
                graphicsContext,
                image, 0, 0, 0); // This does nothing

  for (unsigned int i = 0; i < screen->height_in_pixels; ++i)
    for (unsigned int j = 0; j < screen->width_in_pixels; ++j)
      xcb_image_put_pixel(image, j, i, 0); // Displays nothing

  [...]

  // Into event loop
  case XCB_EXPOSE: {
    xcb_expose_event_t* exposeEvent = reinterpret_cast<xcb_expose_event_t*>(event);
    xcb_copy_area(connection,
                  pixmap,
                  window,
                  graphicsContext,
                  exposeEvent->x, exposeEvent->y, // Top left x & y coordinates of the source's region to copy
                  exposeEvent->x, exposeEvent->y, // Top left x & y coordinates of the destination's region to copy to
                  exposeEvent->width,
                  exposeEvent->height);
    xcb_flush(connection);
    break;
  }

From the examples I found I saw that it didn't need a colormap, but could that be the case? Could anyone tell me where I've gone wrong?

Raylenerayless answered 26/3, 2017 at 13:43 Comment(9)
What kind of trouble? Please be as specific as possible.Proton
img.getBitDepth(), // 8 this could be your problem. The depth must be the same as your window depth, or xcb_copy_area will fail. Do you really have 8-bit deep windows by default? In general you should be prepared to handle all depths.Proton
@n.m. Thanks for your answer! Good point, I edited to specify what was wrong, silly me. I actually don't know which depth my window has, I'm passing XCB_COPY_FROM_PARENT to the window while creating it. By replacing this field with 8, the window does not even appear.Raylenerayless
You need to know what your target window depth is, in bits, and you need to convert your image data to that specific bit depth. It helps if you know your source image data representation, otherwise you won't be able to convert. A no-op conversion a.k.a. is just one of many possible cases.Proton
Actually this is a pretty good start. My window had 24 bit depth (that seems huge since my PNG always has 8), I've expanded PNG's depth to 24 but it now displays an ugly square made of artifacts. I guess there's another thing that's going wrong... Is there a way to downgrade window's depth to 8 or 16?Raylenerayless
"my PNG always has 8". This doesn't sound right to me at all. "Is there a way to downgrade window's depth to 8 or 16" You don't want this.Proton
OK sorry about the confusion. I'm an XLib guy, not xcb. I have actually looked in the xcb manual. xcb_create_pixmap_from_bitmap_data doesn't do what you think it does. It is for source data of depth of one bit. Same thing about xcb_image_create_from_bitmap_data. It's for one bit deep bitmaps. You want xcb_image_create only. This is an intimidating function. Look up usage examples on the net before trying to call it.Proton
To be fair I'm kinda giving up. I've tried using xcb_image_create (which I already tried earlier if I recall correctly) and I keep getting a segfault. I've been on it for two days already, just because XCB isn't able to make a proper documentation. I think I'll simply make GLX windows (I'll need OpenGL later anyway).Raylenerayless
You are calling xcb_image_put on window. This will put the image to the screen if the window is mapped or not at all if the window is not mapped. You will also loose anything put to the window when it is partly covered by an other window or unmaped. Instead you probably want to call xcb_image_put on the pixmap you created. The pixmap have a permanent buffer and you can from there copy the data to the window whenever the window is exposed (witch you already do).Hensel
M
4

I threw together a simple xcb image viewer about 4 years ago, but just noticed this question, so apologies for the necromancy.

It uses xcb_image, stb_image and nanosvg, but compiles to a relatively small static binary (with a musl or uclibc toolchain)

#include <xcb/xcb.h>
#include <xcb/xcb_image.h>
#define STBI_NO_HDR
#define STBI_NO_LINEAR
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define NANOSVG_IMPLEMENTATION
#include "nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvgrast.h"

int main(int argc, char **argv){
   xcb_connection_t *c = xcb_connect(0, 0);
   xcb_screen_t *s = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
   int w, h, n,
      depth = s->root_depth,
      win_class = XCB_WINDOW_CLASS_INPUT_OUTPUT,
      format = XCB_IMAGE_FORMAT_Z_PIXMAP;
   xcb_colormap_t colormap = s->default_colormap;
   xcb_drawable_t win = xcb_generate_id(c);
   xcb_gcontext_t gc = xcb_generate_id(c);
   xcb_pixmap_t pixmap = xcb_generate_id(c);
   xcb_generic_event_t *ev;
   xcb_image_t *image;
   NSVGimage *shapes = NULL;
   NSVGrasterizer *rast = NULL;
   char *data = NULL;
   unsigned *dp;
   size_t i, len;
   uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
      value_mask = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS,
      values[] = { s->black_pixel, value_mask };

   if (argc<2) return -1;
   if ((data = stbi_load(argv[1], &w, &h, &n, 4)))
      ;
   else if ((shapes = nsvgParseFromFile(argv[1], "px", 96.0f))) {
      w = (int)shapes->width;
      h = (int)shapes->height;
      rast = nsvgCreateRasterizer();
      data = malloc(w*h*4);
      nsvgRasterize(rast, shapes, 0,0,1, data, w, h, w*4);
   }else return -1;
   for(i=0,len=w*h,dp=(unsigned *)data;i<len;i++) //rgba to bgra
      dp[i]=dp[i]&0xff00ff00|((dp[i]>>16)&0xFF)|((dp[i]<<16)&0xFF0000);
   xcb_create_window(c,depth,win,s->root,0,0,w,h,1,win_class,s->root_visual,mask,values);
   xcb_create_pixmap(c,depth,pixmap,win,w,h);
   xcb_create_gc(c,gc,pixmap,0,NULL);
   image = xcb_image_create_native(c,w,h,format,depth,data,w*h*4,data);
   xcb_image_put(c, pixmap, gc, image, 0, 0, 0);
   xcb_image_destroy(image);
   xcb_map_window(c, win);
   xcb_flush(c);
   while ((ev = xcb_wait_for_event(c))) {
      switch (ev->response_type & ~0x80){
      case XCB_EXPOSE: {
         xcb_expose_event_t *x = (xcb_expose_event_t *)ev;
         xcb_copy_area(c,pixmap,win,gc,x->x,x->y,x->x,x->y,x->width,x->height);
         xcb_flush(c);
      }break;
      case XCB_BUTTON_PRESS: goto end;
      default: break;
      }
      free(ev);
   }
end:
   xcb_free_pixmap(c, pixmap);
   xcb_disconnect(c);
   return 0;
}
Maltz answered 8/9, 2019 at 23:16 Comment(2)
Where is the allocated data freed? does xcb_image_destroy() or xcb_free_pixmap() call free() on the pointer you supplied?Haga
@PabloAriel xcb_image_destroy(image); frees the variable data because data == image->base because image->base is set to data by xcb_image_create_native. xcb_image_destroy's source code is just three lines cgit.freedesktop.org/xcb/util-image/tree/image/… P.S. Memory leak: ev is not freed.Cockrell

© 2022 - 2024 — McMap. All rights reserved.