トップ 最新 追記

ohai日誌

2003|12|
2004|01|02|03|04|05|06|07|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|06|08|10|11|
2008|01|02|03|04|05|07|09|
2009|01|02|
2013|06|12|
2014|01|02|03|04|06|09|10|12|
2015|04|
2016|09|
2018|02|

2013年12月04日

_ 最近

いろいろ忙しいです。ふと気付くと2週間後に講演が迫っていたりします。


2013年12月05日

_ nmatrixのwheezy上でのコンパイル

debian Wheezy で nmatrix の先端(この日の時点での)をビルドすることを考える。

必要なパッケージ

  • libatlas-dev
  • libblas-dev
  • libatlas-base-dev

nmatrix が atlas に含まれる liblapack を仮定しているため、

update-alternative --config liblapack.so
update-alternative --config liblapack.so.3

とかして、atlasのやつを差すようにしなければならないかもしれない。

コンパイルには --with-opt-include=/usr/include/atlas としておく。cblas.h が atlas のを仮定しているため。gem の場合は

gem install nmatrix -- --with-opt-include=/usr/include/atlas

とする。


2013年12月06日

_ Ruby でバイナリデータを扱う方法

この記事は KMC Advent Calendar 2013 の 6 日目の記事です。 昨日は tsutcho 君の CUDAのメモリの種類が多くて面倒だという話 でした。

この記事ではRuby でバイナリデータを扱う方法について解説します.Gitlab→PT2 on Linux→LINQ→スペインでお買い物→CUDA と来た次です. String#unpackとか結構知られていないようなので,そのあたりについて解説します. あまり長々と解説しても記事を読むのが面倒になるだけなので, ここでは読み込みだけを扱います.

普段Rubyでバイナリデータを扱うことはあまりないでしょう. しかし,

  • ちょっとした画像データの処理をしたいけど Image magick をインストールする ほどではない
  • 入出力にバイナリデータしか受け付けないソフトウェア用のツールの作成
  • バイナリデータを入出力するソフトのテストやデバッグ

といった場合にはバイナリデータをRubyで扱えると便利です.

encoding: ASCII-8BIT

Ruby ではバイナリデータを文字列として扱います.

このとき,文字列のエンコーディングに ASCII-8BIT を指定します. これによってその文字列がバイナリであることをマークします. こうすることによって String#[] が n 番目の文字を返すのではなく n 番目のバイト(を表わすStringオブジェクト)を返すようになります. またバイナリデータを等号で比べるときには,両方の文字列の エンコーディングを ASCII-8BIT で揃えておく必要があります. エンコーディングの指定方法自体は UTF-8 や cp932 などと同じで, IO.open の引数で指定したりします.

Ruby 2.0 以降には String#b というメソッドがあり,これは 文字列の中身(バイト列)は同じでエンコーディングが ASCII-8BIT である データを返します.特にリテラル文字列でバイト列を指定するときに 便利です.例えば

png_magic_8bytes = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a".b

などとします(このバイト列は PNG フォーマットの最初8バイトのマジックナンバです).

String#unpack

バイナリデータフォーマットでは,往々にして「ここから4バイトは,big endian の 符号なし整数で x のサイズを表し,次の4バイトは同じbig endian の 符号なし整数で y のサイズを表す.次の1バイトは0か1か2のいずれかで データの種類を表す…」という構造をしています.ASCII-8BITな文字列を このように解釈するのが String#unpack というメソッドです.

このメソッドはテンプレート文字列によってバイト列のフォーマットを指定し, 文字列をそのフォーマットに従って解釈して変換します. 例えば上の例では "NNC" と指定します.

詳しくはリファレンスのString#unpackの項 を参照してください.

ここでは,PNGフォーマットの画像ファイルのサイズ(幅×高さ)と1画素あたりのビット数を 表示するプログラムを作りましょう.

PNGの仕様は RFC2083 で定義されています. 仕様の必要な部分を解説すると,

  • データは最初8バイトにPNGを表すマジックナンバがあり, そこからチャンクと呼ばれるデータの塊が複数続く
  • 各チャンクの最初4バイトはチャンクのサイズ(バイト数),次の4バイトは チャンクの種類で,そこから先がチャンクのデータ
  • 最初のチャンクは "IHDR" チャンクで,13バイトのデータであり,以下の 内容がこの順で収められている
    • 画像幅(4バイト)
    • 画像高さ(4バイト)
    • 色深度(1バイト)
    • カラータイプ(1バイト)
    • 圧縮形式(1バイト)
    • フィルタの種類(1バイト)
    • インターレース(1バイト)
  • すべての整数データはビッグエンディアン

読み出したいのは画像の幅,高さ,色深度なので,IHDRチャンクのその部分を読み込めばよい わけです.

プログラムは以下の通りです.

exit if ARGV.empty?

# ファイルのエンコーディングに ASCII-8BIT を指定する
file = File.open(ARGV[0], "r:ASCII-8BIT")
# PNGの最初の8ビットのマジックナンバ
png_magic_8bytes = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a".b

# 最初8バイトを File#read で読み込んで png_magic_8bytes と一致しているかを調べる
raise "This file is not a PNG file." if file.read(8) != png_magic_8bytes

# 最初のチャンクの8バイトのヘッダ部を読み込んで
first_chunk_size, first_chunk_type = file.read(8).unpack("Na4")

# 最初のチャンクは"IHDR"で13バイトと決まっているのでそれを確認する
raise "First chunk should be IHDR" if first_chunk_type != "IHDR".b || first_chunk_size != 13
# 続く13バイトを読み込む
chunk = file.read(first_chunk_size)

# 13バイトのバイト列を整数に変換する
width, height, bit_depth, color_type, compression_method, filter_method, interlace_method =
  chunk.unpack("NNCCCCC")

# 各情報を表示
puts "image size: #{width}x#{height} pixels"
puts "depth: #{bit_depth} bits"

file.close

さらなる勉強のために

整数データをバイナリ文字列に変換するためには Array#pack を使います. これは String#unpack と逆向きの変換を行います. これによってバイナリデータの書き込みができます.

明日は dis 君の @生活の知恵@ です。


トップ 最新 追記