mieki256's diary



2022/06/01(水) [n年前の日記]

#1 [xscreensaver][xlib] Xlibについて調べてる

xscreensaver用のスクリーンセーバで何かしらを描画する時は Xlib なるものを使って描画するのが一般的らしいのだけど。その Xlib とやらで拡大縮小描画みたいなことはできないのかなとググってるところ。

スクリーンセーバの面倒なところは、ディスプレイの解像度/ウインドウサイズが、環境によって異なる点。例えば 1920x1080 のサイズで画像等をイイ感じにレイアウトしても、1024x768 だの 3840×2160 だの、そういったディスプレイで表示しようとすると、見た目は滅茶苦茶になってしまう。どうにかならんか。

そこで思いつくのが、320x240、640x480、1280x720等のお決まりのサイズで一旦画面を作っておいて、ソレを目的のウインドウサイズに拡大、もしくは縮小して描画する方法。love2d や Unity では、そういうことをして全画面表示にする場合もちょくちょくあったりする。

そんなわけで、Xlib にそういう機能は無いのかな、拡大縮小描画ができれば楽になるよなとググっているわけだけど…。どうやらそんな機能は存在していない模様。あくまで等倍でしか画像の描画はできませんよ、みたいな話をチラホラ目にした。

そういうことをしたいなら、OpenGL だか glx だか、そのあたりを使わないといかんのだろうか。

ただ、hacks/*.c を眺めたところ、自力で拡大縮小処理をしているものもあるように思えた。例えば apple2-main.c の中には scale_image() という関数があるし、xscreensaver-getimage.c の中にも scale_ximage() という関数がある。名前からして画像の拡大縮小処理をしてそう。1ドットずつ処理をしていくから処理時間がかかりそうではあるけれど。

2022/06/02(木) [n年前の日記]

#1 [nitijyou] 自宅サーバ止めてました

雷が鳴ったので、17:00-21:00の間、自宅サーバを止めてました。申し訳ないです。

#2 [pc] 自宅サーバの電源が入らなくなって四苦八苦

雷が鳴り止んだので、自宅サーバの電源を入れようとしたところ、妙な動作になった。電源タップのスイッチをONにした途端に電源が入って、しかしその数秒後に勝手に電源が切れてしまう。

まさか、またM/Bが壊れてしまったのか。それともACアダプタが弱まって起動できない状態になったのか。それとも、実は外出中に雷が落ちて壊れてしまったのか…。電源タップはOFFにしてたし、LANケーブルも引っこ抜いておいたはずだけどなあ…。

PCデスクの足元から、PCケースごと ―― SilverStone SST-SG05W ごと引っ張り出して眺めてみたら原因が分かった。単に、電源スイッチのカバーが押し込まれたまま戻らない状態になっていた模様。なるほど、電源スイッチがずっと押されっぱなしになっていたから、コンセントに電源ケーブルを差すと途端に電源が入ってしまうし、その後きっかり4秒後に電源が落ちていたのだな…。 *1

それにしても、電源スイッチがLEDで光るお洒落なデザインを追求した結果、そもそも電源スイッチとして機能しない・電源のON/OFFすら怪しい設計になりましたというのは、安物PCケースのあるあるネタだなあ、と。

さておき。どうしたもんか。引っ込んだ電源スイッチカバーがするする出てくるようにならないかと、モイスチャーオイルとやらを垂らしてみたけど全く効果無し。相変わらず引っ込んだまま。

これはもう電源スイッチを全く別のモノと交換するしかないかな、この電源スイッチカバーは取り外して、その穴から別の電源スイッチのケーブルを通そう…。

と思って、電源スイッチカバーを繋ぎとめてる2本のプラスチックの棒をニッパーで切ってみたところ、電源スイッチカバーがパカパカスルスルと動くようになった。これでいいんじゃないかな。

メーカは何故に最初からこういう設計にしておかなかったのだろう。まあ、この状態だと工場での組み立て時にポロポロ落ちて作業員が困るだろうと考えたのかもしれないけれど。その結果、ちょっと強く押したら戻ってこなくなる電源スイッチになってしまったら、それもなんだかトホホだろうに…。

電源ケーブル等を繋ぎ直して再設置。フツーに起動してくれた。やっぱり電源スイッチのせいだったらしい。一安心。
*1: えてしてPCの電源は、電源スイッチを4秒ほど押しっぱなしにすると電源が強制的に切れるようになっている。

#3 [xscreensave][xlib] Xlibについて勉強中

Linuxのデスクトップ画面で何かしらを描く時は Xlib を使うらしいので勉強中。

以下のサイトやページを参考にしつつコピペして動作確認。

_Xlib Programming Lectures
_X11で始めるGUIプログラミング|コアダンプの数だけ強くなれるよ
_X Window Systemプログラミングを勉強してみた - ウィンドウを表示する - Tomitomi's blog

2022/06/03(金) [n年前の日記]

#1 [xlib] Xlibについて勉強中その2

Xlibについて勉強中。

環境は、Ubuntu Linux 20.04 LTS。Windows10 x64 21H2 + VMware Player 上で動かしている。

ウインドウの表示と画像の描画。 :

とりあえず、ウインドウを表示して、四角、円、画像を描画するところまで動作確認してみた。




_02win.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <math.h>

/* include ufo2_xpm */
#include "ufo2.xpm"

#define WIN_W 512
#define WIN_H 512
#define WIN_X 100
#define WIN_Y 100
#define BORDER 4

#ifndef M_PI
#define M_PI 3.14159265358979
#endif

#define deg2rad(x) (x * M_PI / 180.0)

typedef struct _Pixmaps
{
    Pixmap pix;
    Pixmap mask;
} Pixmaps;

static void update(Display *dpy, Drawable win, GC gc, double ang, Pixmaps pixs);

/* get a color value from a color name */
static unsigned long
get_color(Display *dpy, char *colorname)
{
    Colormap cmap;
    XColor near_color, true_color;

    cmap = DefaultColormap(dpy, DefaultScreen(dpy));
    XAllocNamedColor(dpy, cmap, colorname, &near_color, &true_color);
    return near_color.pixel;
}

int main(void)
{
    Display *dpy;
    Window root;
    Window win;
    int screen;
    XEvent evt;
    unsigned long black, white;
    GC gc;
    Pixmaps pixs;
    int busy_loop;
    double ang;

    /* open display */
    dpy = XOpenDisplay(NULL);
    root = DefaultRootWindow(dpy);
    screen = DefaultScreen(dpy);

    /* get color black and white */
    white = WhitePixel(dpy, screen);
    black = BlackPixel(dpy, screen);

    /* create window */
    win = XCreateSimpleWindow(dpy, root,
                              WIN_X, WIN_Y, WIN_W, WIN_H, BORDER,
                              black, white);

    XSelectInput(dpy, win, KeyPressMask | ExposureMask);

    XMapWindow(dpy, win);

    /* wait display window */
    do
    {
        XNextEvent(dpy, &evt);
    } while (evt.type != Expose);

    XMoveWindow(dpy, win, WIN_X, WIN_Y);

    /* create graphics context */
    gc = XCreateGC(dpy, win, 0, 0);
    XSetGraphicsExposures(dpy, gc, False);

    /* load image */
    if (XpmCreatePixmapFromData(dpy, win, ufo2_xpm,
                                &pixs.pix, &pixs.mask, NULL))
    {
        fprintf(stderr, "Error not load xpm.");
        return 1;
    }

    ang = 0;

    busy_loop = 1;
    while (busy_loop)
    {
        /* event */
        while (XPending(dpy))
        {
            XNextEvent(dpy, &evt);
            switch (evt.type)
            {
                /*
            case Expose:
                if (evt.xexpose.count == 0)
                {
                }
                break;
                */
            case KeyPress:
                busy_loop = 0;
                break;
            default:
                break;
            }
        }

        update(dpy, win, gc, ang, pixs);
        ang += 1.0;
        if (ang >= 360)
            ang -= 360;

        XFlush(dpy);
        usleep(16 * 1000); /* wait */
    }

    XFreePixmap(dpy, pixs.pix);
    XFreePixmap(dpy, pixs.mask);
    XFreeGC(dpy, gc);
    XDestroyWindow(dpy, win);
    XCloseDisplay(dpy);
    return 0;
}

/* update and draw */
static void
update(Display *dpy, Drawable win, GC gc, double ang, Pixmaps pixs)
{
    XWindowAttributes xgwa;
    int scrw, scrh;
    int r, x, y, w, h;
    int r2, x2, y2, w2, h2;

    /* get window attributes */
    XGetWindowAttributes(dpy, win, &xgwa);
    scrw = xgwa.width;  /* window width */
    scrh = xgwa.height; /* window height */

    w = 96;
    h = 96;
    r = 200;
    x = r * cos(deg2rad(ang)) + (scrw / 2) - (w / 2);
    y = r * sin(deg2rad(ang)) + (scrh / 2) - (h / 2);

    w2 = 128;
    h2 = 128;
    r2 = 128;
    x2 = r2 * cos(deg2rad(-ang)) + (scrw / 2) - (w2 / 2);
    y2 = r2 * sin(deg2rad(-ang)) + (scrh / 2) - (h2 / 2);

    /* clear window */
    XClearWindow(dpy, win);

    /* draw shape */
    XSetForeground(dpy, gc, get_color(dpy, "blue"));
    XFillRectangle(dpy, win, gc, x - 8, y - 8, w + 16, h + 16);

    XSetForeground(dpy, gc, get_color(dpy, "cyan"));
    XFillArc(dpy, win, gc, x, y, w, h, 0, 360 * 64);

    /* draw image (Pixmap) */
    XSetClipMask(dpy, gc, pixs.mask);                        /* set clip mask */
    XSetClipOrigin(dpy, gc, x2, y2);                         /* set clip origin */
    XCopyArea(dpy, pixs.pix, win, gc, 0, 0, w2, h2, x2, y2); /* copy Pixmap */
    XSetClipMask(dpy, gc, None);                             /* reset clip */
}

使用画像: _ufo2.xpm
Makefile: _Makefile

コンパイルは以下。
gcc 02win.c -o 02win -I /usr/include/X11 -lX11 -lXext -lm -lXpm
Ubuntu Linux だから上のような指定になっているけれど、別のディストリでは「-I /usr/include/X11」のあたりを変えることになるのかもしれない。巷の解説ページでは別の場所を指定している場合がほとんどなので…。

実行は以下。
./02win

少し解説。

  • Xlib を使う時は、#include <X11/Xlib.h> を最初のあたりで書く。コンパイラ(リンカ)には、-lX11 -lXext を追加。
  • xpm画像を扱う時は、#include <X11/xpm.h> を以下略。コンパイラ(リンカ)には、-lXpm を追加。
  • sin,cos 等の数学関数を使う時は、#include <math.h> を以下略。コンパイラ(リンカ)には、-lm を追加。

xpm画像は中身がテキストファイル。C言語のソースファイル内で include して画像データのバイナリにアクセスすることができる。ちなみに、GIMP で xpm としてエクスポートすれば xpm画像を作れる。

画像を扱う時は Pixmap か XImage を使うらしいけど、今回は Pixmap を使って処理してみた。

xpm から Pixmap に変換しているのは以下。

    /* load image */
    if (XpmCreatePixmapFromData(dpy, win, ufo2_xpm,
                                &pixs.pix, &pixs.mask, NULL))
    {
        fprintf(stderr, "Error not load xpm.");
        return 1;
    }
  • ufo2_xpm が、xpm のバイナリが入ってる変数名。
  • pixs.pix に非透過部分が、pixs.mask に透過部分が入るように指定してる。
  • XpmCreatePixmapFromData() は、処理成功で0、失敗でエラーコードが返る。ちなみにC言語は、0は偽、0以外は真になる仕様。

透過画像を描画する時は、XSetClipMask()、XSetClipOrigin()、XCopyArea()、XSetClipMask() を使う。

    /* draw image (Pixmap) */
    XSetClipMask(dpy, gc, pixs.mask);                        /* set clip mask */
    XSetClipOrigin(dpy, gc, x2, y2);                         /* set clip origin */
    XCopyArea(dpy, pixs.pix, win, gc, 0, 0, w2, h2, x2, y2); /* copy Pixmap */

    XSetClipMask(dpy, gc, None);                             /* reset clip */
  • XSetClipMask() で、透過部分が入ってる Pixmap を指定して、クリップマスクを設定。
  • XSetClipOrigin() で、クリップマスクの原点を設定。
  • XCopyArea() で、Pixmap を別の何か(Drawable。ウインドウかPixmap。今回はウインドウが転送先)にコピー。この時、クリップマスクが反映されて、透過ではない部分だけがコピーされる。
  • 最後にクリップマスクをリセットしておかないと、次回、別の何かを描画する際に前回のクリップマスクが働いていしまうので注意。

Pixmap や GC は、プログラムを終了する前に、利用していたメモリ領域を解放しておかないといけない。XFreePixmap()、XFreeGC() を使って解放する。

Pixmapを拡大縮小してみる。 :

画像の描画ができたので、画像を拡大縮小できないか試してみる。

どうしてそんなことをしたいのかというと、例えばスクリーンセーバを作る際に、ウインドウサイズ(ディスプレイサイズ)が異なると画像レイアウトが違ってきてしまうからで…。一旦小さい画面(Pixmap)に描いておいて、その小さい画面をウインドウサイズのPixmapに拡大して、それをウインドウに描画することができれば、ウインドウサイズ(ディスプレイサイズ)が違っていても、画像のレイアウトがめちゃくちゃにならずに済むのではないかと。

処理時間的に厳しいのではないかと予想しながら書き始めてみたけど、やり方次第ではどうにかなりそうな気配を感じた。以下のキャプチャ動画は、640 x 360 の Pixmap を 1440 x 900 の Pixmap に拡大してる例。上下にふわふわしてるけど、これでも毎フレーム拡大処理をしている。




_04scale.c
/*
Scaling Pixmap.

Ryzen 5 5600x + VMware Player + Ubuntu Linux 20.04 LTS.
1920 x 1080.
slow result : 0.510753 sec
fast result : 0.000045 sec
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <math.h>
#include <time.h>

/* include xpm binary */
#include "bg01.xpm"
#define XPM_W 640
#define XPM_H 360
#define XPM_NAME bg01_xpm

/*
#include "bg02.xpm"
#define XPM_W 320
#define XPM_H 180
#define XPM_NAME bg02_xpm
*/

/* kind. 0: slow, 1: fast */
#define SCALING_KIND 1

#define WIN_X 0
#define WIN_Y 64
#define BORDER 0

#ifndef M_PI
#define M_PI 3.14159265358979
#endif

#define deg2rad(x) (x * M_PI / 180.0)

/* get a color value from a color name */
static unsigned long
get_color(Display *dpy, char *colorname)
{
    Colormap cmap;
    XColor near_color, true_color;

    cmap = DefaultColormap(dpy, DefaultScreen(dpy));
    XAllocNamedColor(dpy, cmap, colorname, &near_color, &true_color);
    return near_color.pixel;
}

/* scale Pixmap */
static void
scale_pixmap(Display *dpy, Drawable win,
             Pixmap *src, Pixmap *dst,
             int src_w, int src_h, int dst_w, int dst_h)
{
    GC gc;
    float xscale, yscale;
    int x, y;
    struct timespec start, end;
    double diff;

    xscale = (float)src_w / dst_w;
    yscale = (float)src_h / dst_h;

    gc = XCreateGC(dpy, win, 0, 0);
    XSetGraphicsExposures(dpy, gc, False);

    timespec_get(&start, TIME_UTC);

    switch (SCALING_KIND)
    {
    case 0:
        /* slow */
        for (y = 0; y < dst_h; y++)
        {
            int sy = (int)(y * yscale);
            for (x = 0; x < dst_w; x++)
                XCopyArea(dpy, *src, *dst, gc, (int)(x * xscale), sy, 1, 1, x, y);
        }
        break;
    case 1:
        /* fast */
        for (x = dst_w - 1; x >= 0; x--)
            XCopyArea(dpy, *src, *dst, gc, (int)(x * xscale), 0, 1, src_h, x, 0);
        for (y = dst_h - 1; y >= 0; y--)
            XCopyArea(dpy, *dst, *dst, gc, 0, (int)(y * yscale), dst_w, 1, 0, y);
        break;
    default:
        break;
    }

    XFreeGC(dpy, gc);

    timespec_get(&end, TIME_UTC);
    diff = (end.tv_sec + (double)end.tv_nsec / 1000000000) - (start.tv_sec + (double)start.tv_nsec / 1000000000);
    printf("%lf sec\n", diff);
}

/* update and draw */
static void
update(Display *dpy, Drawable win, GC gc, double ang, Pixmap pix, Pixmap buf)
{
    XWindowAttributes xgwa;
    int r, x, y;
    int buf_w, buf_h;

    /* get window attributes */
    XGetWindowAttributes(dpy, win, &xgwa);
    buf_w = xgwa.width;  /* window width */
    buf_h = xgwa.height; /* window height */

    scale_pixmap(dpy, win, &pix, &buf, XPM_W, XPM_H, buf_w, buf_h);

    r = 64;
    x = 0;
    /* x = r * cos(deg2rad(ang)); */
    y = r * sin(deg2rad(ang));

    /* clear window */
    XClearWindow(dpy, win);

    /* draw image (Pixmap) */
    XSetGraphicsExposures(dpy, gc, False);
    XCopyArea(dpy, buf, win, gc, 0, 0, buf_w, buf_h, x, y); /* copy Pixmap */
}

int main(void)
{
    Display *dpy;
    Window root;
    Window win;
    int screen;
    int display_w, display_h;
    XEvent evt;
    unsigned long black, white;
    XWindowAttributes xgwa;
    GC gc;
    Pixmap pix;
    Pixmap buf;
    int busy_loop;
    double ang;

    /* open display */
    dpy = XOpenDisplay(NULL);
    root = DefaultRootWindow(dpy);
    screen = DefaultScreen(dpy);

    /* get display size */
    display_w = DisplayWidth(dpy, screen);
    display_h = DisplayHeight(dpy, screen);
    printf("Screen: %d x %d\n", display_w, display_h);

    /* get color black and white */
    white = WhitePixel(dpy, screen);
    black = BlackPixel(dpy, screen);

    /* create window */
    win = XCreateSimpleWindow(dpy, root,
                              WIN_X, WIN_Y, display_w, display_h, BORDER,
                              white, black);

    XSelectInput(dpy, win, KeyPressMask | ExposureMask);

    XMapWindow(dpy, win);

    /* wait display window */
    do
    {
        XNextEvent(dpy, &evt);
    } while (evt.type != Expose);

    XMoveWindow(dpy, win, WIN_X, WIN_Y);

    /* create graphics context */
    gc = XCreateGC(dpy, win, 0, 0);
    XSetGraphicsExposures(dpy, gc, False);

    /* load image */
    if (XpmCreatePixmapFromData(dpy, win, XPM_NAME, &pix, NULL, NULL))
    {
        fprintf(stderr, "Error not load xpm.");
        return 1;
    }

    XGetWindowAttributes(dpy, win, &xgwa);
    buf = XCreatePixmap(dpy, win, display_w, display_h, xgwa.depth);
    XSetForeground(dpy, gc, get_color(dpy, "green"));
    XFillRectangle(dpy, buf, gc, 0, 0, display_w, display_h);

    ang = 0;

    busy_loop = 1;
    while (busy_loop)
    {
        /* event */
        while (XPending(dpy))
        {
            XNextEvent(dpy, &evt);
            switch (evt.type)
            {
                /*
            case Expose:
                if (evt.xexpose.count == 0)
                {
                }
                break;
                */
            case KeyPress:
                busy_loop = 0;
                break;
            default:
                break;
            }
        }

        update(dpy, win, gc, ang, pix, buf);
        ang += 4.0;
        if (ang >= 360)
            ang -= 360;

        XFlush(dpy);
        usleep(16 * 1000); /* wait */
    }

    XFreePixmap(dpy, pix);
    XFreePixmap(dpy, buf);
    XFreeGC(dpy, gc);

    XDestroyWindow(dpy, win);
    XCloseDisplay(dpy);
    return 0;
}

使用画像は以下。

_bg01.xpm
_bg02.xpm


コンパイルは以下。
gcc 04scale.c -o 04scale -I /usr/include/X11 -lX11 -lXext -lm -lXpm

実行は以下。
./04scale

少し解説。

最初に処理を書いた時は、愚直に、1ドットずつ XCopyArea() を呼んでみたけれど、このやり方だと圧倒的に処理時間がかかってしまって話にならなかった。

        /* slow */
        for (y = 0; y < dst_h; y++)
        {
            int sy = (int)(y * yscale);
            for (x = 0; x < dst_w; x++)
                XCopyArea(dpy, *src, *dst, gc, (int)(x * xscale), sy, 1, 1, x, y);
        }

そこでふと、xscreensaver のソース群の中で目にした処理を思い出した。一旦横方向にのみ引き延ばして、その横方向に引き延ばしたものを縦に引き延ばすというやり方。

        /* fast */
        for (x = dst_w - 1; x >= 0; x--)
            XCopyArea(dpy, *src, *dst, gc, (int)(x * xscale), 0, 1, src_h, x, 0);
        for (y = dst_h - 1; y >= 0; y--)
            XCopyArea(dpy, *dst, *dst, gc, 0, (int)(y * yscale), dst_w, 1, 0, y);

後者は、前者と比べて圧倒的に速かった。時間を測定してみたところ、以下のような感じになった。
  • slow result : 0.510753 sec
  • fast result : 0.000045 sec
  • CPU: AMD Ryzen 5 5600x
  • OS: Ubuntu Linux 20.04 LTS (Windows10 x64 21H2 + VMware Player上で動作)
  • 画面解像度 : 1920 x 1080

考えてみたら、例えば 1920x1080のサイズの場合、前者は XCopyArea() を 2073600回呼んでしまうけど、後者なら3000回呼ぶだけで済むわけで、そのあたりが効いてるのかなと。ただ、Pixmap へのアクセス回数は、横に引き延ばす段階が入ってくる分、増えてしまうはずなのだけど…。Xlib関係はCPUだけが必死に頑張って処理をしてるものと思い込んでたけど、もしかしてGPUが頑張ってる時もあるのだろうか。それとも、Pixmap間の転送は処理が速くなるように何か工夫してるのだろうか。

何にせよ、このぐらいの速さで拡大描画できるなら、一旦ファミコンレベルの解像度の画面を作っておいて、ソレをデスクトップ一杯に拡大描画しても、60FPS程度なら出せるかもしれない。大昔の2Dゲーム画面っぽいスクリーンセーバを作れそうな予感。

2022/06/05追記。 :

Ubuntu Linux 20.04 LTSの場合、xpm画像を扱う時は、libxpm-dev をインストールしておかないとコンパイルできない模様。
sudo apt install libxpm-dev

2022/06/04() [n年前の日記]

#1 [xlib] Xlibについて勉強中その3

_昨日、 Pixmapを拡大縮小する関数を書いて実験していたけれど、本当に毎フレーム拡大縮小処理ができているのかが気になってきたので、見た目で分かるように処理を書き換えてみた。

環境は、Ubuntu Linux 20.04 LTS。Windows10 x64 21H2 + VMware Player 上で動かしている。

結果はこんな感じに。それなりの速度で拡大縮小できていることが視覚的にも分かった。

ソース。 :

ソースは以下。

_06scale3.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <math.h>
#include <time.h>

/* include xpm binary */
#include "bg01.xpm"
#define XPM_W 640
#define XPM_H 360
#define XPM_NAME bg01_xpm

/*
#include "bg02.xpm"
#define XPM_W 320
#define XPM_H 180
#define XPM_NAME bg02_xpm
*/

/* kind. 0: slow, 1: fast */
#define SCALING_KIND 1

#define WIN_X 0
#define WIN_Y 64
#define BORDER 0

#ifndef M_PI
#define M_PI 3.14159265358979
#endif

#define deg2rad(x) (x * M_PI / 180.0)

/* get a color value from a color name */
static unsigned long
get_color(Display *dpy, char *colorname)
{
    Colormap cmap;
    XColor near_color, true_color;

    cmap = DefaultColormap(dpy, DefaultScreen(dpy));
    XAllocNamedColor(dpy, cmap, colorname, &near_color, &true_color);
    return near_color.pixel;
}

/* scale Pixmap */
static void
scale_pixmap(Display *dpy, Drawable win,
             Pixmap *src, Pixmap *dst,
             int src_w, int src_h, int dst_w, int dst_h)
{
    GC gc;
    float xscale, yscale;
    int x, y;
    struct timespec start, end;
    double diff;

    xscale = (float)src_w / dst_w;
    yscale = (float)src_h / dst_h;

    gc = XCreateGC(dpy, win, 0, 0);
    XSetGraphicsExposures(dpy, gc, False);

    timespec_get(&start, TIME_UTC);

    switch (SCALING_KIND)
    {
    case 0:
        /* slow */
        for (y = 0; y < dst_h; y++)
        {
            int sy = (int)(y * yscale);
            for (x = 0; x < dst_w; x++)
                XCopyArea(dpy, *src, *dst, gc, (int)(x * xscale), sy, 1, 1, x, y);
        }
        break;
    case 1:
        /* fast */
        for (x = dst_w - 1; x >= 0; x--)
            XCopyArea(dpy, *src, *dst, gc, (int)(x * xscale), 0, 1, src_h, x, 0);

        if (src_h <= dst_h)
        {
            for (y = dst_h - 1; y >= 0; y--)
                XCopyArea(dpy, *dst, *dst, gc, 0, (int)(y * yscale), dst_w, 1, 0, y);
        }
        else
        {
            for (y = 0; y < dst_h; y++)
                XCopyArea(dpy, *dst, *dst, gc, 0, (int)(y * yscale), dst_w, 1, 0, y);
        }
        break;
    default:
        break;
    }

    XFreeGC(dpy, gc);

    timespec_get(&end, TIME_UTC);
    diff = (end.tv_sec + (double)end.tv_nsec / 1000000000) - (start.tv_sec + (double)start.tv_nsec / 1000000000);
    printf("%lf sec\n", diff);
}

/* update and draw */
static void
update(Display *dpy, Drawable win, GC gc,
       Pixmap pix, Pixmap buf,
       int new_w, int new_h)
{
    XWindowAttributes xgwa;
    int r, x, y;
    int scrw, scrh;

    /* get window attributes */
    XGetWindowAttributes(dpy, win, &xgwa);
    scrw = xgwa.width;  /* window width */
    scrh = xgwa.height; /* window height */

    scale_pixmap(dpy, win, &pix, &buf, XPM_W, XPM_H, new_w, new_h);

    /* centering */
    x = scrw - ((scrw + new_w) / 2);
    y = scrh - ((scrh + new_h) / 2);

    /* clear window */
    XClearWindow(dpy, win);

    /* draw image (Pixmap) */
    XSetGraphicsExposures(dpy, gc, False);
    XCopyArea(dpy, buf, win, gc, 0, 0, new_w, new_h, x, y);
}

int main(void)
{
    Display *dpy;
    Window root;
    Window win;
    int screen;
    int display_w, display_h;
    XEvent evt;
    unsigned long black, white;
    XWindowAttributes xgwa;
    GC gc;
    Pixmap pix;
    Pixmap buf;
    int busy_loop;
    double new_w, new_h;
    double dx;

    /* open display */
    dpy = XOpenDisplay(NULL);
    root = DefaultRootWindow(dpy);
    screen = DefaultScreen(dpy);

    /* get display size */
    display_w = DisplayWidth(dpy, screen);
    display_h = DisplayHeight(dpy, screen);
    printf("Screen: %d x %d\n", display_w, display_h);

    /* get color black and white */
    white = WhitePixel(dpy, screen);
    black = BlackPixel(dpy, screen);

    /* create window */
    win = XCreateSimpleWindow(dpy, root,
                              WIN_X, WIN_Y, display_w, display_h, BORDER,
                              white, black);

    XSelectInput(dpy, win, KeyPressMask | ExposureMask);

    XMapWindow(dpy, win);

    /* wait display window */
    do
    {
        XNextEvent(dpy, &evt);
    } while (evt.type != Expose);

    XMoveWindow(dpy, win, WIN_X, WIN_Y);

    /* create graphics context */
    gc = XCreateGC(dpy, win, 0, 0);
    XSetGraphicsExposures(dpy, gc, False);

    /* load image */
    if (XpmCreatePixmapFromData(dpy, win, XPM_NAME, &pix, NULL, NULL))
    {
        fprintf(stderr, "Error not load xpm.");
        return 1;
    }

    XSetForeground(dpy, gc, get_color(dpy, "green"));

    XGetWindowAttributes(dpy, win, &xgwa);
    buf = XCreatePixmap(dpy, win, display_w, display_h, xgwa.depth);
    XFillRectangle(dpy, buf, gc, 0, 0, display_w, display_h);

    new_w = display_w;
    new_h = display_h;
    dx = -6;

    busy_loop = 1;
    while (busy_loop)
    {
        /* event */
        while (XPending(dpy))
        {
            XNextEvent(dpy, &evt);
            switch (evt.type)
            {
                /*
            case Expose:
                if (evt.xexpose.count == 0)
                {
                }
                break;
                */
            case KeyPress:
                busy_loop = 0;
                break;
            default:
                break;
            }
        }

        update(dpy, win, gc, pix, buf, new_w, new_h);

        new_w += dx;
        if (new_w <= 32)
        {
            new_w = 32;
            dx *= -1;
        }
        if (new_w >= display_w)
        {
            new_w = display_w;
            dx *= -1;
        }

        new_h = display_h * new_w / display_w;

        XFlush(dpy);
        usleep(16 * 1000); /* wait */
    }

    XFreePixmap(dpy, pix);
    XFreePixmap(dpy, buf);
    XFreeGC(dpy, gc);

    XDestroyWindow(dpy, win);
    XCloseDisplay(dpy);
    return 0;
}

使用画像は以下。

_bg01.xpm
_bg02.xpm


Makefileの例。

_Makefile


コンパイルは以下。実行バイナリ 06scale3 が生成される。
gcc 06scale3.c -o 06scale3 -I /usr/include/X11 -lX11 -lXext -lm -lXpm


実行は以下。
./06scale3

バグを見つけた。 :

_昨日、 書いた処理は、少しバグがあった。元画像より小さい縦サイズに縮小しようとすると、見た目がおかしなことになってしまう。



以下の処理では、(縦方向の)拡大はともかく、縮小はおかしなことになる。参照される部分を上書きコピーで破壊してしまう。

_05scale2.c
        /* fast */
        for (x = dst_w - 1; x >= 0; x--)
            XCopyArea(dpy, *src, *dst, gc, (int)(x * xscale), 0, 1, src_h, x, 0);
        for (y = dst_h - 1; y >= 0; y--)
            XCopyArea(dpy, *dst, *dst, gc, 0, (int)(y * yscale), dst_w, 1, 0, y);


以下のように修正。拡大時と縮小時で処理を分けて、拡大時は下から上に向かってスキャン、縮小時は上から下に向かってスキャンしていけばおかしなことにはならない。

        /* fast */
        for (x = dst_w - 1; x >= 0; x--)
            XCopyArea(dpy, *src, *dst, gc, (int)(x * xscale), 0, 1, src_h, x, 0);

        if (src_h <= dst_h)
        {
            for (y = dst_h - 1; y >= 0; y--)
                XCopyArea(dpy, *dst, *dst, gc, 0, (int)(y * yscale), dst_w, 1, 0, y);
        }
        else
        {
            for (y = 0; y < dst_h; y++)
                XCopyArea(dpy, *dst, *dst, gc, 0, (int)(y * yscale), dst_w, 1, 0, y);
        }

#2 [nitijyou] 自転車でパソコン工房まで行ってきた

天気予報では、今日は雨が降らないとのことだったので、電動自転車に乗ってパソコン工房郡山店まで行ってきた。

新しい電動自転車を購入する際、郡山まで行ける程度のバッテリー容量が欲しいと要望を出したのだけど、その願いは叶ったのかどうかを一度確認してみたかった。ちなみに以前の電動自転車は、帰り道でバッテリーが切れて、しかも前輪駆動だったから、バッテリーが切れると鬼のようにペダルが重くなって地獄だった…。

一応、時間や、走った道をメモ。

09:46-10:42 (55分) : 家からパソコン工房まで、旧・旧国道沿い(須賀川-二本松線、県道355号)を移動。バッテリー残り90%の状態で出発。行きは、Autoモードで ―― アシストが弱いけれどバッテリーの持ちはそこそこらしいモードを利用。パソコン工房郡山店に着いた時点で残り80%。たった10%のバッテリー容量で移動できてしまって驚いた。以前の電動自転車とは違うな…。

ヨドバシカメラ郡山店に寄って、そこから帰ろうとした時点で、残り70%になった。

12:32-13:20 (47分) : ヨドバシカメラから安積永盛のHARD-OFFまで、旧・国道4号線(県道17号線、郡山の街中を通っている道路)を移動。店についてからタイマーを止めるのを忘れてたので、実際はもっと短いはず。

13:27-14:04 (36分) : HARD-OFFから家まで、現・国道4号線(バイパス)を移動。ここからはパワーモードで移動。家に到着した時点で残り60%。

つまり、行きは55分、帰りは83分かかった。ただ、帰りは、他の店に寄った上に、車向けの道を ―― 登ったり下りたりが頻繁にある道を選んで走ってきたので、単純に比較できない。

何にせよ、新しい電動自転車の、今現在のバッテリーの状態なら、バッテリーの1/3程度の容量で、郡山まで行って帰ってくることができると分かった。

ただ、体のほうが全然持たない。右膝が痛い…。行きの時点で、安積永盛のあたりで膝に違和感が…。バッテリーはともかく、自分の体の状態からして、気軽に行けるわけではないなと…。

余談。 :

帰りにHARD-OFFに寄ったけれど、相変わらずヨサゲなPCケースが見当たらなくて残念。通販なら新品でも、一番安いミニタワーPCケースなら3,000円ぐらいで買えるけど。見た目が傷だらけで、今となっては巨大に感じるミドルタワーATXケースで、フロントベイカバーがポツポツ無くなっている中古のケースが3,300円とか5,500円では…。なかなか厳しい。

#3 [pc] パソコン工房の買取サービスを利用してみた

パソコン工房郡山店の店頭で、買取サービスを初めて利用してみた。一応、流れをメモ。

今回持ち込んだ品は以下の3点。
レジの前で「こちらの店舗で買取サービスはしてますか?」と尋ねたところ、店員さんが買取用カウンターに誘導してくれた。担当者が不在だったけれど、用紙を渡されて記入。名前、生年月日、住所、電話番号、職業、を書いた気がする。

10:47頃に受け付け。「査定に1時間ほどかかる」と言われた。「11:47〜」とメモされた、申し込み用紙の控えを渡された。

店内をウロウロしたり、隣のダイソーで買い物を済ませたりして時間を潰した。

11:47頃、買取サービス用カウンターの前に行った。今度は担当者が居たようで、査定内容が印刷された用紙を見せられつつ、査定額を伝えられた。それでいいですと答えたら、担当の方がそこから5分ほどPCに何かを入力。その後、譲渡証明書の控えと、現金を受け取った。

買取金額は以下。

余談。 :

相場より高いのか、それとも安いのか、そのあたりはよく分からない…。譲渡証明書の控えに査定内容が書いてあるのだけど、読み方が分からなくて…。Ryzen 3 2200G が特に分からない。買取基準金額と買取査定額が1,000円と書かれてるけど、最後の品目名で6,600円になってる。最初に1,000円の記述が目に入って、「え。そんなに低いの」と驚いてしまったけど、そういうわけではなかったらしい。

質問したかったけれど、後ろに他の買取客が並んでたので、迷惑になったらいかんよなと、質問しないまま店を出た。ただ、ちょっともやもやする…。自分はちゃんと奇麗な状態で持ち込めたのか、そうでもなかったのか等々、一言二言でいいから聞いておきたかったなと…。

譲渡証明書には「Sofmap U-FRONT」と書かれてた。パソコン工房の実店舗で買取してるけど、パソコン工房が買取してるわけではないということだろうか。パソコン工房の買取なら、土日祝日は買取価格上乗せ云々とサイトに書いてあったけど、郡山店ではSofmapの買取サービスを提供してるからそういうオマケは無し、ということかもしれない。

持ち込む前は、やれここが汚れてるだのなんだの言われて、下手すると全部合わせても3千円にすらならないのではと結構不安だったけど。パソコン工房やドスパラのサイトで確認した際の額と大体似たような結果になってホッとした。わざわざIPAやキムワイプを新規購入して掃除した甲斐はあった…のかどうかそのあたり結局何も分からないままだけど、まあ、結果には満足。

もっとも、この額では現行CPUの最低ランク品すら購入できないわけで、以前、弟が、「PCパーツを買取サービスに持ち込むぐらいなら欲しい人にタダでプレゼントしたほうがマシ」と言った感覚も、なんだかちょっと分かったような気もする。

とは言え今回、M/Bさえあれば手元に残してサブPC用として使っただろうけど、空いてる Socket AM4対応M/Bは無く、このままだとCPUもメモリも使われないまま埃を被ってしまうので、だったら別の誰かの手に渡ったほうが、と思った次第。

#4 [pc] USB2.0ハブを購入

ヨドバシカメラ郡山店でUSB2.0ハブを購入。
_4ポートUSB2.0ハブ ホワイト | ナカバヤシ株式会社

値札は\485-だったのだけど、レジに持っていったら580円と言われて困惑。さては税抜き価格の値札だったかと、その場では思いながらそのまま帰ってきてしまったけれど、帰宅後、ヨドバシのサイトで確認したら580円だった。この値段で正しいらしい。もしかすると値札が古いままだったのかもしれない。店頭で購入するとこういうことがあるのだな。今後は極力、店頭では買わないようにしようか…。もっとも、初期不良品に当たった時のことを考えたら、件の店舗は距離的に遠いので、そもそもそこで買うのはマズかった気もする。今回ちょっと考え無しだったかも。

メインPCの背面に差して、キーボードとプリンタを接続して動作確認したが、どちらも問題無く動いてくれた。これで、現在使用しているM/B、GIGABYTE B450M S2H rev.1.0 の、USB2.0ポートが不足している問題は解決できそう。

ただ、USB2.0ハブごと抜けて動作がおかしくなる可能性はあるなと。キーボードやプリンタの動作がおかしくなったらUSBハブの差し込み具合を確認のこと>未来の自分。まあ、そのうち絶対忘れてドタバタしそうだな…。

2022/06/05() [n年前の日記]

#1 [xlib] Xlibでラスタースクロールっぽい処理

_昨日、 _一昨日 と、Xlib を使って小さい画面(Pixmap)をディスプレイ一杯に拡大して描画できるか試してたけど、小さい画面を対象にして処理するならラスタースクロールっぽい処理もできるかなと思えてきたので試してみた。

環境は、Ubuntu Linux 20.04 LTS。Windows10 x64 21H2 + VMware Player 上で動かしている。

こんな感じになった。




動画の解像度が小さくて見辛いかもしれんけど、画面下半分がチラチラと動いてることぐらいは分かるかなと。元画面は640x360で、ディスプレイ解像度は1440x900。

ソース。 :

ソースと使用画像は以下。

_08rasterscroll.c
_bg03.xpm.txt


コンパイルは以下。
gcc 08rasterscroll.c -o 08rasterscroll -I /usr/include/X11 -lX11 -lXext -lm -lXpm

実行は以下。
./08rasterscroll

ラスタースクロールと言っても、あくまでモドキで…。画面の下半分は、元画像を参照する際のx座標をcos()で変化させて、縦幅1ドットずつ、Pixmap から Pixmap にコピーしてるだけ。

元画像は、640x360の左右に32ドットずつ足して、704x360で作成。左右にcos()で揺らして描画するので、横640ドットぴったりで作ってしまうと描画されない部分が出てきてしまう。左右に少しだけ絵を追加しておかないといけない。

#2 [linux][windows] pecoの代わりにfzyを使ってみた

Windows10 x64 21H2 + MSYS2上で peco を使おうとしたけれど動いてくれなかった。ググってみたら MSYS2上で(というか mintty上で?) peco は動かないらしい。ただ、peco の代わりに fzy というツールが使えるという話を見かけたので導入してみた。

余談。pecoについては以下でメモしてあった。

_mieki256's diary - pecoを導入してみた

使い方。 :

  • pcd add . : カレントディレクトリを登録リストに追加。
  • pcd edit : 登録リストファイルを編集。
  • pcd : 登録リストからインクリメンタルサーチで絞り込んでディレクトリ移動。
  • cdp : カレントディレクトリ内からインクリメンタルサーチで絞り込んでディレクトリ移動。

インストール。 :

pacman -S fzy
$ fzy --version
fzy 1.0 (C) 2014-2018 John Hawthorn

pcdをインストール。 :

pcd を使えば、自分で登録したディレクトリリストをインクリメンタルサーチで絞り込んでディレクトリ移動ができる。

登録リストファイル、~/.peco/.peco-cd ファイルを作成。
mkdir ~/.peco
touch ~/.peco/.peco-cd

~/.bash_pcd を作成。以下を記述。
  • peco を呼び出している場所で fzy を呼ぶように変更。
  • pcd edit と打った際は nanoエディタを呼び出すように変更。

function pcd() {
  local PCD_FILE=$HOME/.peco/.peco-cd
  local PCD_RETURN
  if [ $1 ] && [ $1 = "add" ]; then
    if [ $2 ]; then
      ADD_DIR=$2
      if [ $2 = "." ]; then
        ADD_DIR=$(pwd)
      fi
      echo "add $ADD_DIR to $PCD_FILE"
      echo $ADD_DIR >> $PCD_FILE
    fi
  elif [ $1 ] && [ $1 = "edit" ]; then
    if [ $EDITOR ]; then
       $EDITOR $PCD_FILE
    else
       # editor $PCD_FILE
       nano $PCD_FILE
    fi
  elif [ $1 ] && [ $1 = "." ]; then
    PCD_RETURN=$(/bin/ls -F | grep /$ | fzy -l 20)
  else
    PCD_RETURN=$(cat $PCD_FILE | fzy -l 20)
  fi
  if [ $PCD_RETURN ]; then
    cd $PCD_RETURN
  fi
}

function cdp {
  local dir="$( find . -maxdepth 1 -type d | sed -e  's;\./;;' | sort | fzy -l 20 )"
  if [ ! -z "$dir" ] ; then
    cd "$dir"
  fi
}

~/.bashrc に以下を追記。~/.bash_pcd が存在していたら読み込むようにする。
if [ -f "${HOME}/.bash_pcd" ]; then
  source "${HOME}/.bash_pcd"
fi

source ~/.bashrc で反映。

参考ページ。 :

#3 [zatta] ユニクロで買い物をして驚いた

ユニクロに寄ってTシャツを買ってきたのだけど、初めてセルフレジを使ってみて驚いてしまった。商品を指定の場所に置いただけで、何点置かれてるか、何が置かれてるかが自動で液晶画面に出てきて、「えっ。なんで?」と。これどうやってるんだ…。バーコードをあらゆる方向から読み取ってるんだろうか。いや、でも、バーコードが必ず見えるような状態で置いてなかったと思うけど…。もしかして、魔法…?

夕食時にその話をしたところ、妹曰く「ICタグが導入されてる」「特許絡みで裁判になってた」とのことで。

_ユニクロの「レジ待ち行列」を解消したRFIDタグの威力 | Agenda note (アジェンダノート)
_ユニクロだからできるRFIDタグの破壊力 | GASKET
_ユニクロを特許侵害で訴えた下請け社長語る「ゼロ円でライセンスを要求された」 | Close-Up Enterprise | ダイヤモンド・オンライン
_ユニクロのセルフレジ特許権侵害訴訟を現状整理する 知財高裁で勝っても戦況が明るくない理由(1/2 ページ) - ITmedia NEWS
_ユニクロのセルフレジ特許侵害訴訟、和解へ 互いに訴訟・請求を取り下げ - ITmedia NEWS
_RFID - Wikipedia

RFID/RFIDタグなる技術があるのか…。ゴミ箱に捨ててしまったタグを発掘して光に透かしてみたら、たしかに何かが中に入ってた。スゴイな…。「今頃知ったのかよ」と笑われそうだけど、勉強になりました。あまりに驚いてしまったので、こうしてメモ。

2022/06/06(月) [n年前の日記]

#1 [python] ファイルサイズでソートしてリネームするPythonスクリプトを作成

複数の画像ファイルに対して、手作業でリネームしていく作業が面倒臭くなってきたので、自動化すべくPythonスクリプトを書いた。hoge_1920x1080_[0-9]_*.jpg というファイル群に対し、ファイルサイズを元にしてリネームしていくスクリプト。以下のような感じでリネームする。

python sortrename.py PATH0 PATH1 PATH2
hoge_1920x1080_2_.jpg -> hoge_1920x1080_2.jpg
hoge_1920x1080_2.jpg -> hoge_1920x1080_3.jpg
hoge_1920x1080_3.jpg -> hoge_1920x1080_4.jpg

動作確認環境は、Windows10 x64 21H2 + Python 3.9.13 64bit。

まあ、自分しか使わない・使えないスクリプトだけど、その手の処理の参考事例になりそうな気もするし、一応メモ。

_sortrename.py
import sys
import os
import re


def main():
    renamelist = []

    cnt = len(sys.argv)
    if cnt <= 2:
        print("Error : Please select two or more files.")
    else:
        lst = []
        for i in range(1, cnt):
            path = sys.argv[i]
            size = os.path.getsize(path)
            lst.append([path, size])

        slist = sorted(lst, key=lambda x: x[1])
        count = len(slist) + 1
        for v in slist:
            path, size = v
            dirname = os.path.dirname(path)
            filename = os.path.basename(path)
            newfile = re.sub("_\d_*\.jpg", "_%d.jpg" % count, filename)
            newpath = os.path.join(dirname, newfile)
            print("%s -> %s : %d byte" % (filename, newfile, size))
            renamelist.append([path, newpath])
            count -= 1

    if len(renamelist) >= 1:
        v = input("\nRename ? (Y/n)")
        if v == "y" or v == "Y" or v == "":
            for v in renamelist:
                oldpath, newpath = v
                os.rename(oldpath, newpath)
            print("\n%d file renamed." % len(renamelist))
        else:
            print("\nCancel.")

    v = input("\nPush any key")


if __name__ == '__main__':
    main()

2022/06/07追記。 :

このスクリプトは、リネーム後のファイル名が既に存在していた場合、妙な動作になりそうだなと気づいた。おそらく、「リネームできねえよ」とエラーを出しそうな気もするけど…。一旦、重複しないであろう仮ファイル名でリネームしておいて、そこから本当に変更したいファイル名にリネームしたほうがいいのかもしれない。元ファイル名と新ファイル名の他に、仮ファイル名も作ってリストに入れておけば処理ができそう。

2022/06/07(火) [n年前の日記]

#1 [moho] Moho 13.5.5が公開されたっぽい

_Moho 13.5.5 is available. Update now! - Moho Forum

Windows版のインストーラ、Moho1355_Win.exe をDLして実行してみたところ、Moho 13.5.4 に対して上書きアップデートできた。

修正内容は…相対パスが云々と書いてあるような…よく分からん…。

#2 [linux][ubuntu] VMware Player上のUbuntuにディスプレイ解像度を追加したい

Windows10 x64 21H2 + VMware Player上で Ubuntu Linux 20.04 LTS + Xfce4 を動かして色々実験してるけど、画面解像度を 1600x900 (16:9) にしたいなと。しかし、Ubuntu のディスプレイ設定で選べるリストの中に、1600x900の解像度は無い。そんなわけで、追加する方法をググって試してみた。

参考ページ。 :

設定手順。 :

どうやら、cvt でパラメータを調べて、xrandr で解像度を追加すればいいらしい。

cvt 1600 900 59
or
cvt 1600 900

$ cvt 1600 900 59
# 1600x900 58.93 Hz (CVT) hsync: 55.04 kHz; pclk: 116.25 MHz
Modeline "1600x900_59.00"  116.25  1600 1696 1856 2112  900 903 908 934 -hsync +vsync

$ cvt 1600 900
# 1600x900 59.95 Hz (CVT 1.44M9) hsync: 55.99 kHz; pclk: 118.25 MHz
Modeline "1600x900_60.00"  118.25  1600 1696 1856 2112  900 903 908 934 -hsync +vsync

xrandr --newmode "1600x900" 116.25  1600 1696 1856 2112  900 903 908 934 -hsync +vsync
or
xrandr --newmode "1600x900" 118.25  1600 1696 1856 2112  900 903 908 934 -hsync +vsync

xrandr --addmode Virtual1 "1600x900"

これで、ディスプレイ設定の解像度のリストの中に 1600x900 が増えて、1600x900を選んで「適用」をクリックしたら、それらしい解像度に変わってくれた。

ただ、OSを再起動すると設定は消えてしまう。

/etc/profile.d/ 以下に 何かしらの .sh を作成しておいて、そこに xrandr --newmode ... と xrandr --addmode ... を記述しておくことで、起動時から該当解像度を有効にしておくことができるらしい。

試しに、mydisplay.sh というファイルを作成して、そこに前述の内容を記述してみた。

sudo vi /etc/profile.d/mydisplay.sh
xrandr --newmode "1600x900" 118.25  1600 1696 1856 2112  900 903 908 934 -hsync +vsync
xrandr --addmode Virtual1 "1600x900"

Ubuntu 再起動後も 1600x900 が選べるようになった。

2022/06/08(水) [n年前の日記]

#1 [xlib] Xlibでタイルマップを描画

Xlibを使ってタイルマップ描画ができるか試してみた。タイルマップ描画ができれば、一気に2Dゲーム風の画面になる。はず。たぶん。おそらく。

環境は、Ubuntu Linux 20.04 LTS。Windows10 x64 21H2 + VMware Player 16.2.3 build-19376536 上で動かしてる。

こんな感じになった。




元画面は640x360。それを、ディスプレイ解像度 1440x900に拡大して描画してる。

BG相当を3枚も描画できれば、2Dゲーム風の画面を作るには十分過ぎるかなと。ファミコンやPCエンジンならBGが1面、メガドライブでもBGが2面だったし。

スーファミ(SFC)は何枚まで出せたんだろう。ググってみたらMode0からMode7まであったみたいだけど。Mode0が4枚まで出せたと書いてあるな…。ただ、1パレットが4色か…。

_Super Nintendo Entertainment System - Wikipedia

ソース。 :

ソースと使用画像は以下。

_09tilemap1.c
_scifi_bg_chip.xpm

一応、元画像(.png)も置いておきます。CC0 / Public Domain ってことで自由に使ってください。1チップ(1タイル) 64x64ドットが、8x8個並んでます。インデックスカラー画像、48色。だったはず。たぶん。

scifi_bg_chip.png

_scifi_bg_chip.png

コンパイルは以下。
gcc 09tilemap1.c -o 09tilemap1 -I /usr/include/X11 -lX11 -lXext -lm -lXpm

実行は以下。
./09tilemap1


少し解説。

今回、今までのソースと違って、State という構造体を定義して、処理で利用する変数を全部その State の中に入れることにした。各関数に引数を渡すあたりの記述が面倒臭かったので、State のポインタを渡して全部の変数にアクセスできるようにしてしまえ、みたいな。

1枚の xpm を分割して複数の Pixmap を作るあたりは、split_pixmaps() という関数で処理してる。

ちょっとハマった点。マスク用のPixmapをコピー(XCopyArea)する時は、マスク用のGCを別途用意しないといけない。そのあたりを知らなかったもので、RGB部分とマスク部分の両方を同じGCで処理しようとして、エラーが出て悩んでしまった。RGB部分を持ってる Pixmap と、マスク用の Pixmap は深度(Depth)が違うので、GCも深度別に用意しないといけないのだとか。

タイルマップを描画するあたりは、draw_tilemap() で処理してる。

本来、この手の処理は、BG用の大きなバッファ(Pixmap)を用意しておいて、スクロールに応じて書き換えが必要な場所だけ書き換えて、そのBG用の大きなバッファをソースにして表示用バッファに描画、といった処理をしたほうが速度は稼げるのだけど。今回は面倒臭かったので、毎フレーム、愚直に、チップ(タイル)を何十個も描画し直すやり方を選んでしまった。まあ、これはこれで、BGのアニメーションはしやすかったりもするけれど…。

余談。タイルマップ用のデータは、Tiled 1.8.5 64bit版を使わせてもらいました。ありがたや。

_Tiled | Flexible level editor

ちなみに、Tiled から json でエクスポートするとタイル番号は1から始まるけれど、csv でエクスポートするとタイル番号は0から始まるのですな…。知らなかった…。

Core2Duo機でも動作確認してみた。 :

Intel Core2Duo E8400 (2コア, 3.0GHz) + NVIDIA GeForce 9500GT + Ubuntu Linux 20.04 LTS、ディスプレイ解像度 1920x1200 の環境でも動作確認してみた。今となっては非力なCPU+GPUのはずだけど、それでも大体 3〜 5msec で動いてたので、この程度の描画処理なら60FPSで動かせそうな気配がする。

もっとも、Ryzen 5 5600X + VMware Player + Ubuntu Linux 20.04 LTS、ディスプレイ解像度 1920x1080 では 0.9〜1.1msec で動いたので、Ryzen 5 5600X と比べると Core2Duo E8400 は 1/5 の速度、ということになるのだろうか。いやまあ、仮想PC上ではなくて実機上で動かしたら、また違う処理時間になるかもしれないけど。

OpenGLを使うべきだろうか。 :

Xlib についてググったら、1985年頃に登場したものらしくて。

_Xlib - Wikipedia

そんなに古いライブラリ(?)を使ってもこのくらいの描画はできるのだな、という感想を持ったけれど、さすがにこういう感じの描画をしたいなら OpenGL だか glx だかを使うべきなのでは、という気もしてきた。

_GLX - Wikipedia

xscreensaver のドキュメント内でも、「高速な描画をしたいなら OpenGL/glx を使うことを検討すべきやで」みたいなことが書いてあった気もするし。

#2 [pc] キーボードのキーを押しても電源がONにならない

メインPCの電源スイッチの塗装が微妙に剥げてきた。つまりはそれだけ何回も、電源スイッチを押してるわけで、これはなんだかちょっとアレだなと。

そこで、キーボードを押したら電源が入るようにできないかと思い立った。メインPCのM/B、GIGABYTE B450M S2H (rev. 1.x) のBIOS設定画面を表示して、該当項目を有効にしてみた。

しかし、キーボードを押しても電源が入らない。何故。USBハブを介して接続しているせいだろうか。

であれば、M/Bに直接接続しているマウスならどうだろう。マウスのダブルクリックをしたら電源が入るようにBIOS設定を変えてみた。しかし、こちらも効果無し。

ググってみたら、おそらくだけど理由が分かってきた。

_[マザーボード] BIOS設定で「PS/2キーボードによる電源オン」を有効にする方法 | サポート 公式 | ASUS 日本
_価格.com - 『キーボードからの電源ON』 GIGABYTE GA-EP35-DS3R Rev.2.1 のクチコミ掲示板
_Power on by keyboard/mouse doesn't work on my Gigabyte motherboard - Super User

キーボードやマウスで電源ONにする機能は、「PS/2キーボードなら使えます」と言う話だった模様。自分が使ってるキーボードも、マウスも、USB接続だった…。

親父さん用PCに使っていた Mini-ITX M/B、ASUS PRIME A320I-K と BIOSTAR RACING B350GTN は、キーボードのキーを押しても電源ONになったので、てっきり自分の使ってる GIGABYTE B450M S2H (rev. 1.x) もそのように設定できるだろうと思い込んでた。でも、思い返してみると、親父さんが使ってるキーボードは、PS/2キーボードだった気もする。メンテナンス時に、あの丸いPS/2コネクタを背面に差していたような…。いや、どうだったかな。自信が無くなってきた。そのうち確認してみよう…。

2022/06/09(木) [n年前の日記]

#1 [xscreensaver] xscreensaverでタイルマップ描画

_昨日、 Xlibを使ってタイルマップ描画ができるか試してそれっぽくなったけれど、同じ処理を xscreensaver上でも実現できるのか気になったので試してみた。

環境は、Ubuntu Linux 20.04 LTS。Windows10 x64 21H2 + VMware Player 16.2.3 build-19376536 上で動かしてる。CPU は Ryzen 5 5600X。VMware に3コアを割り振ってる。

こんな感じになった。




動いてくれて一安心。

ただ、CPU使用率が高い気もする…。1440x900の画面解像度なら20%程度だけど、1920x1080にすると30%ぐらいになる。元画面は640x360で固定してるから…画面解像度まで拡大する処理が結構重いのだろうか。

ちなみに、VMware の設定でCPUコアを1つにして動作確認してみたけれど、3コアを指定した場合とCPU使用率は変わらなかった。xscreensaver は、複数コアが載っている環境でも、使用率の表示結果が違ってくるわけではないっぽい。

ソース。 :

今回は、xscreensaver-6.04.tar.gz をDL・解凍して、中にファイルを追加する形で実験。

_XScreenSaver
_XScreenSaver: Download


ソース(*.c)、マニュアル(*.man)。hacks/ 以下にコピーする。

_hellotilemap.c
_hellotilemap.man


設定画面定義ファイル(*.xml)。hacks/config/ 以下にコピーする。

_hellotilemap.xml


使用画像。hacks/images/ にコピーする。

_scifi_bg_chip.png


使用画像を .h に変換したファイル。hacks/images/gen/ 以下にコピーする。ただ、本来は、make時に *.png から自動生成されるはず。

_scifi_bg_chip_png.h


hacks/Makefile.in や driver/XScreenSaver.ad.in の変更点は以下。「hellotilemap」で検索すれば、どのあたりを修正してるか分かるかなと。

_makefile.in.patch
_xscreensaver.ad.in.patch


一応、xscreensaver に自作のスクリーンセーバを追加して、ビルドする手順もざっくりとメモ。公式リポジトリ版を上書きしちゃうけど…。

mkdir ~/pkg
mkdir ~/pkg/xscreensaver
cd ~/pkg/xscreensaver
wget https://www.jwz.org/xscreensaver/xscreensaver-6.04.tar.gz
tar zxvf xscreensaver-6.04.tar.gz
cd xscreensaver-6.04

# file copy

*.c : copy to hacks/
*.man : copy to hacks/
*.xml : copy to hacks/config/
*.png : copy to hacks/images/
*_png.h : copy to hacks/images/gen/
Makefile.in : copy to hacks/
XScreenSaver.ad.in : copy to driver/

# build

./configure --prefix=/usr --libexecdir=/usr/lib
make
sudo make install

rm ~/.xscreensaver
xscreensaver-settings &


少し解説。

基本的には、 _昨日、 書いた、 _09tilemap1.c をコピペしていっただけ。

ただし、xscreensaver 上では、XClearWindow() を呼んでしまうとエラーになることに気づかなくて結構ハマった。 Pixmap を対象にして中身をクリアしたかっただけなので、XFillRectangle() を使って塗り潰してしまえばOKだった。

#2 [anime] 「DEATH NOTE ディレクターズカット完全決着版 リライト・幻視する神」を視聴

BS12で放送されたソレを録画しておいたので視聴。TVアニメ版の総集編だろうか。

実を言うと、「デスノートか…今更見てもな…」と思いながら見始めたのだけど、とにかく演出がキレッキレで、総集編なのにグイグイと引き込まれてしまった。あらゆる場面で、緩急と言うか、コントラストをかなり意識した演出がされていて、その手管が実に鮮やかで…。このアニメはスゴイ。素晴らしい。

以下、ちょっとネタバレになりそうだけど…。

例えば、主人公が某アイテムを手にして驚くシーン。まずはLが映ってるカットで、おそらくはジェット旅客機が近づいてるのか、「キイイィーン」という音が鳴り始めて。その音が最高潮・最大音量に達したタイミングで、音がスッと消えて、無音+主人公が驚いてる止め絵カットに切り替わる。その直後、主人公を演じる宮野真守氏の絶叫と、膨大かつ細切れな映像(記憶)が次々にフラッシュバック。そこからLのカットに切り替わると、若干静寂気味の客観的な視点というか、環境音が鳴ってるカットに。てな感じで、音の使い方があまりにも上手くて、それだけでもゾクゾクしてしまった。

あるいは、例のポテチシーン。とにかく激しく、スタイリッシュに、手前に効果線を載せながら、ポテチ袋を取り出し、破いて、ペンを走らせて、ポテチを食う主人公。その直後にLが見ているカメラ映像に切り替わるけど、客観的には何もしてないように見える主人公の図が提示される。

とにもかくにもコントラスト。「静」の前には「動」を置き、「動」の前には「静」を置く。そんな見せ方が何度も出てくる。

かつてPIXARを率いていたジョン・ラセター監督が、訪日してインタビューを受けるたびに、「我々は宮崎アニメから緩急(コントラスト)を学んだ」と口にしてたけど。その宮崎駿監督のお膝元のスタジオジブリでは、若手演出家さん達が「緩急? なんですかソレ?」状態のアニメを作り続けて…。しかし、ジブリとは縁もゆかりも無いはずのMADHOUSEで、各演出家さん達がここまでコントラストを駆使したアニメを産み出していたとは…。いや、たぶんこのコントラストのつけ方は宮崎アニメあたりから学んだものではなくて、全然違うところから来ている感じもするのだけど。何にせよ、これは上手いなと…。素晴らしい。

顔芸を最初にやり始めたのは誰なのかしら。 :

そういえば、このアニメは原作からして顔芸も駆使しまくった作品だと思うけど。顔芸って何時頃から使われ始めたのだろう…。

「DB」「聖闘士星矢」では顔芸なんて使ってなかったよな…。「ゴルゴ13」あたりも使ってないし…。手塚治虫作品や藤子F作品でも使ってないだろうし…。

「進撃の巨人」「ゴールデンカムイ」等を思い返すと、もう顔芸は漫画表現として定着してる気もする。始祖は誰なんだろう…?

もしかして永井豪先生か石川賢先生あたりだろうか。デビルマンやゲッターロボでやってそうな…。

2022/06/10(金) [n年前の日記]

#1 [blender] blenderで作業中

2D横スクロールSTGに出てきそうな、グルグル回転する雑魚敵っぽいアニメーション画像が欲しくなってきた。blender 2.93.9 x64 LTS を使ってそれっぽいモデルを作成して画像を作成。使い方をかなり忘れてる…。

とりあえず最終的に2D画像が得られればいいかなと雑な感じでモデルを作ってたけど、そのうち3DCGモデルとして使う場面もありそうな気がしてきた。ローポリモデルとしても使えるように作り直したほうがいいかな…。

#2 [cg_tools] ImageMagickでスプライトシート画像を作成

blender で作業をして、512x512ドット、16枚の、雑魚敵っぽい感じのレンダリング画像が得られた。これを、64x64ドットにリサイズして、タイル状に並べて、1枚のスプライトシートにしたい。

以前作業した時は ImageMagick を使った気がする。日記を検索したら作業手順が見つかった。

_ImageMagick montage の覚書

magick montage で目的は果たせそう。

環境は、Windows10 x64 21H2 + ImageMagick 7.1.0-5 Q16 x64。

Lanczosアルゴリズム+アンシャープマスクで、64x64ドットに縮小しつつ、タイル状に8x2個並べる場合。
magick montage -tile 8x2 -filter Lanczos -resize 62x62 -unsharp 10x5+0.7+0 -geometry +1+1 -background none render\*.png PNG32:spritesheet.png

Cubicアルゴリズム+アンシャープマスクで、64x64ドットに縮小つつ、タイル状に8x2個並べる場合。
magick montage -tile 8x2 -resize 62x62 -unsharp 10x5+0.7+0 -geometry +1+1 -background none render\*.png PNG32:spritesheet.png

成果物。 :

そんな感じで、64x64ドットの画像が並んだスプライトシートが得られたので一応アップロード。CC0 / Public Domain ってことで。何か使い道がありそうなら自由に使ってください。

フルカラー画像(RGBA各8bit)。

spritesheet_lanczos.png
_spritesheet_lanczos.png


GIMP 2.10.30 で読み込んで、アルファチャンネルを1bitにしたもの。レイヤー → 透明部分 → アルファチャンネルのしきい値、で調整した。

spritesheet_alpha1bit.png
_spritesheet_alpha1bit.png


EDGE2 1.16 dev015 で48色に減色、かつ、少しだけドット修正したもの。

spritesheet_48col.png
_spritesheet_48col.png


アニメGIFにしたもの。

spritesheet_animegif.gif


レンダリング画像(512x512、16枚)。

_render.zip


元のblenderファイル(.blend)も一応置いときます。blender 2.93.9 x64 LTS で作成。

_enemytypec02_lowpoly.zip

#3 [nitijyou] 日記をアップロード

2022/05/20を最後に日記をアップロードしてなかったのでアップロード。

2022/06/11() [n年前の日記]

#1 [xlib] Xlibでタイルマップの前に雑魚敵モドキを描画してみた

_昨日、 雑魚敵っぽい画像を作成することができたので、 _先日作成した、 Xlibでタイルマップを描画するプログラムに追加して描画してみた。

環境は、Ubuntu Linux 20.04 LTS。Windows10 x64 21H2 + VMware Player 16.2.3 build-19376536 上で動かしてる。

こんな感じになった。




実際に描画してみたら、自分の中ではコレジャナイ感が沸いてきた。雑魚敵の見た目のボリュームが足りないというか、ほっそりしたデザインは雑魚敵として全然合ってない気がする。その手のゲーム画面を眺めて傾向を調べてみたほうがいいのかもしれない。

でもまあ、Xlibを使っても、2Dゲーム風の画面を作れそうな気配はしてきた。

ソース。 :

ソースは以下。

_10objs.c


使用画像は以下。実際に使ってるのは .xpm。*.xpm は、*.c と同じ場所に置く。

_scifi_bg_chip2.xpm
_scifi_bg_chip2.png


コンパイルは以下。
gcc 10objs.c -o 10objs -I /usr/include/X11 -lX11 -lXext -lm -lXpm


実行は以下。
./10objs

xscreensaverにも組み込んでみた。 :

xscreensaver上でも動くかどうか試してみた。 _先日書いたhellotilemap.c を修正。

_hellotilemap.c
_scifi_bg_chip2_png.h
_scifi_bg_chip2.png

一応手元では動いてる。まあ、Xlib利用版と見た目は変わらないけれど…。

2022/06/12() [n年前の日記]

#1 [prg] 昔の2D横スクロールSTGの回転パターンを調べてた

_昨日、 作成した雑魚敵っぽい画像をタイルマップ上に描画してみたものの、なんか違うな、コレジャナイと感じてしまった。大昔の2D横スクロールSTGの雑魚敵の回転パターンってどういう感じに仕上げてあったのだろう、大体何パターンぐらい持つのが一般的だったのだろうかと気になってきて、そのあたりを調べてた。YouTubeでその手の動画を検索して、回転してる雑魚敵っぽいものが出てきたら、一時停止してコマ送りで確認。

余談。今時のYouTubeは、「.」「,」キーでコマ送りができたのですな。今まで知らなかった…。キーボード上で、「,」「.」には「<」「>」も刻印されてるから、割り当てとしては分かりやすい。そういえば Adobe Flash もそういうショートカットキーだった気がする。何にせよ、コマ送りをして何か調べる時は便利だなと。ありがたや。

ところで、この場合の回転パターンというのは、画面のX軸、もしくはY軸を回転軸としている系の回転。

画面のZ軸(奥行き方向?)を回転軸にしている場合も、当時は「回転パターン」と呼んで扱っていたけれど、その向きの回転は気にしなくて良さそうだなと。容量次第で持てるパターン数が違ってくるだろうし、そもそも今時の描画ライブラリなら回転描画もできる場合が多いから、事前に回転パターンを用意しておく必要も無い。いやまあ、回転させても不自然さを感じない光の当たり方を意識して画像を作らないといけない、という注意点はあるかもしれないけど。

さておき。その手の動画を眺めた感じでは、角度変化については、45度、30度、22.5度、この3種類がありそうだなと感じた。0度から180度まで回る際に、45度は4枚必要、30度は6枚必要、22.5度は8枚必要、ということになるのかな。

about_rotate.png

一応、以下のソレを眺めて枚数を数えてみた。
上下、あるいは前後が対象になってるデザインにすれば上記の枚数で済むけれど、非対称のデザインなら360度回した画像を作らないといけないから枚数は倍になるはず。

_先日作成した画像 は…16枚も作ってしまった。昔の2Dゲーム風を再現したいなら、枚数がリッチ過ぎた…。半分の8枚でも良かったのだな。

2022/06/13追記。 :

簡単な3DCGモデルを作って、45度、30度、22.5度で回転させてみた。

45度ずつ回転。
spritesheet_45deg.gif


30度ずつ回転。
spritesheet_30deg.gif


22.5度ずつ回転。
spritesheet_22_5deg.gif


元のモデルの作りがよろしくなくて、なんだか分かりづらい気もするけれど。それでも一応、45度はナシだな、ぐらいのことは分かった。最低でも30度、できれば22.5度の回転パターンじゃないと回ってるように見えない気がする。

いやまあ、リアルタイム3DCGでゲーム画面を作るのが当たり前になったこの時代に、こんな検証をしてみても、という気もするのだけど。昔の2Dゲームらしい画面に近づけたい時はこういうのも関係してくるのかもしれないなあ、ということで。あるいは手描きアニメを作る際にも関係してくるのかもしれない。45度ずつの動画は回ってるように見えない、せめて30度は欲しい、とかそんな感じで。

2022/06/13(月) [n年前の日記]

#1 [cg_tools] スプライトシートからアニメGIFを作りたい

スプライトシートからアニメGIFを作りたい。環境は Windows10 x64 21H2。

スプライトシートというのは、以下のようにアニメパターンがタイル状に並んでる画像のこと。

spritesheet_22_5deg.png


今までは、ドットエディタ EDGE2 でスプライトシート画像を読み込んでアニメGIFを作ってたけど、この作業がちょっと面倒臭くて。
  1. キャプチャフレーム機能を使って、手作業で各フレームの位置を指定。
  2. アニメーションプレイヤウインドウで、アニメの様子を確認しつつ、キャプチャフレームウインドウ上でディレイ値を調整。
  3. キャプチャフレームウインドウの、ファイル → エクスポート → アニメーションGIF形式、を選んで書き出す。

できればスプライトシートから一発でアニメGIFにしたい。何かいい手はないものか。

ググってたら、ImageMagick を使ってスプライトシートからアニメGIFにする方法が紹介されてた。

_imagemagick - Convert sprite sheet to gif animation - Stack Overflow

例えば、128x128ドット単位で自動で切り出してアニメGIFにするなら、以下のような感じで指定すればいいらしい。

magick spritesheet.png -crop 128x128 +adjoin +repage -adjoin -loop 0 -delay 20 output.gif

試しに、Windows10 x64 21H2 + ImageMagick 7.1.0-5 Q16 x64 で作業してみた。たしかにこれでアニメGIFが出力された。

output_old.gif


ただ、一応出力できたけど、これは問題がある。このアニメGIFをIrfanViewで表示すると再生速度がめちゃくちゃ速い。また、Firefox や XnView で表示するとゆっくり動いてるけど、それでも再生速度がおかしい。「-delay 20」を指定してるから 20/100秒 のディレイ(wait値)を指定してるはずなのだけど…。それと、背景が残ってしまっている。

出力されたGIFをGIMPで読み込んでみたら、全フレームのディレイ値が 0ms になっていた。また、GIF/MNG-LCアニメ作成ソフト Giam 2.09 で読み込んでみても、やはりディレイ値が 0ms になってた。

_Giam ダウンロードのページ
_Giam - k本的に無料ソフト・フリーソフト

どうやら、ImageMagick の「-delay」指定は正常に働いてない気がする。

それでも、Firefox や XnView でそれっぽい再生速度になっているのは…。おそらくだけど、妙なディレイ値が指定されているアニメGIFについては、デフォルトのディレイ値(10/100秒)で表示してしまう仕様なのではなかろうか。そしておそらく、IrfanView は指定されたディレイ値(0ms)をそのまま使って表示する仕様だから、めちゃくちゃ速い再生速度になるのではないかと。

ImageMagick のバージョンが古くてバグがあるのだろうかと、ImageMagick-7.1.0-37-Q16-HDRI-x64-static.exe を使って、7.1.0-5 から 7.1.0-37 にアップデートしてみたけれど、結果は変わらなかった。

指定の順番がマズかった。 :

色々試していたところ、以下の指定ならディレイ値が反映してくれるようだと分かった。

magick -delay 20 -dispose Background spritesheet.png -crop 128x128 +adjoin +repage -adjoin -loop 0 output.gif
  • -delay N : ディレイ値を指定。単位は 1/100秒。
  • -dispose (None|Previous|Background) : 背景の消去方法を指定。Background は、フレームを背景色でクリアする。
  • -crop NxM : 1フレーム分を切り出すサイズ。N x M のサイズで切り出していく。
  • +adjoin +repage -adjoin : 不明。とりあえずこの指定をすると、複数のフレームを持っている画像が出力できるらしい。
  • -loop 0 : ループアニメを指定。

要するに、一番最初に「-delay 20」を書いてから、その次に背景の消去方法、「-dispose Background」を記述すれば、期待通りのディレイ値と背景消去方法で生成できる模様。

そんなわけで、以下の画像が得られた。

output.gif


以下のページで使用事例が色々解説されてる。こういったドキュメントをちゃんと読み込めば、もっと色々な変換作業ができるのだろうな…。

_Animation Basics -- ImageMagick Examples

#2 [cg_tools] APNGとWebPの作成を試した

アニメGIFを作る作業が面倒臭くなってきた。そもそも2022年にもなって256色しか使えないとかおかしいだろ。みたいな。

そこでふと、そういえば APNG という画像フォーマットがあったなと思い出した。フルカラーでアニメーション画像を作れるフォーマットだったはず。一時期オワコン扱いされてた記憶があるけど、今時はどういう扱いになってるんだろう。

APNGを試した。 :

APNGについて、今時のブラウザの対応状況はどうなっているのだろう。

_Animated Portable Network Graphics - Wikipedia
_APNG - Wikipedia
_Comparison of web browsers - Wikipedia

昔は対応ブラウザが少なかった、というか Firefox しか対応してなかった気がするのだけど、今現在は Google Chrome も対応してくれたらしい。Google Chrome が対応しているということは、Microsoft Edge も、Opera も、Vivaldi も対応してるはず。つまりメジャーなブラウザなら、そのほとんどが APNG に対応済みと捉えてしまっていいのかもしれない。

試しに作ってみた。APNG Assembler で作れるらしいので、APNG Assembler 2.91 GUI版をDLして作業。32bit版と64bit版、GUI版とCUI版があるらしい。

_APNG Assembler download | SourceForge.net
_APNG Assembler
_APNG AssemblerでアニメーションするPNG(APNG)を書き出す - Qiita

作成した APNG を Firefox 101.0.1 x64 にドラッグアンドドロップしたら、たしかにアニメが表示された。これでいいんじゃないかな…。

ここに載せてみたらどうなるだろう。さて、動いているように見えるだろうか…?

spritesheet_22_5deg_apng.png
_spritesheet_22_5deg_apng.png


Webサーバ側の設定は必要なのだろうか。どうなんだろう。png 扱いのはずだから、特に設定は要らないのかな。

WebPを試した。 :

GIMP で APNG を作れないかとググっていたら、「GIMP 2.10 は アニメーションWebPに標準対応してるからそっちを使ったほうがいい」という話を見かけた。

_APNG (animated PNG) plug-in for Gimp 2.10 Windows64x - GIMP Chat

WebP って何だっけ。

_アニメーション画像の歴史 APNG-WebP戦争 | Otogeworks

上記のページで、APNG と WebP に関しての経緯がまとまっていて大変勉強になった。WebPは、アニメーションも、ロスレス画質(画質劣化が一切無い)も、不可逆圧縮(Jpegのようなもの)も、何から何までサポートしたゴイスな画像フォーマット、と思っておけばいいのだろうか。

今時のメジャーなブラウザなら、APNG、WebP、どちらも対応しているらしい。ますますもってアニメGIFを使う理由が無くなってきた。

GIMPを使って作成できるか試してみた。GIMP 2.10.30 Portable x64 samj版を使用。
  1. 1レイヤーを1フレーム扱いにして読み込む。アニメGIFを作る際の手順そのままで良い。
  2. エクスポート時に、出力ファイル名の拡張子を「.webp」にする。
  3. 保存時のダイアログに「Lossless」や「As Animation」という設定項目があるのでチェックを入れて保存。
これで、たしかにアニメーションするWebPが生成できた。

gimp_webp_ss01.png

spritesheet_22_5deg_gimp210.webp
_spritesheet_22_5deg_gimp210.webp


画質設定でロスレス指定もできるようだし、これでもいいんじゃないかな…。

ちなみに、Webサーバ IIS上でWebPを利用するには、「MIMEの種類」に対して設定が必要だった。追加を選んで、「.webp」に「image/webp」を指定しないといけない。

Webサーバ Apache2上でWebPを利用する場合は、「AddType image/webp webp」を .htaccess あたりに追加しておけばいいのかもしれない。たぶん。

GIMPでAPNGを保存。 :

前述のフォーラムで紹介されているプラグインを使って、GIMP 2.10 64bit版で APNG の保存ができるか試してみた。

環境は、Windows10 x64 21H2 + GIMP 2.10.30 Portable 64bit samj版。Portable版なので、一般的なGIMPとはフォルダ構成が違ってるかもしれない。

_APNG (animated PNG) plug-in for Gimp 2.10 Windows64x - GIMP Chat

  1. apng-Gimp-2.10-64bit.zip をDLして解凍。
  2. libpng15-15.dll を、GIMPインストールフォルダ\bin\ にコピー。libpng16-16.dll というファイルが既に入ってるのだけど、いいのだろうか…?
  3. file-apng.exe を、ユーザフォルダ\.gimp\plug-ins\ 等の場所にコピー。
  4. plug-in-file-apng.ui を、GIMPインストールフォルダ\share\gimp\2.0\ui\ にコピー。
  5. GIMPを起動。

アニメGIFを作る時と同様に、1レイヤーを1フレーム扱いにして画像を作成。

これを APNG として保存する。

  1. ファイル → 名前を付けてエクスポート。
  2. 「ファイル形式の選択」をクリックして、「PNG+APNG Image」を選んで「エクスポート」。
  3. 「拡張子が一致しません。この名前で保存してもよろしいですか?」と尋ねてくるけど、気にせず「保存」。
  4. 何故かDOS窓が開きつつ、ファイル保存設定ダイアログが表示される。
  5. 「Animated PNG Options」の「As animation」にチェックを入れる。
  6. 他はPNG保存時のノリで設定して、「エクスポート」。

保存された png を Firefox 101.0.1 64bit や Google Chrome 102.0.5005.63 64bit にドラッグアンドドロップしてみたけれど、アニメ画像として再生された。

spritesheet_22_5deg_apng_from_gimp210.png
_spritesheet_22_5deg_apng_from_gimp210.png


ただ、この file-apng.exe を導入すると、GIMP の起動時に一瞬DOS窓が開いて閉じるあたりが気になる…。APNG保存時に、DOS窓が開くのも気になる…。でもまあ、動いてるからいいか…。

GIMP 2.8.22 Portableで試してみた。 :

手元の環境では GIMP 2.8.22 Portable x86 もインストールしてあるけれど、そちらにも file-apng.exe は導入済みだった。APNG で保存したい時は GIMP 2.8.22 Portable を使えば済むのかもしれない。

_APNG support for GIMP download | SourceForge.net

などと思ったりもしたのだけど、GIMP 2.8.22 Portable を起動して APNG のエクスポートを試してみたら、保存時の設定ダイアログが表示されないことに気づいた。DOS窓上に file-apng.ui 関係のエラーが大量に発生している。どうやら以前から正常動作してなかったらしい。

エラーメッセージを眺めたら、「GIMP 2.8.22 Portableインストールフォルダ\App\gimp\lib\gimp\2.0\plug-ins\ 以下に file-apng.ui が無いぞ」と言ってるように見えた。もしかすると .ui ファイルを読み込んでくる場所については決め打ちで作ってあるのかもしれない。試しにその場所に file-apng.ui を置いてみたらエラーが出ない状態になって、APNGでのエクスポートができるようになった。

ちなみに、file-apng.exe は、ユーザフォルダ/.gimp-2.8/plug-ins/ に置いてあっても動いてくれた。.ui の置き場所だけは注意、ということになるのだろうなと。

余談。GIMP上でのディレイ値変更について。 :

GIMPでアニメGIF等を作る場合、レイヤー名にディレイ値(wait時間)を書いておくことで、各フレームの表示時間を個別に変更できるのだけど、以下のスクリプトを利用すれば全レイヤーに対して一括で値を変更できるので便利。

_フレーム表示時間変更

フィルタ → アニメーション → フレーム表示時間変更、で呼び出せる。

このあたり、以前もメモしてあった…。

_GIMPでGIFアニメを作成する際のwait値を変更するスクリプトその2

各スクリプトを追加すると、フィルター → アニメーション以下のメニューに追加される。
  • フレーム表示時間変更
  • Rename Layers...
  • Set frames delay...
  • Change frame delays...
最初の2つは、Combine、Replace の指定も選べる。後の2つはディレイ値のみを追加する。最初の2つのうちのどちらかをインストールしておけば済みそうな気がする。

ImageMagickでAPNGを出力。 :

ImageMagick でも APNGを出力できないものかと調べてみた。環境は、Windows10 x64 21H2 + ImageMagick 7.1.0-37 Q16-HDRI x64。static版。

スプライトシート画像からアニメGIFを生成する指定に対して、アニメGIFではなくAPNGで保存するように変更すればOKだったりしないか。ググったところ、「apng:output.png」と書けば APNG を指定することになるらしい。

_ImageMagick - Image Formats

試してみた。どうだろう。動いてるように見えるだろうか。
magick -delay 3 -dispose Background spritesheet.png -crop 128x128 +adjoin +repage -adjoin -loop 0 apng:output.png
spritesheet_22_5deg_apng_from_im7.png
_spritesheet_22_5deg_apng_from_im7.png

ImageMagickでWebPを出力。 :

ImageMagick で アニメーションWebPを出力できるだろうか。以下のページによると、7.0.8-68 で対応、と書いてあるので、使えそうな気がする。

_ImageMagick と WebP - Qiita

試してみた。
magick -delay 7 -dispose Background spritesheet.png -crop 128x128 +adjoin +repage -adjoin -loop 0 output.webp
spritesheet_22_5deg_im7.webp
_spritesheet_22_5deg_im7.webp

これで、ImageMagick を使えばアニメーションWebPを出力することも可能、と分かった。ただ、この指定では、ロスレス画質は指定してないので、不可逆圧縮で出力されてるのではないか。

前述の解説ページに、ロスレスを指定する時は「-define webp:lossless=true」をつける、と書いてあった。これも試してみる。
magick -delay 7 -dispose Background spritesheet.png -crop 128x128 +adjoin +repage -adjoin -loop 0 -define webp:lossless=true output_lossless.webp
spritesheet_22_5deg_im7_lossless.webp
_spritesheet_22_5deg_im7_lossless.webp

ファイルサイズが、10.9 KByte から 61,9 KByte に増えたので、おそらくロスレス画質になっているのではないかな…。たぶん。

でも、見た感じ、違いは全然分かりませんな…。

GIMP 2.10.30 で両方開いて、拡大表示をして確認してみたけれど、たしかに不可逆圧縮とロスレスの違いはあった。でも、等倍表示では分からない程度の微妙な差だなと…。

2022/06/14(火) [n年前の日記]

#1 [cg_tools] ImageMagickでアルファチャンネルを2値化

先日、スプライトシート画像を作成していた際は、GIMP 2.10.30 を使ってアルファチャンネルの2値化をしていたのだけど。一々GIMPを起動するのもなんだかアレだなと思えてきて。自分の環境ではGIMPにプラグインを入れまくってるのでとにかく起動が遅いわけで。そのくらいの処理なら ImageMagick でやれないだろうかと調べてみた。

環境は、Windows10 x64 21H2 + ImageMagick 7.1.0-37 Q16-HDRI x64。

参考ページ。

_ImageMagick - rounding the alpha channel - Stack Overflow
_imagemagick 6 (-threshold -fill -transparent -fazz) | 丸ノ内テックブログ

magick input.png -channel alpha -threshold 85% output.png

これでアルファチャンネルの2値化ができる模様。

減色は上手く行かなかった。 :

ここまでできたら、減色処理も ImageMagick でやれないものかと思って試していたのだけど。

_ImageMagick の減色処理 - Qiita

これはちょっと、思っていた通りの結果にならず。自分は市松模様っぽいディザが好きなので、そういう見た目の減色結果が欲しいのだけど、ImageMagick で処理をすると誤差拡散っぽいディザパターンになってしまう。まだ、EDGE2 か GIMP で減色したほうが自分好みの結果になるなと…。いやまあ、GIMP の減色処理もなかなかに酷いのだけど。あちこちに突飛な色のドットが出現してしまう。なんでだろ。

ImageMagickの使用例について。 :

以下のページに、ImageMagick の使用例が色々載ってるのでメモ。

_Examples of ImageMagick Usage


日本語で膨大な量の解説記事を書いてくれてる方がいらっしゃる。ありがたや。マジで助かります。

_ImageMagick 解説インデックス - Qiita


ImageMagick で射影変換できるなんて知らなかった。ラッパーを書けば便利に使えるのではあるまいか…。

_ImageMagick で射影変換 (Homography, Perspective projection) - Qiita

余談。DB32パレットについて。 :

ググっていたら、DB8、DB16、DB32パレット等がまとめてあるページに遭遇。

_geoffb/dawnbringer-palettes: Limited color palettes by DawnBringer in various formats.

.csv やGIMPのパレットファイルまで置いてある。ありがたや。

2022/06/15(水) [n年前の日記]

#1 [cg_tools] 減色ツールについて調べてた

ImageMagick で奇麗に減色できないものか、-ordered-dither の指定の仕方でそれっぽくならないかと試していたのだけど、他の減色ツールではどうなるのかが気になってきた。

そんなわけで、現在でも利用できるツールを使って、フルカラー画像を32色や16色に変換して結果を眺めているところ。 *1

環境は Windows10 x64 21H2。

利用したツールは以下。括弧内は配布ファイル名。
_Padieの詳細情報 : Vector ソフトを探す!
_多摩川ソフトウェア工房 Padie (WebArchive)
_xPadie Pro (WebArchive)
_結社「障泥烏賊ライブラリ」用地
_COLGAのページ
_ずるねこの減色256色にするよ誤差拡散有り低速の詳細情報 : Vector ソフトを探す!
_Color quantizer
_pngnq-s9 download | SourceForge.net
_pngnq - image quantization
_pngquant - lossy PNG compressor

OPTPiX 1.31 だけが有償ソフト。他はフリーソフト。

OPTPiX は無印だった頃に購入したのでバージョンがとんでもなく古い。今はもうプロ用というか企業様御用達のツールになっているので個人で購入する事例はまず無いだろうなと…。公式サイトを眺めたら、6ヶ月間の使用ライセンスで180,000円と書いてあった…。

xPadie、xPadie Pro、Yukari は、減色エンジンが Padie のソレなので、大体似たような変換結果になる印象を受けた。

色々試してみたけれど、やはり OPTPiX の変換結果が優秀に思えた。その次にイイ感じだったのが Padie系。ディザの種類で「アニメーション」を選ぶとドット絵のソレっぽくなるのがイイ感じ。

「ずるねこの〜」は、256色への減色で固定されていたのが残念。任意の色数への減色結果を確認してみたかった…。

Color quantizer は Bayer ディザが選べるので、比較的好みの減色結果になった。

pngquant は、Floyd-Steinberg ディザしか使えないようで、ちょっと残念。

でもまあ、どれを使っても、ImageMagick で減色するより、はるかにイイ感じになるなと…。その点だけは分かった。

_Quantization -- ImageMagick Examples

上記の解説ページによると、ImageMagick の -ordered-dither を使うと使用色は固定されてしまう、と書いてあるように見える。画像によって、どの色がどれだけ使われているのかは異なってくるわけで、本来はその画像の中でよく使われている色を残しつつ減色処理をすることになるのだろうけど、-ordered-dither を指定するとそんなのおかまいなしに決まった色しか使わなくなるので結果は悲惨なことになるのだとか。上記ページの一番下に、そのあたりの解決策のヒントが書いてあるようにも思えるのだけど…。現状では、ちょっと指定を変えるだけで解決できる状態ではない模様。

減色処理のソースも入手できるらしい。 :

ググっていたら、iZYINS というソースが存在すると知った。

_iZYINS : Color Reducer on .NET Framework Project Top Page - OSDN
xPadieのソースコードを参考にした、.NET FrameworkやMONO上で動作する、C#で書かれた減色プログラムのソースコードを、研究用あるいは教育用にGPLで公開します。

iZYINS : Color Reducer on .NET Framework Project Top Page - OSDN より


こういった研究をする場合は参考になりそう。

また、減色処理について研究を続けている方のブログにも遭遇。少なくとも2015年頃からずっと研究してるらしい。

_画像処理(減色、限定色表示) - ずるやすみねこのリサイズ研究
_作者: zuruneko - Vector
_ZURUYASUMINEKOのホームページ
_減色
_ハーフトーン技術

研究成果の一部については、仕様を限定した状態で Vector で公開されている模様。ソースもブログに貼り付ける形で公開されているけれど、環境も、ビルド方法も分からない…。もっとも、この手の研究をしている方々なら、ソースを眺めれば理解できてしまうものだったりするのかもしれない。

コマンドラインツールの形でひとまず作っておくのがベターかもしれない。 :

減色ツールに限らず、png画像の最適化ツールの類についても調べていたけど、海外ではこの手のツールの作り方についてなんだか傾向があるように感じた。

減色処理部分をGUIツールに含める形で作ってしまうと、そこでそのツールは閉じてしまって、たとえ減色処理が優秀であっても、「操作が不便」「複数画像に対して処理ができない」等の理由で敬遠されてしまう ―― そんな気配があるなと。

しかし、減色処理部分をコマンドラインツールとして作っておけば、別の誰かがそのツールを呼び出すGUIのラッパーを作ってくれたりして、減色処理自体の研究と、変換作業時の利便性追求を、切り分けて実装できる。呼んでいるコマンドラインツールが同じなら生成結果も同じになるので、後は使い勝手に注目してラッパーを選べばよい、みたいな状況になる。例えば pngnq や pngquant には、そういった気配を感じた。

このあたり、*NIXの文化なのかなと…。コアになる部分は単機能のツールとして提供しておいて、それらツール群を別の何かで組み合わせて目的を果たす、みたいな。あるいは、不便さを感じたらその時にラッパー書けばいいんじゃね、で済ませていくノリというか。

*1: どうして32色や16色なのかと言えば、昔の2Dゲーム機って1パレット16色、アーケード基板でも32色とかそんな感じだったので…。昔の2Dゲーム風の画面を再現するなら16色だろう、みたいな。

2022/06/16(木) [n年前の日記]

#1 [cg_tools] 減色処理プログラムのビルドができるか試してみた

減色ツールについてググって調べていた際に、以下のブログで減色処理をするプログラムのソースが公開されてることを知って興味が湧いた。これってビルドできないのだろうか。そのあたり、少し試してみた。

_高速版2色以上任意色数減色ソフト1 - ずるやすみねこのリサイズ研究
_高速版2色以上任意色数減色ソフト2 - ずるやすみねこのリサイズ研究
_高速版2色以上任意色数減色ソフト3 - ずるやすみねこのリサイズ研究
_高速版2色以上任意色数減色ソフト4 - ずるやすみねこのリサイズ研究

ちなみに、本来のプログラム名は bmp_color_reducer (bmp_color_reducer.exe) らしい。ヘルプ表示ではそのように記述されていた。

ソースファイル群の入手。 :

ビルドに必要なファイルは、ソースファイル(.cpp)が3ファイル、ヘッダファイル(.h)が1ファイル。計4ファイルを、上記のブログからコピペして作成。
  • main.cpp
  • mediancut.cpp
  • mediancut_util.cpp
  • mediancut_util.h

ちなみに、コメントが日本語文字列で書かれているので、文字コードは UTF-8N にして保存しておいたほうがヨサゲ。

ビルド環境。 :

ビルドに使った環境は、Windows10 x64 21H2 + MSYS2 MinGW 64bit。

g++ と GNU Make が使える環境であること。

$ g++ --version
g++.exe (Rev2, Built by MSYS2 project) 12.1.0
$ make --version
GNU Make 4.3
このプログラムは x86_64-pc-msys 用にビルドされました
Copyright (C) 1988-2020 Free Software Foundation, Inc.

MSYS2 のインストールは、以下のページが参考になりそう。

_MSYS2/MinGW-w64 (64bit/32bit) インストール手順 メモ
_【msys2】インストール [新石器Wiki]
_MSYS2 MinGW GCC環境をつくる(その1)

ビルドしてみる。 :

ビルドの手順としては、*.cpp をコンパイルして *.o を作って、その *.o 達をリンカで結合して実行バイナリを作るわけだけど…。

勉強も兼ねて、Makefile を書いてみた。Makefile の書き方は詳しくないので、無駄が多い記述になってるかもしれんけど…。

_Makefile.msys2
CXX = g++
CXXFLAGS = -O2
SRCS = main.cpp mediancut.cpp mediancut_util.cpp
OBJS = $(SRCS:.cpp=.o)
PROGRAM = bmp_color_reducer.exe

all: $(PROGRAM)

$(PROGRAM): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $(OBJS) -static -lstdc++ -lgcc -lwinpthread

.cpp.o:
	$(CXX) $(CXXFLAGS) -c $< -o $@

mediancut.o: mediancut_util.h
mediancut_util.o: mediancut_util.h

.PHONY: clean
clean:
	rm -f *.o
	rm -f $(PROGRAM)

余談。Makefile内のインデントは、スペースじゃなくてTAB文字にしておくことに注意。

この Makefile.msys2 を、4つのソースファイルと同じ場所に置く。

Makefile.msys2 を Makefile にリネームすれば、単に make と打ち込むだけでビルドできる。
make

あるいは、使う Makefile を指定しても良い。
make -f Makefile.msys2

ちなみに、make clean と打てば、*.o や *.exe を削除するように Makefile 内で指定しておいた。


問題無くビルドができれば、bmp_color_reducer.exe という実行ファイルが生成される。

せっかくだから、ビルドした bmp_color_reducer.exe を zip に入れて置いときます。

_bmp_color_reducer.zip

MSYS2 をインストールしていないサブPC(Windows10 x64 21H2)上でも動いたから、他の環境でも動くのではないかな…。たぶん。

使い方。 :

ソース配布先のブログでも説明されているけれど、以下のような指定で減色処理ができるらしい。

bmp_color_reducer.exe -i input.bmp -o output
  • 入出力できる画像は、bmp画像のみ。試しに png画像を渡してもエラーを出して受け付けなかった。
  • 入力ファイル名は、input.bmp のように、拡張子まで指定する。
  • 出力ファイル名は、output のように、拡張子は省略する。自動で「.bmp」をつけてくれる。
  • 入力ファイル名、出力ファイル名しか指定していない場合は、256色の画像に減色してくれる。

-irosuu N をつけると、使用する色数を指定できる。以下は、16色、32色を指定して減色する例。
bmp_color_reducer.exe -i input.bmp -o output -irosuu 16
bmp_color_reducer.exe -i input.bmp -o output -irosuu 32

ヘルプ表示は、-h。
bmp_color_reducer.exe -h

その他のオプションは、元ブログの説明を参考に。

_高速版2色以上任意色数減色ソフト1 - ずるやすみねこのリサイズ研究

余談。静的リンクについて。 :

当初、MSYS2上でビルドした bmp_color_reducer.exe をDOS窓上で実行したら、「DLLが無い」とエラーダイアログが表示されて動かなかった。

ググったら、以下が参考になった。

_msys2とC++で特定のDLLに依存しないwindowsバイナリを作る - siunのメモ

ldd というコマンドを使うと、その実行バイナリが何のDLLを要求するのか ―― 共有ライブラリの依存関係が分かるらしい。

$ ldd bmp_color_reducer.exe
        ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffa18af0000)
        KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffa18450000)
        KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffa164e0000)
        msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x7ffa181a0000)
        libstdc++-6.dll => /mingw64/bin/libstdc++-6.dll (0x7ff9b7d10000)
        libgcc_s_seh-1.dll => /mingw64/bin/libgcc_s_seh-1.dll (0x7ff9fe9d0000)
        libwinpthread-1.dll => /mingw64/bin/libwinpthread-1.dll (0x7ff9fe920000)
        libwinpthread-1.dll => /mingw64/bin/libwinpthread-1.dll (0x2a5132b0000)

どうやら、/mingw64 から始まる場所に置かれている .dll を見つけることができなくてエラーが表示されてしまった模様。それらの .dll を、.exe に静的リンクしてやれば解決するらしい。

前述の記事を参考にして、g++ に -static -lstdc++ -lgcc -lwinpthread を渡してビルドしたところ、以下のバイナリになった。

$ ldd bmp_color_reducer.exe
        ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffa18af0000)
        KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffa18450000)
        KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffa164e0000)
        msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x7ffa181a0000)

この状態なら、bmp_color_reducer.exe がDOS窓上でも動いてくれた。

Ubuntu Linux上でもビルドを試した。 :

せっかくだから、VMware Player + Ubuntu Linux 20.04 LTS上でもビルドできるか試してみた。

おそらくだけど、Ubuntu Linux の場合、build-essential というパッケージをインストールすれば g++ も make もインストールされるから、それだけでビルドできる状態になるのではないかと。たぶん。ちょっと自信無いけど。
sudo apt install build-essential

_Ubuntu - focal の build-essential パッケージに関する詳細
_Ubuntu LinuxやDebianで使う、build-essentialってなんだ? - CLOVER
_build-essentialとは - Qiita


*NIX用の Makefile は以下。MSYS2版から、リンカに渡す指定を削って、生成ファイル名の .exe を削っただけ。

_Makefile.unix
CXX = g++
CXXFLAGS = -O2
SRCS = main.cpp mediancut.cpp mediancut_util.cpp
OBJS = $(SRCS:.cpp=.o)
PROGRAM = bmp_color_reducer

all: $(PROGRAM)

$(PROGRAM): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $(OBJS)

.cpp.o:
	$(CXX) $(CXXFLAGS) -c $< -o $@

mediancut.o: mediancut_util.h
mediancut_util.o: mediancut_util.h

.PHONY: clean
clean:
	rm -f *.o
	rm -f $(PROGRAM)

Makefile.unix を Makefile にリネームすれば、make と打つだけでビルドできる。
make

あるいは、Makefile を指定しても良い。
make -f Makefile.unix


make してみたら、いくつか警告は表示されてしまったものの、一応 bmp_color_reducer という実行バイナリが得られた。

実行は以下。
./bmp_color_reducer -i input.bmp -o output

動作確認してみたところ、Windows用バイナリと同様に、減色処理ができているように見えた。

#2 [xlib][xscreensaver] 雑魚敵の画像を作り直した

_先日、雑魚敵っぽい画像を作った ものの、どうも見た目のボリュームが足りない感じがしてきたので、もうちょっと雑魚敵らしく見えそうな画像を作成し直してみた。

1フレーム64x64ドットを8枚並べた。22.5度ずつ回転するタイプ。CC0 / Public Domain ってことで。自由に使ってください。

アニメgif。

enemy_type_d_01.gif
_enemy_type_d_01.gif


フルカラー画像。

spritesheet_lanczos.png
_spritesheet_lanczos.png


32色に減色してドット修正したもの。

spritesheet_32col.png
_spritesheet_32col.png


実験用のタイルマップ画像に合成したもの。

scifi_bg_chip2.png
_scifi_bg_chip2.png


512x512ドットのレンダリング画像8枚 + .blendファイルをzipにしたもの。Blender 2.93.9 x64 LTS で作業。

_render512x512_and_blend.zip

xscreensaver上で表示してみた。 :

xscreensaver 上で実際に表示してみた。

動作確認環境は Ubuntu Linux 20.04 LTS。Windows10 x64 21H2 + VMware Player上で動かしてる状態。




_先日の見た目 よりは、それらしくなってくれた気がする。


ソース群や使用画像は以下。

_hellotilemap.c
_hellotilemap.man
_hellotilemap.xml
_scifi_bg_chip2_png.h
_scifi_bg_chip2.png


ファイルのコピー先や、ビルドの仕方は、以下でメモしてある。

_xscreensaverでタイルマップ描画

Xlib版も試した。 :

xscreensaver 版と似た感じの処理になるように、Xlib 描画版も修正して試してみた。まあ、見た目は xscreensaver版と同じなのだけど…。

ソースと使用画像は以下。

_10objs.c
_scifi_bg_chip2.xpm


ビルド手順や実行の仕方は、以下でメモしてある。

_Xlibでタイルマップの前に雑魚敵モドキを描画してみた
gcc 10objs.c -o 10objs -I /usr/include/X11 -lX11 -lXext -lm -lXpm
./10objs

2022/06/17(金) [n年前の日記]

#1 [povray] POV-Rayを再勉強中

以前、雑魚敵画像を作成するために書いた .pov ファイル(POV-Rayファイル)を、HDDの中から発掘したのだけど。しかしPOV-Rayの使い方を忘れていたので再勉強中。どういうオプションをつければどういう動作になるのか再確認。

Windows10 x64 21H2 + POV-Ray 3.7 + ImageMagick 7.1.0-37 Q16-HDRI x64 を使用。

_POV-Rayで背景を透明にする方法が分からない
_POV-RayとImageMagickでアニメ画像を作成
_POV-Rayで任意のオブジェクトをレンダリング対象から外す

とりあえず、勉強し直した点について、もう一度メモしておこう…。

コマンドラインオプションについて。 :

POV-Rayのコマンドラインオプションとして、例えば以下のような指定ができる。
  • +W512 : 出力画像の横幅を指定。(width=512)
  • +H512 : 出漁画像の縦幅を指定。(height=512)
  • +a0.3 : Antialias を on にして、Threshold 0.3 を指定。(Antialias_Threshold=0.3 Antialias=on)
  • +UA : 出力画像のアルファチャンネルを有効化。(Output_Alpha=on)
  • +KFF20 +KC : フレーム数を20に。ループアニメ有効化。(Final_Frame=20 Cyclic_Animation=on)

+KFFxx を指定することで、clock という変数が 0.0 - 1.0 まで xxフレームで変化するので、オブジェクトの座標指定などに clock を含めた数式を書けばアニメーション画像が作れる。

ただ、ループアニメを作る場合、+KFFxx を指定しただけでは、一番最初のフレームと最後のフレームが同じ画になってしまう。+KC を指定することで最後のフレームを余分にレンダリングしなくて済む状態になる。

オプションを.povに記述。 :

.pov ファイルの最初のあたりに、コメント行として以下のような記述をしておけば、POV-Rayエディタで開いた際に該当行を右クリックしてコマンドラインオプション入力欄にコピーできる。
// +W512 +H512 +a0.3 +UA +KFF20 +KC

「こういう条件でレンダリングしたよ」と未来の自分に伝えたいなら、書いておいたほうがヨサゲ。

レンダリング対象から外す。 :

各オブジェクトの定義の最後で「no_image」を指定すれば、そのオブジェクトについてはレンダリングしない状態にできる。

例えば、背景に相当するオブジェクトを定義しておいて、その中で「no_image」を指定すれば、対象オブジェクトの表面には背景の映り込みをさせつつ、背景部分だけを透明な状態にできる。まあ、これについては以前のメモを見たほうが分かりやすいはず…。

_POV-Rayで任意のオブジェクトをレンダリング対象から外す

複数フレームをスプライトシートにする。 :

複数のレンダリング画像が得られたら、ImageMagick を使ってスプライトシート画像を作れる。

magick montage -filter Lanczos -resize 78x78 -unsharp 10x5+0.7+0 -geometry +1+1 -background none render_512x512/*.png PNG32:spritesheet.png
  • -filter Lanczos : リサイズフィルタに Lanczos を指定。
  • -resize 78x78 : 78x78ドットにリサイズ。
  • -unsharp 10x5+0.7+0 : アンシャープマスクをかける。
  • -geometry +1+1 : (1,1)の位置にずらす。リサイズの大きさが 78x78 だったので、結果的に1フレーム 80x80 になる。
  • -background none : 背景を透明化。
  • render_512x512/*.png : 入力ファイル群を指定。
  • PNG32:spritesheet.png : 出力ファイル名を指定。RGBA各8bitの png が欲しい場合、PNG32: をファイル名の前につける。

スプライトシートからアニメgifを作る。 :

ImageMagick を使って、スプライトシートからアニメgifを作れる。

magick -delay 2 -dispose Background spritesheet.png -crop 80x80 +adjoin +repage -adjoin -layers OptimizeFrame -loop 0 output.gif
  • -dispose Background : 背景の消し方を指定。
  • spritesheet.png : 入力画像を指定。
  • -crop 80x80 : 1フレームを80x80で切り出す。
  • +adjoin +repage -adjoin : 不明。
  • -layers OptimizeFrame : アニメgifを最適化。
  • -loop 0 : ループアニメを指定。(ループ回数制限を無しにしている。)
  • output.gif : 出力ファイル名を指定。

2022/06/18() [n年前の日記]

#1 [nitijyou] OpenGameArtにアップロードしてみた

せっかく画像素材を作ったことだし、いくつか OpenGameArt にアップロードしておいた。まあ、何か実験する時の素材ぐらいにはなるんじゃないかと…。

_mieki256 | OpenGameArt.org

OpenGameArt というのは、ゲームを作る時に使えそうな、『自作』の(画像|音声|3DCG)素材を皆で持ち寄って公開できるサイト。素材公開時のライセンスは色々選べる。

念のために書いとくけど、あくまで『自作素材』じゃないとダメ。他人様が作った画像その他を勝手に投稿しちゃダメです。

#2 [neta] 21世紀版レナ画像コンテストをどこかでやらないものか

妄想メモ。

Python を使って、レナさん画像を読み込んで、画像処理の実験っぽいことをしていたのだけど。

_レナ (画像データ) - Wikipedia
_画像サンプル「レナ」の正体は「PLAYBOY」誌の最多販売部数を記録したプレイメイト - GIGAZINE

作業をしながら、ふと思い出した。たしか、お婆さんになったレナさん御本人が、「もう私の画像を使うのはやめてくれへん?」と言ってたような…。

_リファレンス画像で知られるレナ・ソーダバーグさん、自身の画像データの使用中止を求める | スラド

御本人が嫌がってるのに使い続けるのは良くないよな…。

代わりの画像は無いものかとググってみたのだけど、レナさん画像の代替画像はコレにしよう、みたいなデファクトスタンダードは全く決まってないようで。そもそも、代替画像を決めようぜという流れすら見かけない。皆、平気でレナさん画像を使い続けてる。これはちょっと良くない気配がする。

イベントにできないか。 :

いっそこのあたり、どこかでイベントにしてみたらどうだろうか。例えばドワ〇ゴあたりで、「21世紀のレナさんコンテスト」を開いたりしないか、などとバカ妄想。

「コンピュータの画像処理分野で使われる写真画像を募集します」
「ひょっとすると、50年も100年も200年も、世界中で、英文で書かれた論文の類に頻繁に登場する、そんな扱いになるかもしれません」
「『俺(私)のこのポートレートなら、見てくれ的にかなり自信あるから使っていいよ。何十年も使われちゃう? どんとこいだぜ』と仰ってくれる奇特な方は居ませんか?」

みたいな。

こういうのって、売り出し中の若手芸人さんあたりには美味しい話だったりしないか…。

「前々から気になってたけど、この手の論文にいつも出てくるこの人、一体何者なん?」
「ああ、彼(彼女)は日本のコメディアンらしいよ。たしか名前は…」

世界(の画像処理研究者達の間)で一番有名な男(女)になれる。知名度上げたい人にとっては、ちょっと美味しくないですか。

ただ、画像内で肌色成分が多いと、Google や Apple や Microsoft のAIが卑猥な画像として誤認識して、画像を載せてる論文ごとWeb上から即BANされる可能性が高い…。海パン一枚で持ちネタを披露する系の写真は、テスト画像としてアウトでしょうな…。

TV番組にできないか。 :

レナさん画像の代替画像を選んで決定する過程をTV番組に出来ないものか。

「俺(私)の写真使っていいよ」と言い出した芸人さん・芸能人さんを集めて、プロの写真家さん達に渾身の一枚を撮影してもらう。撮り方次第で被写体はここまで化けますよ、写真家さんってスゴイよね、と紹介するのが第一章。

AI研究をしてる人も呼んできて、画像分析してもらうのが第二章。現時点のAI技術の可能性と限界を紹介する。

「あー。これはダメですね。卑猥画像として認識されました」
「ダメかー。ノースリーブがあかんのかなあ。それとも乳首かなあ」
「これは…人間じゃなくてチンパンジーとして認識されてますね」
「なんでや! ワシは人間や!」
(テロップ「AIにチンパンジー認定された男」)

何らかの方法でNo.1画像が決まったら、いよいよ感動の最終章。モデルになってくれた人に海外まで行ってもらって、レナお婆さんに直接お会いする。番組側で用意した証書を ―― 「これからは私の代わりにテクノロジーの発展に貢献してくださいね」的証書みたいなものを、初代(?)から二代目(?)に手渡してしてもらって、熱いハグ。「こうして伝統(?)は引き継がれた。これからはこの写真をレナさん画像の代わりに使いましょう」と視聴者に啓蒙して終わる。みたいな。

もっとも、最初から狙って用意したこういうアレコレって大体はスルーされがちだし、実際やってみてもどうせ全然普及しないままだろうな…。そして、何故か問題がある画像がいつまでも使われ続ける…。えてしてそういうパターンになるよな…。

以上、馬鹿馬鹿しい妄想メモでした。

余談。 :

レナさん画像の良いところは、目にした瞬間、「ああ、コレって画像処理の話ですね?」と予想できるところかもしれないなと。そういう共通認識が持てるなら、代替画像って実はどんな画像でも良かったりするのかもしれず。

2022/06/19() [n年前の日記]

#1 [python] Pythonで画像処理ができそうか勉強中

Python + Pillow (PIL) を使って簡易的な画像処理ができそうか勉強中。Pillow (PIL) モジュールを使うと、画像の読み書きや、ピクセル単位での変更ができるので、その使い方を調べてた。

環境は、Windows10 x64 21H2 + Python 3.9.13 64bit + Pillow 9.1.1。

実験に使った画像は以下。

mandrill.png
_mandrill.png

参考ページ。 :

Pillow (PIL)のインストール。 :

Pillow (PIL) は、Python に付属している pip というツールを使って、インターネット経由でインストールできる。

pip install Pillow
pip install Pillow -U
  • -U をつけると、モジュールを現行版にアップデートできる。

チャンネル別に分解。 :

RGB画像を読み込んで、チャンネル別(R,G,B)に分解してみる。

以下の事例では、チャンネル別に分解した後、各チャンネル(グレースケール)を2値化して、それをまたRGBに結合? 合成? し直している。

_01split.py
from PIL import Image


def main():
    im = Image.open("mandrill.png")
    width, height = im.size
    print("%d x %d" % (width, height))

    rc, gc, bc = im.split()

    rc1 = rc.convert("1").convert("L")
    gc1 = gc.convert("1").convert("L")
    bc1 = bc.convert("1").convert("L")

    oim = Image.merge("RGB", (rc1, gc1, bc1))
    oim.show()
    oim.save("output_01split.png")


if __name__ == '__main__':
    main()

py 01split.py で実行。以下が得られた。

output_01split.png


各チャンネルを2値化したことで、元画像と比べるとディザがかかった状態になっている。

output_01split_x400.png


各行について、簡単に説明。
  • PIL (Pillow) の Image クラスを使うには、最初のあたりで、from PIL import Image と記述。
  • im = Image.open("mandrill.png") で、画像を読み込む。
  • im.size には、画像の横幅と縦幅が入っている。w, h = im.size で、横幅と縦幅を w と h にコピー。
  • チャンネルを分解するには、.split() を使う。r, g, b = im.split() で、r,g,b に各チャンネルが入る。
  • .convert() を使えば画像のモード(?)を変換できる。"1" を指定すれば2値化される。"L" を指定すればグレースケールになる。
  • チャンネルを結合? 合成? するには、Image.merge() を使う。oim = Image.merge("RGB", (r, g, b)) で、r,g,bチャンネルを合成して、RGB画像にして返す。
  • im.show() で、画像を表示。自分の環境では、png を、画像ビューア IrfanView に関連付けているので、im.show() を呼ぶと IrfanView が起動して画像が表示される。
  • im.save("output.png") で画像を保存。ファイル名の拡張子によって、保存に使う画像フォーマットを Pillow (PIL) が自動で選んでくれる。

1ドットずつ処理をする。 :

画像内から1ドットずつ値を読んで処理をする際は、.getpixle()、.putpixel() を使える。

以下の事例では、RGB画像を読み込んでグレースケールに変換した後、各ドットの値を反転して(255-v)、新規画像に書き込み直している。

_02getpixel.py
from PIL import Image


def main():
    im = Image.open("mandrill.png")
    width, height = im.size
    print("%d x %d" % (width, height))

    gim = im.convert("L")
    oim = Image.new("L", (width, height))

    for y in range(height):
        for x in range(width):
            v = 255 - gim.getpixel((x, y))
            oim.putpixel((x, y), (v))

    oim.show()
    oim.save("output_02getpixel.png")


if __name__ == '__main__':
    main()

py 02getpixel.py で実行。以下が得られた。

output_02getpixel.png

  • im = Image.new("L", (w, h)) で、w x h サイズのグレースケール画像を新規作成。"L" を "RGB" にすれば、グレースケールではなくRGB画像を新規作成できる。
  • im.getpixel((x, y)) で、(x, y) 座標のドットの値を取得できる。
  • im.putpixel((x, y), (r, g, b)) で、(x, y) 座標のドットの値を設定できる。


ただ、.getpixel()、.putpixel() は、処理が遅いという話も見かけた。自分の手元の環境(Ryzen 5 5600X)では、そんなに遅いようには見えてないのだけど…。

少しでも処理を速くしたい場合は、以下のような書き方もできる模様。Pillow 1.1.6a1 の時点での追加機能、と ChangeLog(?) には書いてあるように見える。

_Pillow/CHANGES.rst at main - python-pillow/Pillow
* Added pixel access object. The "load" method now returns a access object that can be used to directly get and set pixel values, using ordinary [x, y] notation:

pixel = im.load()
v = pixel[x, y]
pixel[x, y] = v

If you're accessing more than a few pixels, this is a lot faster than using getpixel/putpixel.

Pillow/CHANGES.rst at main ・ python-pillow/Pillow より



_03getpixel2.py
from PIL import Image


def main():
    im = Image.open("mandrill.png")
    width, height = im.size
    print("%d x %d" % (width, height))

    gim = im.convert("L")
    oim = Image.new("L", (width, height))

    gpixel = gim.load()
    opixel = oim.load()

    for y in range(height):
        for x in range(width):
            opixel[x, y] = 255 - gpixel[x, y]

    oim.show()
    oim.save("output_03getpixel2.png")


if __name__ == '__main__':
    main()

py 03getpixel2.py で実行。以下が得られた。

output_03getpixel2.png

  • pixle = im.load() で、ピクセル値を配列っぽい形で取得。
  • pixel[x, y] で、各ピクセルを読み書きできる。

しかし、処理速度がそれほど変わったようには見えてないのだけど…。もしかして、Pillow (PIL) の現行バージョンでは、.getpixel()、.putpixel() の処理速度は改善済み、というオチだったりしないか…。分からんけど。

グラデーション画像を作成する。 :

Pillow (PIL) を使って、グラデーション画像を作ってみる。

今回は、矩形の塗りつぶしを繰り返すことで目的を果たした。

_04gradation.py
from PIL import Image, ImageDraw


def make_gradation_image(w, h):
    im = Image.new("L", (w, h), (128))
    draw = ImageDraw.Draw(im)
    for y in range(16):
        for x in range(16):
            v = y * 16 + x
            x0 = x * w / 16
            y0 = y * h / 16
            x1 = (x + 1) * w / 16 - 1
            y1 = (y + 1) * h / 16 - 1
            # draw.rectangle((x0, y0, x1, y1), fill=(v), outline=(0))
            draw.rectangle((x0, y0, x1, y1), fill=(v), outline=None)
    return im


def main():
    im = make_gradation_image(512, 512)
    im.show()
    im.save("gradation.png")


if __name__ == '__main__':
    main()

py 04gradation.py で実行。以下の画像が得られた。0-255までの値で塗り潰されている。

gradation.png
_gradation.png


  • Pillow (PIL) で何かしらを描画する際は、ImageDraw が使える。最初のあたりで、from PIL import Image, ImageDraw と記述。
  • draw = ImageDraw.Draw(im) で、描画のためのクラス? グラフィックコンテキスト? を取得。
  • draw.rectangle((x0, y0, x1, y1), fill=(v), outline=None) で、矩形塗り潰し。(x0, y0) が左上の座標。(x1, y1) が右下の座標。fill=(r, g, b) で塗り潰し色を指定。outline=None で境界線無しを指定。outline=(r, g, b) で境界線の色を指定。

ディザリングを試す。 :

諧調を持った画像を0/1のみで疑似的に表現する際に、ディザリングという手法が利用できる。ディザリングの種類は色々あるけど、その中の Ordered dithering というのを試してみた。

_Ordered dithering - Wikipedia
_配列ディザリング - Wikipedia
_Unityでディザリングシェーダを作ってみた - WonderPlanet Developers’ Blog
_105AI研究所: ベイヤーディザフィルタ(BayerDitherFilter)
_ImageMagick/thresholds.xml at main - ImageMagick/ImageMagick

上記の参考ページを眺めてみたけど、処理としては以下のような簡単な処理で実現できるらしい。
  1. ディザ配列から、しきい値の配列を作る。
  2. x, y の座標値を、ディザ配列の数で割って余りを求める。
  3. 得られた余りを使って、しきい値の配列から、しきい値を取得。
  4. そのドットの値と、しきい値を比較して、0 or 1 (0 or 255) で置き換える。

以下は、グレースケール画像を読み込んで、2x2, 3x3, 4x4, 8x8 のディザをかけて保存する例。

_05ordered_dithering.py
from PIL import Image


odtbl2x2 = [
    [1, 3],
    [4, 2]
]

odtbl3x3 = [
    [3, 7, 4],
    [6, 1, 9],
    [2, 8, 5]
]

# odtbl3x3 = [
#     [7, 9, 5],
#     [2, 1, 4],
#     [6, 3, 8]
# ]

odtbl4x4 = [
    [1, 9, 3, 11],
    [13, 5, 15, 7],
    [4, 12, 2, 10],
    [16, 8, 14, 6]
]

odtbl8x8 = [
    [1, 33, 9, 41, 3, 35, 11, 43],
    [49, 17, 57, 25, 51, 19, 59, 27],
    [13, 45, 5, 37, 15, 47, 7, 39],
    [61, 29, 53, 21, 63, 31, 55, 23],
    [4, 36, 12, 44, 2, 34, 10, 42],
    [52, 20, 60, 28, 50, 18, 58, 26],
    [16, 48, 8, 40, 14, 46, 6, 38],
    [64, 32, 56, 24, 62, 30, 54, 22]
]

odtbls = {
    "2x2": [5, odtbl2x2],
    "3x3": [10, odtbl3x3],
    "4x4": [17, odtbl4x4],
    "8x8": [65, odtbl8x8]
}


def get_dither_table(odtype):
    d, tbl = odtbls[odtype]
    w = len(tbl[0])
    h = len(tbl)
    odtbl = [[0 for i in range(w)] for j in range(h)]

    for y in range(h):
        for x in range(w):
            odtbl[y][x] = tbl[y][x] * 256 / d

    return w, h, odtbl


def get_dither_image(odtype, im):
    width, height = im.size
    oim = Image.new("L", im.size)

    tw, th, od = get_dither_table(odtype)

    src_pixel = im.load()
    dst_pixel = oim.load()

    for y in range(height):
        dy = y % th
        for x in range(width):
            dx = x % tw
            if src_pixel[x, y] < od[dy][dx]:
                dst_pixel[x, y] = 0
            else:
                dst_pixel[x, y] = 255

    return oim


def main():
    # im = Image.open("mandrill.png").convert("L")
    im = Image.open("gradation.png")

    for odtype in ["2x2", "3x3", "4x4", "8x8"]:
        oim = get_dither_image(odtype, im)
        oim.save("output_dither_%s.png" % odtype)


if __name__ == '__main__':
    main()

py 05ordered_dithering.py で実行。4つの画像が生成される。

先ほど作成した gradation.png を読み込んで変換してみた。

元画像。256段階の諧調が含まれている。
gradation.png


2x2。5段階の疑似諧調を表現できる。
output_dither_2x2.png


3x3。10段階の疑似諧調を表現できる。
output_dither_3x3.png


4x4。17段階の疑似諧調を表現できる。
output_dither_4x4.png


8x8。65段階の疑似諧調を表現できる。
output_dither_8x8.png


猿(マンドリル)画像でも試してみる。

2x2。
m_output_dither_2x2.png

3x3。
m_output_dither_3x3.png

4x4。
m_output_dither_4x4.png

8x8。
m_output_dither_8x8.png


どれもそれっぽく変換できたような気がする。

さて、グレースケール画像を0/1で表現することはできたけど…。複数の色を指定して変換するにはどうしたらいいのだろう…。

RGB8色のみを使って表現するだけでも良いのであれば、RGBチャンネル別にディザリングをかけて、最後に合成すれば済みそうな気がする。しかし、一般的な減色処理として行いたいなら、もうちょっと何かやらないといけないはず…。画像内で使われている色の中から残したい色を決定して、その残したい色では表現できない部分についてはディザを使って疑似表現、ということになるはずで…。どうすればいいんだろうな…。

余談。レナさん画像が懐かしい。 :

半日ほど作業して思ったのですが。猿の画像では学習モチベーションが上がらない…。やっぱりレナさん画像のようなテスト画像を使いたいなあ…。

この手の実験をする時って、何十回、何百回、何千回と、その画像を拡大表示しながら「ここのドットが変だな…」「このドットの色はおかしくないか…」とチェックを繰り返すわけでして。なのに、その画像が、猿の画像ですよ。猿ですよ、猿。いくら「カラフルで奇麗な画像だよねー」って言ってみたところで猿は猿なんだよ! 下手すると何千回も猿を見つめ続けるわけですから当然精神的には苦痛なわけですよ。こんなん頭がおかしくなるわ。少し上の猿4連発を眺めただけでも自分ちょっと嫌な感じの汗出てきましたもん。コレ、ボディブローのように地味に効いてくるわ…。

どうせなら美しさを感じる画像を ―― 何度凝視したって全然ウンザリしてこない画像を使って実験したい。だから皆、こぞってレナさん画像で実験してたわけでね…。レナさんの御姿なら、256回でも65536回でも余裕で凝視できますわ。それぐらいの美しさがレナさん画像にはある。その美しさのおかげで心が折れずにトライアンドエラーを繰り返せる。レナさん画像がテクノロジーの発展に寄与してきたというのはガチですわ。猿に睨まれてたら挫けてた。レナさんがあの眼差しでこっちを見つめてくれて、こっちもソレを延々見つめ返したから、画像処理という分野はここまで来れたわけですよ。

でも、御本人が「もうやめろや」と仰られているのでは…ねえ…。代替画像、無いのかなあ…。

2022/06/20(月) [n年前の日記]

#1 [cg_tools] 2枚の画像を表示して見比べたい

2枚の png 画像を表示して、どの程度違うのか見比べたい。良さそうなビューアはないものか。ということで少し探してみたのでメモ。

環境は Windows10 x64 21H2。

見比べ君2 1.31。 :

_見比べ君2 -ソフトウェアの部屋-
_見比べ君2 - k本的に無料ソフト・フリーソフト

コレは良さそう。と思ったのだけど png に対応してなかった。残念。jpg には対応している模様。

DiffImg 2.2.0。 :

_DiffImg download | SourceForge.net

画像の異なる部分を色をつけて表示してくれる。pngも開けた。数ドット程度の違いを見つけ出したい場合は便利かもしれない。

ただ、入力ファイル名を指定するウインドウに、エクスプローラ等からファイルをドラッグアンドドロップできなかったのと、画像のサムネイルをクリックしないと画像表示を切り替えられなかったのがちょっと残念。

pixDiff x64 1.26。 :

_pixDiff - 使い方
_pixDiffの詳細情報 : Vector ソフトを探す!

ワイパーを使って画像を見比べることができる。pngも開けた。スコープ表示も ―― マウスカーソルの近くに四角を表示して、四角の中だけ別画像を表示する機能も便利そう。

ただ、別々のフォルダに入っている画像をドラッグアンドドロップで指定することはできないっぽい。ファイル → 画像を開く、から別々のフォルダに入ってる画像を指定することはできた。

512x512の画像を1:1で表示した際、画面の真ん中に妙な線が入るあたりは少し気になった。OpenGLを使っているっぽいので、ドライバ側の問題かもしれない。環境は、NVIDIA GeForce GTX 1060 6GB。OpenGL Version 4.6.0 NVDIA 512.77、と表示されている。

画像ワイパー 1.11。 :

_画像ワイパー

pixDiff の前身となったソフトだろうか…? pngも開けた。ウインドウへのファイルのドラッグアンドドロップはできない模様。

WinMerge v2.16.20+-jp-2。 :

_WinMerge 日本語版
_「WinMerge」2つのテキストの異なる点を色分け表示 - 窓の杜
_開発ツール/画像比較もできるWinMerge - Windowsと暮らす

WinMerge でも画像比較ができると今頃になって知った。左側と右側に別々の画像を表示して比較できる。エクスプローラ上で右クリックして「左側として選択」「比較」を選べば、別々のフォルダに入った画像も比較できる。

自分の場合、今まで WinMerge をテキストファイルの比較に使っていたのだけど、画像に対してもいつものノリで比較できる点はありがたいなと。ひとまずコレさえインストールしておけば、比較作業ができるのは助かる…。

#2 [python] Pythonで画像にディザリングをかけてみる

_昨日、 Python + Pillow (PIL) を使って、グレースケールの画像に対してディザリング処理(Ordered dithering)をしてみたわけだけど。RGB画像に対してはどのように処理をすればいいのか分からなかったので、そのあたりを試してみた。

環境は、Windows10 x64 21H2 + Python 3.9.13 64bit + Pillow 9.1.1。

実験に使った画像は、昨日と同様、猿(マンドリル)画像。

mandrill.png
_mandrill.png

ちなみに、この画像の使用色数を調べてみたら、230427色だった。

RGBチャンネル別に処理する。 :

RGB画像を、Rチャンネル、Gチャンネル、Bチャンネルに分解して、それぞれに対してディザリングをしてから最後に合成すればそれっぽくならないかなと思えてきたので試してみた。

_06ordered_dithering_8col.py
from PIL import Image


odtbl2x2 = [
    [1, 3],
    [4, 2]
]

odtbl3x3 = [
    [3, 7, 4],
    [6, 1, 9],
    [2, 8, 5]
]

# odtbl3x3 = [
#     [7, 9, 5],
#     [2, 1, 4],
#     [6, 3, 8]
# ]

odtbl4x4 = [
    [1, 9, 3, 11],
    [13, 5, 15, 7],
    [4, 12, 2, 10],
    [16, 8, 14, 6]
]

odtbl8x8 = [
    [1, 33, 9, 41, 3, 35, 11, 43],
    [49, 17, 57, 25, 51, 19, 59, 27],
    [13, 45, 5, 37, 15, 47, 7, 39],
    [61, 29, 53, 21, 63, 31, 55, 23],
    [4, 36, 12, 44, 2, 34, 10, 42],
    [52, 20, 60, 28, 50, 18, 58, 26],
    [16, 48, 8, 40, 14, 46, 6, 38],
    [64, 32, 56, 24, 62, 30, 54, 22]
]

odtbls = {
    "2x2": [5, odtbl2x2],
    "3x3": [10, odtbl3x3],
    "4x4": [17, odtbl4x4],
    "8x8": [65, odtbl8x8]
}


def get_dither_table(odtype):
    d, tbl = odtbls[odtype]
    w = len(tbl[0])
    h = len(tbl)
    odtbl = [[0 for i in range(w)] for j in range(h)]

    for y in range(h):
        for x in range(w):
            odtbl[y][x] = tbl[y][x] * 256 / d

    return w, h, odtbl


def get_dither_image(odtype, im):
    width, height = im.size
    oim = Image.new("L", im.size)

    tw, th, od = get_dither_table(odtype)

    src_pixel = im.load()
    dst_pixel = oim.load()

    for y in range(height):
        dy = y % th
        for x in range(width):
            dx = x % tw
            if src_pixel[x, y] < od[dy][dx]:
                dst_pixel[x, y] = 0
            else:
                dst_pixel[x, y] = 255

    return oim


def main():
    im = Image.open("mandrill.png")
    r, g, b = im.split()

    odtype = "4x4"
    rim = get_dither_image(odtype, r).convert("L")
    gim = get_dither_image(odtype, g).convert("L")
    bim = get_dither_image(odtype, b).convert("L")

    oim = Image.merge("RGB", (rim, gim, bim))
    oim.save("output_8col.png")


if __name__ == '__main__':
    main()

py 06ordered_dithering_8col.py で実行。output_8col.png というファイル名で、以下のRGB画像が得られる。

output_8col.png


見た感じ、一応どうにかカラー画像(RGB画像)に対してもディザがかかっているように見えなくもないなと…。

ただ、RGB各チャンネルが、0 or 1 (0 or 255) の値しか持ってないから、全部で 2 x 2 x 2 = 8色しか使ってないわけで…。当然ながら、結構悲惨な見た目になってるというか。

もっとも、230427色使ってた画像を、たった8色しか使ってない画像に変換して、このぐらいそれらしさが残ってるなら、これはこれでイイ感じなのでは、という気もしてくる。

ちなみに、昨日試した、Pillow の .convert("1") を使った事例 ―― RGBチャンネルを分解して、それぞれ2値化して、合成した結果とかなり近い結果になった気もする。使用色数はどちらも8色なわけで…。

output_01split.png

違いは、ディザの種類が Ordered dithering か、Floyd Steinberg dithering か、ぐらいではないのかなと…。たぶん。

_フロイド-スタインバーグ・ディザリング - Wikipedia

諧調に段階を持たせてみる。 :

RGB各チャンネルに対して0/1しか使ってない状態にしてしまうから厳しい見た目になるのではないか、もっと段階を持たせたら改善しないかなと思えてきた。もちろん、段階を持たせた分、使用色数は増えるわけだけど。

つまり、以下のような感じで処理できないかな、と。

about_dither_01.png

そんなわけで試してみた。

_07ordered_dithering_ncol.py
from PIL import Image
import sys

odtbl2x2 = [
    [1, 3],
    [4, 2]
]

odtbl3x3 = [
    [3, 7, 4],
    [6, 1, 9],
    [2, 8, 5]
]

# odtbl3x3 = [
#     [7, 9, 5],
#     [2, 1, 4],
#     [6, 3, 8]
# ]

odtbl4x4 = [
    [1, 9, 3, 11],
    [13, 5, 15, 7],
    [4, 12, 2, 10],
    [16, 8, 14, 6]
]

odtbl8x8 = [
    [1, 33, 9, 41, 3, 35, 11, 43],
    [49, 17, 57, 25, 51, 19, 59, 27],
    [13, 45, 5, 37, 15, 47, 7, 39],
    [61, 29, 53, 21, 63, 31, 55, 23],
    [4, 36, 12, 44, 2, 34, 10, 42],
    [52, 20, 60, 28, 50, 18, 58, 26],
    [16, 48, 8, 40, 14, 46, 6, 38],
    [64, 32, 56, 24, 62, 30, 54, 22]
]

odtbls = {
    "2x2": [5, odtbl2x2],
    "3x3": [10, odtbl3x3],
    "4x4": [17, odtbl4x4],
    "8x8": [65, odtbl8x8]
}


def get_dither_table(odtype, vrange, ofs):
    d, tbl = odtbls[odtype]
    if d >= vrange:
        print("Error: dither table range >= vrange")
        sys.exit()

    w = len(tbl[0])
    h = len(tbl)
    odtbl = [[0 for i in range(w)] for j in range(h)]

    for y in range(h):
        for x in range(w):
            odtbl[y][x] = tbl[y][x] * vrange / d + ofs

    return w, h, odtbl


def get_dither_image(odtype, im, level):
    width, height = im.size
    oim = Image.new("L", im.size)

    tw, th, _ = get_dither_table(odtype, 256, 0)

    ods = []
    for i in range(level):
        v0 = (256 * i) // level
        v1 = (256 * (i + 1)) // level
        vrange = v1 - v0
        _, _, od = get_dither_table(odtype, vrange, v0)
        ods.append([v0, v1, od])

    src_pixel = im.load()
    dst_pixel = oim.load()

    for y in range(height):
        dy = y % th
        for x in range(width):
            dx = x % tw
            v = src_pixel[x, y]
            for v0, v1, od in ods:
                if v0 <= v and v <= v1:
                    if v < od[dy][dx]:
                        dst_pixel[x, y] = v0
                    else:
                        dst_pixel[x, y] = v1

    return oim


def main():
    im = Image.open("mandrill.png")
    r, g, b = im.split()

    # odtype = "2x2"
    odtype = "4x4"
    level = 4
    
    rim = get_dither_image(odtype, r, level).convert("L")
    gim = get_dither_image(odtype, g, level).convert("L")
    bim = get_dither_image(odtype, b, level).convert("L")

    oim = Image.merge("RGB", (rim, gim, bim))
    oim.save("output_ncol_%s_lvl%d.png" % (odtype, level))


if __name__ == '__main__':
    main()

py 07ordered_dithering_ncol.py で実行。output_ncol_4x4_lvl4.png、といった感じのファイル名で生成画像が得られる。

まずは、ディザ配列の大きさは4x4、256諧調を分割しない状態で試してみた。

output_ncol_4x4_lvl1.png

前述の、8色しか使ってないディザ画像と同じ結果が得られた。

では、256諧調を2分割して、それぞれにディザをかけてみるとどうなるか…。

output_ncol_4x4_lvl2.png

かなり改善できた気がする。RGB各チャンネルが3段階を持ってる状態になってるから、使用できる色数は 3 x 3 x 3 = 27色のはず。8色から27色に増えると、見た目もこのぐらい違ってくるのだな…。

ただ、上記の画像は、本来27色まで使えるはずなのに、21色しか使ってなかった。おそらく、元画像がどんな色を使っているかで、変換後の画像の使用色数も変わってくるのではないかと。

この調子で、分割数を増やしてみる。

256諧調を3分割。最大64色中、41色を使用。
output_ncol_4x4_lvl3.png

4分割。最大125色中、75色を使用。
output_ncol_4x4_lvl4.png

5分割。最大216色中、112色を使用。
output_ncol_4x4_lvl5.png

6分割。最大343色中、170色を使用。
output_ncol_4x4_lvl6.png

7分割。最大512色中、234色を使用。
output_ncol_4x4_lvl7.png

8分割。最大729色中、309色を使用。
output_ncol_4x4_lvl8.png

9分割。最大1000色中、402色を使用。
output_ncol_4x4_lvl9.png

ということで、使用色数8色でディザをかけると悲惨な見た目になるけれど、256諧調の分割数を増やして、色数も増やせば、その分見た目もそれなりに改善されていくことが確認できた。ただ、分割数が増えれば増えるほど、差を感じにくくなってくる気もする。

一応、元画像も再掲。230427色。
mandrill.png


ところで…。ImageMagick の Ordered dithering も、おそらくはコレと似たような処理をしているのではないかと想像してみたりもして。あちらも、何かの値を増やしていくと見た目がどんどん改善されていくので…。

_Quantization - ImageMagick Examples

「Ordered Dither with Uniform Color Levels」の項で、ディザ配列サイズの後ろに何かを指定してる。
magick gradient.png -ordered-dither o4x4,6 od_o4x4_6.gif
この値が、たぶんソレなのかなと。

(※2022/06/21追記。ImageMagick 7.1.0-37 Q16-HDRI x64 で試してみたら、予想通り似た感じの変換結果が得られた。全く同じ結果にはならなかったけど、おそらくしきい値の取り方が微妙に違ってるから違う結果になっただけで、やってることは大体同じなのだろう、と…。追記終わり。)

さておき。今回試したこの減色処理(?)は、ImageMagick の Ordered dithering と同様の問題を抱えている。最終的に残る色が固定パレット相当になるあたりがよろしくない。もし、ちゃんとした減色処理をしたいなら、画像毎に使っている色を分析して、その画像特有のパレットを生成して、そのパレットを使いながらディザをかけないといけないはず。

しかし、任意のパレットを指定しながら、この種のディザをかける方法が分からないなと…。何せ、あの ImageMagick ですら、その実装はしていないらしいので…。 *1 もっとも、例えば GIMP や EDGE2 は任意のパレットを指定しながらディザをかけて減色ができてるので、仕組みさえ分かれば実装できるはずだよなと…。

それはそれとして。Pillow関係の解説記事を眺めてたら、そもそも Pillow は減色機能も持ってるようで。メディアンカットとかkmeans(k平均法)とかサポート済みっぽい。

_Pythonで画像の減色をする

そのあたりを使いつつ、この手のディザをかけたら、もっとそれっぽくなるのだろうか。どうなんだろう。

妄想。 :

今回、256諧調を均等に分割して実験してみたわけだけど。画像によっては256諧調のうち、この範囲は頻繁に使うけどこの範囲はほとんど使ってない、といったものもあるかもしれないなと。そういった画像の場合は、分割位置に偏りを持たせたら、更に結果が改善されるのかもしれないなと妄想したりもして。まあ、そんなの絶対誰かしらが既に思いついて試しているだろうなー、とも思いますけど。

画像によっては、R,G,B成分の使用具合にばらつきがあるかもしれないな、とも。その場合、例えばR成分とG成分は細かく分割するけれど、B成分は粗く分割して使用色数を抑える、といったこともできたりするのかもしれない。まあ、これも絶対誰かしらが既に思いついて試してるやろ、とも思いますが。

RGBじゃなくて、YCbCr(輝度+色差)、HSV(色相、彩度、明度)でこの手の処理をしたらどうなるだろう、という疑問も湧いたりして。まあ、そのあたりも既に誰かが以下略。

余談。レナさん画像を使いたいです。 :

昨日は猿4連発だったけど、今日なんか猿10連発ですよ。気が狂いそう。嗚呼、レナサン画像を使いたい…。レナお婆ちゃんも少しはこの苦しみを理解してくれないものか…。

*1: ImageMagick も、任意のパレットを指定しつつ Floyd Steinberg っぽいディザをかける機能なら実装済み。ただ、Ordered dithering を指定すると固定パレットになってしまう。意外と実装が難しい処理なのだろうか…?

2022/06/21(火) [n年前の日記]

#1 [python] ディザリング関係のページを眺めてる

RGB画像に対して任意のパレットを使ってディザリングをかけつつ減色する方法についてどこかで解説されてないものかと、ググって関連ページを眺めているところ。

もしかして、色の差を求めないといかんのだろうか…。与えられたパレット値の中から、処理しようとしているドットの色に近い色を見つけ出して、みたいなことをしないといかんのではなかろうか。そのためには、この色とこの色の差はどのくらいなのかを数値化しないといけない。しかし、色の差とやらは一体どうやって求めればいいのか…。

ググってたら、色差を計算する方法があるようで。

_色差 - Wikipedia

こういう計算が必要になるのだろうか。でも、計算が大変そう。XYZとかLabとか一体それは何でしょうか? な状態。

その後もググってたら、Pythonで色を置き換える処理を試している方に遭遇。

_Pythonで、いくつかの指定した色に似た色になるように、画像のピクセルを置き換えたい

skimage (scikit-image) というモジュールを使うと、色差を計算してくれるっぽい。わざわざ自分で実装しなくても済みそうだな…。助かる…。

_scikit-image: Image processing in Python - scikit-image
_3.3. Scikit-image: 画像処理 - Scipy lecture notes

更にググってたら、自分がやってみたい処理について、そのものズバリで解説している記事に遭遇。

_Arbitrary-palette positional dithering algorithm

英文記事だから何が何だか分からんけど、ソースも掲載されているからなんとかなりそう。じっくり眺めてみないと…。

Photoshop上での処理は特許切れらしい。 :

上記の記事の中で、Adobe Photoshop が実装してたパターンディザ化の処理は特許が切れてるかも、みたいな記述があった。

Adobe Systems Incorporated is in possession of US Patent number 6606166, applied for on 1999-04-30, granted on 2003-08-12, but which expired on 2019-11-16. It describes an algorithm called pattern dithering invented by Thomas Knoll. For the sake of documentation, we will explain how that algorithm works as well.

Arbitrary-palette positional dithering algorithm より

パテントナンバー 6606166 は、1999/04/30に申請して2003/08/12に特許が取れた。でも 2019/11/16 に特許切れした。と書いてあるのかな。たぶん。ちょっと気になる。

ただ、遥か大昔にグラフィッカーさん達が、「Photoshopの減色はイマイチだ」「OPTPiXで減色するほうが奇麗」とこぞって言ってた記憶もあるのだよな…。でもまあ、それでも、上記の記事の減色結果を眺める限り、GIMPの減色処理よりは全然マシに見える。GIMPのソレは突拍子もない色のドットがポツポツと入ってしまうわけで。

そうか…Photoshopのソレは特許が切れているのか…。ググってみたけど、たぶん以下がその特許内容なのかな。

_US Patent for Pattern dithering Patent (Patent # 6,606,166 issued August 12, 2003) - Justia Patents Search
_US6606166B1 - Pattern dithering - Google Patents

ざっと眺めてみたけど何が何やらさっぱり。

2022/06/22(水) [n年前の日記]

#1 [prog][cg_tools] ディザリング処理について勉強中

任意のパレットでディザリングをかける処理について解説してくれている記事を眺めて勉強中。

_Arbitrary-palette positional dithering algorithm

上記の記事は、Joel Yliluomaさん(?)が考えた3種類のディザリングアルゴリズムと、Adobe が特許を持っていた(けど2019年に切れた)パターンディザリングアルゴリズムについて、ソースも載せつつ解説している模様。

加えて、以下についても解説されてる。
何にせよ、せっかくソースが公開されているのだから、ビルドしてみて動作確認をしたい。C++のソースらしいから、MSYS2 + g++ でコンパイルできないかなと手元の環境で試しているところ。

libgdが必要らしい。 :

各ソースが最初のあたりで gd.h を include してるのが気になる。ググってみたら libgd なるライブラリのようで、png、bmp、gifの読み書きができるようになるらしい。幸い MSYS2 にも libgd パッケージが用意されていたので、インストールは簡単だった。
pacman -S mingw-w64-x86_64-libgd
pacman -S mingw-w64-i686-libgd

_GD Graphics Library
_libgd/libgd: GD Graphics Library
_Package: mingw-w64-x86_64-libgd - MSYS2 Packages
_libgd メモ
_GD Graphics Library - Wikipedia
_Debian7 CPANでinstall GDするとjpegテストエラーでこける - Qiita
_ubuntu C言語 GDライブラリのインストール - Qiita

#2 [neta] ヒーローの見た目

妄想メモ。

昨晩、とある深夜アニメを眺めていたら、モンスター(?)が瀕死の人間の記憶や外観をコピーして今まで人間のふりをして云々、という設定が披露されていて。それを見てなんとなく、「ウルトラマンみたいだなあ…」「でも、見た目がモンスターだとこうまで印象が変わるのか…」と思ってしまって、そこからちょっと妄想してしまったのでメモ。

見た目は大事なのかそうでもないのか。 :

ウルトラマンって、ああいう見た目だから、人類の味方として認知される展開になったけど。アレがもし、ゴモラだのレッドキングだのバルタン星人みたいな見た目だったら、はたして人類の味方として認知されたのかなあ、という疑問が湧いた。どうなんだろう。

いやまあ、ゴジラやガメラの事例を考えると、色々やってるうちに「コイツはこういう見た目でも人類の味方かもしれん」という展開になっていくのだろうけど。そういやウルトラセブンのカプセル怪獣もあったっけ。見た目が怪獣だからと言って、最後まで敵扱いされる展開は、一般的な娯楽作品であれば無いのかもしれないな…。

とは言え、見るからにヤバそうな見た目のヒーローだったら、やはり最初のうちは「コイツは敵なのでは」と人類側から思われちゃうのが自然な流れのような気もする。逆に、見た目がスマートでカッコよかったら、実は人類にとって敵であっても、「コイツは俺達の味方なのかな」と勘違いされそうでもあるなと…。

そういう感じの設定を盛り込んだ特撮ヒーローが既に居たような気がする。映画版「ハカイダー」は近かったりしないか。あるいは未見だけど、「アクマイザー3」あたりはどうなんだろう。「ライダー電王」のモモタロスあたりもそういうアレなのかなあ。見た目はヤバそうだけどこっち側、みたいな。

怪獣好きのヒロイン。 :

仮にヒーローがモンスターっぽいデザインに変身したとして。しかしヒロインが怪獣大好き少女だったらどうだろう。

「スゴイ! その腕、レッドキングみたいじゃん! 足なんかガラモンだよ! 頭のてっぺんなんかウルトラマンの初期企画のベムラーにクリソツ! 君、実にいいよ! その姿、たまらない!」

ハッピーエンドですね。

モフモフ好きのヒロイン。 :

仮にヒーローが狼男みたいなモンスターに変身したとして。しかしヒロインがモフモフ大好き少女だったらどうだろう。

「うわっ…モフモフだ! モフモフしたい! モフモフさせて! モフモフー! モフー!」

ハッピーエンドですな。

実は宇宙人の作戦。 :

現代日本においては、ウルトラマンシリーズ等を通じて様々な宇宙人のデザインがTVで流れて、子供達がそれらデザインに親しみを感じていることも多いだろうけど。

実は、地球人の間にひっそりと紛れ込んだ異星人達が、いずれ来るであろう地球人とのファーストコンタクトの日のために、その手の見た目に対する地球人側の拒否反応を薄めようと画策した故の現状…だったりしたらどうしよう。

アナウンサー「さあ、いよいよ異星人がUFOから降りてきます。一体どういう姿なのでしょう…」
手を振りながら姿を現す異星人。
視聴者「アレッ? コイツ…メトロン星人じゃん!」

策士だな…異星人…。

以上、バカ妄想でした。

2022/06/23(木) [n年前の日記]

#1 [prog][cg_tools] ディザリング処理をするサンプルプログラムをビルドした

任意のパレットを指定してディザリングをかけるサンプルプログラム群を、Windows10 x64 21H2 + MSYS2 や、Ubuntu Linux 20.04 LTS でビルドしてみた。

_Arbitrary-palette positional dithering algorithm

ソースファイル群。 :

前述の解説ページからコピペして、C++のソースファイル(.cpp)として保存してみた。

_yliluoma_ordered_dither_1a.cpp
_yliluoma_ordered_dither_1ba.cpp
_yliluoma_ordered_dither_1bb.cpp
_yliluoma_ordered_dither_1c.cpp
_yliluoma_ordered_dither_1d.cpp
_yliluoma_ordered_dither_2a.cpp
_yliluoma_ordered_dither_2b.cpp
_yliluoma_ordered_dither_3a.cpp
_adobelike_pattern_dither.cpp

処理内容の簡単な説明は以下。
  • yliluoma_ordered_dither_1a.cpp : 2色の組み合わせを評価して妥当な2色を選んでディザをかける。
  • yliluoma_ordered_dither_1ba.cpp : 異なり過ぎる2色の組み合わせは除外して処理する。
  • yliluoma_ordered_dither_1bb.cpp : 2色の差を CCIR 601 で求める。
  • yliluoma_ordered_dither_1c.cpp : ループで結果を求めず数式で求めて高速化。ただし品質は落ちる。
  • yliluoma_ordered_dither_1d.cpp : 3色のディザリングで処理する。
  • yliluoma_ordered_dither_2a.cpp : ディザは2色の組み合わせという前提を捨てて考えた版。
  • yliluoma_ordered_dither_2b.cpp : 2a のガンマ値を補正した版。
  • yliluoma_ordered_dither_3a.cpp : 2b のアレンジ版。
  • adobelike_pattern_dither.cpp : Photoshopに近い処理。
前述のページを眺めないと、どんなことをしているのかピンとこないだろうけど…。とりあえず、色々な方法を試しているのだな、ぐらいの認識でもいいかと…。

コンパイルする。 :

コンパイルには、g++、libgd が必要になる。できれば GNU Make も欲しい。
$ g++ --version
g++.exe (Rev2, Built by MSYS2 project) 12.1.0
...

$ make --version
GNU Make 4.3
このプログラムは x86_64-pc-msys 用にビルドされました
...

libgd は、MSYS2 も Ubuntu Linux 20.04 LTS もパッケージが用意されてるので、以下でインストールできる。

MSYS2の場合。
pacman -S mingw-w64-x86_64-libgd
or
pacman -S mingw-w64-i686-libgd

Ubuntu Linux 20.04 LTSの場合。
sudo apt install libgd-dev


コンパイルの一例。

MSYS2の場合。
g++ yliluoma_ordered_dither_1a.cpp -fopenmp -o yliluoma_ordered_dither_1a.exe -lgd -lm

Ubuntu Linuxの場合。
g++ yliluoma_ordered_dither_1a.cpp -fopenmp -o yliluoma_ordered_dither_1a -lgd -lm

ちなみに、「-fopenmp」を指定しなくても実行バイナリは作れるけれど、その場合、ディザをかける処理時間がかなり長くなって結構待たされる。「-fopenmp」を指定して実行バイナリを作ったら並列処理が有効になったようで、変換がかなり速くなった。

一応、Makefile も書いてみた。Makefile というファイル名にリネームして、*.cpp と同じ場所に置いておいて、make と打てば全ファイルをビルドできる。一応、make clean で、実行バイナリを削除できるようにもしておいた。

_Makefile.msys2
_Makefile.unix

ディザをかけてみる。 :

前述のページから、元画像をコピーしてきた。

_scene.png

各プログラムの使い方は以下。
# MSYS2
./xxxx.exe INPUT.png OUTPUT.png

# Linux
./xxxx INPUT.png OUTPUT.png

一応、全部のプログラムに画像を生成させるシェルスクリプトも書いてみた。chmod +x test.sh で実行権限を与えてから ./test.sh で実行できる。

_test.sh
chmod +x test.sh
./test.sh


DOS窓上で動かす場合は以下のbatファイルが使えるけれど、MSYS2上でビルドした今回のプログラムをDOS窓上で動かすためには、*.dll群が必要になる。

_test.bat

一応、DOS窓上で動かせるようにするための .dll群を MSYS2 (/mingw64/bin/) からコピーしてくるシェルスクリプトも書いてみた。MSYS2上で、chmod +x copydll.sh で実行権限を与えて ./copydll.sh を実行すれば、動作に必要になるdll群がカレントディレクトリにコピーされる。シェルスクリプトの最初のあたりで、.dll が置かれてる場所を指定してるので、MSYS2 のインストール場所を変更してる場合は注意。

_copydll.sh

Windows用実行バイナリ。 :

一応、MSYS2 (MinGW64) の .dll群も含めた実行バイナリその他を zip にして置いときます。

_yliluoma_ordered_dither.zip (14.6 MByte)

MSYS2 をインストールしてない環境で動くのかどうかは分からんけど…。一応サブPC(Windows10 64bit)でも動くことは確認しましたが…。

静的リンクしたかったのだけど、-static をつけてビルドしたら大量にエラーが出てしまって、しかし解決することができなくて。そのあたりは今後の課題。

動作確認してみた感想。 :

出来上がった実行バイナリ群を使って、一応動作確認をして、前述のページに貼られている画像と比較してみたのだけど、見た感じ、結果が同じにはならなくて…。もしかすると、元画像が違うんじゃないかと邪推しているところ。

ただ、アルゴリズムに改良を加えると生成画像の品質が上がっていくことや、別のアルゴリズムを使えば生成画像の傾向が変わってくることはたしかに確認できた。

「とにかく処理が遅い」と説明されてたけど、たしかにこれはかなり時間がかかる処理だなと…。Photoshop の処理は全然速くて、かなり上手い具合にやってるらしいことも確認できた。

2022/06/24(金) [n年前の日記]

#1 [anime] 「トイ・ストーリー4」を視聴

TV放送されてたので視聴。初見。

フツーに面白かった。なんでも、1-3とはテーマが違っているとのことで一部で大不評だったらしいのだけど、自分はそのあたり特に気にならなかった。というかそういう面倒臭い話は日本の深夜アニメあたりでやってくれませんか、何もディズニーやPIXARアニメでやらんでも。

とりあえず、これでメインキャラの『呪縛』が解けて続編を作りやすくなったかなあ、めでたしめでたし。てな印象を受けた。基本的に子供部屋を舞台にして話を進めるという制約はキツかったはずで。実際、既存作でも、玩具達をどうにか子供部屋の外に出すべく四苦八苦してたわけで。たしか3の時点でも、スタッフさんが「この基本設定ではコレ以上話を作れない…どうしよう…」と悩んでた、てな話をどこかで見た記憶もあるし。その制約が今作を通じて消滅した ―― ようやく鎖から解き放たれたのだから、今後はもうちょっと『自由』に話を作れるはず。…作れるといいな。

それにしても、相変わらずCG技術がスゴイ…。いや、作業量もとんでもない…。画面に登場するものは、基本的には全て作ってるのだろうし…。3DCGだから今まで作ってきたものはライブラリ化できるメリットはあるだろうけど、それにしたって物量が…。

それはさておき。子供さん達の評価が気になるところ。子供さんが喜んで見ていたら、それはそれでひとまず良い出来のアニメと判断していいと思うのだよな…。そして、子供さんが喜んで見てるのに、おじさんおばさん達が眉をしかめて、やれテーマが云々と言い出して作品を叩いてるとしたら、それって結構大人げない光景だろうなと…。いやまあ、ディズニーやPIXARのアニメは大人も楽しめるからリターンが期待できるし、リターンが期待できるからこそリソースも際限なく投入できるわけで、故に大人達がブーブー言わない内容も常に求められてしまうのだろうけど。でも、まずは子供さんが楽しんでくれるかどうかが大事だろうと…。大人達は「ついでに」楽しめたらラッキーですね、ぐらいなもんで…。大人を楽しませることを優先しちゃったら、それはおかしいよな…。大人向けのアニメなんて山ほどあるんだからそっちを見ろよ、ただでさえ子供向けのアニメが少なくなってるのにコレ以上大人が子供からアニメを取り上げるんじゃねえよ、自分達が子供の頃はたくさんアニメを見せてもらってきたのだから今度は自分達が子供にアニメを見せてやる番だろう。てな感じのもやもやした気持ちもあったりするわけでして。

まあ、子供さんにとってもおそらく面白いアニメだったのではあるまいか。アクションシーンも多かったし。何より、自分が今遊んでる玩具だってひょっとすると今後世界に旅立って冒険するのかも、てな妄想は子供さんにとってかなり楽しいはず。子供さんの妄想が広がるのであれば、それはそれでおそらく良いアニメ、だったのではないかなあ…。まあ、自分は子供が居ないので実際のところは分からんですけど。とにかく子供さんが楽しんでくれてたらいいな、と…。

2022/06/25() [n年前の日記]

#1 [nitijyou] 暑い

朝07:00の時点で部屋の温度が28度。これはヤバイのではと思ったら、やはり昼間は33度ぐらいに達したようで。一応エアコンを入れたけど、万が一エアコンが故障したらかなりヤバイことになるだろうな…。

2022/06/26() [n年前の日記]

#1 [python][cg_tools] ディザリング処理をするプログラムをPythonに移植中

任意のパレットを指定してディザリングをかけるサンプルプログラム群を ―― C++ で書かれてるソレを Python で書き直しているところ。

_Arbitrary-palette positional dithering algorithm

C/C++ で書くと処理が速いプログラムになるけれど、ソースがちょっと分かりづらくて魔改造しにくい。例えば、RGB値を取り出すために、ビットシフトして0x0ffでand、とかやらないといけない。そんな部分があちこちに出てくるけれど、それってアルゴリズムの本質的な部分ではないよなと…。これがPythonだったら、(r, g, b) といった感じで、タプルで渡せば済んじゃうので、ざっと眺めても分かりやすいソースになる。まあ、処理は遅くなるだろうけど…。Python は遅いし…。

そんなことを思って書き直し始めたのけど。動作確認してみたら、C++版と比べてとんでもなく処理時間がかかるようになってしまった。これはプログレスバーか何かを表示しておかないとあかんなと…。

CUIでプログレスバーを表示したい。 :

CUIでプログレスバーを出したい。tqdm というモジュールが便利らしい。

pip install tqdm

Python 3.9.13 x64 上で導入してみたけど、Windows10 x64 21H2 + DOS窓/ConEmu では、そのままだと正常表示されなかった。tqdm(... ,ascii=True) をつけて、ASCIIコードのみで出力すれば改善される模様。

from tqdm import tqdm

for i in  tqdm(range(100), ascii=True):
    hoge()

以下、参考ページ。

_Pythonで進捗バーを表示する(tqdm) - Narito Blog
_Tqdm Issues and Tips - jdhao's digital space
_Wrong output in Windows console - Issue #454 - tqdm/tqdm

2022/06/27(月) [n年前の日記]

#1 [python][cg_tools] ディザリング処理をするプログラムをPythonに移植中その2

任意のパレットを指定してディザリングをかけるサンプルプログラム群を ―― C++ で書かれてるソレを Python で書き直しているところ。

_Arbitrary-palette positional dithering algorithm

アルゴリズム1を移植(?)中。tri-tone まで対応したけど、どうにも処理が遅い。8x8 のディザで処理をしているけれど、これって 4x4 ぐらいでも十分なのではないかという気がしてきたので、2x2、4x4 を選べるような仕様に改造中。

コマンドラインオプションで処理を変えるようにしたいので、argparse を使ってみた。

def main():
    parser = argparse.ArgumentParser(description="Yliluoma ordered dithering 1")
    parser.add_argument("-i", "--input", required=True, help="Input png filename")
    parser.add_argument("-o", "--output", required=True, help="Output png filename")
    parser.add_argument("-m", "--mode", type=int, default=3, help="job kind 0-4. default: 3")
    parser.add_argument("-d", "--dither", type=int, default=8,
                        help="Dither type 2,4,8 (2x2,4x4,8x8). default: 8")
    args = parser.parse_args()

    if args.mode < 0 or args.mode > 4:
        print("Error: Unknown mode = %d" % args.mode)
        sys.exit()

    if args.dither != 2 and args.dither != 4 and args.dither != 8:
        print("Error: Unknown dither = %d" % args.dither)
        sys.exit()

    srcim = Image.open(args.input)
    im = convert_dither(srcim, args.mode, args.dither)
    im.save(args.output)

ただ、help文字列の記述の仕方が分からない。一般的にはどういう感じのテキストを書くのだろう。どこかにそのあたりの解説記事が無いものか…。

2022/06/28(火) [n年前の日記]

#1 [python][cg_tools] ディザリング処理をするプログラムをPythonに移植中その3

任意のパレットを指定してディザリングをかけるサンプルプログラム群を ―― C++ で書かれてるソレを Python で書き直しているところ。

_Arbitrary-palette positional dithering algorithm

アルゴリズム2の移植を始めたものの、Python版の生成画像が、C++版とかなり違う生成画像になってしまった。おそらく std::sort() のあたりの処理を正しく書けてない気がする…。

/* Luminance for each palette entry, to be initialized as soon as the program begins */
static unsigned luma[16];

bool PaletteCompareLuma(unsigned index1, unsigned index2)
{
    return luma[index1] < luma[index2];
}

// ...

    // Sort the colors according to luminance
    std::sort(result.colors, result.colors + MixingPlan::n_colors, PaletteCompareLuma);
    return result;

あらかじめパレットデータから作っておいた輝度の配列の値を元にして、result.colors という配列の中身をソートするのかなと思ったのだけど…。そもそも Python でそういう処理ってどう書くのだろう。

    lst = []
    for i in range(n_colors):
        lst.append([r_colors[i], luma[i]])
    nlst = sorted(lst, key=lambda x: x[1])
    cols = [row[0] for row in nlst]
    return cols

上のように書いてみたけど、めちゃくちゃな結果になる…。

色々試してみたところ、以下のような記述だと似た結果になった。

    cols = sorted(r_colors, key=lambda x: luma[x])
    return cols

本当にこれで合ってるんだろうか? 自信無し。

2022/06/29(水) [n年前の日記]

#1 [python][cg_tools] ディザリング処理をするプログラムをPythonに移植中その4

任意のパレットを指定してディザリングをかけるサンプルプログラム群を ―― C++ で書かれてるソレを Python で書き直しているところ。

_Arbitrary-palette positional dithering algorithm

アルゴリズム2の移植中。アルゴリズム2は、CCIR601 とは別に CIEDE2000 を使った処理も記述されているけれど、試しに CIEDE2000 に切り替えたら真っ白な画像になってしまった…。

元々のC++版もコンパイルし直して画像を生成してみたけど、そちらはそれらしい結果が出てきた。Pythonで書き直す時にエンバグしてしまったのだな…。

それはさておき。元々のC++版で気になる部分を見つけた。以下の数式なのだけど…。

        C = sqrt(a * a + b + b);

Pythonで書き直したら上記の場所でエラーが出た。平方根を求める関数 sqrt() にマイナス値が入ってしまう場合があって、「そんな値を渡されても計算できないよ」と怒られる。C++の場合は、sqrt() にマイナス値が入った時の動作は規定されてなくて、処理系(?)次第で動作が違うのだとか。

なんとなくだけど、本当は以下だったりしないのかなと…。これなら必ず正の値になるし。

        C = sqrt(a * a + b * b);

2022/06/30(木) [n年前の日記]

#1 [nitijyou] 暑い

ここ最近、暑い日が続いていて何もやる気が起きない…。今朝なんて朝の時点で部屋の温度が29度…。

以上、30 日分です。

過去ログ表示

Prev - 2022/06 - Next
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project