: O. Yuanying

Ruby で PNG 画像を解析する

なんで解析する羽目になったか

nwdiagblockdiag で 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_pngDatastream では、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_pngDatastreampHYs タイプのチャンクの中身を見てみる。

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)>

うまくいった模様。