いろんな画像をAA Quine化するよ!

この辺の流れを見て楽しそうだなぁと思ったので真似してみました。

ミクさん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化しやすいタイプの画像です。

$ ruby mikusan.rb ozeusama.png 80 0.6



早苗さん

ドット絵もQuine化に適しています。というかこのAA Quine自体、文字によるドット絵なので当然といえば当然ですね。*5

$ ruby mikusan.rb sanaesan.png 80 0.49


ドット絵はこちらからお借りしました。ありがとうございます。

自重しろ

完全に一致。*6

$ ruby mikusan.rb jichoushiro.jpg 200


画像はこちらからお借りしました。ありがとうございます。

大きなミクさん

頑張ればこんな大きなイラストだって!*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画像でないと、生成は難しいかもしれません。

最後の最後に

元ネタの元ネタ的な資料もはっておきます。

本当に凄いQuineは意味がわかりません。*10

※追記(2010年9月20日

後から見直してみたら色々と無駄が多かったので、 mikusan.rbquineaa.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強くらい