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