Ruby で PNG 画像を解析する
なんで解析する羽目になったか
nwdiag や blockdiag で PNG ファイルを生成してウハウハしてたのだが、 画像生成オプションに解像度 (dpi) 指定が無いために印刷する際に微妙に困った。 ImageMagick とか使えば指定はできるんだ(ろう)けど ImageMagick とかインストールしたくないし…。
PNG なんだからフォーマット公開されてるし簡単に解像度くらい指定できるのでは?
PNG 画像について
フォーマットについて簡単に調べてみた。
要するに チャンク と呼ばれるブロックの配列らしい。なんと単純。
chunky_png
Ruby で png 画像を解析する簡単なライブラリがあった。
ファイルパスを渡すと チャンク の配列にして返してくれる。
require 'chunky_png'
stream = ChunkyPNG::Datastream.from_file('portal-network.png')
stream.each_chunk do |chunk|
puts chunk.type
end
画像サイズなどのメタデータは IHDR というタイプの チャンク に含まれているらしい。
chunky_png の Datastream では、header_chunk という属性で取得できる。
irb(main):018:0> stream.header_chunk.type => "IHDR" irb(main):019:0> stream.header_chunk.width => 1413 irb(main):020:0> stream.header_chunk.height => 1069
PNG 画像の解像度について
解像度を読む
PNG 画像の解像度は pHYs というタイプの チャンク に含まれており、オプショナルである。
このチャンクのフォーマットはこんな感じ。
- X軸方向画素数 (単位あたり) 4バイト
- Y軸方向画素数 (単位あたり) 4バイト
- 単位指示子 1バイト
「X軸方向画素数/Y軸方向画素数」というのは 「単位指示子」に 1 が指定されていた場合、 1メートルあたりのピクセル数となるそうだ。 普通、解像度というとインチあたりのピクセル数 (dpi) なので dpi で指定したかったら変換が必要のようだ。
chunky_png の Datastream で pHYs タイプのチャンクの中身を見てみる。
irb(main):036:0> stream.each_chunk do |c|
irb(main):037:1* if c.type == 'pHYs'
irb(main):038:2> p c.content
irb(main):039:2> p encoded_content = c.content.unpack('N2C')
irb(main):040:2> puts encoded_content[0] / 39.3700787
irb(main):041:2> end
irb(main):042:1> end
"\x00\x00.$\x00\x00.$\x01"
[11812, 11812, 1]
300.02480030602527
=> nil
X 軸および Y 軸ともに、1メートルあたり 11812 ピクセルの解像度が指定されていることがわかる。
"1メートル" は "39.3700787 インチ" なので、39.3700787 で割ってやると、
おおよそ、300 dpi の解像度のファイルということがわかった。
解像度の変更
読み込んだファイルの pHYs チャンクの値を変えてやれば解像度を変更できるのでやってみた。
150 dpi を指定するには 5906 dot/m ということになる。
irb(main):044:0> stream.each_chunk do |c|
irb(main):045:1* if c.type == 'pHYs'
irb(main):046:2> c.content = [5906, 5906, 1].pack('N2C')
irb(main):047:2> end
irb(main):048:1> end
=> nil
irb(main):049:0>
irb(main):050:0* stream.save('../portal-network.png')
=> #<File:../portal-network.png (closed)>
うまくいった模様。