LoginSignup
5
3

More than 3 years have passed since last update.

Rubyの正規表現でバイナリファイルからPNG形式の画像を抽出

Last updated at Posted at 2019-07-28

バイナリファイルからPNGを抜き出してみたくなった、ただそれだけ。Rubyでサクッとやる。

コードと実行例

extract-png.rb
filename = ARGV[0]
prefix = File.basename(filename, ".*")

PNG_REGEXP = /
    \x89PNG\r\n\x1A\n
    \x00\x00\x00\x0D IHDR .{13} .{4}
    .*?
    \x00\x00\x00\x00 IEND \xAE\x42\x60\x82
/mnx

File.binread(filename).enum_for(:scan, PNG_REGEXP).with_index(1) do |data, i|
    File.binwrite("#{prefix}-%04d.png" % i, data)
end
console
$ ruby extract-png.rb imageres.dll
$ ls *.png
imageres-0001.png
imageres-0002.png
...
imageres-0337.png
imageres-0338.png

説明

PNGの仕様

PNGのマジックナンバー(ファイル種別を示す先頭の固定バイト列)は \x89PNG\r\n\x1A\n の8バイト。

その後はチャンクと呼ばれる <データ長><チャンク名><データ本体><CRC-32> の並びが何個も続く。

  • 最初のチャンクは IHDR でデータは常に13バイトなので、チャンク名までの8バイトが固定値となる。マジックナンバーと合わせるとPNGの先頭16バイトが固定値。
  • 最後のチャンクは IEND でデータは無いので、データ長とCRC(巡回冗長検査)も含む12バイトが固定値となる1。これがPNGの末尾。

PNGを厳密に探すには最低でも、マジックナンバーから始まってチャンクが連続することを確認しなければいけない。今回はそこまでするのが面倒なので、バイナリデータからPNGの先頭と末尾を探し出せばOKとした

正規表現の構築

PNGが複数ある場合もあるので、PNGの先頭~末尾はなるべく短くなるよう抽出しなければならない。言い換えると、PNGの途中に先頭や末尾と同じ文字列が登場してはいけない。しっかり対策するには否定先読みや非包含オペレーターのような機能が必要だが、先頭や末尾の条件を長くしておけば誤検知はほぼ防げる2。そのため今回は最短マッチ .*? だけで済ませた。

正規表現にはいくつかオプションを指定している。

  • m: . を改行コードにもマッチさせる。今回はテキストでなくバイナリなので、改行コードを特別扱いさせてはいけない。
  • n: 正規表現の文字コードをバイナリ(ASCII-8BIT)に指定する。
  • x: 正規表現中の空白を無視する。単に可読性を高めるためで、利用は必須ではない。

ファイル入出力と文字列スキャン

本当はファイルが巨大な場合を考慮して少しずつ入力+スキャンできればよかった(StringScannerのIO版みたいな感じ?)が、方法がわからなかったので IO.binread で丸ごと読み込むことにした。※IOクラスに慣れなくてFileクラスを使った。

正規表現による抽出は String#scan でいいものの、何となく以下のことを考慮して Object#enum_for を組み合わせた。

  • scan(PNG_REGEXP) { ... } だと、連番を振る変数を別に用意しなければいけない。
  • scan(PNG_REGEXP).each.with_index(1) { ... } だと、抽出した全PNGデータが一旦配列で保持されてしまいメモリを消費する。

参考


  1. IENDチャンクのCRCはコマンドでも確かめられる。 $ crc32 <(echo -n IEND) #=> ae426082 

  2. 今回試したファイルには、PNGでない場所にもマジックナンバーの列があり、 IHDR まで含めないと誤検知した。 

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3