ではダラダラ説明は終わりにして、早速作ってみましょう。
APIや構造体の宣言は全て同じですが、BITMAPINFO構造体の宣言のみ以下のように変えてください。
'DIB情報
Public Type BITMAPINFO
bmiHeader As BITMAPINFOHEADER
bmiColors(255) As RGBQUAD ' 256色
End Type |
今回は256色までのカラーテブルを読み込む為、256個の配列として宣言します
「なんで、わざわざ静的に宣言するの?」
「動的確保じゃダメなんですか?」
はい。これについては後述します。
まずコードを見てみましょう。
そんなに難しくはありません。
Private Const BM As Long = &H4D42 ' BM
Private Const DIB_RGB_COLORS As Long = 0 ' RGBパタン
Private Const DIB_PAL_COLORS As Long = 1 ' パレット
'ビットマップファイル情報
Public Type BITMAPFILEHEADER
bfType As Integer ' BM
bfSize As Long ' ファイルサイズ
bfReserved As Long ' リザーブ(0)
bfOffBits As Long ' ビットパタンまでのオフセット
End Type
'DIB情報ヘッダ
Public Type BITMAPINFOHEADER
biSize As Long ' 構造体のサイズ
biWidth As Long ' 画像の横幅
biHeight As Long ' 画像の高さ
biPlanes As Integer ' プレーン数
biBitCount As Integer ' ビット数
biCompression As Long ' 圧縮コード(RLE圧縮)
biSizeImage As Long ' 画像のサイズ(バイト)
biXPelsPerMeter As Long ' 水平解像度
biYPelsPerMeter As Long ' 垂直解像度
biClrUsed As Long ' カラーテーブルの数
biClrImportant As Long ' 重要な色の数(0〜biClrImportantまで)
End Type
'1ピクセルの色
Public Type RGBQUAD
rgbBlue As Byte
rgbGreen As Byte
rgbRed As Byte
rgbReserved As Byte
End Type
'DIB情報
Public Type BITMAPINFO
bmiHeader As BITMAPINFOHEADER
bmiColors(255) As RGBQUAD ' 256色
End Type
' DIBをデバイスに描画する
' 宣言はAPIビューワのものと多少違います
Public Declare Function SetDIBitsToDevice Lib "gdi32" ( _
ByVal HDC As Long, _
ByVal X As Long, _
ByVal Y As Long, _
ByVal dx As Long, _
ByVal dy As Long, _
ByVal SrcX As Long, _
ByVal SrcY As Long, _
ByVal Scan As Long, _
ByVal NumScans As Long, _
Bits As Any, _
BitsInfo As Any, _
ByVal wUsage As Long _
) As Long
|
Public Sub Main()
Dim BFH As BITMAPFILEHEADER
Dim BI As BITMAPINFO
Dim Bit() As Byte
Dim Cmd As String
Dim Argv() As String
Dim Argc As Long
Dim NO As Integer
Cmd = Command$()
Argc = ArgvArgc(Cmd, Argv)
If Argc <= 0 Then Exit Sub
NO = FreeFile()
Open Argv(0) For Binary As #NO
Get #NO, , BFH
If BFH.bfType <> BM Then
' ビットマップではない
MsgBox "形式エラー"
Close
Exit Sub
End If
Get #NO, , BI.bmiHeader
With BI.bmiHeader
If .biBitCount < 16 Then
' 8Bit以下の場合カラーテーブルを読み込む
Get #NO, , BI.bmiColors
End If
' ReDim Bit(.biHeight * .biWidth * 4) As Byte ' 余分に確保
ReDim Bit(((.biWidth * .biBitCount + 31) \ 32) * 4 * .biHeight) As Byte ' バイト数を計算
End With
Seek #NO, BFH.bfOffBits + 1 ' ピクセルビットのポインタへ移動
Get #NO, , Bit
Close
Load Form1
Form1.Width = (BI.bmiHeader.biWidth * Screen.TwipsPerPixelX) + _
(Form1.Width - (Form1.ScaleWidth * Screen.TwipsPerPixelX)) ' Twipに変換
Form1.Height = (BI.bmiHeader.biHeight * Screen.TwipsPerPixelY) + _
(Form1.Height - (Form1.ScaleHeight * Screen.TwipsPerPixelY)) ' Twipに変換
Form1.Caption = Argv(0)
' DIBを転送
Call SetDIBitsToDevice(Form1.HDC, 0, 0, BI.bmiHeader.biWidth, BI.bmiHeader.biHeight, _
0, 0, 0, BI.bmiHeader.biHeight, Bit(0), BI, DIB_RGB_COLORS)
Form1.Show
End Sub |
まず引数の解析をし、ビットマップファイルをバイナリで開きます。
引数解析のArgvArgc関数は自作関数です。コードはサンプルプロジェクトを見てください。
BITMAPFILEHEADER構造体を読み込み、ビットマップ形式かどうかを判別します。
どんなファイルが渡されるか分かりませんので、こういう処理は必ず入れましょう。
次にBITMAPINFOHEADR構造体を読み込みます。
ここでビットマップの1ピクセルのビット数によってカラーテーブルを読み込むかどうかをIF分岐させます。
ピクセルビットを読み込む為の領域を確保します。
前回は説明しませんでしたが、どのビットマップ(DIB)でも画像の横幅のバイト数は4の倍数でなくてはならないという制約があります。
これを4バイト境界と呼びます。
もしバイト数が4の倍数ではない場合、0(ゼロ)で空き領域を埋め、無理矢理4の倍数にさせなくてはなりません。(ビットマップ保存する場合)
横幅のピクセル数をwとし、ビット数をbとすると、計算式は次のようになります。
w × b で横幅のビット数を出します。(バイト数ではありません)
w × b + 31 としているのは、4の倍数はその余りから、
バイト数 = 4w
バイト数 = 4w + 1
バイト数 = 4w + 2
バイト数 = 4w + 3
のいずれかで表せるからです。
4の倍数より1ビットでも多ければ、+4バイト(+32ビット)すれば良いわけなので31ビット足します。
(w × b + 31) ¥ 32 でDWORDの数を出します。DWORDは4バイトです。
((w × b + 31) ¥ 32) × 4 でバイト数を出します。
((w × b + 31) ¥ 32) × 4 × 高さ で全体のバイト数が出せます。
実はこれだと、実際の領域+1の領域を確保した事になりますが(0から要素が始まるから)、多く確保する分には全然構いません。
計算が面倒臭く感じる人は、コメントアウトしている計算式でもOKです。前述した通り多く確保しても関数は構造体からその画像サイズを計算する為です。
では話を戻して、seekメソッドでピクセルビットへのポインタまで移動します。
これはカラーテーブルを読み込んだ場合、256個分ファイルポインタが移動してしまう為です。(カラーテーブルが256個とは限らない)
あとは前回と同じです。
SetDIBitsToDevice関数の最後の引数はDIB_RGB_COLORSとなります。
256色以下の場合、ピクセルビットはインデックス値を表すのでDIB_PAL_COLORSを指定しそうなもんですが、このフラグはデバイスのパレットを参照する場合にのみ使います。
デバイスのパレットと、読み込んだDIBのカラーテーブルは同じ内容ではありませんので、このフラグを指定すると色がグチャグチャになります。
逆にいえば、デバイスのパレットをカラーテーブルの内容と同じにすれば、今回と同じ結果が得られるという事になります。
関数はBITMAPINFO構造体から、カラーテーブルその他もろもろを読み込んで、表示まで全てやってくれます。
|