2024/02/03(土) [n年前の日記]
#2 [basic] FreeBASICでexeに画像バイナリを含めたい
FreeBASICで生成した実行形式ファイル(exeファイル)の中に、画像のバイナリデータを含めてしまって、それをウインドウに描画したい。
環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。
FreeBASIC の公式掲示板で検索しまくってみたところ、以下のような方法があるらしい。
環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。
FreeBASIC の公式掲示板で検索しまくってみたところ、以下のような方法があるらしい。
- Windows限定になるけれど、リソースファイルに画像データを含めてしまう。BASICから FindResource() や LoadResource() 等でアクセスする。
- bin2bas と言うツールを使って、バイナリをBASICの配列の形にする。ただ、この方法はファイルサイズ制限があるらしい。
- FreeBASICはアセンブラの記述も盛り込めるので、asm - end asm や .incbin を使ってバイナリを内包してしまう。
- バイナリをオブジェクトファイルに変換して、リンカで結合して内包させる。
◎ リソースファイルを利用する方法 :
Windows限定になってしまうけれど、リソースファイルを利用する方法を試してみた。
バイナリ(リソース)にアクセスするところまでは FreeBASIC の標準機能だけで処理できるけれど、メモリ上の画像バイナリをFreeBASICの描画に利用できるように変換するあたりは、FreeBASIC標準機能だけでは難しいらしい。今回は FBImage という画像処理ライブラリの LoadRGBAMemory() を使わせてもらって、メモリ上の画像バイナリを変換することにした。
_mieki256's diary - FreeBASICで画像描画
_FBImage static Win/Lin 32/64-bit - freebasic.net
サンプルソースを書いてみた。一式は以下。
_take1_png_in_rc.zip
一応ソース群も貼っておく。
_loadpnginmemory.bas
リソースファイルは以下。"pngimage" というリソース名、データ種類は RCDATA、png画像ファイル名は "image_png.png" と記述してある。
_resource.rc
使用画像は以下。RGBA 32bit のpng画像。50,344 byte。
_image_png.png
コンパイルは以下。 _mk.bat も一応書いておいた。
生成された loadpnginmemory.exe を実行すると、以下のようにウインドウが開いて、png画像が描画される。
少し解説。
リソースファイルでpng画像を含めるように指定する際は、RCDATA という種類を指定すると良さそう。他の種類として BMP や ICON もあるけれど、ググった感じでは、どれにも当てはまらない時は RCDATA を指定することが多いように見えた。
リソースにアクセスする際は以下の関数を使う。
バイナリ(リソース)にアクセスするところまでは FreeBASIC の標準機能だけで処理できるけれど、メモリ上の画像バイナリをFreeBASICの描画に利用できるように変換するあたりは、FreeBASIC標準機能だけでは難しいらしい。今回は FBImage という画像処理ライブラリの LoadRGBAMemory() を使わせてもらって、メモリ上の画像バイナリを変換することにした。
_mieki256's diary - FreeBASICで画像描画
_FBImage static Win/Lin 32/64-bit - freebasic.net
サンプルソースを書いてみた。一式は以下。
_take1_png_in_rc.zip
一応ソース群も貼っておく。
_loadpnginmemory.bas
' リソースにアクセスするために windows.bi を使う #include "windows.bi" ' 画像読み込み用ライブラリ FBImage を使う #include once "FBImage.bi" ' カレントディレクトリを exeファイルのある場所にする chdir exepath() ' リソースにアクセスするための名前 Const res_name = "pngimage" Dim As HRSRC hResInfo Dim As HGLOBAL hResData Dim As ubyte Ptr resPtr Dim As DWORD resSize ' リソースを探す hResInfo = FindResource(NULL, strptr(res_name), RT_RCDATA) If hResInfo = Null Then Print "Error : Not found resource" : end End If ' リソースを読み込み hResData = LoadResource(0, hResInfo) If hResData = Null Then Print "Error : Not load resource" FreeResource(hResData) : end End If ' リソースのポインタを取得 resPtr = CPtr(ubyte Ptr, LockResource(hResData)) If resPtr = Null Then Print "Error : Not get resource pointer" FreeResource(hResData) : end End If ' リソースのサイズを取得 resSize = SizeofResource(NULL, hResInfo) If resSize = 0 Then Print "Error : Can not get resource size" FreeResource(hResData) : end End If ' デスクトップ解像度を指定 Dim As Integer scrw = 512 Dim As Integer scrh = 288 ' ウインドウサイズと色深度(bpp)を指定 screenres scrw, scrh, 32 ' メモリ上にある画像データを読み込む。ポインタとバイト数を渡す var img = LoadRGBAMemory(resPtr, resSize) ' リソースを解放 FreeResource(hResData) ' 画像を描画。RGB=(255, 0, 255) のピクセルは透明色として扱う Put (16, 16), img, TRANS ' キー入力があるまで待ち続ける sleep ' 画像を使い終わったので破棄 ImageDestroy img
リソースファイルは以下。"pngimage" というリソース名、データ種類は RCDATA、png画像ファイル名は "image_png.png" と記述してある。
_resource.rc
pngimage RCDATA "image_png.png"
使用画像は以下。RGBA 32bit のpng画像。50,344 byte。
_image_png.png
コンパイルは以下。 _mk.bat も一応書いておいた。
fbc loadpnginmemory.bas resource.rc
生成された loadpnginmemory.exe を実行すると、以下のようにウインドウが開いて、png画像が描画される。
少し解説。
リソースファイルでpng画像を含めるように指定する際は、RCDATA という種類を指定すると良さそう。他の種類として BMP や ICON もあるけれど、ググった感じでは、どれにも当てはまらない時は RCDATA を指定することが多いように見えた。
リソースにアクセスする際は以下の関数を使う。
- FindResource() ... リソースを検索する。
- LoadResource() ... リソースを読み込む。
- LockResource() ... リソースのアドレスを取得する。
- SizeofResource() ... リソースのサイズを取得する。
- FreeResource() ... 読み込んだリソースを解放する。
◎ bin2basを利用する方法 :
bin2bas というツールがあるらしい。バイナリファイルをBASICの ubyte (Unsigned byte)配列にして出力してくれるツールだそうで…。BASICソースの中に、配列としてバイナリを含めてしまえば、たしかに目的は果たせそう。
FreeBASICの掲示板で bin2bas のソースが貼ってあった。
_Bin2Bas - freebasic.net
例えば、test.bin という、48 byte しかないテストバイナリファイルを変換してみると、以下のような内容になる。
test.bi
test.bas
しかし、この方法は問題がある…。バイナリファイルのサイズが小さい場合はこれでもイケるけれど、大きいファイルサイズになると FreeBASIC がコンパイルできずにクラッシュしてしまう。fbc.exe が .asm を途中まで生成したところで終了してしまうので困ってしまった。Windowsのイベントビューアを確認したら、fbc.exe がクラッシュしまくっていた。
以下の報告によると、20,000 byte 前後に限界値があるらしい。ただ、DATA文を利用した形に書き直せば、もう少し大きいサイズを含めることができる模様。
_fbc.exe has stopped working - freebasic.net
_fbc can't compile large array ! - freebasic.net
そんなわけで、Python を使って、DATA文の形にして標準出力に出力するスクリプトを書いてみた。先に出力結果を貼っておく。
DATA を読み込んで、配列の中に格納していく形になっている。これを .bas の最初のほうで include しておけば、その行以降で、この配列を参照できるはず。
変換ツール bin2bas.py は以下。CC0 / Public Domain ってことで。
_bin2bas.py
使い方は以下。
この bin2bas.py を利用して、ubyte配列を得て、exe の中に含めてみる。
ファイル一式は以下。
_take2_bin2bas.zip
ソース群も一応貼っておく。
元画像、image_png.png は以下。
_image_png.png
bin2bas.py を使って、image_png.png を image_png.bas に変換。
_image_png.bas
basicのソースは以下。
_loadpngbin2bas.bas
リソースファイルを利用する版に比べて、かなり短くなった。
以下を打って、.basをコンパイル。
実行すると以下のようにウインドウが表示される。pngが表示できている。
しかし、この方法はファイルサイズ制限があるあたりが…。必ず動作するソースになってくれるのかどうか、そのあたりは微妙だなと…。
FreeBASICの掲示板で bin2bas のソースが貼ってあった。
_Bin2Bas - freebasic.net
- 上記のページで紹介されているソースをコピーして、bin2bas.bas というファイル名でソースを保存する。
- fbc bin2bas.bas と打てば bin2bas.exe が生成できる。
- 使い方は、bin2bas.exe INPUT_FILE。bin2bas.exe hoge.png と打てば、hoge.bi と hoge.bas が生成される。
例えば、test.bin という、48 byte しかないテストバイナリファイルを変換してみると、以下のような内容になる。
bin2bas.exe test.bin
test.bi
extern test_data(0 to 48-1) as ubyte
test.bas
#include once "test.bi" dim shared test_data(0 to 48-1) as ubyte = { _ &h0,&h1,&h2,&h3,&h4,&h5,&h6,&h7,&h8,&h9,&hA,&hB,&hC,&hD,&hE,&hF,&h10,&h11,&h12,&h13,&h14,&h15,&h16,&h17,&h18,&h19,&h1A,&h1B,&h1C,&h1D,&h1E, _ &h1F,&h20,&h21,&h22,&h23,&h24,&h25,&h26,&h27,&h28,&h29,&h2A,&h2B,&h2C,&h2D,&h2E,&h2F }
しかし、この方法は問題がある…。バイナリファイルのサイズが小さい場合はこれでもイケるけれど、大きいファイルサイズになると FreeBASIC がコンパイルできずにクラッシュしてしまう。fbc.exe が .asm を途中まで生成したところで終了してしまうので困ってしまった。Windowsのイベントビューアを確認したら、fbc.exe がクラッシュしまくっていた。
以下の報告によると、20,000 byte 前後に限界値があるらしい。ただ、DATA文を利用した形に書き直せば、もう少し大きいサイズを含めることができる模様。
_fbc.exe has stopped working - freebasic.net
_fbc can't compile large array ! - freebasic.net
そんなわけで、Python を使って、DATA文の形にして標準出力に出力するスクリプトを書いてみた。先に出力結果を貼っておく。
python bin2bas.py -i test.bin
' input file = test.bin Dim As Integer test_bin_data_length = 48 Dim shared test_bin_data(0 To (test_bin_data_length - 1)) As ubyte For i As Integer = 0 To (test_bin_data_length - 1) read test_bin_data(i) Next i DATA _ &H00,&H01,&H02,&H03,&H04,&H05,&H06,&H07,&H08,&H09,&H0a,&H0b,&H0c,&H0d,&H0e,&H0f, _ &H10,&H11,&H12,&H13,&H14,&H15,&H16,&H17,&H18,&H19,&H1a,&H1b,&H1c,&H1d,&H1e,&H1f, _ &H20,&H21,&H22,&H23,&H24,&H25,&H26,&H27,&H28,&H29,&H2a,&H2b,&H2c,&H2d,&H2e,&H2f
DATA を読み込んで、配列の中に格納していく形になっている。これを .bas の最初のほうで include しておけば、その行以降で、この配列を参照できるはず。
変換ツール bin2bas.py は以下。CC0 / Public Domain ってことで。
_bin2bas.py
import struct import argparse def main(): desc = "Cpnvert binary file to basic source" parser = argparse.ArgumentParser(description=desc) parser.add_argument("-i", "--input", help="Input binary file", required=True) args = parser.parse_args() data = [] with open(args.input, "rb") as f: while True: b = f.read(1) if b: data.append(struct.unpack("B", b)[0]) else: break dataname = args.input.replace(".", "_") dataname += "_data" lengthname = dataname + "_length" print("' input file = %s" % (args.input)) print("") print("Dim As Integer %s = %d" % (lengthname, len(data))) print("Dim shared %s(0 To (%s - 1)) As ubyte" % (dataname, lengthname)) print("") print("For i As Integer = 0 To (%s - 1)" % (lengthname)) print(" read %s(i)" % (dataname)) print("Next i") print("") print("DATA _") s = "" cnt = 0 for i in range(len(data)): s += "&H%02x," % data[i] cnt += 1 if cnt % 16 == 0: if cnt == len(data): print(s[:-1]) else: print("%s _" % s) s = "" if s != "": print(s[:-1]) if __name__ == "__main__": main()
使い方は以下。
> python bin2bas.py --help usage: bin2bas.py [-h] -i INPUT Convert binary file to basic source options: -h, --help show this help message and exit -i INPUT, --input INPUT Input binary file
この bin2bas.py を利用して、ubyte配列を得て、exe の中に含めてみる。
ファイル一式は以下。
_take2_bin2bas.zip
ソース群も一応貼っておく。
元画像、image_png.png は以下。
_image_png.png
bin2bas.py を使って、image_png.png を image_png.bas に変換。
python bin2bas.py -i image_png.png > image_png.bas
_image_png.bas
' input file = image_png.png Dim As Integer image_png_png_data_length = 50344 Dim shared image_png_png_data(0 To (image_png_png_data_length - 1)) As ubyte For i As Integer = 0 To (image_png_png_data_length - 1) read image_png_png_data(i) Next i DATA _ &H89,&H50,&H4e,&H47,&H0d,&H0a,&H1a,&H0a,&H00,&H00,&H00,&H0d,&H49,&H48,&H44,&H52, _ &H00,&H00,&H01,&H00,&H00,&H00,&H01,&H00,&H08,&H06,&H00,&H00,&H00,&H5c,&H72,&Ha8, _ ' ... &Hba,&H45,&Hf0,&H3f,&Hac,&H83,&H26,&H35,&H43,&H1b,&H66,&Hed,&H00,&H00,&H00,&H00, _ &H49,&H45,&H4e,&H44,&Hae,&H42,&H60,&H82
basicのソースは以下。
_loadpngbin2bas.bas
' 画像読み込み用ライブラリ FBImage を使う #include once "FBImage.bi" ' ubytes配列の画像データバイナリ #include "image_png.bas" ' カレントディレクトリを exeファイルのある場所にする chdir exepath() ' デスクトップ解像度を指定 Dim As Integer scrw = 512 Dim As Integer scrh = 288 ' ウインドウサイズと色深度(bpp)を指定 screenres scrw, scrh, 32 ' メモリ上にある画像データを FBImage で読み込む。ポインタとバイト数を渡す Dim As Long size = image_png_png_data_length var img = LoadRGBAMemory(@(image_png_png_data(0)), size) ' 画像を描画。RGB=(255, 0, 255) のピクセルは透明色として扱う Put (16, 16), img, TRANS ' キー入力があるまで待ち続ける sleep ' 画像を使い終わったので破棄 ImageDestroy img
リソースファイルを利用する版に比べて、かなり短くなった。
以下を打って、.basをコンパイル。
fbc loadpngbin2bas.bas
実行すると以下のようにウインドウが表示される。pngが表示できている。
しかし、この方法はファイルサイズ制限があるあたりが…。必ず動作するソースになってくれるのかどうか、そのあたりは微妙だなと…。
◎ asmを利用する方法は失敗 :
asm - asm end を記述して、中で .incbin "hoge.png" をする方法はどうだろうと試してみたのだけど、これは失敗に終わった。
_loadpngasm.bas
fbc loadpngasm.bas でコンパイルして、生成された loadpngasm.exe を実行してみたら、クラッシュしかしない…。
ファイルサイズが大き過ぎるとダメなのだろうかと疑って、小さいファイルと差し替えて試してみたけれど、結果は同じで、正常動作しないプログラムになった。
以下を参考にして記述してみたのだけど…。どういう書き方をすればいいのやら。
_Displaying images from files and memory - freebasic.net
_loadpngasm.bas
' 画像読み込み用ライブラリ FBImage を使う #include once "FBImage.bi" #define IMAGEDATA_SIZE 50344 ' #define IMAGEDATA_SIZE 297 ' 画像バイナリをアセンブラの記述で内包する Asm .data .global imgdata imgdata: .incbin "image_png.png" End Asm ' カレントディレクトリを exeファイルのある場所にする chdir exepath() ' デスクトップ解像度を指定 Dim As Integer scrw = 512 Dim As Integer scrh = 288 ' ウインドウサイズと色深度(bpp)を指定 screenres scrw, scrh, 32 ' 画像バイナリのアドレスを取得している? Dim As Any Ptr image Asm push OFFSET imgdata pop [image] End Asm ' メモリ上にある画像データを FBImage で読み込む。ポインタとバイト数を渡す var img = LoadRGBAMemory(image, IMAGEDATA_SIZE) ' 画像を描画。RGB=(255, 0, 255) のピクセルは透明色として扱う Put (16, 16), img, TRANS ' キー入力があるまで待ち続ける sleep ' 画像を使い終わったので破棄 ImageDestroy img
fbc loadpngasm.bas でコンパイルして、生成された loadpngasm.exe を実行してみたら、クラッシュしかしない…。
ファイルサイズが大き過ぎるとダメなのだろうかと疑って、小さいファイルと差し替えて試してみたけれど、結果は同じで、正常動作しないプログラムになった。
以下を参考にして記述してみたのだけど…。どういう書き方をすればいいのやら。
_Displaying images from files and memory - freebasic.net
[ ツッコむ ]
以上です。