日記帳

日記です。

X11 でスクリーンキャプチャー

X11 でも.

  • ルートウィンドウを取得
  • XGetImage() でルートウィンドウのコピーとなる XImage を得る
  • XImage はクライアント側のラスター画像でピクセルにアクセスできるので好きにいじる

でも XGetImage() で取得できる XImage ってルートウィンドウと同じ depth やカラーマップを持つものなるのでXサーバの設定によってフォーマットが変ることになる. X って depth がいろいろあって(32, 24, 16, 8 はまだしも 4 とか 1 とかもありえる)個別に処理するのは面倒なんですよねぇ… できればこちらで指定したフォーマット(32 bpp RGBとか)に X11 側で出力してくれるか,あるいは取得した XImage のフォーマットの変換が API で簡単にできるといいんだけど Xlib ではできなかったんだっけ?

フォーマット変換の方法として以下の手順をを考えてみた.

  • XCreatePixmap() でサーバ側に 32 bpp の Pixmap を作成
  • XCopyArea() でルートウィンドウの内容を Pixmap にコピー
  • XGetImage() で Pixmap の内容を XImage として取得

しかし XCopyArea() は同一の depth の Window や Pixmap の間でしかコピーできない模様で挫折.

こういうフォーマットの変換って Xlib の API では無理で自前でやるしかないんだっけか? 何か X 拡張でそういう機能があった気もしたけど思い出せない… まぁ X 拡張じゃあまり嬉しくないですが…

以下 32 bpp RGB な場合限定のサンプル.

/**
* gcc capture_screen_x11-01.c -L/usr/X11/lib -lX11
**/

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <X11/Xlib.h>

#define IMAGE_FILE_PATH "capture_screen.ppm"

static
void writeXImageToP3File(XImage *image, const char *file_path)
{
	FILE *file;

	file = fopen(file_path, "wb");
	if (file != NULL)
	{
		int x, y;
		char *cp;
		uint32_t pixel;

		fprintf(file, "P3\n");
		fprintf(file, "%d %d\n", image->width, image->height);
		fprintf(file, "255\n");
		for (y = 0;y < image->height;y++)
		{
			cp = image->data + y * image->bytes_per_line;
			for (x = 0;x < image->width;x++)
			{
				pixel = *(uint32_t *)cp;
				fprintf(file, "%d %d %d ",
					(pixel & 0x00ff0000) >> 16,
					(pixel & 0x0000ff00) >> 8,
					(pixel & 0x000000ff) >> 0);
				cp += 4;
			}
			fprintf(file, "\n");
		}
		fclose(file);
	}
}

int main(int argc, char* argv[])
{
	Display* display;
	int screen;
	Window rootWindow;
	XWindowAttributes attributes;
	int width, height;
	XImage *image;

	display = XOpenDisplay("");
	screen = DefaultScreen(display);
	rootWindow = RootWindow(display, screen);

	XGetWindowAttributes(display, rootWindow, &attributes);
	width = attributes.width;
	height = attributes.height;

	image = XGetImage(display, rootWindow,
		0, 0, attributes.width, attributes.height,
		AllPlanes, ZPixmap);

	if (image != NULL)
	{
		if (image->bits_per_pixel == 32)
			writeXImageToP3File(image, IMAGE_FILE_PATH);
		else
			fprintf(stderr, "Not Supported format : bits_per_pixel = %d\n", image->bits_per_pixel);
		XFree(image);
	}
	
	XCloseDisplay(display);
	return 0;
}