いろんな画像をAA Quine化するよ!
この辺の流れを見て楽しそうだなぁと思ったので真似してみました。
- RubyでうどんげQuine(とAA型Quineの作り方講座) - ぬいぐるみライフ(仮)
- 404 Blog Not Found:perl - Quine.pm で(ほぼ)あらゆるPerl Scriptをquineに
- うどんげQuineに対抗して遊んでみた - As Sloth As Possible
ミクさんQuine
まずはこちらから。
※2010年9月20日追記:ミクさんを更新しました。Gistのコードも更新済みです。詳しい更新内容は記事末尾の追記欄にて。
eval$s=%w~ a=->(b,c, d,e,f){%`#{(c)?"re quire'zlib';":' '}g=Marshal.load(#{ (c)?'Z lib::Inflate.infl ate(':''}'#{b}'. unpack('m')[0]#{(c)?')': ''});h= 'eval$s =%w'<<126<<($s*#{d} );i=' ';j=- 1; #{ e*f}.times{|k|i<< (g[ k]== 1? h[j+=1]:32);i<<10i f (k% #{e} ==#{e-1})};i[-7,6] =' ' < <126<< '.join';puts(i)#`}; $*[ 0]? ( require' RMagick';include(Ma gi ck );l=$*[0];m =($*[1]||80).to_i;n= Qu a ntu mRange*( $*[2]||0.5).to_f;o= Im a g eL ist.new(l ).flatten_images;p= o. c olu mns; q=o.rows;r=->(o){(p > q ) ? o .re size (m ,m*q/p/2):o.resize (m * p / q ,m /2)}; o=( p>m||q>m)?r[o.bile v el_ch an ne l (n )] :r [o].b ile vel_channel(n);e= o. columns ;f= o.r o w s;rais e('INVALID_ IMAGE_DATA')if(e<0||f <0 );s=''; o.ea ch _pixel {|t|s<<((t.red< n)?'1':'0')};s[0,10] ='1 '*10;s[ -6,6 ]= '1'*6; u= s.count('1');v=Marshal.dump(s.rev erse.to_i(2 ));r eq u ire'z l ib';w=Z lib::Deflate.deflate(v) ;c= v.size> w.siz e+37;b=[(c) ?w:v] .pack('m').tr(10.chr,'');d=u/b. size+1;x=a[b,c,d,e,f]; raise(' INSU FFICIENT_ CAPACITY')if(u<x.size +15);e val($s=x)):eva l(a[' eJ wlkLFL w1AQxr/XlKbQQjpmaiq4u XSz4JD 8KQHXDs4iJOIf0 FmoHZ3 ddJJIoHYodXUMLnVo8Yl gXjHm 81694fhx7+5731 2zPT5S C/ yHDiUZzzDQdIGKAYpSEmo qpGQiVClk5A4IUoW44hqp i6Ft niBT0CIgE3E00I5QiNFoo D1ome12YUIhooXeiCiEH N Ue1o6QiD Xx4FqKcB id9edkNug1ovhiRUbGv x pn ZkrG9bTzm 3FnaabMJ 0u ysHRNiYKzg6KT1Lb2y N yt 9zWWrlf/iN4H145vdnt 6 9mC9pC+cyILyCjLwrHvK L vY Qi5DWdwNYJsSr1IGt5H faa2z2ffY4my3xbX96KkGcW0f3d Cq ccC2 dQ cukN8yT6tLr mm xJJ1 jlbttEc6p+fer4On6juuXxna+HBVX+lW hf/wF n292 m',true, 4 ,80, 40])#a=->(b ,c,d,e,f){%`#{(c)?"requ ir e'zlib';":' '}g=Ma rs hal .load(#{(c) ?'Zlib::Inflate.infl ate(' :' '}'#{ b} '.u npack('m')[ 0]#{(c)?' )':''}) ;h='e va l$s=% w' << 126<<($s*#{d });i= ''; j=-1 ;#{e* f }.tim e s{ |k|i<<(g[k] ==1? h[j+=1]:3 2) ;i << 10if (k % #{e}==#{e-1} )} ; i[-7,6]=''<< 12 6< < '. join '; puts(i)#`};$ *[ 0]?( require'RMa gick '; ~.join
ミクさんです。
元イラストはこちらからお借りしました。この場を借りて御礼申し上げます。
AA Quine を生成する AA Quine
Quineというからには、普通に実行すれば当然自分自身を出力します。
$ ruby mikusan.rb | diff mikusan.rb - $
が、親切なミクさんは、さらに引数として画像ファイルを指定すると、その画像のQuineも生成してくれます。
例えばこんなかんじ。
$ ruby mikusan.rb mikusan2.gif | tee mikusan2.rb eval$s=%w` d=Marshal.load ('BA hsKwHI/wM AAAAAAAAAAAA AAAA AAAAAAAAAgP8fAAAAAAAAAODxHwAAAMD/Aw B4/P/ //x/w+Q MAHv 9/AADg+/wHAN//B wAAAH 7+PwD /fwAAAAA/ ///A/ x8AAACAnz+AYP wDIAAA gP8fABD4ACCAA ID/DwAMfAA44AEA /w8ABh wAf2BuAP4PAAMOgF 9wCAD8HwABB8DHM CAA+B8 AgAPwgz C AI fAfAMAD+ I E5HCTwHwDAIfwc O QAw8D 8 A 4Dn+fzr+P/A/A PA 9Pv88 +D/ 4PwD wPx//OPg//D8 A+ D8f +HDIP/w fA Pi/ D+ MAGDD+EwD8/ w/ DAB gw/hEA / P8H f gBwOP8YAP7/ B wAA gL9/G AD +/ w 8AAADYHxgA /v9 /AAAA+ A MI AP / //w9wAP8A DAD/ ///j/x 88 AAw A// / / Az4fDgAEA P// / wGzcQA ABg D /// +B 8/AAAAYA// //wf vYA AACAP////HP/wE A AwD ////xzL8BAAEA ////+fz4A4 ABAP/ ///3 E+APA AAD///9/ ZvgH YAAA////f2P wB2AA/A=='. unpack('m' )[0]) ;c='eval $s= %w'<<96<<($ s*3);r='';j= -1;3200.tim es{|i |r<<( d[i ] ==1?c[j+ =1]:32);r<< 10if(i%80== 79)}; r[ -7, 6] =' '<<96<<'. j oin';puts(r)#d=Mar sh al .l oa d('BAhsK w HI/wMAAAAAAAAAAAA AAAAAA AAA AAA AgP8fAAA AA AAAAODxHwAAAMD/AwB 4/P///x /w+QMAHv 9/ AADg+/wHAN//BwAAAH7 +P wD/fwAA AA A////A/x8AAACAnz+AYPwD IAAAgP8 f ABD4ACCAAID/DwAMfAA44AEA/w8A Bhw Af2BuAP4 PA AMOgF9wCAD8HwABB8DHMCAA+B8 AgAPwgzCAIfAfAMA D+IE 5H CTwHwDAIfwcOQAw8D8A4Dn+fzr +P/A/ APA9P v88 + D/4PwDwPx//OPg//D8A+D8f+H DI P/ wf APi /D +MAGDD+EwD8/w/DABgw/hEA/P 8Hf gBwO P8YA P7 /BwAAgL9/GAD+/w8AAADYHxgA /v9/ AAAA+ AM IA P ///w9wAP8ADAD////j/x88AAw A////Az4 fDgAEAP///w Gz cQAABgD///+B8/AAAAYA////w fvYA AA CAP////H P/ w EAAwD////xzL8BAAEA////+fz 4A4AB AP//// 3E+APAA AD ///9/ZvgHYAAA////f2PwB2AA /A=='. u np ack('m' )[ 0]);c='eval$s=%w'<<96<<($s*3);r =' '; j=-1;320 0. times{|i|r<<(d[i]==1?c[j+=1]:32 ); r< <10if(i %8 `.join $ ruby mikusan2.rb | diff mikusan2.rb - $
元イラストはこちらからお借りしました。ありがとうございます。
使い方
$ ruby mikusan.rb [画像ファイルのパス] [長辺のサイズ(デフォルト:80)] [二値化のしきい値(デフォルト:0.5)]
ざっくりとした使い方は以下の通りです。
- 実行には RMagick 2 が必要です。*1
- 引数をつけずに実行すると、自身を出力します。
- 画像ファイルのパスを指定すると、その画像からAA Quineを生成して出力します。認識可能な画像の形式は RMagick にリンクされた ImageMagick に依存します。
- 2つ目の引数に長辺のサイズを指定できます。画像はこのサイズまで縮小あるいは拡大してからAA化されます。横長の画像の場合は横サイズ、縦長の画像の場合は縦サイズがここで指定した値に合わせられます。
- 3つ目の引数には、画像を白黒二値化する際に、どれくらいの濃さの色までを黒として判定するかを0.0〜1.0までの値で指定します。1.0に近づけるほど、より多くの色が黒として判定されるようになります。
さらに注意事項として。
- どんな画像でもQuine化できるわけではありません。AA化した際に、文字を配置できる領域…つまり黒と判定される領域が十分に存在しないと、自分自身のコードを格納しきれないため、エラーを吐いて止まります。引数で与えるサイズやしきい値を変更すると黒の領域が増減しますので、色々調整してみてください。*2
- 実際には画像の縦サイズは指定された値よりもさらに半分に縮小されます。一般的な1バイト文字が縦長なためです。それでもまだアスペクト比に違和感がある場合、フォント*3を変更してみるといいかもしれません。
- 線の細い画像や色の薄い画像はQuine化しにくいです。しきい値をあげるなどして無理やりQuine化することもできますが、あまり綺麗な結果になりません。Quine化に適した画像は、線が黒くて太くてはっきりとしていて、画像全体に大きく対象が描かれているタイプのものとなります。複雑で大きなイラストはどうしても難しく、アイコン画像系が比較的適していると思います。
中身の話もさらっと。
- 生成されるQuineには、左上に "eval$s=%w`" が、右下に "`.join" が必ず挿入されます。この辺も上手いこと調整できるようにしたかったのですが、コードが長くなりすぎて常識的なサイズのAAに収まらなくなりそうだったので断念しました。一番最初のミクさんだけは手動で調整してあります。
- 二値化したAAデータは簡単なランレングス符号化を行ってサイズを圧縮しています。画像のタイプによっては符号化しない方がサイズが小さいケースもありますので、よりサイズの小さいものを採用するようになっています。
- その他は、記事の最初にはった元ネタリンク先とほぼ同じ手法を使わせてもらっています。
いろいろ変換してみる
いちいちQuine化したコードをそのまま貼り付けると長くなってしまうので、元画像と、Quine化したものを画像化したもの*4を並べます。
早苗さん
ドット絵もQuine化に適しています。というかこのAA Quine自体、文字によるドット絵なので当然といえば当然ですね。*5
$ ruby mikusan.rb sanaesan.png 80 0.49
ドット絵はこちらからお借りしました。ありがとうございます。
大きなミクさん
頑張ればこんな大きなイラストだって!*7
$ ruby mikusan.rb mikusan3.png 1024 0.6
画像はこちらからお借りしました。ありがとうございます。
こんなサイズでもちゃんとQuineになってます。
$ ruby mikusan.rb mikusan3.png 1024 0.6 > ookinamikusan.rb $ ruby ookinamikusan.rb | ruby | ruby | ruby | ruby | ruby | diff ookinamikusan.rb - $
最後に
一番最初のミクさんQuineを生成したコードをはっておきます。
こいつを以下のように実行して、一番最初のミクさんを作りました。いわば原初のミクさんとその創造主。
$ ruby quineaa.rb mikusan.png 90 0.6 > mikusan.rb
こちらはジェネレータとしてのコードサイズが結構ある*8ため、ある程度キャパシティが大きい*9画像でないと、生成は難しいかもしれません。
※追記(2010年9月20日)
後から見直してみたら色々と無駄が多かったので、 mikusan.rb と quineaa.rb を更新しました。
処理部のコード量が減った*11ため、ミクさんがスリム化してます。
新しいミクさんは
$ ruby quineaa.rb mikusan.png 80 0.6 > mikusan.rb
で生成してます。
更新内容は以下の通りです。
- 大抵の場合において、ランレングス符号化よりZlibを使って圧縮したほうがデータサイズが小さくなったため、素直にZlibを使うようにしました。
- ラムダ式の構文をより短いものに変更しました。
- if文を三項演算子に置き換えました。(if識別子は除く)
- その他、同じ機能を持つより短い名称のメソッドを呼ぶようにするなど、文字数を削減する調整を行ないました。
- 生成するコード中にバックスラッシュが含まれており、特定のAAパターンで正しく動作しなくなっていたため、それを修正しました。
あと、書き忘れていましたが、Ruby 1.9以降でないと動作しないと思います。
*1:バージョン1系では動作確認していません。
*2:ただし、元々真っ白な画像はどう頑張ってもQuine化できません。
*3:あるいはline-heightなどの値
*4:ややこしい
*5:なので、日本で一般的なプロポーショナルフォントを活用したAAとは根本的に性質が異なります。あっちは本当にアート。
*6:「アスペクト比がずれているが大丈夫か?」「大丈夫だ、問題ない」
*7:ターミナルでは大きすぎて何が何だかわかりません。
*8:1300byte弱くらい
*9:つまり黒と判定できる領域の占める割合が大きい
*10:思考ルーチン組み込みとかクリスマスソング演奏機能組み込みとか。それをやろうと思った発想も含めて。
*11:1300byte弱から1000byte強くらい