use xrender to capture screenshots
This commit is contained in:
parent
4dafea185f
commit
36c8d41914
1 changed files with 109 additions and 116 deletions
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue