X11: XRender (2) - Picture

Picture
Render 拡張機能では、イメージの合成を行うために、Picture 型の XID が必要になります。

Picture には、「イメージを参照する Drawable」「イメージのフォーマット」「合成時に使われる属性 (XRenderPictureAttributes 構造体)」などが関連付けられています。

Render のイメージとして扱えるのは、Drawable (Window か Pixmap) だけなので、XImage は使えません。
関数
Picture の作成
//Picture の作成
Picture XRenderCreatePicture(Display *dpy, Drawable drawable,
    _Xconst XRenderPictFormat *format,
    unsigned long valuemask, _Xconst XRenderPictureAttributes *attributes);

//Picture の解放
void XRenderFreePicture(Display *dpy, Picture picture);

新しい Picture を作成し、そこに「イメージとなる Drawable」「drawable イメージのフォーマット」「Picture の属性」を関連付けた上で、drawable 引数のドローアブルに Picture を関連付けます。

drawable が破棄された場合は、Picture も自動的に破棄されます。

※drawable の深さと、フォーマットの深さは同じである必要があります。
※drawable がウィンドウの場合、RGB のマスクが、フォーマットのマスクと一致する必要があります。

属性については、Render 拡張機能 をご覧ください。
特に指定する属性がない場合は、valuemask = 0, attributes = NULL にできます。
合成
void XRenderComposite(Display *dpy, int op, Picture src,
    Picture mask, Picture dst,
    int src_x, int src_y, int mask_x, int mask_y, int dst_x, int dst_y,
    unsigned int width, unsigned int height);

src の Picture に関連付けられている Drawable のイメージを、dst の Picture に関連付けられている Drawable に合成します。
合成方法は op で指定します (通常のアルファ合成なら、PictOpOver)。

mask はアルファマスクのイメージとして使われ、合成前にソースに対してアルファ値が適用されます。
mask = None の場合、アルファ値は 1.0 として扱われます。

src_x などの各位置は、それぞれの Drawable の原点位置が基準となります。

※src, mask, dst のそれぞれのフォーマットが異なる場合、いずれかのフォーマットで、精度を保ったまま変換が可能な場合は、そのフォーマットに変換されます。
合成の注意点
ソースの画像を、イメージ内のアルファ値 (または別のイメージにあるアルファ値) を使って、合成先に合成したい場合、通常は以下のような計算式で合成されます。

src * Alpha + dst * (1.0 - Alpha)

ただし、XRender の場合は、この計算式に当てはまる合成オペランドがありません。
一番近いのは PictOpOver で、「src * 1.0 + dst * (1.0 - srcA)」の計算になります。

この場合、src にアルファ値が掛けられていないので、PictOpOver の合成だけでは、通常のアルファ合成は行えません。

ただし、PictOpOver の合成前に、ソースにアルファ値を掛ければ、結果として通常のアルファ合成と同じ計算になるので、その方法で合成することになります。
アルファマスクを使う
XRenderComposite 関数などでの合成時、mask 引数に、アルファ値が含まれた Picture を指定することで、そのアルファ値をソースに適用 (乗算) することができます。

ソースが RGBA イメージで、合成にソースのアルファ値を使用したい場合は、src と mask に同じ Picture を指定して PictOpOver 合成を行えば、通常のアルファ合成が行えます。
アルファマスクを指定しなかった場合
ソースが RGBA イメージで、合成時にアルファマスクを指定しなかった場合は、意図したアルファ合成になりません。

例えば、ソースが赤色&アルファ値付きで、合成先が青色だった場合、アルファマスクなしで PictOpOver で合成すると、以下のようになります。

[PictOpOver]
C = src * 1.0 + dst * (1.0 - srcA)

[src:A = 0]
dstR = srcR(255) * 1.0 + dstR(0)   * (1.0 - 0) = 255
dstG = srcG(0)   * 1.0 + dstG(0)   * (1.0 - 0) = 0
dstB = srcB(0)   * 1.0 + dstB(255) * (1.0 - 0) = 255

[src:A = 0.5]
dstR = 255
dstG = 0
dstB = srcB(0) * 1.0 + dstB(255) * (1.0 - 0.5) = 128

[src:A = 1.0]
dstR = 255
dstG = 0
dstB = srcB(0) * 1.0 + dstB(255) * (1.0 - 1.0) = 0

ソースの色はそのままで、合成先にソースのアルファ値が掛けられるので、ソースで透明な部分は #FF00FF (紫色) になります。
本来のアルファ合成であれば青色になるはずですが、ソースの Red にアルファ値が掛けられていないので、このような結果になります。
Pixmap
Render の合成ソース・合成先として使えるイメージは Drawable だけなので、XImage をイメージとして使うことはできません。
ただし、XImage でイメージを用意して、それを XPutImage() で Drawable に転送することはできます。

ウィンドウではないイメージを作りたい場合は、Pixmap を作成する必要があります。
Pixmap の作成
Pixmap は、以下の関数で作成できます。

Pixmap XCreatePixmap(Display *display, Drawable d,
    unsigned int width, unsigned int height, unsigned int depth);

//解放
void XFreePixmap(Display *display, Pixmap pixmap);

depth は、スクリーンでサポートされている深さであれば、指定したビット数のイメージにすることができます。

Pixmap のイメージは X サーバーが保持しているので、直接バッファを操作することはできません。
Pixmap に描画するには、通常の X グラフィック関数を使います。

バッファを直接操作可能な Pixmap を作成したい場合は、MIT-SHM 拡張機能を使って、共有メモリを使用した Pixmap を作ることができます。
共有メモリ Pixmap
共有メモリ Pixmap は、XYPixmap または ZPixmap のフォーマットの、いずれか1つのみに対応しています。
X.Org 実装の場合は、ZPixmap になります (1px ごとにピクセル値が並ぶ)。

MIT-SHTM のページを参考にして、XShmSegmentInfo 構造体に値をセットした後、XShmCreatePixmap() で作成できます。
解放する時の手順もほぼ同じですが、戻り値の Pixmap は XFreePixmap() で解放します。

Pixmap XShmCreatePixmap(Display *display, Drawable d,
    char *data, XShmSegmentInfo *shminfo,
    unsigned int width, unsigned int height, unsigned int depth);

共有メモリ Pixmap は、スクリーンがサポートしている深さには依存せず、スクリーンにも依存しません。

バッファサイズ
XImage の場合は、bytes_per_line に Y1行ごとのバイト数の値がセットされていたので、それを元にバッファサイズを決めることができましたが、Pixmap の場合は、自分でバッファサイズを計算する必要があります。

ZPixmap フォーマットの場合、Y1行のサイズは、BitmapPad(display) マクロで取得できるビット数の単位にしなければならないので、例えばこの値が 32 だった場合は、4 byte 単位にする必要があります。

depth = 8 の Pixmap を作成する場合、幅が 201 px であれば、(201 + 3) & ~3 で、Y1行は 204 byte になります。
プログラム
背景色が青のウィンドウに、赤色で、アルファ値がグラデーションしているイメージを合成します。
閉じるボタンで終了します。

$ cc -o run e05-picture.c util.c -lX11 -lXrender

<e05-picture.c>
#include <stdint.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#include <X11/extensions/Xrender.h>
#include "util.h"

XShmSegmentInfo g_seginfo;

#define WIDTH  200
#define HEIGHT 200

static Pixmap _create_image(Display *disp)
{
    Pixmap pixmap;
    uint32_t *pd,a;
    int ix,iy;

    //depth が 32 以下なら、Y1列のバイト数を BitmapPad(display) のビット単位にすること

    g_seginfo.shmid = shmget(IPC_PRIVATE, WIDTH * 4 * HEIGHT, IPC_CREAT | 0777);
    g_seginfo.shmaddr = shmat(g_seginfo.shmid, 0, 0);
    g_seginfo.readOnly = False;

    XShmAttach(disp, &g_seginfo);

    pixmap = XShmCreatePixmap(disp, g_disp.root, g_seginfo.shmaddr,
        &g_seginfo, WIDTH, HEIGHT, 32);

    //赤 + A グラデーション

    pd = (uint32_t *)g_seginfo.shmaddr;

    for(iy = 0; iy < HEIGHT; iy++)
    {
        for(ix = 0; ix < WIDTH; ix++)
        {
            a = ix * 255 / (WIDTH - 1);
            *(pd++) = 0xff0000 | (a << 24);
        }
    }

    return pixmap;
}

int main(int argc,char **argv)
{
    Display *disp;
    Window win;
    XEvent ev;
    Pixmap pixmap;
    Picture picwin,picimg;
    XRenderPictFormat *pfwin,*pfimg;
    int major,minor;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    set_display(disp);

    XShmQueryExtension(disp);

    //Render

    if(!XRenderQueryVersion(disp, &major, &minor))
        printf("unsupported XRender\n");
    else
        printf("XRender ver %d.%d\n", major, minor);

    //

    win = create_test_window2(disp, WIDTH, HEIGHT, rgb_to_pixel(0x0000ff), ExposureMask);

    //ウィンドウの Picture

    pfwin = XRenderFindVisualFormat(disp, DefaultVisualOfScreen(g_disp.screen));
    picwin = XRenderCreatePicture(disp, win, pfwin, 0, NULL);

    //ソース

    pixmap = _create_image(disp);

    pfimg = XRenderFindStandardFormat(disp, PictStandardARGB32);
    picimg = XRenderCreatePicture(disp, pixmap, pfimg, 0, NULL);

    //イベント

    XMapWindow(disp, win);

    while(1)
    {
        XNextEvent(disp, &ev);

        if(event_quit(&ev)) break;

        switch(ev.type)
        {
            case Expose:
                XRenderComposite(disp, PictOpOver, picimg, picimg, picwin,
                    0,0, 0,0, 0,0, WIDTH, HEIGHT);
                break;
        }
    }

    //

    XShmDetach(disp, &g_seginfo);
    shmdt(g_seginfo.shmaddr);
    shmctl(g_seginfo.shmid, IPC_RMID, 0);

    XFreePixmap(disp, pixmap);

    XCloseDisplay(disp);

    return 0;
}
解説
ウィンドウは、デフォルトのビジュアルから取得した PictFormat を使って、Picture を作成します。

また、合成元のソースとして、depth = 32 の共有メモリ Pixmap を作成します。
色は赤、アルファ値は X 方向に 0〜255 のグラデーションになるようにセットします。

Expose イベントが来て、再描画のタイミングが来た時、ウィンドウの背景色によって、ウィンドウの内容は青色になっているので、その上にソースイメージを合成します。

青色の上に、赤のグラデーションが合成される形になります。
他の機能
他にも、Picture から Cursor (カーソル形状の XID) を作成したり、アニメーションカーソルを作成する機能があります。

また、アンチエイリアス付きのテキストを描画するために、各グリフのイメージを X サーバーに送って、サーバー側でグリフイメージを合成させることもできます。

なお、Xft ライブラリは、fontconfig、FreeType、Render 拡張機能を使って、テキスト描画を行うためのライブラリです。

ただし、グリフイメージはすべて X サーバーが保持する形になるので、日本語のようなグリフ数が多いフォントを使う場合、あまり効率が良いとは言えません。

少々手間はかかりますが、クライアントが自分で fontconfig、FreeType を処理して、XImage などのイメージに色をセットした後、そのイメージをウィンドウに転送する方が、総合的に見て使いやすくなると思います。