🌊

Rubyでステガノグラフィーを作ってみた

2024/06/04に公開

概要

この記事の対象者:
Ruby初心者やRubyKaigiに興味がある方
奇妙なコードやステガノグラフィー技術に興味がある方

この記事の内容:
RubyKaigi2024の紹介
ステガノグラフィー技術を用いたコードの例

この記事を読むとわかること:
「Writing Weird Code」の講演内容
実際にステガノグラフィー画像を作成する手順

序章

みなさん、Rubyはお好きでしょうか?
私は、先日まで宝石のRubyも言語のRubyも触ったことがありませんでした。
しかし5/15~17に那覇で開催されたRubyKaigi2024に参加し、
Rubyの面白さ(言語の方)に触れることができました。
今日はその紹介をしたいと思います。

Writing Weird Code

https://rubykaigi.org/2024/presentations/tompng.html#day1

RubyKaigiでは様々な講演がありましたが、特に印象に残っているのは
tompngさんの「Writing Weird Code(奇妙なコードを書く)」です。

コードはできるだけみやすく書くのが原則だと思います。
しかしこの講演は逆でいかに奇妙なコードを書き、その奇妙さや芸術性(!?)を楽しむものでした。

著者のtompngさんは超絶技巧Ruby意味不明コンテスト2022で金メダルを取られたプログラマーです。
奇妙なコードを書くメリットとして、アートの感性が得られるだけでなく
なかなか見つからなかったバグの発見にも繋がるらしいです。

また、超絶技巧Ruby意味不明コンテストは不定期で開催され2024年はなかったそうです。
そこで自分一人で超絶技巧Ruby意味不明コンテスト2024を開かれ6つのコードを紹介されていました。
1つ1つ紹介するたび会場は大いに盛り上がり、さすが奇妙な人(褒め言葉)だと感じました。

特に私が心に刺さったのはMost floral部門のコードです。

言葉だけだと伝わりづらいのですが、具体的には一見美しい花の画像がありました。

しかし、これをRubyで実行すると、画像からアスキーアートのアニメーションが飛び出してくるのです。
(なんとも奇妙ですねー・・・)

ステガノグラフィーの仕組み

このような現象の背後には、
画像内にコードを隠蔽するステガノグラフィー技術が使われています。
オリジナルのPNG画像のデータを微妙に改変し、新たなBMP画像として出力することで、見た目には普通の画像に見えるのに、その中にRubyコードが埋め込まれているのです。

具体的には、このBMPファイル内のRGB値の下位2ビット分にRubyコードを埋め込んでいます。
下位2ビットの違いは人間の目にはほとんど分からないため、
見た目の色を変えずにコードを埋め込むことができるのです。

    col = (color >> (8 * (cidx + 1))) & 0xff
    ((col & 0b11111100) | bits.shift(1).join.to_i(2)).chr

1行目のcolはRGB値を8ビットに変換しています。
2行目で下位2ビットを一旦クリアし、Rubyのコード情報2ビット分に置き換えています。

自分で作ってみた

このような奇妙なコードを見るとどうもやってみたいという気持ちに襲われてしまい、
作ってみました♡

こちら「ちゅらデータ」のロゴ画像ですが、ruby out.bmpを実行すると
なんとRubyのアイコンと「ちゅらデータ」の文字のアニメーションが出てきます!

実際に作ったコードは以下にあります。
https://github.com/chukei2/ruby_bmp

作り方

  1. オリジナルの画像をPNGファイルで保存します。
    今回はinput.pngとしました。
  2. 表示したい文字を決め、SVGファイルを作成します。
    今回はfont.svgとし、XY座標を考え1文字1文字作成しました。
    (適切なSVGファイル作成方法がありましたら教えて欲しいです!)
    今回は「CHURADATA RUBY」としたいので、「C、H、U、R、A、D、T、B、Y」の9文字を作成します。
    <g class="H">
      <path d="M0,0 L0,7 L1,7 L1,0z" />
      <path d="M4,0 L4,7 L5,7 L5,0z" />
      <path d="M1,4 L4,4 L4,3 L1,3z" />
    </g>

M(Moveto)は新しい描画位置にペンを移動を意味します。
例: M0,0は、ペンを座標(0,0)に移動します。
L (lineto): 現在の位置から指定した位置まで直線を意味します。
例: L0,7は、現在の位置から座標(0,7)まで直線を引きます。
つまりM0,0 L0,7 L1,7 L1,0zは(0,0)から始まり(0,7)→(1,7)→(1,0)となる
逆時計回りの長方形を描いています。
同様に上から2番目のpathM4,0 L4,7 L5,7 L5,0zは右サイドの縦棒で
上から3つ目のpathM1,4 L4,4 L4,3 L1,3zは横棒を描画しています。

3. ruby font.rbを実行し、文字コードを取得します。
こちらは先ほど作ったパスデータを数値(座標)に変換するコードです。
次に実行する3dグラフィックで扱えるようにデータの形状を変換してやります。
4. code.rb内に、先ほど実行で取得した文字コード部分を埋め込みます。
例えば、Hの場合は以下の通りです。

FONT={
  H:[[[0,0],[2,0],[0,14]]...
  1. code.rbに、任意のアニメーションを追加します。
    今回はRubyロゴ(菩薩になった気持ちで見てください)がだんだん小さくなるアニメーションを追加します。
    アニメーションは以下のように実装しました
# 三角形の定義 
def inverted_triangle(scale_factor)
  vertices = [
    [-base_size, 0, y_offset],
    [base_size, 0, y_offset],
    [0, height, y_offset]  # 上の頂点
  ]
  triangles = [
    [vertices[0], vertices[1], vertices[2]]
  ]
  
  triangles
end
# アニメーションの定義
  if t < 3
    scale_factor = 10 - t * 4 
  elsif t >= 2
    scale_factor = 0
  end
  inverted_triangle(scale_factor).each do |tri|
    c.triangle(*tri)
  end
  1. ruby code.rbを実行しイメージ通りのアニメーションができているか確認します。
    イメージ通りにできていなければ、code.rbを変更し再確認します。
  2. 最後に、ruby generator.rbout.bmpファイルを出力すれば完成です!!!!

結論

今まで友達にあぶり絵を渡していたのですが、今度からはこのBMP形式で渡そうと思います。

脱線

今回紹介しましたtompngさんは、私が社会人初の名刺を渡した方になります。
奇妙なコードを書く」著者に奇妙な出会いができ、こうして奇妙な記事を書くことができました。
入社したばかりの私にも、会社からRubyKaigiに参加するチャンスを頂けたり、
tompngさんなど先輩から多くのRubyistを紹介してもらえたりして、とても嬉しかったです。
「ちゅらデータ」に興味ある方いましたらお気軽にお声掛けください!
https://churadata.okinawa/

参照

tompngさんの紹介されていたリポジトリはこちらです

https://github.com/tompng/selftrick2024

https://github.com/tompng/hcmpl_img_gen/tree/rk2024

Discussion