去年の11月、フレームバッファに直接値を書き込んでプログラムからお絵描きがするような、手軽なプログラミングをやってみたい人向けに LCD 風ディスプレイシミュレータを書きました。

ある通常ファイルを /dev/fb0 のようなデバイスファイルに見立て、ファイルを開いてシークして値を書き込むことによって、疑似画面上にドットを打つことができます。

ダウンロード

Gist に Base64 エンコードした display.zip を置いておきました。Linux や Mac OS の場合は、base64 コマンドに -d オプションをつけることによって、元のバイナリにデコードできます。Windows の場合は、Lhaplus などのアーカイバでバイナリに戻せます。

使い方

display.exe はコマンドラインからオプション付きで実行します。

display 横ドット数 縦ドット数 1ドットあたりの大きさ 中間ファイルパス

次のように実行します。

display 64 24 5 fb0.bin

上の例では、解像度 64×24 ドットのグラフィック LCD ディスプレイとしてシミュレートし、1ドットあたりの表示ピクセル数は5、ファイルに fb0.bin を用いて起動しています。

仕組み

プログラム側から、

  1. ファイルを開く
  2. ビット列を書き込む
  3. ファイルを閉じる

の一連の作業を行います。アニメーションさせる場合は、1 から 3 を sleep() を入れて繰りかえします。

サンプルプログラム

次のサンプルでは、ファイル fb0.bin を中間ファイルとして display.exe から監視させています。

display_sample.c (Gist)

#include <stdio.h>
#include <math.h>

#define COL 64
#define ROW 24

FILE* f;

/* 画面を白く塗りつぶす */
void clear()
{
  int i;
  char n = 0x00;
  fseek(f, 0, SEEK_SET);
  for (i = 0; i<COL*ROW; i++)
    fwrite(&n, sizeof(char), 1, f);
}

/* 画面を黒く塗りつぶす */
void fill()
{
  int i;
  char n = 0xFF;
  fseek(f, 0, SEEK_SET);
  for (i = 0; i<COL*ROW; i++)
    fwrite(&n, sizeof(char), 1, f);
}

/* XY座標をバイト・ビットインデックスに変換する */
void xy2i(int x, int y, int* by, int* bi)
{
  int a = y * COL + x;
  *by = a / 8;
  *bi = a % 8;
}

/* 点を描く */
void point(int x, int y, int flag)
{
  int by, bi;
  char b;
  xy2i(x, y, &by, &bi);
  if (by < 0 || by > COL*ROW / 8) {
    return;
  }
  if (x < 0 || x >= COL)
    return;
  if (y < 0 || y >= ROW)
    return;
  fseek(f, by, SEEK_SET);
  fread(&b, sizeof(char), 1, f);
  if (flag)
    b |= (1 << (7 - bi));
  else
    b &= ~(1 << (7 - bi));
  fseek(f, by, SEEK_SET);
  fwrite(&b, sizeof(char), 1, f);
}

/* 直線を描く */
void line(int x1, int y1, int x2, int y2)
{
  int x, y;
  int dx, dy;
  dx = abs(x2 - x1);
  dy = abs(y2 - y1);

  if (dx > dy) {
    if (x1 > x2) {
      x = x1;
      x1 = x2;
      x2 = x;
      y = y1;
      y1 = y2;
      y2 = y;
    }
    for (x = x1 >= 0 ? x1 : 0; x <= x2 && x < COL; x++) {
      point(x, (y2 - y1) * (x - x1) / dx + y1, 1);
    }
  }
  else {
    if (y1 > y2) {
      x = x1;
      x1 = x2;
      x2 = x;
      y = y1;
      y1 = y2;
      y2 = y;
    }
    for (y = y1 >= 0 ? y1 : 0; y <= y2 && y < ROW; y++) {
      point((x2 - x1) * (y - y1) / dy + x1, y, 1);
    }
  }
}

/* 矩形を描く */
void rect(int x, int y, int w, int h)
{
  line(x, y, x + w, y);
  line(x, y, x, y + h);
  line(x + w, y, x + w, y + h);
  line(x, y + h, x + w, y + h);
}

void load_bitmap(char* path)
{
  FILE* ff = fopen(path, "r");
  char line[COL + 2];
  int ln = 0;
  while (fgets(line, COL + 2, ff) != NULL) {
    for (int i=0; i<COL && strlen(line); i++) {
      point(i, ln, line[i] == '*' ? 1 : 0);
    }
    ln++;
    if (ln > ROW)
      break;
  }
  fclose(ff);
}

int main()
{
  f = fopen("fb0.bin", "w+b");
  clear();
  // load_bitmap("image.txt");
  fclose(f);

  for (int i=0; i<COL * 100; i++) {
    f = fopen("fb0.bin", "w+b");
    clear();
    line(0, ROW/2, COL - 1, ROW/2);
    double xang;
    double y;
    int px, py;
    int yy;
    for (int x=0; x<COL + i; x++) {
      xang = x * 3.141592 / 16;
      y = sin(xang);
      y *= 8; 
      if (x > 0) {
        line(px - i, py + ROW/2, x - i, (int)y + ROW/2);
      }
      if (x - i == 10) {
        yy = (int)y + ROW / 2;
      }
      px = x;
      py = (int)y;
    }
    rect(10, yy, 3, 3);
    fclose(f);
    sleep(50);
  }

  return 0;
}

このサンプルを実行しながら display.exe で表示を行うと、次のような絵が表示されて、波がアニメーションします。

anime