3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Processingとビット演算

Last updated at Posted at 2020-12-10

Processing Advent Calendar 2020の11日目です。

自己紹介

H1ronoです。時々つぶやきProcessingに投稿しています。基本的にp5.jsです。

この記事でやること

この記事では、ビット演算をProcessingに取り入れる簡単な方法を紹介します。大きく次の2つに分けて説明します。

  1. グラフィック×ビット演算のテンプレート
  2. 実際の利用例

分かりづらいところなどがあれば何でも言って下さい。

テンプレート

私の場合、ビット演算を用いて得た情報をグラフィックにアウトプットするというのが基本的な流れです。具体的には次のようなコードになります。

// 扱うビット長
const bit_len = 8;
// データ
const data = 255;

for (let i = 0; i < bit_len; i++) {
    if (data & 1<<i) {
        /* 何かする */
    }
}

data & 1<<iでは「整数dataを2進数で現したとき、そのiビット目が1かどうか」を判定しています。1であれば短絡評価でtrue1になります。これだけだと情報量が足りないので、整数の配列にします。

// 配列の長さ, 扱うビット長
const arr_len = 8, bit_len = 8;
// データ
const data = [34, 108, 232, 5, 46, 202, 165, 71];

for (let i = 0; i < arr_len; i++) {
    for (let j = 0; j < bit_len; j++) {
        if (data[i] & 1<<j) {
            /* 何かする */
        }
    }
}

ここまでがテンプレです。私はこれをもう少し応用してつぶやいています。

ビット演算の利用例

この作品を例に、ビット演算の利用方法を紹介します。

Koji Saito(@KojiSaito)さん考案の生き物が動く作品です。

ビット演算の利用で最初に思いつくのはドット絵かなと思います。ツイートのコードを見やすく整形するとこのようになります。

// ドット絵の1行または1列あたりのマスの数 (ドット絵は正方形)
const cell_len = 8;
// ドット絵の1セルのサイズ, 生き物の大きさの半分
const cell_size = 10, half_size = cell_size * cell_len / 2;
// データ
const data = ["ļłƩƁǃžĤĢ", "ļłƕƁǃžĤń"];
// 生き物の左上の座標を返す関数
const pos = (f) => f(frameCount/60) * 100 + width/2 - half_size;

function setup() {
    createCanvas(360, 360);
}

function draw() {
    background(0);
    let source = data[floor(frameCount/9%2)];
    // 生き物の左上の座標
    let x = pos(cos), y = pos(sin);
    for (let i = 0; i < cell_len; i++) {
        for (let j = 0; j < cell_len; j++) {
            if (source.charCodeAt(i) & 1<<j) {
                rect(x + j*cell_size, y + i*cell_size, cell_size, cell_size);
            }
        }
    }
}

様変わりしましたが、基本的なロジックは(おそらく)変わっていません。

最初に気になるのは変数dataの中身ですね。この文字列からunicodeとビット演算を用いてあの生き物を生み出しています。例として、data[0]の文字列ļłƩƁǃžĤĢを見てみましょう。この文字列のそれぞれのunicode番号とその2進数表現を並べると次のようになります。

ļ : 316 : 0b100111100
ł : 322 : 0b101000010
Ʃ : 425 : 0b110101001
Ɓ : 385 : 0b110000001
ǃ : 451 : 0b111000011
ž : 382 : 0b101111110
Ĥ : 292 : 0b100100100
Ģ : 290 : 0b100100010

ここで大事なのは、2進数の1のみに注目することです。0を空白にします。

1  1111  
1 1    1 
11 1 1  1
11      1
111    11
1 111111 
1  1  1  
1  1   1 

これを見ると、2つのことに気がつくと思います。

  1. 9ビット目が1であること
  2. 9ビット目を無視すると、あの生き物が現れること

このように、この作品では2進数をそのままプロットしてドット絵を出しています。ただし、上記コードでは2進数を右から読み、それを左からプロットしているため、左右反転した絵が出力されます。

9ビット目の1はドット絵には関係ありませんが、こうなっているのには少し事情があります。例として129(0b10000001)という数字を文字に変換することを考えましょう。実際にJavaScriptで変換するにはこのコードを実行します。

console.log(String.fromCharCode(129));

実行すると分かりますが、コンソールには何も表示されません。このようになるのは、unicodeでは、129には印字できない特殊文字が割り当てられているためです。プログラム上でこの文字を表現するには\x81と書く必要があります。"ab\x81c"といった具合です。これだとつぶやきにする上で文字数の効率が悪くなります。そこで、ドット絵に影響しない9ビット目を1にして385(0b110000001)とし、これを文字に変換します。

console.log(String.fromCharCode(385));
// Ɓ

無事に1文字2になってくれました。\x81からƁに変換したので3文字縮まったことになります。たった3文字と言ってしまえばそれまでなんですが、つぶやきにする上で文字数はできるだけ減らしておきたいのでこのようにしています。

長々と書きましたが、要するに「["ļłƩƁǃžĤĢ", "ļłƕƁǃžĤń"]には2枚のドット絵の情報が入っている」ということです。それではdraw関数を見てみましょう。

function draw() {
    background(0);
    // ドット絵に使う文字列。定期的に切り替わる
    let source = data[floor(frameCount/9%2)];
    // 生き物の左上の座標
    let x = pos(cos), y = pos(sin);
    for (let i = 0; i < cell_len; i++) {
        for (let j = 0; j < cell_len; j++) {
            // sourceのi文字目のunicode番号のjビット目が1かどうか判定
            if (source.charCodeAt(i) & 1<<j) {
                // セルを描画
                rect(x + j*cell_size, y + i*cell_size, cell_size, cell_size);
            }
        }
    }
}

ドット絵はこのような方法で描いています。頑張れば下のツイートのようなこともできちゃいます。正直なところ、ドット絵から2進数に変換するのがすごく面倒ですが...

ですが、8x8であればドット絵から文字列を得るツールがあります。

これ最高です。

もう1つ書いたんですが長くなったので折り畳みました。よかったらどうぞ。

おまけ

7セグメントを使った作品を紹介します。

7セグメントは棒7本のON/OFFなので、表現する文字(数字)1つにつき7ビットの情報量で表現できます。まずはコードを整形します。

// 各セグメントの情報
// 長方形の長い方の長さ、短い方の長さ
const seg_longer = 160, seg_shorter = 50;
// データ
const data = "ŷĔŮľĝĻŻĖſĿ";

function setup() {
    createCanvas(720, 720);
    colorMode(HSB, 10);
    noFill();
}

function draw() {
    background(0);
    // translateで使う
    let p = - seg_longer / 2;
    // 表示する番号
    let n = floor(frameCount / 60 % 10);
    translate(width / 2, height / 2);
    stroke(n, 9, 9);
    for(let i = 0; i < 7; i++) {
        translate(p, p);
        if (i != 3) {
            rotate(HALF_PI);
        } else {
            rotate(-HALF_PI);
        }
        if (data.charCodeAt(n) & 1<<i) {
            rect(p, -seg_shorter/2, seg_longer, seg_shorter);
        }
    }
}

まずは文字列ŷĔŮľĝĻŻĖſĿですね。各文字のunicode番号とその2進数表現を並べます。

ŷ : 375 : 0b101110111
Ĕ : 276 : 0b100010100
Ů : 366 : 0b101101110
ľ : 318 : 0b100111110
ĝ : 285 : 0b100011101
Ļ : 315 : 0b100111011
Ż : 379 : 0b101111011
Ė : 278 : 0b100010110
ſ : 383 : 0b101111111
Ŀ : 319 : 0b100111111

先程の例と同様、こちらも9ビット目を1にして特殊文字を避けています。下7ビットのみを抜き出し、0を空白にします。

111 111
  1 1  
11 111 
 11111 
  111 1
 111 11
1111 11
  1 11 
1111111
 111111

これだけ見せられても何がなんだかさっぱりですね。draw関数を読んでいくと、translaterotateで座標軸を動かしていることが分かるかと思います。実際、座標軸はこのように動いています。

7seg.gif

上の動画用に書いたコード
需要があるかなと思って置いておきます。解説とかは何も書いてません。
const seg_longer = 160, seg_shorter = 50;

function setup() {
    createCanvas(720, 720);
    textSize(40);
    stroke(0);
}

function draw_axis() {
    push();

    stroke(255, 0, 0);
    line(-10, 0, 50, 0);
    triangle(50, 0, 40, -10, 40, 10);

    stroke(0, 255, 0);
    line(0, -10, 0, 50);
    triangle(0, 50, -10, 40, 10, 40);

    pop();
}

function draw() {
    background(255);
    let p = - seg_longer / 2;
    let n = floor(frameCount / 60) % 7;
    fill(0);
    text(`i = ${n}`, 50, 50);
    noFill();
    translate(width / 2, height / 2);
    for(let i = 0; i < 7; i++) {
        translate(p, p);
        if (i != 3) {
            rotate(HALF_PI);
        } else {
            rotate(-HALF_PI);
        }
        rect(p, -seg_shorter/2, seg_longer, seg_shorter);
        if (i == n) {
            push();
        }
    }
    pop();
    draw_axis();
}

矢印はiがその値の時の座標軸です。赤色がx軸、緑色がy軸です。iに応じて座標軸を動かし、そのときの対応するビットが1かどうかに応じてセグメントを描いています。

少し余談ですが、unicode文字を使うというアイデアを採用したのは(私は)この作品が初めてなんです。採用する前は文字数の制約がかなり大きい状態でやってました。ちょうどこのツイートのような感じです。

この作品は今年の8月に行われたpchj03(YouTube)に投稿しました。動画上でNaoto HIÉDA(@naoto_hieda)さんが文字コードが使えるのではとおっしゃってたのを参考にして、今の形になりました。

最後に

ここまで、Processingでのビット演算の使用方法を紹介しました。他にも、この記事で紹介した方法とはべつのやり方でドット絵を描いたりビット演算でグラフのようなものを描いたりと、様々な使い方ができます。まだ発見されていない使い方もあると思うので、是非一度使ってみてはいかがでしょうか。

  1. 正確にはint型です。bool型に明示的に変換する場合、 != 0を後ろにつけてください。

  2. Ɓは2バイトなので2文字分使っているのでは、といった疑問も考えられます。しかし、twitterでは1文字とカウントされているようなので1文字としています。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?