作ってみた:Pietコード埋め込みプログラム(2)

前回に引き続き、 難解プログラミング言語Piet」の 話です。
今回は後半戦、本題です。

今回の成果物ダイジェスト

hello,world富士山つくるよ!
富士山どこにいるかわかりにくいけど!

f:id:y-mos:20210613193439p:plain
「hello,world富士山」

なんとこの富士山・・・。

f:id:y-mos:20210613194117p:plain
「hello,world富士山」実行結果

hello,worldするよ!

…というわけで、Pietコードを任意の画像に埋め込むプログラム
PietEmbedder
を作りました。
なお、使った言語はC++です。
(言い忘れてましたが前回もC++です。)

手順

まず、Pietに実行させたいプログラムを処理フローファイルに記述します。
例えば、hello,worldの処理を記述した以下のコード:

push3 push4 mul
push1 dup sub
goto:cnt

:push_h
pop
push3 push4 add
goto:cnt

:push_e
pop
push2 push2 mul
goto:cnt

:push_l
pop
push2 push3 dup mul add
goto:cnt

:push_o
pop
push2 push3 push4 add mul
goto:cnt

:push_w
pop
push1 push3 dup push4 add mul add
goto:cnt

:push_r
pop
push1 push4 dup mul add
goto:cnt

:push_d
pop
push3
goto:cnt

:push_comma
pop
push1 push2 sub push4 dup push3 mul push1 add mul push1 add mul
goto:cnt

:cnt
push2 push1 roll
push1 sub

dup if::finished
dup
push1 sub dup if::push_h
push1 sub dup if::push_e
push1 sub dup if::push_l
push1 sub dup if::push_l
push1 sub dup if::push_o
push1 sub dup if::push_comma
push1 sub dup if::push_w
push1 sub dup if::push_o
push1 sub dup if::push_r
push1 sub dup if::push_l
push1 sub dup if::push_d

:finished
pop
push1 push2 dup dup dup dup push3 mul mul mul mul mul add

:out
dup
push3 push2 roll
dup if::end
add outc
goto:out

:end
pop pop pop
end

長っ! (自分で言うか)

これを適当なテキストファイル
"hello_world.txt"に保存します。

次に、GridPietGeneratorを使って、
Pietソースコード画像に変換しますが、
今回は次のように、3つ目の引数を与えます。

./GridPietGenerator hello_world.txt hello_world.ppm hello_world_out.txt

実行後には、
Pietソースコード画像「hello_world.ppm」(今回は使いません)と、
「hello_world_out.txt」というファイル
(こちらは使います。以下「Piet配置ファイル」と呼びます)ができます。

続いて、Pietコードを埋め込みたい画像を用意します。

f:id:y-mos:20210613192820p:plain
Pietコードを埋め込む画像

いつぞや撮った富士山の写真を用意しました。
(リサイズして「mt_fuji.png」という名前で保存。)

ここでいよいよPietEmbedderの出番です。
先ほど作ったPiet配置ファイル「hello_world_out.txt」と、
用意した埋め込み先の画像「mt_fuji.png」、
そして、出力画像の保存ファイル名「out.png」、
この3つを入力引数に与えて、PietEmbedderを実行します。

./PietEmbedder hello_world_out.txt mt_fuji.png out.png

すると・・・。

f:id:y-mos:20210613193439p:plain
「hello,world富士山」(out.png)

なんということでしょう!

「hello,world富士山」の出来上がりです。
保存時の拡張子はpngtiffbmpのどれかがいいと思います。
途中、小さなウィンドウに出力画像が表示されて
処理が止まりますが、何かしらキーを押せば消えます。1

埋め込み原理

画像をまじえて説明します。

まずは前提。
GridPietGeneratorが出力した
「Piet配置ファイル」とは何ぞや、という話です。
これは、Pietソースコード画像の情報を記したテキストファイルです。
具体的には、生成したPietソースコード
サイズ、色配置、さらにはPietインタプリタの通り道などが記載されています。

それでは処理の内容です。

まず、PietEmbedderは、入力画像「mt_fuji.png」を、 Pietソースコードと同じサイズにリサイズします。
さらに、Pietで使うことのできる色だけを使って、画像を描きなおします。

その結果、下のような画像ができます。

f:id:y-mos:20210613201912j:plain
リサイズ&色変換後の富士山

次にPietEmbedderは、Piet配置ファイルに基づいて、
Pietコードの埋め込み方を決めます。
正確に言うと、Pietコード中の白色領域のうち、どこに富士山をお絵描きできるかを決定します。

実際の画像で説明します。

生成されたhello,worldのPietコード:

f:id:y-mos:20210613010627p:plain
hello,worldソースコード(再掲)

この白色の領域にはお絵描きができます。
ただし白色であればどこでもいいわけではありません。
前回もチラっと触れましたが、
自由にお絵描きができる場所には条件があります。

その条件を計算し、どこにお絵描きができるかを示す「マスク画像」を作ります。

f:id:y-mos:20210613210458j:plain
マスク画像

マスク画像の黒い部分にはお絵描きができません。
この部分にお絵描きすると、元のPietソースコードの挙動が変わってしまいます。
一方で、マスク画像の白い部分にはお絵描きができます。

つまり、マスク画像の黒いところにはPietソースコードをコピーし
マスク画像の白いところには富士山画像をコピーすれば
富士山画像にPietソースコードを埋め込んだような画像を作ることができます。

かくして「hello,world富士山」が完成します。
f:id:y-mos:20210613193439p:plain これで自慢ができます2やったぜ

How to インストール

PietEmbedderのインストール方法はgithubに書いてあります。
ただし、OpenCVが必要です。

OpenCVは画像処理用のオープンソースのライブラリです。

OpenCVを使うおかげで、今まで扱えなかったpngbmpといった
一般的な画像フォーマットを簡単に使えるようになります。

一方で、ビルドやインストールに苦戦するかもしれないのと、そこそこ容量が大きいといった、 とっつきにくい点もあります。3
慣れてしまえば、どうってことないですが。

最後に

2回にわたって、画像にPietを埋め込んだ話を書きました。
初めての記事なのでつたない点もあったかと思いますが、
最後まで読んでいただきありがとうございます。

ちなみにこの「Piet埋め込み構想」は3年くらい前からあったのですが、
なかなか重い腰が上がらず難しくて、ようやく形となりました。

あと、やはりPietを詳しく解説しないと、
「なんのこっちゃ」って感じですね。 気が向いたら別の機会にPietの解説もしようかと思います。

では。


  1. 消えない場合は小窓を一度クリックしてから、何かキーを押してみてください。

  2. この「hello,world富士山」は、私のはてなのアイコンにしました。自慢します。

  3. 前回、標準ビューアで見れない「.ppm」で妥協したのは、なるべくOpenCVを使わないようにしたかったからです。