use xrender to capture screenshots

This commit is contained in:
Chris Lee 2019-02-24 01:49:01 +01:00
parent 4dafea185f
commit 36c8d41914

View file

@ -21,6 +21,7 @@
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrender.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -383,28 +384,42 @@ void smooth_thumbnail(cairo_surface_t *image_surface)
}
}
// This is measured to be slightly faster.
#define GetPixel(ximg, x, y) ((u_int32_t *)&(ximg->data[y * ximg->bytes_per_line]))[x]
//#define GetPixel XGetPixel
// This is measured to be slightly faster than XGetPixel.
static u_int32_t GetPixel(XImage *ximg, unsigned x, unsigned y)
{
if (x >= ximg->width || y >= ximg->height) {
fprintf(stderr, RED "GetPixel read overflow: %u %u %d %d\n",
x, y, ximg->width, ximg->height);
return 0;
}
return ((u_int32_t *)&(ximg->data[y * ximg->bytes_per_line]))[x];
}
// TODO: the xrender method only works well if there is a compositor running
cairo_surface_t *get_window_thumbnail_ximage(Window win, size_t size, gboolean use_shm)
{
fprintf(stderr, GREEN "tint2: win=%u: getting thumbnail of size %u" RESET "\n", win, size);
cairo_surface_t *result = NULL;
XWindowAttributes wa;
if (!XGetWindowAttributes(server.display, win, &wa) || wa.width <= 0 || wa.height <= 0 ||
wa.map_state != IsViewable)
wa.map_state != IsViewable) {
fprintf(stderr, YELLOW "tint2: win=%u: no win attributes" RESET "\n", win);
goto err0;
}
if (window_is_iconified(win))
if (window_is_iconified(win)) {
fprintf(stderr, YELLOW "tint2: win=%u: no thumbnail for minimized window" RESET "\n", win);
goto err0;
}
size_t w, h;
w = (size_t)wa.width;
h = (size_t)wa.height;
w = (size_t)wa.width + wa.x;
h = (size_t)wa.height + wa.y;
size_t tw, th, fw;
size_t ox, oy;
tw = size;
th = h * tw / w;
double scale = th / (double)h;
if (th > tw * 0.618) {
th = (size_t)(tw * 0.618);
fw = w * th / h;
@ -414,22 +429,69 @@ cairo_surface_t *get_window_thumbnail_ximage(Window win, size_t size, gboolean u
fw = tw;
ox = oy = 0;
}
if (!w || !h || !tw || !th || !fw)
if (!w || !h || !tw || !th || !fw) {
fprintf(stderr, YELLOW "tint2: win=%u: no thumbnail for size 0x0" RESET "\n", win);
goto err0;
}
XShmSegmentInfo shminfo;
XImage *ximg;
if (use_shm)
ximg = XShmCreateImage(server.display,
wa.visual,
(unsigned)wa.depth,
ZPixmap,
NULL,
&shminfo,
(unsigned)w,
(unsigned)h);
else
ximg = XGetImage(server.display, win, 0, 0, (unsigned)w, (unsigned)h, AllPlanes, ZPixmap);
XImage *ximg = NULL;
XGetWindowAttributes(server.display, win, &wa);
if (wa.map_state != IsViewable) {
fprintf(stderr, YELLOW "tint2: win=%u: no thumbnail for non-viewable window" RESET "\n", win);
goto err4;
}
Pixmap pix = XCreatePixmap(server.display, win, tw, th, wa.depth);
if (!pix) {
fprintf(stderr, YELLOW "tint2: win=%u: no pixmap" RESET "\n", win);
goto err4;
}
XRenderPictureAttributes attrs = {
.repeat = RepeatNone
};
XRenderPictFormat *fmt = XRenderFindVisualFormat(server.display, wa.visual);
if (!fmt) {
fprintf(stderr, YELLOW "tint2: win=%u: no XRender format" RESET "\n", win);
goto err4;
}
Picture src_pic = XRenderCreatePicture(server.display, win, fmt, CPRepeat, &attrs);
if (!src_pic) {
fprintf(stderr, YELLOW "tint2: win=%u: no src picture" RESET "\n", win);
goto err4;
}
Picture dst_pic = XRenderCreatePicture(server.display, pix, fmt, CPRepeat, &attrs);
if (!dst_pic) {
fprintf(stderr, YELLOW "tint2: win=%u: no src picture" RESET "\n", win);
goto err4;
}
fprintf(stderr, GREEN "tint2: win=%u: transformation %u %u %f xy %d %d" RESET "\n", win, ox, oy, scale,
wa.x, wa.y);
XTransform transform = {
{
{
XDoubleToFixed(1./scale),
XDoubleToFixed(0),
XDoubleToFixed(ox-wa.x/scale)
},
{
XDoubleToFixed(0),
XDoubleToFixed(1./scale),
XDoubleToFixed(oy-wa.y/scale)
},
{
XDoubleToFixed(0),
XDoubleToFixed(0),
XDoubleToFixed(1)
}
}
};
XRenderSetPictureTransform(server.display, src_pic, &transform);
XRenderSetPictureFilter(server.display, src_pic, FilterBilinear, NULL, 0);
XRenderComposite(server.display, PictOpSrc, src_pic, None, dst_pic, 0, 0, 0, 0, 0, 0, w, h);
XSync(server.display, False);
ximg = XGetImage(server.display, pix, 0, 0, (unsigned)tw, (unsigned)th, AllPlanes, ZPixmap);
if (!ximg) {
fprintf(stderr, RED "tint2: !ximg" RESET "\n");
goto err0;
@ -438,87 +500,32 @@ cairo_surface_t *get_window_thumbnail_ximage(Window win, size_t size, gboolean u
fprintf(stderr, RED "tint2: unusual bits_per_pixel" RESET "\n");
goto err1;
}
if (use_shm) {
shminfo.shmid = shmget(IPC_PRIVATE, (size_t)(ximg->bytes_per_line * ximg->height), IPC_CREAT | 0777);
if (shminfo.shmid < 0) {
fprintf(stderr, RED "tint2: !shmget" RESET "\n");
goto err1;
}
shminfo.shmaddr = ximg->data = (char *)shmat(shminfo.shmid, 0, 0);
if (!shminfo.shmaddr) {
fprintf(stderr, RED "tint2: !shmat" RESET "\n");
goto err2;
}
shminfo.readOnly = False;
if (!XShmAttach(server.display, &shminfo)) {
fprintf(stderr, RED "tint2: !xshmattach" RESET "\n");
goto err3;
}
if (!XShmGetImage(server.display, win, ximg, 0, 0, AllPlanes)) {
fprintf(stderr, RED "tint2: !xshmgetimage" RESET "\n");
goto err4;
}
}
XGetWindowAttributes(server.display, win, &wa);
if (wa.map_state != IsViewable)
goto err4;
fprintf(stderr, GREEN "tint2: creating image surface %ux%u" RESET "\n", tw, th);
result = cairo_image_surface_create(CAIRO_FORMAT_RGB24, (int)tw, (int)th);
u_int32_t *data = (u_int32_t *)cairo_image_surface_get_data(result);
memset(data, 0, tw * th);
// Fixed-point precision
const size_t prec = 1 << 16;
const size_t xstep = w * prec / fw;
const size_t ystep = h * prec / th;
const size_t offset_y1 = 0 * ystep / 8;
const size_t offset_x1 = 3 * xstep / 8;
const size_t offset_y2 = 1 * ystep / 8;
const size_t offset_x2 = 6 * xstep / 8;
const size_t offset_y3 = 4 * ystep / 8;
const size_t offset_x3 = 2 * xstep / 8;
const size_t offset_y4 = 4 * ystep / 8;
const size_t offset_x4 = 4 * xstep / 8;
const size_t offset_y5 = 4 * ystep / 8;
const size_t offset_x5 = 7 * xstep / 8;
const size_t offset_y6 = 6 * ystep / 8;
const size_t offset_x6 = 1 * xstep / 8;
const size_t offset_y7 = 7 * ystep / 8;
const size_t offset_x7 = 6 * xstep / 8;
const u_int32_t rmask = (u_int32_t)ximg->red_mask;
const u_int32_t gmask = (u_int32_t)ximg->green_mask;
const u_int32_t bmask = (u_int32_t)ximg->blue_mask;
for (size_t yt = 0, y = 0; yt < th; yt++, y += ystep) {
for (size_t xt = 0, x = 0; xt < fw; xt++, x += xstep) {
size_t j = yt * tw + ox + xt;
u_int32_t c1 = (u_int32_t)GetPixel(ximg, (int)((x + offset_x1) / prec), (int)((y + offset_y1) / prec));
u_int32_t c2 = (u_int32_t)GetPixel(ximg, (int)((x + offset_x2) / prec), (int)((y + offset_y2) / prec));
u_int32_t c3 = (u_int32_t)GetPixel(ximg, (int)((x + offset_x3) / prec), (int)((y + offset_y3) / prec));
u_int32_t c4 = (u_int32_t)GetPixel(ximg, (int)((x + offset_x4) / prec), (int)((y + offset_y4) / prec));
u_int32_t c5 = (u_int32_t)GetPixel(ximg, (int)((x + offset_x5) / prec), (int)((y + offset_y5) / prec));
u_int32_t c6 = (u_int32_t)GetPixel(ximg, (int)((x + offset_x6) / prec), (int)((y + offset_y6) / prec));
u_int32_t c7 = (u_int32_t)GetPixel(ximg, (int)((x + offset_x7) / prec), (int)((y + offset_y7) / prec));
u_int32_t b = ((c1 & bmask) + (c2 & bmask) + (c3 & bmask) + (c4 & bmask) + (c5 & bmask) * 2 + (c6 & bmask) +
(c7 & bmask)) /
8;
u_int32_t g = ((c1 & gmask) + (c2 & gmask) + (c3 & gmask) + (c4 & gmask) + (c5 & gmask) * 2 + (c6 & gmask) +
(c7 & gmask)) /
8;
u_int32_t r = ((c1 & rmask) + (c2 & rmask) + (c3 & rmask) + (c4 & rmask) + (c5 & rmask) * 2 + (c6 & rmask) +
(c7 & rmask)) /
8;
data[j] = (r & rmask) | (g & gmask) | (b & bmask);
for (size_t y = 0; y < th; y++) {
for (size_t x = 0; x < tw; x++) {
u_int32_t c = (u_int32_t)XGetPixel(ximg, x, y);
data[y * tw + x] = c;
}
}
imlib_context_set_drawable(pix);
Imlib_Image img = imlib_create_image_from_drawable(0, 0, 0, tw, th, 1);
//Imlib_Image img = imlib_create_image_using_data(tw, th, data);
imlib_context_set_image(img);
char path[256];
static unsigned count = 0;
sprintf(path, "thumb-%u-%u.png", win, count++);
imlib_save_image(path);
imlib_free_image();
// Convert to argb32
if (rmask & 0xff0000) {
// argb32 or rgb24 => Nothing to do
@ -541,24 +548,23 @@ cairo_surface_t *get_window_thumbnail_ximage(Window win, size_t size, gboolean u
}
// 2nd pass
smooth_thumbnail(result);
//smooth_thumbnail(result);
cairo_surface_flush(result);
if (ximg) {
XDestroyImage(ximg);
ximg = NULL;
}
err4:
if (use_shm)
XShmDetach(server.display, &shminfo);
err3:
if (use_shm)
shmdt(shminfo.shmaddr);
err2:
if (use_shm)
shmctl(shminfo.shmid, IPC_RMID, NULL);
err1:
err4:
if (ximg)
XDestroyImage(ximg);
if (src_pic)
XRenderFreePicture(server.display, src_pic);
if (dst_pic)
XRenderFreePicture(server.display, dst_pic);
if (pix)
XFreePixmap(server.display, pix);
err0:
return result;
}
@ -578,24 +584,11 @@ gboolean cairo_surface_is_blank(cairo_surface_t *image_surface)
cairo_surface_t *get_window_thumbnail(Window win, int size)
{
cairo_surface_t *image_surface = NULL;
const gboolean shm_allowed = FALSE;
if (shm_allowed && server.has_shm && server.composite_manager) {
image_surface = get_window_thumbnail_ximage(win, (size_t)size, TRUE);
if (image_surface && cairo_surface_is_blank(image_surface)) {
cairo_surface_destroy(image_surface);
image_surface = NULL;
}
if (debug_thumbnails) {
if (!image_surface)
fprintf(stderr, YELLOW "tint2: XShmGetImage failed, trying slower method" RESET "\n");
else
fprintf(stderr, "tint2: captured window using XShmGetImage\n");
}
}
if (!image_surface) {
image_surface = get_window_thumbnail_ximage(win, (size_t)size, FALSE);
if (image_surface && cairo_surface_is_blank(image_surface)) {
fprintf(stderr, RED "tint2: captured blank thumbnail" RESET "\n");
cairo_surface_destroy(image_surface);
image_surface = NULL;
}