t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBAでバイナリの読み書き

昨今の企業はどこでもセキュリティが厳しくて、勝手にフリーソフトウェアをインストールしたりできないことが多いと思う。

業務に関係のないソフトは入れるなという最低限の基準をもうけているところもあれば、いかなるソフトも勝手にインストールしてはいけない(できない)ところもある。
私の職場は後者の方であるが、マニュアル作成などでバージョン管理システムが使えないのがつらい。
Wordの校閲機能などでも一応差分は取れるが、あれも慎重に触らないと履歴が残らない。

バージョン管理システムとは、ファイルの変更を差分で管理し、いつでも元に戻せるようにする機能を持つソフトである。
状態を保存することをコミットといい、コミットするとそれが一つのバージョンになる。
コミットする際にコメントを書くことができ、誰が何をどう変更したのかが記録としてのこる。

さて、Wordのようなファイルに対して変更前と変更後を差分で管理しようと思うと、バイナリの差分をファイルとして保管することが考えられる。
VBAで対応しようとしてできないことは無いだろうけど、多分面倒くさいのでやめておく。

今回の記事では、その可能性に触れておしまいにしたい。

まず、実はVBAでもバイナリの読み書きができる。

【参考】
https://www.cocoaliz.com/excelVBA/index.php/47/


上記を参考にして、コードから説明に不要なものを取り除くとこうなる。

Sub バイナリコピーテスト()
    Dim 画像() As Byte
    
    '読み込み
    Open "C:\BinTest\Picture1.bmp" For Binary As #1
        ReDim 画像(LOF(1))
        Get #1, , 画像
    Close #1

    '書き込み
    Open "C:\BinTest\Picture2.bmp" For Binary As #2
        For i = 0 To UBound(画像) - 1
            Put #2, , 画像(i)
        Next
    Close #2
End Sub

ここで注意して欲しいのは、上記のコードは説明したい箇所をスッキリさせるために、実用性を犠牲にしているということ。
本当は変数は宣言すべきだし、ファイル名のハードコードはよろしくない。

ただ、ごちゃっとしたサンプルは読む気がしないので、色々と省略している。
【どうも読む気がおきないコード例】
VBA応用(バイナリモードでの読み書き)

※このサイトにVBA初心者の頃にお世話になっている。非常に詳しく書かれていて良いサイトである。
 ただ、頭を使わないと直感的に理解できないので、最近サンプルとして参考にするのは辛く感じる。
 (歳のせいだろうか。。)


さて、本題にもどろう。

CドライブにBinTestフォルダを準備し、適当な画像をPicture1.bmpとして準備する。
f:id:t-hom:20150815142356p:plain

そして上記のコードを実行すると、BinTestフォルダ内にPicture1.bmpと全く同じ内容のPicture2.bmpが生成される。


次にPicture2.bmpを少々変更して、赤丸の内側を黄緑に塗ってみる。
f:id:t-hom:20150815143819p:plain

ここで、編集を行った差分を取りたい。
単純に差分を取るだけなら、各ビットをXOR演算すればできそうだ。

XORについては、XOR暗号でも紹介したが、どちらか片方のビットが1かつ、もう片方が0ならば1になる演算である。
thom.hateblo.jp

つまり、Picture1とPicture2で、変更があるビットだけが1になるということ。

ということでVBAで差分をとってみよう。

Sub Xor差分パッチ生成()
    'Picture1読み込み
    Dim 画像1() As Byte
    Open "C:\BinTest\Picture1.bmp" For Binary As #1
        ReDim 画像1(LOF(1))
        Get #1, , 画像1
    Close #1
    
    'Picture2読み込み
    Dim 画像2() As Byte
    Open "C:\BinTest\Picture2.bmp" For Binary As #2
        ReDim 画像2(LOF(2))
        Get #2, , 画像2
    Close #2

    '書き込み
    Open "C:\BinTest\Patch.bin" For Binary As #3
        For i = 0 To UBound(画像1) - 1
            Put #3, , 画像1(i) Xor 画像2(i)
        Next
    Close #3
End Sub

Patch.binというファイルができた。
ただこれ、当たり前だがバイト数はPicture1、Picture2と同じである。
何かしら効率よく保管しないとパッチの意味がない。
これについては後で検討しよう。

次にPicture2を削除し、Picture1にパッチをあてることで、Picture2を再現できるかやってみる。

Sub パッチ適用()
    'Picture1読み込み
    Dim 画像1() As Byte
    Open "C:\BinTest\Picture1.bmp" For Binary As #1
        ReDim 画像1(LOF(1))
        Get #1, , 画像1
    Close #1
    
    'パッチ読み込み
    Dim パッチ() As Byte
    Open "C:\BinTest\Patch.bin" For Binary As #2
        ReDim パッチ(LOF(2))
        Get #2, , パッチ
    Close #2

    '書き込み
    Open "C:\BinTest\Picture2.bmp" For Binary As #3
        For i = 0 To UBound(画像1) - 1
            Put #3, , 画像1(i) Xor パッチ(i)
        Next
    Close #3
End Sub

上記を実行すると、見事にPicture2.bmpが再現された。

書き込み処理がちょっとわかりにくいと思うので解説する。

例えば、以下のようなビット列だったとする。

Picture1 11110000
Picture2 11110010

パッチは、Xorを取るのでこうなる。

Picture1 11110000
Picture2 11110010
Patch 00000010

パッチは、変更箇所だけが1になっている。

それをPicture1とXorさせると、Picture2のビット列に戻る。

Picture1 11110000
Patch 00000010
Xor結果 11110010

これで差分パッチを適用する方法が分かった。
さっきは小さいファイルだったが、もう少し大きなファイルでやってみよう。
タイ旅行のタイガーテンプルで撮影したバッファロー
f:id:t-hom:20150815151146j:plain

JPEGだと編集するたびに容量が変わるのでbmpに直し、これをPicture1.bmpとした。
容量は27.4MB。

次にこれをペイントで加工したものをPicture2.bmpとした。
f:id:t-hom:20150815151420j:plain

Xor差分パッチ生成を実行してみると。。。
5分くらいかかってようやくPatch.binが完成。
こちらも27.4MBだが、変更が無い箇所は0で埋め尽くされたファイルなので、zip圧縮してみると顕著に容量が減る。

以下、それぞれを圧縮した結果
f:id:t-hom:20150815152141p:plain

つまり、Picture1.bmpとPatch.zipで合計28,153KBのファイルがあれば、Picture2を再現できるということ。
※実際には、いちいちzip化してられないので、本当はパッチの作り方を工夫する必要があるが、今回は原理の紹介なので手抜き。

さて、これでPicture2を削除してパッチ適用マクロを実行すると、また5分ほどかかったけれど頭にハートを載せたバッファローの画像が再現できた。

今回紹介したマクロは、同じサイズのバイナリ同士でしか使えない。
興味がある方はこれを発展させて異なるファイルサイズでもパッチ適用できるようにしたり、最初から少ないファイルサイズのパッチを高速に作ったりできるように工夫してみて欲しい。

※そして、完成したら私にください。

以上

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。