VBAの勉強を始めてみた

色々試しています。

ビットマップ画像をセルの背景色で描画する

今回は、エクセルのシート上にセルの背景色を使ってビットマップ画像を描画してみようと思います。手作業で1セルずつちまちまと・・・・・・ではなくVBAで、です。何故、それをやろうと思ったのかは自分でも謎です。

では、やってみましょう!

 

ビットマップファイルの構造を理解する

プログラムの前に、まず、ビットマップファイルの構造を理解する必要があります。下の画像は、ビットマップファイルをバイナリエディタで開いたものです。ファイルの0~53番目がヘッダ、54番目~末尾が画像データになっています。

f:id:kouten0430:20190601143431p:plain

ヘッダには画像に関する様々な情報が入っています。今回は、画像の横幅(ピクセル)と縦幅(ピクセル)のみプログラム内で使用します。
画像データにはピクセルごとの色が数値で記録されています。ただ、色ビット数によって、1ピクセルを表現するバイト数が異なります。24ビットカラーであれば、3バイト(R・G・B)で1ピクセルを表現しています。色ビット数に関係なく、画像データは左下から右上の順に記録されています。

また、データの並び順はリトルエンディアンです。プログラムでデータを扱う際には、この点に注意します。

 

ビットマップファイルについて参考にしたサイト 

www.umekkii.jp

algorithm.joho.info

qiita.com

 

プログラミングしてみます

今回は、画像データの表現方法が一番分かりやすい、24ビットカラー限定でプログラミングしてみました。

Sub ビットマップ画像をセルで描画する()
    '24ビットビットマップのみ処理可能
    Dim ファイル名 As Variant
    Dim 配列() As Byte
    Dim i As Long
    Dim tmp As String
    Dim 横 As Long
    Dim 縦 As Long
    Dim 詰物 As Integer
    Dim 行 As Long
    Dim 列 As Long
    Dim 色 As String
    
    ファイル名 = Application.GetOpenFilename(Title:="ビットマップ画像を選択")
        If TypeName(ファイル名) = "Boolean" Then Exit Sub
    
    Open ファイル名 For Binary As #1
        ReDim 配列(LOF(1))
        Get #1, , 配列
    Close #1
    
    tmp = ""
    For i = 18 To 21    '画像の横幅(ピクセル)を取得
        tmp = WorksheetFunction.Dec2Hex(配列(i), 2) & tmp
    Next i
    横 = WorksheetFunction.Hex2Dec(tmp)
    
    tmp = ""
    For i = 22 To 25    '画像の縦幅(ピクセル)を取得
        tmp = WorksheetFunction.Dec2Hex(配列(i), 2) & tmp
    Next i
    縦 = WorksheetFunction.Hex2Dec(tmp)
    
    If (横 * 3 Mod 4) <> 0 Then 詰物 = 4 - (横 * 3 Mod 4)   '横幅が4バイトで割り切れない場合の詰物の数を算出
    
    i = 54   '画像データ開始位置
    
    For 行 = 縦 To 1 Step -1
        For 列 = 1 To 横
            色 = WorksheetFunction.Dec2Hex(配列(i), 2) _
            & WorksheetFunction.Dec2Hex(配列(i + 1), 2) _
            & WorksheetFunction.Dec2Hex(配列(i + 2), 2)
            Cells(行, 列).Interior.Color = WorksheetFunction.Hex2Dec(色)
            i = i + 3
            DoEvents
        Next 列
        
        i = i + 詰物
        
    Next 行
End Sub

 プログラムの大まかな説明

  • 描画したいビットマップ画像のフルパスを取得します。今回は、ダイアログボックスでファイル選択できる形にしました。
  • ビットマップファイルをバイナリ形式で開き、バイト型の配列にまるっと格納します。こうすることで、ビットマップファイルの先頭から何バイト目であるかを、配列の添字でズバリ指定することができます。
  • 画像の横幅(ピクセル)と縦幅(ピクセル)を取得します。データの並びが、リトルエンディアンであることに注意します。
  • 画像の横幅が4バイトで割り切れない場合に、0埋めされているバイト数を算出します ※詳細は後述
  • 画像データの開始位置から、3バイトずつ色(を表現した数値)を取得し、セルの背景色に指定していきます。これを縦幅と横幅の分だけ、行と列にループさせます。画像データは左下から右上の順に入っているので、下から上に向かって描画させるようにします。余談ですが、セルの背景色はリトルエンディアンのまま(BGRの並び)で指定することができます。(RGB関数を使用するなら、引数はRGBの順なので混乱しないように・・・・・・)

 

今回、ヘッダからは横幅と縦幅の情報しか得ていませんが、他の情報を利用することで、細かく処理を分岐させることもできます。例えば、ビットマップ画像以外であればエラー表示して終了する、24ビットカラー以外のカラーに対応する、など。

 

東北ずん子(フリー素材)を描画してみるよ!

これが元画像

f:id:kouten0430:20190601144623j:plain

 

マクロ実行後に表示されるダイアログボックスで画像を選択できるので、画像はどこに置いてあっても大丈夫です。

では、さっそく描画させてみます!

f:id:kouten0430:20190601144715g:plain

上手くいったようです!

なお、マクロの実行前に手動でセルを正方形(10ピクセル×10ピクセル)にし、表示倍率は20%にしてあります。

ついでに、色が多すぎる画像を描画すると「セルの書式が多すぎるため、書式を追加できません」なるエラーが出るので、ご注意。

 

画像データの横幅が4バイトで割り切れない場合の話

ビットマップ画像は横方向のバイト数が4の倍数になるよう、データが記録されています。4の倍数に満たないときは、不足分が0で埋められています(今回は、0埋めしたバイトを「詰物」と表現します)。以下は、24ビットカラーの例です。

  • 横幅が1ピクセルなら3バイトで、4で割り切れないため、詰物を1つ入れて4バイトに。
  • 横幅が2ピクセルなら6バイトで、4で割り切れないため、詰物を2つ入れて8バイトに。
  • 横幅が3ピクセルなら9バイトで、4で割り切れないため、詰物を3つ入れて12バイトに。
  • 横幅が4ピクセルなら12バイトで、4で割り切れるため、詰物は無し。
  • 横幅が5ピクセルなら15バイトで4で割り切れないため、詰物を1つ入れて16バイトに。

f:id:kouten0430:20190601183655p:plain

 

以降、ピクセル数が増えてもこの繰り返し。 

縦方向に、この詰物はありません。

 

 おわりに

自分では思いつかない部分もあったので、いくつかのサイトを参考にしました。また、これは画像が描画される様子を見て個人的に楽しむのが目的であり、他人様に見せびらかすと暇人扱いされる場合があるのでご注意。