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)>
うまくいった模様。