DXL 活用の調査・検証で、実現できたことや発見したことご紹介する『DXL Step-by-Step』シリーズの第 13 回です。
これまで数回にわたりイメージリソースを題材に画像データの取り扱いをまとめてきました。DXL で画像を操作していると、画像データのファイル形式を判定する必要が出てくることがあります。ファイルの拡張子で判定するのが簡単ですが、必ずしも正確にフォーマットを表しているとは限らないですよね。
そこで、ファイルの中身(バイナリーデータ)から情報を取得する方法についてまとめます。今回は DXL で画像を処理する上で必要となる情報のファイルフォーマットと画像サイズの取得だけに絞って記載いたします。
各フォーマットの画像ファイルをバイナリエディタで参照した画像を添付しています。画像内の黄色アンダーラインがフォーマットの判定、赤色が画像サイズを取得している箇所となります。
PNG
◇ フォーマットの判定
PNG は、ファイルの先頭 8 バイトが固定されています。特に 2 ~ 4 バイト目には Ascii コードの "PNG" という文字がセットされています。
◇ 画像サイズ
幅が 17 ~ 20 バイト目、高さが 21 ~ 24 バイト目となっています。これを 10 進数変換すれば取得できます。4 バイト分も確保されているので、ずいぶん大きな画像にも対応しているのですね。
GIF
◇ フォーマットの判定
GIF は、ファイルの先頭 3 バイトが Ascii コードの "GIF" という文字がセットされています。
◇ 画像サイズ
幅が 6 ~ 7 バイト目、高さが 8 ~ 9 バイト目となっています。GIF の画像サイズは、下位バイト、上位バイトの順となっていますので、サイズ(10進数)に変換時する際は注意が必要です。
JPEG
◇ フォーマットの判定
JPEG ファイルは、ファイルの中身をマーカと呼ばれる特定の値で管理する仕様です。JPEG ファイルのスタートを表すマーカは、FF D8 です。ファイルの最初の 2 バイトがこの値となっていれば JPEG と判定できます。
◇ 画像ファイル
JPEG ファイルの中身は、マーカーで区切られたセグメントに分割され管理されています。セグメントには、画像圧縮条件などのパラメータ、画像の大きさなどの基本情報、画像データの本体などそれぞれ役割が定義されています。
マーカーは 2 バイトで構成されており、1 バイト目が FF、2 バイト目でその役割が判定できます(水色)。セグメントのマーカーでは、その次の 2 バイトでセグメントのサイズを表しています(紫)。これを利用すると次のマーカーが簡単に取得できます。
今回必要となる画像サイズを持つマーカーは FF C0 となります。そのマーカーまでたどり着くには、次のような流れとなります。
- ファイル形式を判定した次の 2 バイトを取得します
- FF E0 なので目的のマーカーではない
- 次の 2 バイトからセグメントのサイズを取得して、次のマーカーまでファイルを空読みする
- 次のマーカーが目的のマーカー FF C0 であるか判定し、違う場合は 3 に戻る
- 画像サイズを取得する
なお、実際にはこのマーカーは C0 ~ C3 と幅があるようです。
サンプルプログラム
上記の仕様に従い画像フォーマットの判定とサイズを取得する関数を作成してみました。
まずは、メインプログラムです。画像ファイルをストリームでオープンし、そのファイル形式と画像サイズを取得して、メッセージボックスで表示しています。
なお、画像形式は定数として定義しています。
Option Declare Public Const DXL_Image_Unknown = 0 Public Const DXL_Image_JPEG = 1 Public Const DXL_Image_GIF = 2 Public Const DXL_Image_PNG = 3 Sub Initialize Dim ns As New NotesSession Dim nst As NotesStream Dim s As String Dim iType As Integer Dim iSizeX As Integer Dim iSizeY As Integer Set nst = ns.CreateStream() Call nst.Open("D:\TitleS.png") iType = xGetImageFileInfo(nst, iSizeX, iSizeY) If iType > 0 Then If iType = DXL_Image_PNG Then s = "PNG" If iType = DXL_Image_GIF Then s = "GIF" If iType = DXL_Image_JPEG Then s = "JPEG" s = "画像形式 = " & s s = s & Chr(10) & "幅 = " & CStr(iSizeX) s = s & ", 高さ = " & CStr(iSizeY) MsgBox s, 64 Else MsgBox "画像形式を判定できませんでした。", 48 End If End Sub |
関数のネスト順とは逆転するのですが、まず、画像形式を判定する関数から紹介します。
最初に整理した通り、画像形式の判定であればファイルの先頭から 4 バイトあれば判定できます。その部分だけをファイルから読み取り判定に使用しています。
Function xGetImageFileType(vnstImage As NotesStream) As Integer Dim vTmp As Variant Dim iT As Integer iT = DXL_Image_Unknown '先頭 4 バイト取得 vnstImage.Position = 0 vTmp = vnstImage.Read(4) If vTmp(0) = &h47 And vTmp(1) = &h49 Then 'GIF iT = DXL_Image_GIF ElseIf vTmp(0) = &hFF And vTmp(1) = &HD8 Then 'jpeg iT = DXL_Image_JPEG ElseIf vTmp(1) = &h50 And vTmp(2) = &h4E And vTmp(3) = &h47 Then 'PNG iT = DXL_Image_PNG End If xGetImageFileType = iT End Function |
次の関数がメインルーチンからコールされている画像形式と画像サイズを取得している関数です。引数は、画像ファイルをオープンしたストリームとサイズ情報です。サイズ情報の riSizeX と riSizeY は値を返すための引数です。また、戻り値は画像の形式を返します。
まず、上記 xGetImageFileType をコールして画像形式を取得して、形式毎に画像サイズを取得しています。セグメントを捜索する処理のある JPEG が少し複雑になっていますが、上記画像フォーマットの説明の通りのコードとなっています。
Function xGetImageFileInfo(vnstImage As NotesStream, riSizeX As Integer, riSizeY As Integer) As Integer Dim vTmp As Variant Dim iT As Integer Dim iX As Integer Dim iY As Integer On Error GoTo ErrProc '画像形式取得 iT = xGetImageFileType(vnstImage) If iT = 0 Then GoTo ExitProc '画像サイズ取得 vnstImage.Position = 0 If iT = DXL_Image_PNG Then vTmp = vnstImage.Read(16)'空読み '大きな画像はない前提に下2バイトのみ使用 '幅 vTmp = vnstImage.Read(4) iX = CInt(vTmp(2)) * 256 + CInt(vTmp(3)) '高さ vTmp = vnstImage.Read(4) iY = CInt(vTmp(2)) * 256 + CInt(vTmp(3)) ElseIf iT = DXL_Image_GIF Then vTmp = vnstImage.Read(6) '空読み vTmp = vnstImage.Read(4) '幅 iX = CInt(vTmp(1)) * 256 + CInt(vTmp(0)) '高さ iY = CInt(vTmp(3)) * 256 + CInt(vTmp(2)) ElseIf iT = DXL_Image_JPEG Then vTmp = vnstImage.Read(2) 'フォーマットのマーカ空読み SearchMarker: 'マーカー確認 vTmp = vnstImage.Read(2) If vTmp(0) = &HFF And vTmp(1) >= &HC0 And vTmp(1) <= &HC3 Then 'マーカー発見 vTmp = vnstImage.Read(8) iX = CInt(vTmp(5)) * 256 + CInt(vTmp(6)) iY = CInt(vTmp(3)) * 256 + CInt(vTmp(4)) Else 'セグメントのサイズを取得し空読みして次のマーカを確認 vTmp = vnstImage.Read(2) vnstImage.Position = vnstImage.Position + vTmp(0) * 256 + vTmp(1) - 2 GoTo SearchMarker End If End If ExitProc: riSizeX = iX riSizeY = iY xGetImageFileInfo = iT Exit Function ErrProc: iT = DXL_Image_Unknown iX = 0 iY = 0 Resume ExitProc End Function |
どちらの関数もなのですが、ファイルを確実に先頭から読み込むためにストリームの Position を 0 (=先頭)に設定しています。このように、読み込み位置を自在に扱えるのでストリームは便利ですね。
まとめ
今回は、画像形式と画像サイズを取得する方法をまとめました。
ローコードとかノーコードとかが話題となっている昨今において、まさかバイナリーファイルをバイト単位で読み込むような処理を作ることになるとは思っていませんでした。今どきの開発者の方にはなじまないのかもしれないですね。
私は?というと、バイナリエディタでファイルを開くなんて久しぶりの経験でした。35 年ほど前、某ファ○コンのスーパー○リオのジャンプ力や走力を魔改造してた頃を思い出し、懐かしさを感じながら調査していました(笑)
前回 | DXL Step-by-Step | 次回 |
0 件のコメント:
コメントを投稿