2025/12/07

QR コードの作画:#9)DXL でリッチテキストに表示

LotusScript で QR コードを作画するこの連載は今回が最終回となります。

DXL がビットマップに対応していないことから、暫定策として GIF ファイルに変換する対応をしました。今回は出来上がった GIF ファイルを文書に貼り付ける部分を作成します。


エージェントの作成と関数の追加

前回作成した CreateQRcode_GIF エージェントをコピペして DrawQRcode を作成します。このエージェントに関数を追加して作業を進めます。

リッチテキストフィールドに QR コードをインラインイメージ(見える状態)で貼り付ける処理には DXL を活用しなければなりません。この連載の本題ではないので詳細は割愛しますが、DXL にご興味がある方は以下の連載をご確認ください(一部関数は連載より流用)。


◇ 文書を DXL に変換

既存文書を DXL で操作できるよう変換処理を行う関数です。

Function xGetDOMParser(vnd As NotesDocument) As NotesDOMParser
   'Dominoデータ を DXL に変換する準備
   Dim dexp As NotesDXLExporter
   Set dexp = xns.CreateDXLExporter()
   Call dexp.SetInput(vnd)

   'パーサーに変換する DXL をセット
   Dim dprs As NotesDOMParser
   Set dprs = xns.CreateDOMParser()
   Call dprs.SetInput(dexp)

   'DXL 変換を実行
   Call dexp.Process()

   Set xGetDOMParser = dprs
End Function

この関数は『DXL Step-by-Step:#3)文書を DXL で取得』で紹介しています。


◇ QR コードの表示

リッチテキスト内に QR コードをインラインイメージで貼り付ける関数です。

引数 vsFld で指定された名前のリッチテキストフィールドにセットするのですが、フィールドをいったん削除しています。フィールド内の既存コンテンツはクリアされますので注意してください。

Function xSetDXL_GIF(vdprs As NotesDOMParser, ByVal vsFld As String, ByVal vsFP_GIF As String, viX As Integer, viY As Integer)
   Dim ddn As NotesDOMDocumentNode
   Dim den As NotesDOMElementNode
   Dim denDoc As NotesDOMElementNode
   Dim denItem As NotesDOMElementNode
   Dim denRT As NotesDOMElementNode
   Dim denPar As NotesDOMElementNode
   Dim denPic As NotesDOMElementNode
   Dim denGIF As NotesDOMElementNode
   Dim dtn As NotesDOMTextNode
   Dim nst As NotesStream

   Set ddn = vdprs.Document

   'document ノード取得
   Set denDoc = ddn.DocumentElement

   '既存フィールドを削除
   Call xRemoveItemByName(denDoc, vsFld)

   'リッチテキストフィールド作成
   Set den = ddn.CreateElementNode("item")
   Call den.SetAttribute("name", vsFld)
   Set denItem = denDoc.AppendChild(den)

   'リッチテキスト作成
   Set den = ddn.CreateElementNode("richtext")
   Set denRT = denItem.AppendChild(den)

   '段落定義 id='1'
   Set den = ddn.CreateElementNode("pardef")
   Call den.SetAttribute("id", "1")
   Call denRT.AppendChild(den)

   '段落の作成
   Set den = ddn.CreateElementNode("par")
   Call den.SetAttribute("def", "1")
   Set denPar = denRT.AppendChild(den)

   'イメージリソースの追加
   Set den = ddn.CreateElementNode("picture")
   Call den.SetAttribute("align", "baseline")
   Call den.SetAttribute("width", CStr(viX) & "px")
   Call den.SetAttribute("height", CStr(viY) & "px")
   Set denPic = denPar.AppendChild(den)

   '画像の作成
   Set den = ddn.CreateElementNode("gif")
   Set denGIF = denPic.AppendChild(den)

   'GIF をストリームで開く
   Set nst = xns.CreateStream()
   Call nst.Open(vsFP_GIF, "binary")

   '画像の中身
   Set dtn = ddn.CreateTextNode(StreamToBase64(nst))
   Call denGIF.AppendChild(dtn)

   Call nst.Close()
End Function

この関数については合致する記事はありませんが、インラインイメージの貼り付けについては『DXL Step-by-Step:#41)インラインイメージの貼り付け』で触れています。

また、今回は画像サイズは QR コードのサイズであり、事前にわかっています。そこで、画像ファイルから取得するのではなく、関数の引数で受け取り処理を簡略化しています。


◇ 指定したフィールドの削除

xSetDXL_GIF からコールされているサブ関数で、DXL でフィールドを削除する関数です。

Function xRemoveItemByName(vden As NotesDOMElementNode, ByVal vsName As String)
   Dim dn As NotesDOMNode
   Dim den As NotesDOMElementNode
   Dim sName As String
   Dim s As String

   sName = LCase(vsName)

   Set dn = vden.FirstChild
   Do Until dn.Isnull
      If dn.NodeType = DOMNODETYPE_ELEMENT_NODE Then
         If dn.NodeName = "item" Then
            Set den = dn
            s = LCase(den.GetAttribute("name"))

            If sName = s Then
               Call vden.RemoveChild(den)
               Exit Function
            End If
         End If
      End If
      Set dn = dn.NextSibling
   Loop
End Function

この関数の参考記事は現時点でありません。フィールドである item ノードから名前が一致するものを探し出し削除しています。


◇ 画像のエンコード

こちらも xSetDXL_GIF からコールされているサブ関数です。DXL 内の画像は Base64 でエンコードしておく必要があります。そのエンコード処理を行う関数です。

'OpenNTF LotusScript Gold Collection より拝借(StreamToBase64)
Function StreamToBase64(streamIn As NotesStream) As String
   Dim s As String

   On Error GoTo theOldWay
   ' ReadEncoded function is not documented. In case it doesn't work have a backup.
   s = streamIn.ReadEncoded(ENC_BASE64, 76)
   s = Replace(s, Chr$(13), "")
   s = Replace(s, Chr$(10), "")
   StreamToBase64 = s
   Exit Function

theOldWay:
   Dim session As New NotesSession
   Dim db As NotesDatabase
   Dim doc As NotesDocument
   Dim mime As NotesMIMEEntity

   Set db = session.CurrentDatabase
   Set doc = db.CreateDocument
   Set mime = doc.CreateMIMEEntity("Body")
   streamIn.Position = 0
   Call mime.SetContentFromBytes(streamIn, "image/gif", ENC_NONE)
   mime.EncodeContent(ENC_BASE64)
   s = mime.ContentAsText
   s = Replace(s, Chr$(13), "")
   s = Replace(s, Chr$(10), "")
   StreamToBase64 = s
End Function

コメントに記載した通り、 OpenNTF の LotusScript Gold Collection プロジェクトより拝借した関数です。『DXL Step-by-Step:#10)イメージリソースの新規作成』で紹介しています。


◇ 文書の保存

最後の関数は文書を保存する関数です。

Function DXL_Import(vdprs As NotesDOMParser, ByVal viOption As Integer, ByVal vbIsDesign As Boolean) As Boolean
   Dim nst As NotesStream
   Dim ndb As NotesDatabase
   Dim dimp As NotesDXLImporter

   On Error GoTo Err_Proc

   'DXL の抽出準備
   Set nst = xns.CreateStream()
   Call vdprs.SetOutput(nst)
   Call vdprs.Serialize()

   '保存(インポート)
   Set ndb = xns.CurrentDatabase
   Set dimp = xns.CreateDXLImporter()
   If vbIsDesign = True Then
      '設計の保存
      dimp.DesignImportOption = viOption
   Else
      '文書の保存
      dimp.DocumentImportOption = viOption
   End If

   'DXL の保存
   Call dimp.Import(nst.ReadText(), ndb)
   DXL_Import = True

Exit_Proc:
   Exit Function

Err_Proc:
   MsgBox Error$, 16, "DXL_Import"
   DXL_Import = False

   Resume Exit_Proc
End Function

この関数は『DXL Step-by-Step:#23)サンプルコード(段落と文字の装飾①)』で紹介しています。


メインルーチンの修正

エージェントの Initialize を修正し、今回作成した QR コード表示機能を追加します。

Sub Initialize
                  ・・・
   Dim iX As Integer    '画像の幅
   Dim iY As Integer    '画像の高さ
   Dim dprs As NotesDOMParser
                  ・・・
      '② GIF ファイルの作成
      Call DrawQR_GIF(abQR, "c:\tmp\QR.gif")

      '③ リッチテキストにインラインで貼り付け
      Set dprs = xGetDOMParser(nd)
      iX = UBound(abQR, 1) + 1    '画像の幅
      iY = UBound(abQR, 2) + 1    '画像の高さ
      Call xSetDXL_GIF(dprs, "QRcode", "c:\tmp\QR.gif", iX, iY)
      Call DXL_Import(dprs, 5, False)    '文書を更新
   End If
End Sub


ビューの修正と動作検証

エージェントが完成したら、アクションボタンをビューに追加します。

ビューを保存後、動作検証します。正常に実行されると冒頭の画像のように QR コードが表示されます。


まとめ

今回の連載は、LotusScript だけを使って QR コードをビットマップ画像として描画する方法について解説しました。あわせて、ビットマップ画像のフォーマット仕様についても整理し、仕組みを理解しながら作り上げる流れをご紹介しました。

本来であれば、できる限り幅広い環境で動作させるために LotusScript だけで完結させることを目指していましたが、現状の DXL ではビットマップに対応していないため、最終的な GIF 変換には Windows の機能に頼らざるを得ない部分がありました。その結果、完全に LotusScript だけで完結する構成にはできなかった点は少し心残りです。

機会があれば GIF フォーマットのファイルを LotusScript で生成することにも挑戦してみたいと思います。仕様が理解できたらという制約はありますが...


前回 QR コードの作画


2025/12/06

QR コードの作画:#8)BMP → GIF 変換

前回は、DXL はビットマップファイルに対応していないと(個人的に)衝撃的な事実を知ったことを紹介しました。せっかく作ったライブラリを何とか使いたいので、GIF ファイルに変換して利用したいと思います。

こういった汎用的なコンバート処理は、AI に聞きながら開発すると効率的ですよね。そこで、今回は ChatGPT 先生に相談しながら作業することにします。


BMP → GIF 変換

WIA(Windows Image Acquisition)を使うなどいくつかの方法を提案してくれましたが、64 ビット版の Notes でも動作させたいので、今回は PowerShell を利用して実現することにします。次のコマンドを実行すればよいそうです。

Add-Type -AssemblyName System.Drawing
$bmp = [System.Drawing.Bitmap]::FromFile('C:\tmp\BMP.bmp')
$bmp.Save('C:\tmp\BMP.gif', [System.Drawing.Imaging.ImageFormat]::Gif)

各行は、

  1. .NET の System.Drawing 名前空間を PowerShell セッションにロードします。
    (System.Drawing は Bitmap や Image、ImageFormat の定義を含むライブラリ)
  2. 指定した BMP ファイルを読み込み、System.Drawing.Bitmap オブジェクト(ビットマップ画像を表す .NET オブジェクト)を作成して $bmp に代入
  3. $bmp に保持されている画像を、指定したパスに GIF 形式で保存

となっているそうです。


LotusScript で関数化

教えてもらった PowerShell のコマンドを実行する LotusScript の関数の作成も依頼してみます。頼んでないのに複数ファイルを一括変換してみたり、Domino Designer に貼り付けると文法エラーになったりと、少々回り道をしましたが、何とか完成しました。

完成に向けていくつか要望を出してみたのですが、それぞれ叶えてくれました。

PowerShell 実行ウィンドウの非表示 非表示で WSH を起動し、PowerShell を実行させる
全角対応UTF-8 で PowerShell スクリプトを保存
これによりパスやフォルダ名に日本語(全角文字)が含まれても安全に動作

出来上がった関数は以下の通りです。ChatGPT が出力した関数を不要な部分を削除し、命名規則を整えたバージョンです。

Private Function xConvertBMPtoGIF_PS(ByVal vsFP_BMP As String, ByVal vsFP_GIF As String) As Boolean
   Dim oFSO As Variant
   Dim oShell As Variant
   Dim sPS_FP As String
   Dim sPS As String
   Dim sCMD As String
   Dim sCRLF As String

   ' 初期化
   On Error GoTo ErrorHandler

   xConvertBMPtoGIF_PS = False
   sCRLF = Chr(13) & Chr(10)

   ' 変換元ファイルの存在チェック
   Set oFSO = CreateObject("Scripting.FileSystemObject")
   If Not oFSO.FileExists(vsFP_BMP) Then Exit Function

   ' PowerShell スクリプトの作成
   sPS_FP = Environ$("TEMP") & "\convert_bmp_to_gif.ps1"

   ' PowerShellスクリプト内容(全角パス対応)
   sPS = "Add-Type -AssemblyName System.Drawing"
   sPS = sPS & sCRLF & "$src = '" & Replace(vsFP_BMP, "'", "''") & "'"
   sPS = sPS & sCRLF & "$dst = '" & Replace(vsFP_GIF, "'", "''") & "'"
   sPS = sPS & sCRLF & "$bmp = [System.Drawing.Bitmap]::FromFile($src)"
   sPS = sPS & sCRLF & "$bmp.Save($dst, [System.Drawing.Imaging.ImageFormat]::Gif)"
   sPS = sPS & sCRLF & "$bmp.Dispose()"

   ' UTF-8で.ps1を書き出し(全角対応のため)
   Call xWriteUtf8File(sPS_FP, sPS)

   ' PowerShell実行
   Set oShell = CreateObject("WScript.Shell")
   sCMD = |powershell -NoProfile -ExecutionPolicy Bypass -File "| & sPS_FP & |"|
   oShell.Run sCMD, 0, True       ' 0=ウィンドウ非表示, True=完了待ち

   ' 結果確認
   If oFSO.FileExists(vsFP_GIF) Then
      xConvertBMPtoGIF_PS = True
   End If

ExitFunc:
   ' 一時ファイル削除
   On Error Resume Next
   oFSO.DeleteFile sPS_FP

   Exit Function

ErrorHandler:
   xConvertBMPtoGIF_PS = False
   Resume ExitFunc
End Function

' UTF-8 (BOMなし) でテキストファイルを書き出し
Private Sub xWriteUtf8File(ByVal vsFP As String, ByVal vsText As String)
   Dim oStream As Variant
   Set oStream = CreateObject("ADODB.Stream")
   With oStream
      .Charset = "UTF-8"
      .Open
      .WriteText vsText
      .SaveToFile vsFP, 2    ' 上書き
      .Close
   End With
End Sub


ライブラリへの組み込み

上記関数を前回までに作成した lsDrawQRcode ライブラリに追加します。

続いて、GIF ファイルを作成するメインルーチンも作成します。ビットマップ作成と上記の関数を実行するだけの単純な構造です。

%REM
QR コードの論理データ(Boolean 型の 2 次元配列)から GIF ファイルを作成します。

◆ 引数
   vabQR Boolean QR コードの論理データ(Boolean 型の 2 次元配列)
   vsFP_GIF String フルパスで指定されたファイル名

◆ 戻り値 Booelan
   ファイルが作成出来れば True
%END REM

Public Function DrawQR_GIF(vabQR As Variant, ByVal vsFP_GIF As String) As Boolean
   Dim sFP_BMP As String

   sFP_BMP = vsFP_GIF & ".bmp"
   If DrawQR_BMP(vabQR, sFP_BMP) Then
      Call xConvertBMPtoGIF_PS(sFP_BMP, vsFP_GIF)

      ' 一時ファイル削除
      On Error Resume Next
      'Kill sFP_BMP
   End If
End Function

なお、一時ファイルであるビットマップは最後に削除するコードを書いています。検証のためコメントアウトしていますが、確認後はコメント解除してください。


動作検証

関数が完成したので動作検証を行います。

前回作成したエージェントをコピペして CreateQRcode_GIF を作成します。画像を作成する部分を今回作成した関数に差し替えます。

         ・・・
      '② GIF ファイルの作成
      Call DrawQR_GIF(abQR, "c:\tmp\QR.gif")
         ・・・

このエージェントを実行するアクションボタンをビューに追加します。

正常に動作すると出力フォルダに GIF ファイルが作成されます。


次回の予定

これで GIF 形式の QR コードが出力できるようになりました。次回の最終回では DXL を使ってこの画像をリッチテキストにインラインで貼り付けるコードを紹介します。


前回 QR コードの作画 次回


2025/12/04

QR コードの作画:#7)BMP は作成できたけど...

前回でスクリプトライブラリは完成しましたので、正しくビットマップファイルが作成できるか動作検証をしましょう。


フォームの作成

新規でフォームを作成します。名称にこだわりはないのですが、ここでは QR_Pict とします。フィールドは次の 2 個作成します

項目 フィールド名 種類 補足
変換する文字列 StrSrc テキスト 編集可能
QR コード QRcode リッチテキスト 編集可能

入力した文字列を QR コード化にしてリッチテキストに表示することをイメージした単純なフォームとなります。


エージェントの作成

続いてビューで選択した文書に QR コードを添付するエージェントを作成します。とりあえず今回は、QR コードに変換しビットマップファイルとして保存するところまでを作成します。

LotusScript のエージェント CreateQRcode_BMP を新規作成し、プロパティで実行対象を”なし”に設定します。

エージェントのコード全体は次の通りです。

まず、前回までに作成したライブラリ lsDrawQRcode を組み込みます。

選択した文書から変換する文字列を取得して QR コードにエンコードします(①)。ここまでは、連載『ノーツで QR コード』で作成した lsQRCode ライブラリを利用した部分ですね。

今回作成した関数は ② で使用しています。動作検証なので C:\tmp\ フォルダに QR.bmp という名前で固定して出力させています(実行前にはフォルダを用意ください)。

Option Declare
Use "lsDrawQRcode"

Private xns As NotesSession

Sub Initialize
   Dim ndb As NotesDatabase
   Dim ndc As NotesDocumentCollection
   Dim nd As NotesDocument
   Dim sSrcText As String

   Dim sEnc As String             'エンコード文字列
   Dim abQR() As Boolean    'QR コードの論理データ(Boolean 型の 2 次元配列)

   Set xns = New NotesSession
   Set ndb = xns.CurrentDatabase
   Set ndc = ndb.UnprocessedDocuments

   If ndc.Count <> 1 Then
      MsgBox "QR コードを作成する文書を1文書だけ選択してください。", 16, ndb.Title
   Else
      Set nd = ndc.GetNthDocument(1)
      sSrcText = nd.StrSrc(0)    'QRコード化したい文字列

      '① QR コードのエンコード
      sEnc = EncodeBarcode(sSrcText, 3)    '3 = 誤り訂正レベル
      Call bc_2Dms_New(sEnc, abQR)         'QR コードの論理データに変換

      '② ビットマップファイルの作成
      Call DrawQR_BMP(abQR, "c:\tmp\QR.bmp")
   End If
End Sub


ビューの作成

先に作成したフォームの文書を表示するためのビューを作成します。フォーム同様特にこだわりはないので適当に作成します。

ただ、このビューは QR コードを作成を実行する役割があります。[QRコード作成]アクションボタンで作成したエージェントを実行するようにします。


動作検証

テストできる環境が整ったので動作検証を行います。適当な文字列を入力したテストデータを作成し、[QRコード作成]ボタンをクリックします。

正常に動作すると出力フォルダにビットマップファイルが作成されます。


想定外の大どんでん返し...

最難関と考えていた画像生成ができたので、あとは QR コードをフォームに貼り付けるだけです。

リッチテキストフィールドに画像をインライン(見える状態)で貼り付けるには、通常の NotesRichText??? クラスでは実現できません。LotusScript で DXL を使用する必要があります。

他の連載『DXL ことはじめ』『DXL Step-by-Step』でまとめている通り、DXL には心得があります。それを使って実現しようとしたのですが、なんと!DXL は BMP 未対応だったんです !? 

GIF や JPEG などの画像形式に対応しているので、てっきり対応しているものと思っていました...


次回の予定

仕方がないので、次に仕様が簡単そうな GIF 形式を調べてみたのですが、画像圧縮する機能あり、ビットマップよりずいぶん複雑です。現時点では、残念ながら理解に至っていません。

そこで今回は、ビットマップを GIF に変換することで暫定対応としたいと思います。詳しくは次回紹介します。


前回 QR コードの作画


2025/12/01

QR コードの作画:#6)ビットマップファイルの出力

前回はビットマップの画像データを作成する関数 xBMP_MakePictureData を作成しました。今回はメインルーチンからコールしているその他の関数を紹介します。

  • ファイルヘッダの出力
  •  情報ヘッダの出力
  •  カラーパレットの出力
  • 画像データの出力

これらは『#2)白黒ビットマップの構造』ビットマップの構造順にファイルを出力するための処理となります。また、各ブロックの構造については『#3)ビットマップのエンコード仕様』を参照ください。


ファイルヘッダの出力

ファイル出力するエリアとなる Byte 型配列を用意して、そこに出力する値をセットします。すべての値が準備できたら、NotesStream の Write メソッドでファイルに書き込みます。

ファイルヘッダで画像に応じて可変になる項目はファイルサイズだけでした。

ファイルサイズは、前回作成した画像データから取得し、他のブロック数を加算して算出ります。それ以外の項目は固定の値となります。

Private Function xBMP_WriteFileHeader(vnst As NotesStream, vvBitMap As Variant)
   Dim lSize As Long
   Dim sSize As String
   Dim i As Integer
   Dim abyFileHeader(13) As Byte

   '固定
   abyFileHeader(0) = Asc("B")
   abyFileHeader(1) = Asc("M")

   'ファイルサイズ
   lSize = Ubound(vvBitMap(0)) + 1               '1行のバイト数
   lSize = lSize * (UBound(vvBitMap) + 1)    '行数を掛ける
   lSize = lSize + 14 + 40 + 8     '画像データ以外のブロックを加算
   'リトルエンディアンでセット(4 バイト)
   sSize = Right("0000000" & Hex(lSize), 8)
   For i = 0 To 3
      abyFileHeader(5-i) = CByte(xHexToDec(Mid(sSize, i*2+1, 2)))
   Next

   'オフセットビット
   abyFileHeader(10) = 62

   'ファイルヘッダ部をファイルに出力
   Call vnst.Write(abyFileHeader)
End Function


情報ヘッダの出力

情報ヘッダで可変となる項目は、画像のサイズ(幅と高さ)、画像データのバイト数でした。

画像データのサイズはファイルヘッダで算出していたのと同様です。画像サイズは、QR コードの論理データ(Boolean 型の 2 次元配列)から簡単に取得できます。よってこの関数の引数には、QR コードの論理データ vabQR と画像データ vvBitMap が引数になっています。

Private Function xBMP_WriteInfoHeader(vnst As NotesStream, vabQR As Variant, vvBitMap As Variant)
   Dim lSize As Long
   Dim sSize As String
   Dim i As Integer
   Dim abyInfoHeader(39) As Byte

   '情報ヘッダのサイズ
   abyInfoHeader(0) = 40

   '画像の幅(リトルエンディアン)
   lSize = (UBound(vabQR, 1) + 1)
   sSize = Right("0000000" & Hex(lSize), 8)
   For i = 0 To 3
      abyInfoHeader(7-i) = CByte(xHexToDec(Mid(sSize, i*2+1, 2)))
   Next

   '画像の高さ(リトルエンディアン)
   lSize = (UBound(vabQR, 2) + 1)
   sSize = Right("0000000" & Hex(lSize), 8)
   For i = 0 To 3
      abyInfoHeader(11-i) = CByte(xHexToDec(Mid(sSize, i*2+1, 2)))
   Next

   'プレーン数
   abyInfoHeader(12) = 1

   'ビットカウント
   abyInfoHeader(14) = 1

   '画像データのサイズ(リトルエンディアン)
   lSize = UBound(vvBitMap(0)) + 1               '1行のバイト数
   lSize = lSize * (UBound(vvBitMap) + 1)    '行数を掛ける
   sSize = Right("0000000" & Hex(lSize), 8)
   For i = 0 To 3
      abyInfoHeader(23-i) = CByte(xHexToDec(Mid(sSize, i*2+1, 2)))
   Next

   '情報ヘッダ部をファイルに出力
   Call vnst.Write(abyInfoHeader)
End Function


カラーパレットの出力

今回は白黒のビットマップですのでカラーパレットブロックの内容はすべて固定でした。ですので、出力関数内は単純です。


Private Function xBMP_WriteColorPallet(vnst As NotesStream)
   Dim sCol_HEX As String
   Dim i As Integer
   Dim abyColor(7) As Byte

   'パレット0
    sCol_HEX = "00000000"
   For i = 0 To 3
      abyColor(i) = CByte(xHexToDec(Mid(sCol_HEX , i*2+1, 2)))
   Next

   'パレット1
    sCol_HEX = "FFFFFF00"
   For i = 0 To 3
      abyColor(4 + i) = CByte(xHexToDec(Mid(sCol_HEX , i*2+1, 2)))
   Next

   'カラーパレットをファイルに出力
   Call vnst.Write(abyColor)
End Function


画像データの出力

画像データは Variant 型配列の各要素に 1 行分が入っていました。ファイルへの出力は For ループで 1 行ずつ順に出力するだけです。

Private Function xBMP_WritePictureData(vnst As NotesStream, vvBitMap As Variant)
   Dim iY As Integer

   '1 行ずつファイルに出力
   For iY = 0 To UBound(vvBitMap)
      Call vnst.Write(vvBitMap(iY))
   Next
End Function


リトルエンディアンとは?

ビットマップファイルでは画像サイズやファイルサイズなど数値を出力する場合、リトルエンディアンで出力します。これは、複数バイトで表現される整数を下位バイトから順に並べる 方式となります。

例えば 16 進数で 8 桁となる数値を 4 バイトで出力するには、次のようになります。


16 進数 → 10 進数変換

リトルエンディアンのようにバイナリーファイルを扱う場合 16 進数を 10 進数に変換することがあります。10 進数 → 16 進数では Hex という関数があるのですが、逆の関数はありません。そこで関数 xHexToDec を作成します。

Private Function xHexToDec(sHex As String) As Long
   '16 進数を 10 進数に変換
   xHexToDec = xVirtualNumber2Dec(sHex, "0123456789ABCDEF")
End Function

関数は 2 段階になっています。xVirtualNumber2Dec では、引数 vsNumChrList で表現された“仮想的な進数”を 10 進数へ変換する関数 になっています。16 進数からの変換だけであればもっとシンプルに記述できますが、n 進数で使える汎用的な関数としています。

Private Function xVirtualNumber2Dec(ByVal vsNum As String, ByVal vsNumChrList As String) As Long
   'psNumChrListで構成されるLen(psNumCheList)進数のpsNumを10進数に変換
   Dim iNum As Integer
   Dim iTgetNum As Integer
   Dim sTgetNum As String
   Dim sNum As String
   Dim lResult As Long
   Dim iMinus As Integer
   Dim i As Integer
   Dim iNumChrList As Integer

   sNum = vsNum
   If Left(sNum, 1)="-" Then
      sNum = Right(sNum, Len(sNum) - 1)
      iMinus = -1
   Else
      iMinus = 1
   End If
   iNum = Len(sNum)
   iNumChrList = Len(vsNumChrList)
   lResult = 0
   For i = 1 To iNum
      sTgetNum = Mid(sNum, i, 1)
      iTgetNum = InStr(1, vsNumChrList, sTgetNum) - 1
      lResult = lResult + iTgetNum * iNumChrList ^ (iNum - i)
   Next i

   xVirtualNumber2Dec = lResult * iMinus
End Function


前回 QR コードの作画 前回


2025/11/30

QR コードの作画:#5)画像データの変換

今回はビットマップの作成で一番重要な画像データを作成する関数 xBMP_MakePictureData を作成します。少し長い関数になりますので、ポイントとなる点を順にまとめます。最後に関数全体を掲載していますので、そちらを参照しながら読み進めてください。


関数の機能

この関数の機能は QR コードの論理データからビットマップファイルに設定する画像データブロックを作成することですが、データブロックのサイズ(バイト数)を事前に把握する役割もあります。前回 記載しましたが、ビットマップを出力する際に、画像データブロックより先に、ヘッダブロック出力が必要だったためです。

そこで関数のインターフェースは次のように定義します。

◇ 引数

1 vabQR Variant QR コードの論理データ
(Boolean 型の 2 次元配列)
2ravBitMap  Variant (戻り値)画像データ(Variant 型の配列)
1要素に1行分の画像データを保持

ravBitMap で渡される変数は、前回 紹介したメインルーチンで行数分の配列として宣言されていました。

      ReDim avBitMap(UBound(vabQR, 2))     '保存エリア確保
      Call xBMP_MakePictureData(vabQR, avBitMap)

この変数の各要素に 1 行ごとの画像データを格納します。1行分の画像データは1行を構成する Byte 型配列とします。保持仕様を図式化すると次のようになります。


1行分のバイト数

白黒ビットマップの場合、横幅のピクセル数が必要なビット数となります。これをバイトに変換するのですが、エンコード仕様により、4 バイト単位に調整する必要がありました。

具体的には、以下の演算でバイト数を算出して、保存エリアを確保しています。

   '初期化(1行分の保存に必要なエリアを確保)
   iByte = UBound(vabQR, 1) + 1    '画像の幅
   iByte = (iByte + 7) \ 8                       '必要なビット数(バイト単位)
   iByte = ((iByte + 3) \ 4) * 4             '1行は4バイト単位
   ReDim abyLine(iByte - 1)


1行分のデータ作成

ビットマップは画像の下から上にエンコードする仕様でした。これを実現しているのが以下の For ループで、Step が -1 となっています。

   For iY = UBound(vabQR, 2) To 0 Step -1     '行は下から上

ループ内ではまず、1 行分のワークエリアである abyLine を初期化しています。初期化で全要素をいったん 0 にしておき、必要な要素だけ値をセットしようという算段です。

      '1行分のワークエリアの初期化
      iByte = 0
      ReDim abyLine(UBound(abyLine))     '配列内初期化

コード内のコメントの ① の部分は、1 行分のQR コード論理データを順に処理しています。処理はサブルーチン SetBit で行い、8 ビット分たまったら、WriteByte サブルーチンでバイトに変換して、1 行分のワークエリアに格納しています。

画像の幅が 8 の倍数ではないとき、余ったビットを格納するために ② の処理を実行しています。


関数全体

最後に関数 xBMP_MakePictureData の全体を掲載します。

%REM
QR コードの論理データ(Boolean 型の 2 次元配列)から画像データブロックを作成します。

◆ 引数
   vabQR Boolean QR コードの論理データ(Boolean 型の 2 次元配列)
   ravBitMap Variant 画像データ(配列の各要素は1行分の画像データ(Byte 配列))
%END REM

Private Function xBMP_MakePictureData(vabQR As Variant, ravBitMap As Variant) As Boolean
   Dim iY As Integer
   Dim iX As Integer
   Dim iB As Integer                 'ビットカウンタ
   Dim abBit() As Boolean    '1バイト分を保存するワークエリア
   Dim bBit As Boolean
   Dim byByte As Byte
   Dim iByte As Integer         '1行のバイト数(カウンタ)
   Dim abyLine() As Byte     '1行分のワークエリア(バイト数分の配列)
   Dim iLine As Integer

   '初期化(1行分の保存に必要なエリアを確保)
   iByte = UBound(vabQR, 1) + 1    '画像の幅
   iByte = (iByte + 7) \ 8                       '必要なビット数(バイト単位)
   iByte = ((iByte + 3) \ 4) * 4             '1行は4バイト単位
   ReDim abyLine(iByte - 1)

   '1行ずつ順に処理
   iLine = 0
   ReDim abBit(7)     '1バイト分のビット格納エリアのリセット
   iB = 0
   For iY = UBound(vabQR, 2) To 0 Step -1     '行は下から上
      '1行分のワークエリアの初期化
      iByte = 0
      ReDim abyLine(UBound(abyLine))     '配列内初期化

      '① 画像の出力(1ピクセル毎)
      For iX = 0 To UBound(vabQR, 1)
         bBit = vabQR(iX, iY)
         GoSub SetBit
      Next

      '② 1バイトに満たないビットがあればバイトに変換
      If iB > 0 Then GoSub WriteByte

      '戻り値配列にセット
      ravBitMap(iLine) = abyLine
      iLine = iLine + 1
   Next

   Exit Function

SetBit:     '1ビットを格納
   abBit(iB) = bBit
   iB = iB + 1
   '1バイト分たまったら格納
   If iB = 8 Then GoSub WriteByte

   Return

WriteByte:     '1バイトを格納
   byByte = 0
   For iB = 0 To 7
      If abBit(iB) = False Then     '白黒反転
         byByte = byByte + 2 ^ (7-iB)
      End If
   Next

   '1バイト書き込み
   abyLine(iByte) = byByte
   iByte = iByte + 1

   '1バイト分のビット格納エリアのリセット
   ReDim abBit(7)
   iB = 0

   Return
End Function


次回の予定

これでビットマップ作製に必要な情報がそろいました。次回は、バイナリファイルに出力する部分を作成します。


前回 QR コードの作画 前回


2025/11/29

QR コードの作画:#4)作画ライブラリ作成開始

今回からは、いよいよ QR コードの作画(ビットマップファイルの作成)するためのコーディングを開始ます。


QR コードライブラリの準備

まず、QR コードの論理データ(Boolean 型の 2 次元配列)へ変換は、連載『ノーツで QR コード』で作成したスクリプトライブラリ lsQRCode を使用します。連載では、元となった Excel VBA のコードを逐次修正する形で紹介していました。ライブラリの中身を知りたい方は連載をご確認ください。完成したスクリプトライブラリがあればよいという方は MISC Market で無償公開しています。以下のリンクよりダウンロードください。

QR コード変換ライブラリ for LotusScript


◇ QR コードの論理データについて

lsQRCode ライブラリが出力する QR コードの論理データについて補足します。

変数宣言は Boolean 型の 2 次元配列となっており、1 次元目が幅で、2次元目が高さとなります。画像サイズの取得方法は次の通りです。

横幅 UBound(vabQR, 1) + 1
高さ UBound(vabQR, 2) + 1

要素番号が 0 始まりなので + 1 しています。数と要素番号を混同しないように注意しましょう。


作画ライブラリの作成

まず、新規で作成したデータベースに先ほどのライブラリ lsQRCode を配置します。

続いて、新しいライブラリ lsDrawQRcode を新規作成します。このライブラリが、QR コードを作画するライブラリとなります。lsQRCode が出力した QR コードの論理データから QR コードのビットマップファイル作成するコードを記述することになります。

ライブラリ lsQRCode  を呼び出し、NotesSession を初期化した状態からスタートとします。

Option Declare
Use "lsQRcode"     ' QR コードの論理データ(Boolean 型の 2 次元配列)変換

Private xns As NotesSession

Sub Initialize
   Set xns = New NotesSession
End Sub


メインルーチンの処理

ビットマップファイルを出力するには、バイナリデータをファイルに書き込む必要があります。その操作は NotesStream クラスを使用するのが便利です。書き込みには Write メソッドを使用するのですが、ファイルを前から順に書き込む必要があります。

ビットマップの仕様には QR コードのサイズにより可変となる項目があります。画像の幅と高さは QR コード論理データ配列の要素数から取得できますが、画像データのサイズ(バイト数)は不明です。事前に算出しておかないと、すべての項目が埋められません。

ブロック 項目
ファイルヘッダ ファイルサイズ
情報ヘッダ 画像の幅、画像の高さ、画像データサイズ
画像データ ファイルサイズ、画像データサイズを知るために必要

そこで、今回作成するメインルーチンでは、まず、画像データ部を生成する関数を実行しています。これで、ファイルヘッダ、情報ヘッダに設定する値が決定できます。


メインルーチンの作成

作成する関数インターフェースは次の通りです。

◇ 引数

1 vabQR Variant QR コードの論理データ
(Boolean 型の 2 次元配列)
2vsFP_BMP  String 出力するファイル名

◇ 戻り値

Boolean  ファイル出力ができたら True


コードの全体は次の通りです。

%REM
QR コードの論理データ(Boolean 型の 2 次元配列)からビットマップファイルを作成します。

◆ 引数
   vabQR    Boolean    QR コードの論理データ(Boolean 型の 2 次元配列)
   vsFP_BMP   String    フルパスで指定されたファイル名

◆ 戻り値 Booelan
   ファイルが作成出来れば True
%END REM

Public Function DrawQR_BMP(vabQR As Variant, ByVal vsFP_BMP As String) As Boolean
   Dim nst As NotesStream
   Dim avBitMap As Variant    ’
画像データ
   Dim s As String

   On Error GoTo Err_General

   Set nst = xns.CreateStream()
   If nst.Open(vsFP_BMP, "binary") Then
      '画像データ部の変換
      ReDim avBitMap(UBound(vabQR, 2))     '保存エリア確保
      Call xBMP_MakePictureData(vabQR, avBitMap)

      'ファイルの中身をいったん削除
      Call nst.Truncate()

      '① ファイルヘッダの出力

      Call xBMP_WriteFileHeader(nst, avBitMap)

      '② 情報ヘッダの出力
      Call xBMP_WriteInfoHeader(nst, vabQR, avBitMap)

      '③ カラーパレットの出力
      Call xBMP_WriteColorPallet(nst)

      '④ 画像データの出力
      Call xBMP_WritePictureData(nst, avBitMap)

      '保存
      Call nst.Close()
      '戻り値セット
      DrawQR_BMP = True
   End If

Exit_Func:
   Exit Function

Err_General:
   DrawQR_BMP = False

   s = "DrawQR_BMP でエラーが発生しました。" & Chr(10)
   s = s & Error$ & " (Err = " & CStr(Err) & ", Erl = " & CStr(Erl) & ")"
   MsgBox s, 16, "DrawQR_BMP"

   Resume Exit_Func
End Function

① ~ ④ までがビットマップデータの各ブロックをファイル出力する関数です。事前に出力データがそろっているので、ファイルフォーマットに従い出力させるだけの役割となります。


次回の予定

メインルーチンからコールしているサブ関数の詳細は次回以降に順次紹介します。次回は、画像データ部を生成する関数 xBMP_MakePictureData を紹介します。


前回 QR コードの作画 次回



2025/11/26

QR コードの作画:#3)ビットマップのエンコード仕様

前回は、ビットマップデータが 4 つのブロックに分かれていることを紹介しました。今回は各ブロックの詳細な仕様と画像データのエンコードについてまとめます。 


ファイルヘッダ

ファイルヘッダの構造は単純で、14 バイトの固定長となっています。ブロック内は4つの役割に分割されています。


Byte 数
役割 説明
2 ファイルタイプ ビットマップを表す固定の文字列
4 ファイルサイズ ファイル全体のサイズ(バイト数)
4 予約領域 固定で 0 を設定
4 画像データの開始位置 ファイルの先頭からのバイト数を指定


◇ ファイルタイプ

ファイルタイプには 0x42 と 0x4D を指定します。これは文字に変換すると "B" と "M" で BitMap ファイルであることを表します。ファイルを読み込むプログラムではこの最初の 2 バイトでこのファイルはビットマップであると判定できるということですね。

別の記事『DXL Step-by-Step:#13)イメージの形式とサイズの取得』で PNG、GIF、JPEG のフォーマットについて解説しましたが、どのフォーマットも決められた位置にフォーマットを表す文字(値)がセットされています。後続のフォーマットがまねているので、ビットマップが画像ファイルの元祖って感じがしますね。


◇ ファイルサイズ

ビットマップファイル全体のバイト数を指定します。よって、この項目は画像データとして出力データ量が明確になってからでないと決定できない項目となります。


◇ 予約領域

今回利用しない項目です(何に使うか不明)。すべて 0x00 をセットすれば OK です。


◇ 画像データの開始位置

前回記載したように、白黒のビットマップではヘッダ領域のサイズは固定となり、62 バイトでした。ですので、 最後の項目の開始位置には 0x3E、0x00、0x00、0x00 を指定します。


情報ヘッダ

情報ヘッダも固定長で、40 バイトで構成されます。このエリアには、ビットマップファイルの画像フォーマットに関する情報が集約されています。色数や画像の幅や高さだけでなく、解像度の項目もあります。また、圧縮に関する項目もあるようなので、ビットマップって無圧縮だけではないようですね。

今回は白黒の QR コードを前提にしますので、情報ヘッダは次の通りとなります。赤い部分が画像に応じて可変になる部分です。


Byte 数
役割 説明
4 ヘッダサイズ 40 byte
4 画像の幅 ピクセル数
4 画像の高さ ピクセル数
2 プレーン数 1 を指定
2 色数(ビット数) 1 : 白黒、8 : 256色、24 : True Color など
4 圧縮形式
4 画像データサイズ 画像データブロックの byte 数
4 水平解像度
4 垂直解像度
4 パレットの色数 0 で色数から算出
4 重要色数 正確に表示すべき色数(パレットの前から)


カラーパレット

256 色以下のビットマップで存在するブロックとなります。パレットというだけあって、ビットマップ内の色番号を何色で表示するか指定する項目です。4 byte で 1 パレットの色を表します(R G B で 1 byte、予備 1 byte)。

今回は色数が 1 bit = 2 色なので、8 バイトの固定長となります。事例では、1 色目が黒で、2 色目が白としています。


画像データ

◇ エンコードの流れ

まず、画像データブロックの全体像について整理します。エンコードの基本仕様として、画像の下から上に変換するのが一般的です。

情報ヘッダの画像の高さにマイナス値を設定すると上から下にエンコードする仕様が定義されているようなのですが、画像ビューアが対応していないなど一般的ではないこともあるそうで、今回は利用しません。


◇ 1行分のエンコード

次に1行分のエンコード仕様についてまとめます。

今回色数は 1 bitなので画像は 2 進数として表現できます。1行分の各ピクセルを、白は1、黒は0で並べます。

1 行分のブロックは 4 バイトの倍数で表現する仕様なので、余った bit には 0 をセットします。これを 8 bit(= 1 byte)毎にファイルに書き込むという流れになります。


次回の予定

ビットマップの構造とエンコード仕様がわかったので、次回からはいよいよコーディングを開始します。


前回 QR コードの作画 次回


2025/11/25

QR コードの作画:#2)白黒ビットマップの構造

まずは、ビットマップファイルの構造を整理します。


ビットマップと画像フォーマット

画像を構成する各点(ピクセル)の色をそのまま記録したファイル形式です。非圧縮が基本的となるので画像の品質は劣化しませんが、ファイルサイズは大きくなりがちです。Windows 3.0 からは仕様も安定し一気に普及しました。当時は PC の能力が低かったこともあり、非圧縮でそのまま読み込めるビットマップ形式が扱いやすかったというからだそうです。

Windows の GUI 向上に伴い、8 bit(256色)、16 bit(High Color)、24 bit(True Color)と色数が拡張されたました。ただ、Web の登場に伴い PNG(可逆圧縮)や JPEG(非可逆圧縮)などファイルサイズが比較的小さいフォーマットが主流となりました。

ビットマップは Windows で扱える画像フォーマットの元祖のような存在ですので、仕様がシンプルです。特に今回作成する画像は QR コード、色数は 1 bit(2色)となります。もっとも単純な仕様に限定してまとめます。


ビットマップファイルの構造

ビットマップファイルは次の4つのブロックで構成されています。

サイズ ブロック 説明
14 byte ファイルヘッダ ビットマップファイルの総合的な管理情報
40 byte 情報ヘッダ 画像の大きさ、色数など画像フォーマットの情報
8 byte カラーパレット カラーパレット情報
可変長 画像データ 画像の各ピクセルを 16 進数に変換して記録

次のようなシンプルなビットマップ画像を例にします。

画像ファイルをバイナリエディタで開きブロックごとに色分けすると次のようになります。


次回の予定

今回はビットマップを構成する 4 つのブロックについて説明しました。次回は各ブロックの仕様についてまとめます。


前回 QR コードの作画 次回


2025/11/24

QR コードの作画:#1)開発のきっかけ

開発のきっかけ

先日、例によって(?)モノ言う読者から『作成中のノーツアプリで QR コードを表示したい!』と連絡がありました。このブログで QR コードの作成手法を過去にまとめたことを知っての依頼と直感しました。しめしめと思い当時のライブラリを引っ張り出し紹介しました。

ところが、残念ながら気に入ってもらえなかったのです...

理由は QR コードが大きすぎるからでした。サンプル然とさせるためもあり QR コードの 1 マスを 5 x 5 Pixel のイメージリソース画像を組み合わせで実現していたためです。

イメージリソースの画像を 1 x 1 Pixel と小さくするだけで対応できると思ったのですが、残念なことにうまくいきません。

DXL で QR コードを書いたときには、イメージリソース間に何もなく、イメージリソースの羅列としていました。ところが、編集モードにして間にカーソルを入れると 10 point と表示され、これが原因で行間があいてることがわかりました。試しにノーツクライアントで全体を選択し、フォントサイズを小さくすると、きれいに表示されます。

整えた文書を DXL 化して、元の DXL と比較しても全く同じだったことから DXL をインポート(保存)する際の問題と判断しました。

これでは DXL をいくら駆使しても解決できないですね...


ないなら作ってしまえ!

対策として思いついたのが、LotusScript で画像ファイルを自力作成する方法です。ビットマップファイルであれば非圧縮でアルゴリズムの簡単なはず。白黒に限定すれば何とかなるかも...と考えました。

ということで、ビットマップの仕様を調べながら、QR コードの画像をビットマップで作成するライブラリの作成に着手しました。


今後の予定と顛末

今回の連載『QR コードの作画』では、ビットマップファイルのファイル構造の紹介とQR コード画像を作成する LotusScript ライブラリを紹介します。せっかく時間をかけて調べたこと、開発したプログラムを無駄にしたくないのでまとめます。わざわざバイナリーで画像ファイルなんて作らんでも...という方は読み飛ばしてください (^ω^;)。

ちなみに、モノいう読者からのリクエストはというと、対応に時間がかかったこともあって、別の方法で実現することとなりました。仲間内の同業者にさらわれることになってちょっと悔しいです...(採用した手法については、ブログ Domino Lab の以下の記事を参照ください)。

Notes でQR コードを作成する

この方法は Java を使った方法となります。私は Java 使いではないこともあって、LotusScript 単独での実現を目標とします。Java が利用できない環境の場合、参考になるかもしれませんね。

ビットマップファイルの作成までは何とか完成したのですが、まさか、最後に大どんでん返しを食らうとは...


QR コードの作画 次回