【Excel VBA】GIF画像ファイルがアニメーションGIFかどうか判別するツールの作成_その①(VBAの機能のみを使って実装)


1.この記事について

こんなケースを想定。
《GIF画像がアニメーションGIFであるか普通のGIFであるかどうかを判別したい》
《環境の制約でOfficeソフトのVBAしか使えないときに
(C#のImageAnimator.CanAnimateメソッドやimagemagick等の優れた外部ライブラリを使用できないパターンを想定)、
VBAのみで問題を解決したい》
《客先の要望などで、保守性の観点からVBAのみで実装してほしいと言われた場合》

2.やりたいことと参考にした資料

GIF画像ファイルの構造については下記のサイトを参考にさせていただきました。

GIFフォーマットの詳細:http://www.tohoho-web.com/wwwgif.htm

その中に、下記の記述がありました。

一般的なGIFアニメーションは、GIF Header の後に Application Extension があり、その後に Graphic Control Extension と Image Block が交互に連続する。

Application Extension は Netscape によって拡張された Loop Control として使用され、Application Identifier には >"NETSCAPE"、Application Authentication Code には "2.0" の文字が格納される。Application Data として1バイトの 0x01 および2バ>イトのループ回数(0~65535)が、例えばループ回数が5回の場合は 0x01 0x05 0x00 の3バイトが格納される。

すなわち、画像ファイルのバイト列から「NETSCAPE」という文字列のバイト列と一致する部分があれば、アニメーションGIFであるということです。

よって、
①まず「指定の文字列をバイト列に変換するツール」を作成してNETSCAPEをバイト列に変換し、
②「①」で得た結果を用いてアニメーションGIFを判別するツールを作成する
という手順を取りました。

3.作成したコード

(1)

まず①の指定の文字列をバイト列に変換するツールから。

M_JudgeAnimationGif.bas
'******************************************************************************************
'*関数名    :convertByteArrayFromString
'*機能      :指定文字列をバイト列に変換し、指定セルにカンマ区切りで出力
'*引数(1)   :変換対象文字列格納セルオブジェクト
'*引数(2)   :出力先のセルオブジェクト
'******************************************************************************************
Public Sub convertByteArrayFromString(ByVal inputStrCell As Range, _
                                        ByRef outputCell As Range)

    '定数
    Const FUNC_NAME As String = "convertByteArrayFromString"

    '変数
    Dim inputStr As String               '対象文字列
    Dim outputByte() As Byte             '格納先バイト列
    Dim cnt As Long                      'ループカウンタ


    On Error GoTo ErrorHandler
    '---以下に処理を記述---

    '///出力先を初期化
    outputCell.Value = ""

    '///文字列を取得
    inputStr = StrConv(inputStrCell.Value, vbFromUnicode)

    '///バイト列に格納
    outputByte = inputStr

    '///出力処理
    With outputCell
        '出力
        For cnt = LBound(outputByte) To UBound(outputByte)
            .Value = .Value & outputByte(cnt)
            'カンマを付加
            If cnt <> UBound(outputByte) Then
                .Value = .Value & ","
            End If
        Next
    End With


ExitHandler:

    Exit Sub

ErrorHandler:

        MsgBox "エラーが発生しましたので終了します" & _
                vbLf & _
                "関数名:" & FUNC_NAME & _
                vbLf & _
                "エラー番号" & Err.Number & Chr(13) & Err.Description, vbCritical

        GoTo ExitHandler

End Sub

これをシートのボタンに設定して、引数として適切なセルオブジェクトを与えると、下のようになりました。

--------------------NETSCAPEに対して実行--------------------

(2)

次に、78,69,84,83,67,65,80,69のバイト列を用いて
アニメーションGIFの判別コードを作成します。

M_JudgeAnimationGif.bas
'******************************************************************************************
'*関数名    :isAnimationGifImage
'*機能      :指定のGIF画像ファイルがアニメーションであるかどうか判別する
'*引数(1)   :指定する画像ファイルのフルパス
'*引数(2)   :アニメーションであるかどうかの判定を出力するセルオブジェクト
'******************************************************************************************
Public Sub isAnimationGifImage(ByVal filePath As String, _
                                ByRef outputCell As Range)

    '定数
    Const FUNC_NAME As String = "isAnimationGifImage"

    '変数
    Dim fileExpression As String            'ファイル拡張子
    Dim picObject As Object                 '画像ファイル格納オブジェクト
    Dim byteData() As Byte                  'バイト列
    Dim fileNum As Long                     'ファイル番号
    Dim cnt As Long                         'ループタウンタ

    On Error GoTo ErrorHandler
    '---以下に処理を記述---

    '///拡張子取得
    fileExpression = Mid(filePath, InStrRev(filePath, ".") + 1)
    '拡張子がgifであるファイルのみ処理を続行
    If fileExpression <> "gif" Then
        outputCell.Value = "指定されたファイルはGIFファイルではありません。"
        GoTo ExitHandler
    End If

    '///画像ファイルのみ処理を続行
    On Error GoTo 0
    On Error Resume Next
    Set picObject = LoadPicture(filePath)
    On Error GoTo 0
    On Error GoTo ErrorHandler
    '正しくloadできていないならExitHandlerへ
    If picObject Is Nothing Then
        outputCell.Value = "指定されたファイルは画像ファイルではありません。"
        GoTo ExitHandler
    End If

    '///バイト列の格納処理
    '余っているファイル番号取得
    fileNum = FreeFile

    '指定されたファイルをバイナリモードで開く
    Open filePath For Binary As #fileNum
        'ファイルの長さで配列を初期化
        ReDim byteData(0 To LOF(fileNum))
        'ファイルをバイナリで読み込んでByte配列に格納
        Get fileNum, , byteData
    Close #fileNum

    '///gifアニメ判別処理
    'バイト列をループする
    For cnt = 0 To UBound(byteData) - 7
        'アニメーションGIFの識別文字列「NETSCAPE」のバイナリコードが見つかるまでループする
        If byteData(cnt) = 78 Then
            If byteData(cnt + 1) = 69 Then
                If byteData(cnt + 2) = 84 Then
                    If byteData(cnt + 3) = 83 Then
                        If byteData(cnt + 4) = 67 Then
                            If byteData(cnt + 5) = 65 Then
                                If byteData(cnt + 6) = 80 Then
                                    If byteData(cnt + 7) = 69 Then
                                        outputCell.Value = "TRUE:指定したファイルはアニメーションGIF画像ファイルです。"
                                        GoTo ExitHandler
                                    End If
                                End If
                            End If
                        End If
                    End If
                End If
            End If
        End If
    Next

    '見つからなければ非アニメーションGIF
    outputCell.Value = "FALSE:指定したファイルはアニメーションGIF画像ファイルではありません。"


ExitHandler:

    Exit Sub

ErrorHandler:

        MsgBox "エラーが発生しましたので終了します" & _
                vbLf & _
                "関数名:" & FUNC_NAME & _
                vbLf & _
                "エラー番号" & Err.Number & Chr(13) & Err.Description, vbCritical

        GoTo ExitHandler

End Sub

これをシートのボタンに設定して、引数として適切なセルオブジェクトを与えると、下のようになりました。

4.コードの解説

IFの多重ネストについて

見栄えは悪いですが、AND演算子を使うよりも処理は早いです(VBAの仕様上、ANDでつないだ全てについて評価するため)
参考URL:VBScriptのAnd、Orには気をつけろ~Java慣れ開発者のとんだ落とし穴 | | 株式会社シンメトリック公式ブログ

改善案:GOTO文を使って疑似Continue処理をすれば可読性は向上するかもしれませんね。

5.終わりに

※20191208
続きとなる記事を書きました。
【Excel VBA】GIF画像ファイルがアニメーションGIFかどうか判別するツールの作成_その②「異なる方法で処理速度を比較」

今回作成したツールについて、
Githubにあげたので良かったら参照してください。

Github

なにか補足がありましたらコメントください。