mieki256's diary



2024/02/03() [n年前の日記]

#2 [basic] FreeBASICでexeに画像バイナリを含めたい

FreeBASICで生成した実行形式ファイル(exeファイル)の中に、画像のバイナリデータを含めてしまって、それをウインドウに描画したい。

環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。

FreeBASIC の公式掲示板で検索しまくってみたところ、以下のような方法があるらしい。

リソースファイルを利用する方法 :

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
' リソースにアクセスするために 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画像が描画される。

loadpnginmemory_ss.png


少し解説。

リソースファイルでpng画像を含めるように指定する際は、RCDATA という種類を指定すると良さそう。他の種類として BMP や ICON もあるけれど、ググった感じでは、どれにも当てはまらない時は RCDATA を指定することが多いように見えた。

リソースにアクセスする際は以下の関数を使う。
  • FindResource() ... リソースを検索する。
  • LoadResource() ... リソースを読み込む。
  • LockResource() ... リソースのアドレスを取得する。
  • SizeofResource() ... リソースのサイズを取得する。
  • FreeResource() ... 読み込んだリソースを解放する。
このあたりの関数は、FreeBASICインストールフォルダ/inc/win/winbase.bi 内でプロトタイプ宣言されているので、引数の型が違うと怒られた時は winbase.bi を確認してみると問題個所が分かるかもしれない。

bin2basを利用する方法 :

bin2bas というツールがあるらしい。バイナリファイルをBASICの ubyte (Unsigned byte)配列にして出力してくれるツールだそうで…。BASICソースの中に、配列としてバイナリを含めてしまえば、たしかに目的は果たせそう。

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が表示できている。

loadpngbin2bas_ss.png


しかし、この方法はファイルサイズ制限があるあたりが…。必ず動作するソースになってくれるのかどうか、そのあたりは微妙だなと…。

asmを利用する方法は失敗 :

asm - asm end を記述して、中で .incbin "hoge.png" をする方法はどうだろうと試してみたのだけど、これは失敗に終わった。

_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

以上です。

過去ログ表示

Prev - 2024/02 - Next
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project