Processing Advent Calendar 2020の11日目です。
自己紹介
H1ronoです。時々つぶやきProcessingに投稿しています。基本的にp5.jsです。
この記事でやること
この記事では、ビット演算をProcessingに取り入れる簡単な方法を紹介します。大きく次の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
であれば短絡評価でtrue
1になります。これだけだと情報量が足りないので、整数の配列にします。
// 配列の長さ, 扱うビット長
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) {
/* 何かする */
}
}
}
ここまでがテンプレです。私はこれをもう少し応用してつぶやいています。
ビット演算の利用例
この作品を例に、ビット演算の利用方法を紹介します。
Saito-inspired
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つのことに気がつくと思います。
- 9ビット目が
1
であること - 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進数に変換するのがすごく面倒ですが...
mario
ですが、8x8であればドット絵から文字列を得るツールがあります。
8x8-dot-editor
これ最高です。
もう1つ書いたんですが長くなったので折り畳みました。よかったらどうぞ。
おまけ
7セグメントを使った作品を紹介します。
7segment
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
関数を読んでいくと、translate
とrotate
で座標軸を動かしていることが分かるかと思います。実際、座標軸はこのように動いています。
上の動画用に書いたコード
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文字を使うというアイデアを採用したのは(私は)この作品が初めてなんです。採用する前は文字数の制約がかなり大きい状態でやってました。ちょうどこのツイートのような感じです。
pchj03
この作品は今年の8月に行われたpchj03(YouTube)に投稿しました。動画上でNaoto HIÉDA(@naoto_hieda)さんが文字コードが使えるのではとおっしゃってたのを参考にして、今の形になりました。
最後に
ここまで、Processingでのビット演算の使用方法を紹介しました。他にも、この記事で紹介した方法とはべつのやり方でドット絵を描いたり、ビット演算でグラフのようなものを描いたりと、様々な使い方ができます。まだ発見されていない使い方もあると思うので、是非一度使ってみてはいかがでしょうか。