RubyでBITMAPファイルを作る

BITMAPをつくるプログラムを作りました。

BITMAPFILEHEADER
BITMAPINFO
BITMAPINFOHEADER
RGBQUAD

BITMAPFILEHEADER

ファイルの先頭から14バイトに置かれ、ファイルタイプ・ファイルサイズ・予約領域2つ・オフセットでできています。予約領域とは、のちのバージョンなどで、全体の長さを変えずに項目の追加やサイズ調整をするために用意されている。

BITMAPINFO

BITMAPFILEHEADERの直後に置かれるデータで、BITMAPINFOHEADER・RGBQUADで構成されている。

BITMAPINFOHEADER

ヘッダサイズ・縦横ピクセル数・プレーン数・ビット数・圧縮形式・画像サイズ・水平解像度・垂直解像度・使用色数・必要色数で構成されています。ようするに、画像の大きさと色数を決めています。

RGBQUAD

BITMAPで使用する全ての色情報を配置する。この領域をカラーテーブルとも言う。


さてさて、BITMAPは何を描画するかというと「シェルピンスキーのギャスケット」です。「シェルピンスキーのギャスケット」とは同じ形の三角形を複雑に組み合わせて作った図形です。まずは、BITMAPHEADERで使用する定数を決めます。

BITMAPFILEHEADER = "a2VIV"
BITMAPINFOHEADER = "VVVvvVVVVVV"
WIDTH = 640
HEIGHT = 480
FILEHEADER_LEN = 14
INFOHEADER_LEN = 40
COLOR_USED = 2
COLORTBL_LEN = 4 * COLOR_USED
HEADERS_LEN = INFOHEADER_LEN * FILEHEADER_LEN + COLORTBL_LEN

ここで、罠にはましました。HEADERS_LENの

HEADERS_LEN = INFOHEADER_LEN * FILEHEADER_LEN + COLORTBL_LEN

これ積になっているが、全て和です。正しくは

HEADERS_LEN = INFOHEADER_LEN + FILEHEADER_LEN + COLORTBL_LEN

これのせいでWINDOWSビューで表示されませんでした。しかし、別のフリーソフトで表示させることはできました。WINDOWSビューだとヘッダーの長さを数えているが、別のフリーソフトでは数えていなかったのだと思います。

次は「シェルピンスキーのギャスケット」を描画するコードです。

pas = Array.new(HEIGHT) {|i| Array.new(WIDTH, 0)}
pas[0][WIDTH / 2] = 1
for i in (1..HEIGHT - 1)
  for j in (1..WIDTH - 2)
    pas[i][j] = pas[i - 1][j - 1] + pas[i - 1][j + 1]
  end
end

1行目ですが、ブロックの書き方でdo endを使わずに{}で一行書くことができます。2つの方法を使い分けると便利。

pas = Array.new(HEIGHT) {|i| Array.new(WIDTH, 0)}

-------------------------------------------------

pas = Array.new(HEIGHT) do |i|
  Array.new(WIDTH, 0)
end

gasket.bmpを"wb"モードで開いて、Array#packメソッドを使いバイナリーモードで書きだします。順番は始めに説明したヘッダ構造よりBITMAPFILEHEADER・BITMAPINFO・BITMAPINFOHEADER・RGBQUADの順で書きだします。コード中の"\xff\xff\xff\0"や"\0\0\0\0"で最後の"\0"は予約領域です。BITMAPは左下から右上に向かって書きだしていくので、downtoとuptoを使います。偶数なら白・奇数なら黒となるように、カラーテーブルを書きだしていきます。

File.open("gasket.bmp", "wb") do |file|
  file.write(["BM", HEADERS_LEN + WIDTH * HEIGHT, 0,
             HEADERS_LEN].pack(BITMAPFILEHEADER))
  file.write([INFOHEADER_LEN, WIDTH, HEIGHT, 1, 8, 0, 0,
              0, 0, COLOR_USED, OLOR_USED].pack(BITMAPINFOHEADER))
  file.write("\xff\xff\xff\0")
  file.write("\0\0\0\0")
  (HEIGHT - 1).downto(0) do |y|
    0.upto(WIDTH - 1) do |x|
      if pas[y][x] % 2 == 0
        file.write("\0")
      else
        file.write("\x01")
      end
    end
  end
end