X11: 画像 (XImage)

画像
ウィンドウや Pixmap は、X サーバー内でイメージデータが確保されているため、イメージバッファを操作できません。

イメージバッファを直接操作して描画したい場合は、クライアント側でバッファを確保する XImage を使います。
イメージフォーマット
XImage では、3つのフォーマットを使用できます。
イメージフォーマットに関する値は、ImageByteOrder(display) などのマクロで取得できます。

イメージデータは左上から順に、X は右方向、Y は下方向に並びます。
XYBitmap
1bit のイメージ。
1px = 1bit を、bitmap_unit 単位でパックします。
bitmap_unit = 32 なら、32bit の整数に各 1bit のデータが詰められます。
bitmap_unit 単位のバイト順は byte_order で、ビット順は bitmap_bit_order になります。
XYPixmap
色の各ビットごとに、1プレーンのブロックを作って並べます。
4bit イメージなら、1bit の全体データ x 4 ブロックとなります。
あまり使うことはないでしょう。
ZPixmap
1px ごとにピクセル値を並べます。通常はこちらを使います。
整数のバイト順は byte_order になります。

サポートされている ZPixmap のフォーマットは、XListPixmapFormats() で取得できます。
関数
XImage 構造体
typedef struct _XImage {
 int width, height;
 int xoffset;               /* X方向のオフセットピクセル数 */
 int format;                /* XYBitmap, XYPixmap, ZPixmap */
 char *data;                /* イメージデータ */
 int byte_order;            /* バイトオーダー: LSBFirst, MSBFirst */
 int bitmap_unit;           /* 8, 16, 32 */
 int bitmap_bit_order;      /* ビットオーダー: LSBFirst, MSBFirst */
 int bitmap_pad;            /* スキャンラインのビット単位: 8, 16, 32 */
 int depth;                 /* 深さ */
 int bytes_per_line;        /* スキャンラインのバイト数 */
 int bits_per_pixel;        /* ピクセルのビット数 (ZPixmap) */
 unsigned long red_mask;    /* R,G,B のビットマスク */
 unsigned long green_mask;
 unsigned long blue_mask;
 XPointer obdata;
 struct funcs {
      struct _XImage *(*create_image)();
      int             (*destroy_image)();
      unsigned long   (*get_pixel)();
      int             (*put_pixel)();
      struct _XImage  *(*sub_image)();
      int            (*add_pixel)();
 } f;
} XImage;

XImage 自体は、ただの構造体です。
各情報をセットして、data に自分が確保したイメージバッファのポインタをセットします。

depth は、色として実際に使われるビット数です。
bits_per_pixel は、バッファ上の 1px あたりのビット数です。ZPixmap の場合、1, 4, 8, 16, 24, 32 の値になります。
作成
XImage *XCreateImage(Display *display, Visual *visual, unsigned int depth, int format, int offset,
    char *data, unsigned int width, unsigned int height, int bitmap_pad, int bytes_per_line);

XImage 構造体を確保して、各値を設定します。

ただし、イメージバッファは確保されないので、自分で確保する必要があります。
通常は、data に NULL を渡して XImage を作成した後、(ximage->bytes_per_line * ximage->height) のバイト数でバッファを確保し、ximage->data にそのポインタをセットしてください。

byte_order, bitmap_unit, bitmap_bit_order は、display から取得されます。
{red,green,blue}_mask は、visual から設定されます。

visualRGB のマスク値をセットするために使われます。
NULL の場合、マスク値は 0 になります。
参考情報として設定されるだけなので、転送時にマスク値が使用されることはありません。
formatXYBitmap, XYPixmap, ZPixmap
offset各スキャンラインの先頭において、無視するピクセル数
dataイメージバッファのポインタ。
構造体にそのまま値をセットするだけなので、NULL でも構いません。
bitmap_padスキャンラインのビット単位 (8, 16, 32)。
bytes_per_line を計算する時、このビット単位のサイズになります。
bytes_per_lineY1列ごとのバイト数。
0 で、余分な余白がないものとして計算されます。
解放
<X11/Xutil.h>
void XDestroyImage(XImage *ximage);

XImage を解放します。
実際は関数ではなくマクロになっており、ximage->f.destroy_image(ximage) が実行されます。

XCreateImage、XGetImage、XSubImage 関数を使用して作成された場合は、イメージデータのメモリも解放されます。

※XImage は X リソースではないので、XCloseDisplay() 時に自動で解放されません。
描画
void XPutImage(Display *display, Drawable d, GC gc, XImage *image, int src_x, int src_y,
    int dest_x, int dest_y, unsigned int width, unsigned int height);

XImage の指定範囲を、ドローアブルに書き込みます。

XYPixmap or ZPixmap の場合、イメージの深さとドローアブルの深さが同じである必要があります。

byte_order や bitmap_unit がサーバーの要求と異なる場合、自動的に適切な変換が行われます。
プログラム
XImage を作成し、グラデーションを描画してウィンドウに出力します。

左クリックで、赤→緑→青のグラデーションに変化します。
それ以外のボタンを押すと、終了します。

※ウィンドウのサイズ変更時の対応はしていません。

$ cc -o run 19-image.c util.c -lX11

<19-image.c>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "util.h"

#define WIDTH  200
#define HEIGHT 200

int g_cur_type = 0;

/* バッファにピクセル値をセット */

static void _setpixel(XImage *img,uint8_t *buf,uint32_t pixcol)
{
    //マシンとイメージのバイトオーダーが同じであると想定

    switch(img->bits_per_pixel)
    {
        case 32:
            *((uint32_t *)buf) = pixcol;
            break;
        case 24:
            if(img->byte_order == LSBFirst)
            {
                buf[0] = (uint8_t)pixcol;
                buf[1] = (uint8_t)(pixcol >> 8);
                buf[2] = (uint8_t)(pixcol >> 16);
            }
            else
            {
                buf[0] = (uint8_t)(pixcol >> 16);
                buf[1] = (uint8_t)(pixcol >> 8);
                buf[2] = (uint8_t)pixcol;
            }
            break;
        case 16:
            *((uint16_t *)buf) = pixcol;
            break;
    }
}

/* グラデーションを描画 */

static void _draw_image(XImage *img,int type)
{
    uint8_t *pd,*pdY;
    int ix,iy,bytes,n;
    uint32_t pixcol;

    pdY = (uint8_t *)img->data;
    bytes = img->bits_per_pixel / 8;

    for(iy = 0; iy < img->height; iy++)
    {
        pd = pdY;

        n = iy * 255 / (img->height - 1);
        
        pixcol = rgb_to_pixel_sep(
            (type == 0)? n: 0,
            (type == 1)? n: 0,
            (type == 2)? n: 0);
        
        for(ix = 0; ix < img->width; ix++)
        {
            _setpixel(img, pd, pixcol);
            
            pd += bytes;
        }

        pdY += img->bytes_per_line;
    }
}

int main(int argc,char **argv)
{
    Display *disp;
    XSetWindowAttributes attr;
    Window win;
    XEvent ev;
    XImage *ximg = NULL;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    set_display(disp);

    //ウィンドウ作成

    attr.event_mask = ButtonPressMask | ExposureMask;

    win = XCreateWindow(disp, DefaultRootWindow(disp),
        0, 0, WIDTH, HEIGHT, 0,
        CopyFromParent, CopyFromParent, CopyFromParent,
        CWEventMask, &attr);

    //XImage

    ximg = XCreateImage(disp, DefaultVisualOfScreen(g_disp.screen), g_disp.depth,
        ZPixmap, 0, NULL, WIDTH, HEIGHT, BitmapPad(disp), 0);

    if(!ximg)
    {
        printf("failed XCreateImage\n");
        goto END;
    }

    printf("depth: %d\nbits_per_pixel: %d\nbytes_per_line: %d\n",
        ximg->depth, ximg->bits_per_pixel, ximg->bytes_per_line);

    ximg->data = (char *)malloc(ximg->bytes_per_line * HEIGHT);
    if(!ximg->data) goto END;

    _draw_image(ximg, g_cur_type);

    //イベント

    XMapWindow(disp, win);

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

        switch(ev.type)
        {
            case Expose:
                XPutImage(disp, win, g_disp.gc, ximg,
                    ev.xexpose.x, ev.xexpose.y,
                    ev.xexpose.x, ev.xexpose.y,
                    ev.xexpose.width, ev.xexpose.height);

                printf("[Expose] (%d,%d)-(%dx%d)\n",
                    ev.xexpose.x, ev.xexpose.y, ev.xexpose.width, ev.xexpose.height);
                fflush(stdout);
                break;
            case ButtonPress:
                if(ev.xbutton.button == Button1)
                {
                    g_cur_type++;
                    if(g_cur_type == 3) g_cur_type = 0;
                
                    _draw_image(ximg, g_cur_type);

                    XPutImage(disp, win, g_disp.gc, ximg,
                        0, 0, 0, 0, WIDTH, HEIGHT);
                }
                else
                    goto END;
        }
    }

END:
    if(ximg) XDestroyImage(ximg);

    XCloseDisplay(disp);

    return 0;
}
解説
背景
今回は、ウィンドウの背景色を設定していません。

ウィンドウサイズが変更された場合、XImage の画像サイズを超える範囲は描画されないので、未定義の状態となります。
描画内容はおかしな感じになりますが、動作自体に問題はありません。
メモリ確保
XImage は基本的に、XCreateImage() で作成した後、設定された値を使って、後からイメージバッファのメモリを確保した方が良いです。

XCreateImage() 時、bytes_per_line 引数が、実際に必要な最小サイズよりも小さい場合は、NULL が返るので、bytes_per_line を 0 にして自動計算させた後、その値を使ってバッファサイズを計算した方が良いです。
ビット数
depth (実際に色として使用されるビット数) が 24 でも、bits_per_pixel (バッファ上の 1px あたりのビット数) が 32 になる場合があります。
その場合、バッファ上では 32bit の数値単位で扱うことになります。

16 bit カラーの場合、depth は 15 か 16 になります。bits_per_pixel は 16 です。
ピクセル値
XImage のバッファ上では、色を「ピクセル値」として扱うので、RGB 値をピクセル値に変換する必要があります。
最終的にウィンドウへ出力するのであれば、ウィンドウで使うビジュアルからマスク値を取得して、それを元に変換します。

このプログラムの場合、ウィンドウは親 (ルートウィンドウ) と同じビジュアルを指定しているので、スクリーンのデフォルトのビジュアルが使われます。