mieki256's diary



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

#1 [basic] FreeBASICでスクリーンセーバを作成する

FreeBASICを使ってWindows用スクリーンセーバを作成したい。環境は、Windows10 x64 22H2 + FreeBASIC 1.10.1。

FreeBASICの公式掲示板(?)で、FreeBASIC Screensaver Kit というものを公開してくれてる方が居た。昔公開されていたものを、比較的新しい FreeBASIC でも動くように修正してアップロードしてくれたらしい。ありがたや。

_freeBASIC Screensaver Kit Updated - freebasic.net
_FreeBASIC Screensaver Kit - freebasic.net

これを使わせてもらえば、比較的簡単にスクリーンセーバを作れるのでは…?

サンプルを試用してみる :

前述のページ内の「Download it here」というリンクをクリックすると Dropboxのページが開くので、ページ右上のあたりにある、下向きの矢印(ダウンロード)をクリックすれば、関連ファイル一式が入っている FB_ScreenSaverKit Updated.zip をダウンロードできる。

消えると怖いので、一応ここにも置かせてもらいます。元になった最初のファイル群はリンク切れになってて入手できない状態だし…。こういうのはあちこちに置いとかないと後から入手できなくなるので…。

_fb_screensaverkit_updated.zip

解凍すると、以下のファイルが入っている。

  • FB_ScreenSaverKit.bas ... スクリーンセーバ作成時に必要になる処理をまとめたファイル
  • Mambazo Doodle.bas ... スクリーンセーバの描画処理部分。これを改造して自分なりのスクリーンセーバを作る。
  • Mambazo Doodle.exe ... スクリーンセーバの実行形式。拡張子を .scr にすればスクリーンセーバになる。
  • Mambazo Doodle.rc ... リソースファイル。スクリーンセーバの設定ダイアログのレイアウトが記述してある
  • Mambazo Doodle.xml ... 謎。リソースファイルから内包するように指定されていた

以下を打てば、FreeBASICでコンパイルして、Mambazo Doodle.exe を生成できる。
fbc "Mambazo Doodle.bas" "Mambazo Doodle.rc" -s gui
  • 「-s gui」は、「GUIアプリとして生成せよ」という指定。これをつけないと、生成したアプリを実行するたびにDOS窓が開いてしまう。

.exe が生成されたら、コピーして拡張子を .scr にする。Windows用のスクリーンセーバは拡張子が .scr だけど、実態は .exe を .scr にリネームしたものなので…。
copy "Mambazo Doodle.exe" "Mambazo Doodle.scr"


エクスプローラ等で、Mambazo Doodle.scr を右クリックして「Test」を選べば、フルスクリーンで表示される。以下が実行した際のスクリーンショット。長いので途中で映像を飛ばしてるけど、雰囲気は伝わるかと…。




これは余談だけど、このスクリーンセーバ、描画処理部分が一切 sleep せずに全力でループをブンブン回して描画するので、CPU使用率が凄いことになる…。適切なタイミングで sleep を入れるように改造したほうが良さそう。

サンプルを少し修正 :

前述のサンプルを見る限り、たしかに FreeBASIC でスクリーンセーバを作れそうだなと分かった。

ソースを眺めてみたけれど、描画処理を担当する Mambazo Doodle.bas は、以下のような簡素な作りになっていた。
  • FB_ScreenSaverKit.bas を include して、
  • StartScreenSaver() を呼んで、
  • SaverInfo.IsClosing が TRUE にならない限り、ひたすら描画に専念する。

FB_ScreenSaverKit.bas の中で書かれている StartScreenSaver() が肝っぽいと眺めてみたけれど、以下のような処理をしている。
  • コマンドラインオプションを判別して、設定モード(/c)、プレビュー表示モード(/p xxxx)、フルスクリーン表示モード(/s) で処理を分けてる。ただ、この実装はちょっとバグがある。それについては後述。
  • 設定モードの時は、リソースファイル Mambazo Doodle.rc に記述されてるダイアログを表示して、ダイアログが閉じられたら終了する。それ以外は何もしない。
  • プレビュー表示モード、フルスクリーン表示モードの時は、ウインドウを新規作成する。
  • そのウインドウで、キー入力やマウス操作があったか等を調べて、何か操作されてたらウインドウ破棄メッセージを送る。
  • タイマーを設定して、一定時間毎に、bitmapバッファの内容をウインドウにコピーしている。このbitmapバッファに、前述の描画処理担当部分(Mambazo Doodle.bas)が、せっせと描画しているらしい。
  • ウインドウの破棄メッセージが来たら、SaverInfo.IsClosing を TRUE にしている。

つまり、描画処理担当と、スクリーンセーバとして画面上に表示されているウインドウ、この2つが別々に存在していて、並列して動いている。
  • メインメモリ上のbitmapバッファに対して、描画処理担当が描画。ウインドウ担当は、そのバッファ内容を、一定の時間間隔で自分のウインドウ内にコピーしている。
  • スクリーンセーバの終了するタイミングは、ウインドウ担当が、SaverInfo.IsClosing を通じて知らせてくれる。描画処理担当は、SaverInfo.IsClosing が TRUE になったら終了処理をする。
そういう作りになっているようだなと…。

コマンドラインオプションの解析にバグがある :

ところで、StartScreenSaver() のコマンドライン解析には、些細なバグがあった。

Windows用のスクリーンセーバは、与えられたコマンドラインオプションによって、以下のように動作を変えないといけない。
  • /c を与えられた場合、設定ダイアログを表示。
  • /p xxxx を与えられた場合、xxxx(ウインドウハンドルを10進数で示した数値) を親とした子ウインドウを新規作成して、その新規作成したウインドウにスクリーンセーバの内容を描画する。
  • /s を与えられた場合、フルスクリーン表示でスクリーンセーバを描画する。
  • 何も与えられてない場合、設定ダイアログを表示。

例えば、エクスプローラ上で .scr を右クリックして「構成」を選んだ際は設定ダイアログが表示されるのだけど、この場合、コマンドラインオプションとしては何も与えられてない状態で .scr が呼ばれている。

しかし、StartScreenSaver() の実装では、設定ダイアログではなく、フルスクリーン表示になってしまう。

そんなわけで、以下のように修正して使うことにした。

Sub StartScreenSaver()
    Select Case LCase(Command(1))
    Case "/c"
        ' Config
        StartConfigDialog()
        End
    Case "/p"
        ' Preview
        Dim As String tmpHWnd = Command(2)
        SaverInfo.IsPreview = TRUE
        If tmpHWND <> "" Then
            SaverInfo.hWndParent = Cast(HWND__ Ptr, CLng(Val(tmpHWnd)))
        End If
        SetupScreenMode()
        ThreadCreate(@WindowThread)
    Case "/s"
        ' Full screen
        SetupScreenMode()
        ThreadCreate(@WindowThread)
    Case Else
        ' No options. Config
        StartConfigDialog()
        End
    End Select
End Sub
  • LCase() は、文字列を小文字化してくれる関数。
  • Command() は、コマンドラインオプションが入っている。何も指定されてなかったら "" が返ってくる。
  • "/p xxxx" が与えられた時、command(1) には "/p" が、command(2) には "xxxx" が入る。

簡単なサンプルを作ってみた :

Mambazo Doodle.bas は綺麗な図形が描かれるけど、その分、どんな処理をしているのかちょっと分かりづらい気もした。

そこで、試しに、簡単な処理をするサンプルを書いてみることにした。環境は、Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。

処理としては、いつものアレ。画面の中をボールが跳ねるヤツ。




アイコン画像その他も含めたファイル一式は以下。

_ssfbscrsample.zip

zip内に入っている build.bat を実行すれば、FreeBASIC でコンパイルできる。やっていることは以下。
fbc ssfbscrsample.bas ssfbscrsample.rc -s gui
copy ssfbscrsample.exe ssfbscrsample.scr

ちなみに、以下を打てば、.exe を作らずに、いきなり .scr を作れる。「-x hoge」で、出力ファイル名を指定できるらしい。
fbc -x ssfbscrsample.scr ssfbscrsample.bas ssfbscrsample.rc -s gui

出来上がった .scr は、以下のフォルダにコピーすれば、スクリーンセーバとして使えるようになる。
  • スクリーンセーバが32bit版アプリで、Windows が 64bit版の場合、C:\Windows\SysWOW64\ にコピー。
  • スクリーンセーバが32bit版アプリで、Windows が 32bit版の場合、C:\Windows\System32\ にコピー。
  • スクリーンセーバが64bit版アプリで、Windows が 64bit版の場合、C:\Windows\System32\ にコピー。

自分が書いた部分/作った画像は、CC0 / Public Domain ってことで。ただ、FB_ScreenSaverKit.bas のライセンスは、ちょっと分からない…。でもまあ、たぶん、自由に使っていいタイプのソレじゃないのかな…。何とか kit を名乗っていて自由に使えなかったら公開してる意味が無いし…。


一応ソースも個別に置いておく。

_build.bat
_fb_screensaverkit.bas
_ssfbscrsample.xml

_ssfbscrsample.bas
#include "fb_screensaverkit.bas"

' use mmsystem
#include "windows.bi"
#include "win/mmsystem.bi"

timeBeginPeriod(1)

Randomize Timer

Const MAX_FPS = 60.0
Const PI = 3.141592653

Dim As Double start_time, prev_time, now_time, next_time, delta, one_frame
Dim As Integer frame_count
Dim As String fps_text = "FPS"

Dim As Integer sw, sh

'Start the Screen Saver
StartScreenSaver

#If 1
' get screen size. width and height
ScreenInfo sw, sh
#Else
'You can also find the width & height of the screen by using SaverInfo.ScrWidth & SaverInfo.ScrHeight
sw = SaverInfo.ScrWidth
sh = SaverInfo.ScrHeight
#Endif

' init work
Dim As Double x, y, dx, dy, r
x = sw / 2.0
y = sh / 2.0
dx = (CDbl(sw) / MAX_FPS) * 0.6
dy = (CDbl(sh) / MAX_FPS) * 0.4
r = sh / 16.0

start_time = Timer
prev_time = start_time
frame_count = 0

' main loop
Do Until SaverInfo.IsClosing = True

    ' get delta
    now_time = Timer
    delta = now_time - prev_time
    prev_time = now_time
    next_time = now_time + (1.0 / MAX_FPS)
    If delta < 0 Then delta = 1.0 / MAX_FPS

    ' count FPS
    If now_time >= start_time Then
        If (now_time - start_time) >= 1.0 Then
            fps_text = Str(frame_count) & " FPS"
            start_time += 1.0
            frame_count = 0
        End If
    Else
        start_time = now_time
    End If
    frame_count += 1

    ' move ball position
    x += (dx * MAX_FPS * delta)
    y += (dy * MAX_FPS * delta)

    If (x <= (r / 2) And dx < 0) Or (x >= (sw - (r / 2)) And dx > 0) Then dx *= -1.0
    If (y <= (r / 2) And dy < 0) Or (y >= (sh - (r / 2)) And dy > 0) Then dy *= -1.0

    If SaverInfo.IsClosing <> True Then
        ' draw start
        ' ScreenLock

        ' clear screen
        ' Line (0, 0)-(sw, sh), Rgb(0, 0, 0), BF
        color RGB(0, 0, 0), RGB(0, 0, 0)
        cls

        ' draw ball
        circle (x, y), r, RGB(255, 0, 0), , , , F

        ' darw FPS
        Draw String ((sw - Len(fps_text) * 8) / 2, 10), fps_text, RGB(255, 255, 255)

        ' draw end
        ' ScreenUnlock
    End If

    ' sleep
    If Timer < next_time Then
        Dim As Double wait_time = (next_time - Timer) * 1000.0
        If wait_time > 0 Then
            sleep wait_time
        End If
    End If
Loop

timeEndPeriod(1)

見ればわかる通り、SaverInfo.IsClosing が TRUE になるまでメインループを回し続けてひたすら描画しかしていない。


_ssfbscrsample.rc
/* Screensaver title */
STRINGTABLE
BEGIN
    1 "FreeBASIC Screensaver"
END

/* icon */
100 ICON "fbscricon.ico"

/* dialog */
FB_SCRNSAVER_ABOUT DIALOGEX 6, 18, 160, 62
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_NOFAILCREATE
 FONT 12,"Arial"
CAPTION "FB Screensaver Sample"
BEGIN
    DEFPUSHBUTTON "OK" IDOK, 55, 40, 50, 14
    CTEXT "FB Screensaver Sample" -1, 0, 5, 160, 8
    CTEXT "Version 1.0" -1, 0, 15, 160, 8
END

1 24 "ssFBscrSample.xml"

リソースファイルの中で、スクリーンセーバ名や、アイコンファイルのファイル名を指定できる。


余談。当初、描画する際に、ScreenLock と ScreenUnlock を呼んでいたのだけど、その2つは画面に見えてるバッファに対して描画する際は有効だけど、メインメモリ上にあるバッファに描画する際は何もしない、という記述を見かけたので今回はコメントアウトしておいた。

問題点 :

たったこれだけでスクリーンセーバが作れてしまうなんて…。素晴らしい。そう思ったものの、これはこれでちょっと問題があるような気もしてきた。

問題その1。描画と表示の同期が、おそらく取れてない。

例えば今回書いたサンプルでは、描画担当の、メインメモリ上のbitmapバッファへの描画は60FPS前後で処理できているけれど。画面に表示されているソレは、かなりガクガクした動きに見える。60FPSで動いてるようには全然見えない。と言うのも、ウインドウ担当は60FPSでバッファ内容をコピーしてきているわけではなく、謎のタイミングでコピーしているので…。

問題その2。状況によっては表示がおかしくなりそう。
  • 描画担当がバッファに描画してる最中に、ウインドウ担当がバッファ内容をコピーしようとしたら、どうなるか。
  • ウインドウ担当がバッファ内容をコピーしてる最中に、描画担当がバッファを書き換えようとしたら、どうなるか。
そう考えると、「バッファに描画中/描画してない」「バッファをコピー中/コピーしてない」のフラグを用意して、描画をキャンセルしたり、コピーをキャンセルしたりする処理が必要になりそうな気もする。

あるいは、描画担当側が自前でループを回して処理するのではなく、ウインドウ担当が1フレーム毎に関数を呼びに来る流れのほうがシンプルにならないか…。

問題その3。メインメモリ上のバッファ内容をウインドウ上にコピーしてるあたりからして、描画はCPUがソフトウェア処理で行っているのだろうなと…。ハードウェア描画と比べて、処理速度の面では不利だろうから、凝った描画はできない予感。

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

#1 [basic] FreeBASICでスクリーンセーバを作成その2

_昨日、 FreeBASIC Screensaver Kit を使って、FreeBASIC 1.10.1 でWindows用スクリーンセーバの雛形を作成したけれど。

_freeBASIC Screensaver Kit Updated - freebasic.net
_FreeBASIC Screensaver Kit - freebasic.net

おそらくは描画処理と表示のタイミングが同期していない(のであろう)点が気になった。せっかくだから、スクリーンセーバ用ウインドウの処理の中で、1フレーム毎に描画処理を呼ぶ形にできないか試してみた。これで、バッファに描画した直後にウインドウ内容の更新(バッファからウインドウにコピー)をするから、タイミングがずれることはないはず…。

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

実行した結果は以下のような感じ。昨日書いた版と見た目の違いはないけれど、描画と表示の同期は取れているはず。




ちなみに、メインPC (CPU: Ryzen 5 5600X)、サブPC (CPU: AMD Athlon 5350)上では、60FPS前後をふらふらしている。このくらいのシンプルな描画なら、低消費電力重視のCPU、ソフトウェア処理による描画でも、60FPSぐらいは出せるようだなと…。

ファイル一式 :

ファイル一式は以下。

_ssfbscrsample3.zip

解凍して、build.bat を実行すれば、FreeBASICでコンパイルできる。ssfbscrsample3.exe が生成されたら、ssfbscrsample3.scr にリネームコピーされる。

ssfbscrsample3.scr をエクスプローラ上で右クリックして、「Test」「構成」を選べば、動作確認ができる。

出来上がった .scr を以下のフォルダにコピーすれば、スクリーンセーバとして使えるようになる。
  • スクリーンセーバが32bit版アプリで、Windows が 64bit版の場合、C:\Windows\SysWOW64\ にコピー。
  • スクリーンセーバが32bit版アプリで、Windows が 32bit版の場合、C:\Windows\System32\ にコピー。
  • スクリーンセーバが64bit版アプリで、Windows が 64bit版の場合、C:\Windows\System32\ にコピー。

自分が書いた部分は、CC0/ Public Domain ということで…。

ソースも貼っておく :

一応ソースも貼っておく。

_build.bat
_ssfbscrsample3.xml

_fb_screensaverkit.bas

fb_screensaverkit.bas については、以下の変更を行った。
  • ScreenSaverProc() の中に、時間計測処理と、初期化処理、描画処理、終了処理を呼び出す部分を追加した。
  • StartScreenSaver(time_msec) に、タイマーをセットする際のミリ秒値を指定できるようにした。ここで与えたミリ秒の間隔で画面が更新される。
  • FPSもカウントしてる。SaverInfo.fps の中に、計測したFPSが入っている。


描画処理を担当しているのは ssfbscrsample3.bas。このファイルをカスタマイズすれば自分好みのスクリーンセーバが作れる。

_ssfbscrsample3.bas
#include "fb_screensaverkit.bas"

' use mmsystem
#include "windows.bi"
#include "win/mmsystem.bi"

' global work
Type wk
    scrw As Integer
    scrh As Integer
    x As Double
    y As Double
    dx As Double
    dy As Double
    r As Double
    max_fps As Double
End Type

Dim Shared wk As wk

'--------------------
' Initialize work

Sub InitProc()

    ' get screen size. width and height
    ' ScreenInfo scrw, scrh
    wk.scrw = SaverInfo.ScrWidth
    wk.scrh = SaverInfo.ScrHeight
    wk.max_fps = SaverInfo.max_fps

    wk.x = wk.scrw / 2.0
    wk.y = wk.scrh / 2.0
    wk.dx = (CDbl(wk.scrw) / wk.max_fps) * 0.6
    wk.dy = (CDbl(wk.scrh) / wk.max_fps) * 0.4
    wk.r = wk.scrh / 16.0

    ' timeBeginPeriod(1)
End Sub

'--------------------
' Rendering

Sub Render(ByVal delta As Double)
    If delta >= 1.0 Then delta = 1.0 / wk.max_fps
    
    ' move ball position
    wk.x += (wk.dx * wk.max_fps * delta)
    wk.y += (wk.dy * wk.max_fps * delta)
    If (wk.x <= wk.r And wk.dx < 0) Or (wk.x >= (wk.scrw - wk.r) And wk.dx > 0) Then wk.dx *= -1.0
    If (wk.y <= wk.r And wk.dy < 0) Or (wk.y >= (wk.scrh - wk.r) And wk.dy > 0) Then wk.dy *= -1.0

    ' draw start

    ' clear screen
    ' Line (0, 0)-(scrw, scrh), Rgb(0, 0, 0), BF
    color RGB(0, 0, 0), RGB(0, 0, 0)
    cls

    ' draw ball
    circle (wk.x, wk.y), wk.r, RGB(255, 0, 0), , , , F

    ' Get and draw FPS
    Dim As String fpstext = Str(SaverInfo.fps) & "FPS"
    Draw String ((wk.scrw - Len(fpstext) * 8) / 2, 10), fpstext, RGB(255, 255, 255)
    fpstext = ""  ' destroying a string

    ' draw end
End Sub

'--------------------
' End process

Sub ExitProc()
    ' timeEndPeriod(1)
End Sub

' --------------------
' main routine

Randomize Timer

'Start the Screen Saver. Set timer XX sec
StartScreenSaver(14)

' Wait until the screensaver ends
Do Until SaverInfo.IsClosing = True
    sleep 10
Loop

以下の3つの関数/サブルーチンをカスタマイズすればいい。
  • InitProc() : 初期化処理。スクリーンセーバ用ウインドウが作成されたタイミングで呼ばれる。
  • Render(delta) : 描画処理。毎フレーム呼ばれる。delta に、前回フレームからの経過時間(単位は秒。小数点以下有り)が入っているので、座標更新処理に使える。
  • ExitProc() : 終了処理。スクリーンセーバ用ウインドウが破棄されるタイミングで呼ばれる。


リソースファイル。スクリーンセーバ名、アイコン画像ファイル名、設定ダイアログのレイアウトが記述されている。今回、設定ダイアログは、スクリーンセーバ名とOKボタンを表示するだけのダイアログになっている。

_ssfbscrsample3.rc
/* Screensaver title */
STRINGTABLE
BEGIN
    1 "FreeBASIC Screensaver 3"
END

/* icon */
100 ICON "fbscricon.ico"

/* dialog */
FB_SCRNSAVER_ABOUT DIALOGEX 6, 18, 160, 62
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_NOFAILCREATE
 FONT 12,"Arial"
CAPTION "FB Screensaver Sample 3"
BEGIN
    DEFPUSHBUTTON "OK" IDOK, 55, 40, 50, 14
    CTEXT "FB Screensaver Sample 3" -1, 0, 5, 160, 8
    CTEXT "Version 1.0" -1, 0, 15, 160, 8
END

1 24 "ssfbscrsample3.xml"

問題点 :

サブPC上で動かしていたら、何かの拍子にボールが画面内から消えた…。

おそらく、何かのプログラムがCPUリソースをごっそり持っていく時があるようで、スクリーンセーバの処理が数秒に渡って呼ばれない → 前フレームからの経過時間が数秒に達する → 移動速度がとんでもなく大きくなる → ボールが画面外に消えてしまう、という状態じゃないかなと。

であれば…。前フレームからの経過時間が、例えば1.0秒を超えるときは、全体的に負荷がかかっていてまともに処理できる状態じゃないのだろうから、経過時間を強制的に1/60秒 = 0.016秒程度にして計算する、等の処理が必要かもしれない。一応、その処理も入れてみた。


また別の話。今のところ、多重起動禁止をしてないのだけど、これでもいいのだろうか…。windows.bi を include して CreateMutex() を使うやり方で多重起動禁止はできそうな気がするけど…。MinGW の scrnsave.c を眺めたら、そちらも多重起動禁止をしてないように見えたのだよな…。実はその手の処理を入れなくてもいいのだろうか…?

余談。FreeBASICのStringについて :

C言語で文字列(char配列など)を扱う際は、あらかじめ十分足りそうなだけのサイズを用意して、その領域内を書き換えながら処理したりするけれど。FreeBASIC の場合、そのあたりはどうなっているのか気になった。

_String
_STRING (関数)

よく分からないな…。とりあえず、"" を代入すると割り当てが解放される云々と書いてあるので、一応やっておこう…。

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

#1 [basic] FreeBASICで多重起動禁止

Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit で、多重起動禁止ができるのかどうか動作確認。

以下が参考になった。

_Mutex instances - freebasic.net
_Mutex Troubles [Solved] - freebasic.net
_Windows上で多重起動を防止する方法

Windowsに特化した方法になってしまうけど、CreateMutex() を使えば多重起動禁止ができるはず。

サンプルソースを書いてみた。

_mutex.bas
#include once "windows.bi"

Const mutex_name = "MyProgramName"

Dim As HANDLE hMsp

hMsp = CreateMutex(NULL, TRUE, strptr(mutex_name))
If hMsp <> 0 Then
    Dim As DWORD ret = GetLastError()
    If ret = ERROR_ALREADY_EXISTS Or ret = ERROR_ACCESS_DENIED Then
        ' Already
        Print "Already running. Exit."
        ReleaseMutex(hMsp)
        CloseHandle(hMsp)
        end
    End If
End If

Print "Running ... "
Print "Push Any Key"
sleep

ReleaseMutex(hMsp)
CloseHandle(hMsp)

fbc mutex.bas で、mutex.exe を生成できる。

実行すると、最初の起動時は以下になる。
> mutex.exe
Running ...
Push Any Key

1つ目のプログラムがまだ実行されている間に、別のDOS窓で同じプログラムを実行すると、以下の表示になって終了する。
> mutex.exe
Already running. Exit.

多重起動禁止になっているようだなと…。

#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

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

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

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

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

調べた範囲では、方法は4つある。昨日メモした内容を再度メモ。
リソースファイル利用、bin2bas利用、asm記述の利用については昨日試した。asm記述の利用だけは上手く行かなかったけど…。

今回は、バイナリファイルをオブジェクトファイルに変換してリンカで結合する方法を試してみた。

オブジェクトファイル化して結合する方法 :

以下で詳しく説明されてた。ありがたや。

_How to embed any binary file into your FB executable - freebasic.net

まずは、バイナリファイルのオブジェクトファイル化。MinGW や MSYS2 についてくる ld.exe、または、objcopy.exe を利用して、バイナリファイル、 _image_png.png を、オブジェクトファイル image_png.o に変換する。
ld -r -b binary -o image_png.o image_png.png

or

objcopy -I binary -O elf32-i386 -B i386 image_png.png image_png.o


これを、.bas と一緒にして、FreeBASIC でコンパイルする。
fbc loadpngld.bas image_png.o


.bas のソースは以下。

_loadpngld.bas
' 画像読み込み用ライブラリ FBImage を使う
#include once "FBImage.bi"

' カレントディレクトリを exeファイルのある場所にする
chdir exepath()

' リンクしたバイナリオブジェクトの先頭アドレスと終了アドレス
#If defined( __FB_WIN32__ ) And Not defined( __FB_64BIT__ )
' Windows 32bit
extern image_png_png_start alias "binary_image_png_png_start" as byte
extern image_png_png_end alias "binary_image_png_png_end" as byte
#Else
' Windows 64bit and other
extern image_png_png_start alias "_binary_image_png_png_start" as byte
extern image_png_png_end alias "_binary_image_png_png_end" as byte
#endif

' デスクトップ解像度を指定
Dim As Integer scrw = 512
Dim As Integer scrh = 288

' ウインドウサイズと色深度(bpp)を指定
screenres scrw, scrh, 32

' バイナリデータの先頭アドレスとサイズを取得
dim as byte ptr imgdata = @image_png_png_start
dim as Integer imgdata_length = @image_png_png_end - @image_png_png_start

' メモリ上にある画像データを FBImage で読み込む。ポインタとバイト数を渡す
var img = LoadRGBAMemory(imgdata, imgdata_length)

' 画像を描画。RGB=(255, 0, 255) のピクセルは透明色として扱う
Put (16, 16), img, TRANS

' キー入力があるまで待ち続ける
sleep

' 画像を使い終わったので破棄
ImageDestroy img


生成された loadpngld.exe を実行したら、ウインドウ内にpng画像が描画された。

loadpngld_ss.png


ということで、この方法でも画像データを exe に内包して利用できると分かった。

ただ、この方法は、ld か objcopy が必要になる点がちょっと厳しいかもしれない。MinGW や MSYS2 をインストールしてある環境なら、 _GNU Binutils パッケージ をインストールして ld や objcopy を使えるけれど、FreeBASIC しかインストールしてない環境ではオブジェクトファイル化が難しいだろうなと…。


少し解説。

ld もしくは objcopy を使って、バイナリファイルをオブジェクトファイル化すると、そのオブジェクトファイルにはいくつかのシンボルが入ってる。objdump -t hoge.o でシンボルを確認できる。
> objdump -t image_png.o

image_png.o:     file format pe-i386

 SYMBOL TABLE:
[  0](sec  1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000000 _binary_image_png_png_start
[  1](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x0000c4a8 _binary_image_png_png_size
[  2](sec  1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x0000c4a8 _binary_image_png_png_end

hoge.png というファイルをオブジェクト化したなら、以下のようなシンボルになる。
  • 開始アドレスを示すシンボルは _binary_hoge_png_start に。
  • 終了アドレスを示すシンボルは _binary_hoge_png_end に。
この2つのシンボルを頼りにして(?)、FreeBASIC側からアクセスすればいい。

注意点。FreeBASIC が32bitか、64bitかで、FreeBASICからアクセスする際のシンボル名が微妙に変わるらしい。32bit版は、先頭の「_」が無くなるのだとか。
32bit:
binary_image_png_png_start
binary_image_png_png_end

64bit:
_binary_image_png_png_start
_binary_image_png_png_end


FreeBASIC側の仕様についてもメモ。

  • __FB_WIN32__ は、Windows上でコンパイルする際に定義されるシンボル。
  • __FB_64BIT__ は、64bitアプリとしてコンパイルする際に定義されるシンボル。
___FB_WIN32__
___FB_64BIT__

#ifdef - #else - #endif を使えば、Windowsとそれ以外、64bitとそれ以外、といった具合に条件を分けてコンパイルできる。

_条件付きコンパイル


FreeBASICでは、シンボル?の前に「@」がつくと、アドレスを示す値になるらしい。あちこちのソースに出現していて、これは一体何だろうと思ってた…。また、ptr はポインタ変数であることを示してる。

_演算子 @ (のアドレス)
_POINTER | PTR

#2 [basic] FreeBASICで反転描画してみたかったができなかった

FreeBASIC は put を使って画像を描画することができる。ただ、水平反転、垂直反転描画ができないような気もする…。これではSEGAのゲームギアじゃないか…。 *1

put に与える画像横幅、画像縦幅をマイナス値にしたらあっさり反転描画できたりしないかなと気になったので試してみた。

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

結果を先に書くけど、ダメだった。そんな美味しい話は無さそう。

_obj.bmp

_put_flip.bas
' put flip test

#ifdef __FB_WIN32__
' Windowsの場合、mmsystemを利用
#include "windows.bi"
#include "win/mmsystem.bi"
#endif

' fbgfxモードを使う
#Include "fbgfx.bi"
Using fb

' 時間計測用の変数
Dim As Double start_time, prev_time, now_time, delta, one_frame, next_time
Dim As Integer frame_count
Dim As String fps_text = "FPS"

Dim As Integer scrw, scrh  ' ウインドウサイズ
Dim As Integer imgw, imgh  ' 画像サイズ

chdir exepath()  ' カレントディレクトリを exeファイルのある場所にする

' ウインドウサイズと色深度を指定
scrw = 512
scrh = 288
Screenres scrw, scrh, 32

' 画像読み込み。FreeBASIC標準のbmp読み込みを使う場合
Dim img As any ptr = ImageCreate(128, 64)
Bload "obj.bmp", img
imageinfo img, imgw, imgh  ' 画像の幅と高さを取得

#ifdef __FB_WIN32__
timeBeginPeriod(1)         ' タイマー精度を1msecに向上
#endif

Dim As Double MAX_FPS = 60.0  ' FPS
one_frame = 1.0 / MAX_FPS     ' 1フレームあたりの本来の時間

start_time = Timer            ' 開始時間を取得
prev_time = start_time
frame_count = 0

Dim As Boolean running = True
Dim As Double x, y
x = scrw / 2
y = scrh / 2

Dim As Double anime_t = 0.0

' メインループ
While (running)

    ' 前回フレームから何秒経過したか取得。単位は秒(小数点以下有り)
    now_time = Timer
    delta = now_time - prev_time
    prev_time = now_time
    next_time = now_time + one_frame
    If delta < 0 Then delta = one_frame

    If now_time >= start_time Then
        If (now_time - start_time) >= 1.0 Then
            ' 1秒経過したのでFPSを取得
            fps_text = "FPS: " & frame_count
            start_time += 1.0
            frame_count = 0
        End If
    Else
        start_time = now_time
    End If
    frame_count += 1

    ' ESCキー、qキー、ウインドウの閉じるボタンを検出
    Dim As String k = inkey$
    If k = Chr$(27) Or k = "q" Or k = Chr$(255) + "k" Then
        running = False  ' メインループ終了
    End If

    anime_t += delta  ' アニメ表示用カウンタを更新

    ScreenLock  ' 描画開始
    color RGB(255, 255, 255), RGB(52, 164, 255)
    cls         ' 画面クリア

    ' 画像を描画
    Dim As Integer n, sx, sy, sw, sh
    n = Int(anime_t / 0.5) Mod 2  ' 0 or 1
    sw = (imgw / 2)  ' 幅
    sh = imgh        ' 高さ
    sx = 0           ' 描画元 x
    sy = 0           ' 描画元 y
    If n = 1 Then
        sx += sw
        sw = -sw
    End If
    Put (x - (sw / 2), y - (sh / 2)), img, (sx, sy) - Step(sw, sh), TRANS

    ' 文字列を描画
    Draw String (10, 10), fps_text
    Draw String (scrw / 2 - (8 * 6), scrh * 0.8), "HELLO WORLD"

    ScreenUnlock ' 描画終了

    If Timer < next_time Then
        ' 本来の1フレーム時間がまだ経過してないので sleep させる
        Dim As Double wait_ms = (next_time - Timer) * 1000.0
        If wait_ms > 0.0 Then sleep wait_ms
    End If
Wend

While Inkey <> "": Wend  ' キーバッファを空にする

#ifdef __FB_WIN32__
timeEndPeriod(1)  ' タイマー精度を本来のスペックに戻す
#endif

ImageDestroy img  ' 画像を使い終わったので破棄


fbc put_flip.bas で、put_flip.exe を生成。実行結果は以下。

put_flip_ss.gif


ダメだった。

しかし、コレってどういう状態になってるんだろう…?

2024/02/05追記 :

反転描画を試みる際に、表示位置もずれるように書いちゃってることに気づいた。まあ、どのみち反転描画はできてないので、このままで。


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

#1 [basic] FreeBASICで反転描画

FreeBASICの標準機能によるグラフィック描画で、画像の反転描画はどうやるのかについて調べてる。

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

FreeBASICのグラフィック描画機能は、メインメモリ上にバッファを作って、そのバッファに、CPUによるソフトウェア処理でRGBA値を書き込んでいくものらしくて…。そういう仕組みなものだから、公式掲示板では、「反転描画ってどうすればいいの?」という問いに「自分で処理を書け」と言われちゃうのがお決まりの流れに見えた。そのくらいは用意しておいてくれてもいいのになあ、と少し思ってしまったりもする。

公式掲示板(?)で、いくつかの処理が紹介されてるようなので、それぞれ動作確認してみた。

1ラインずつ逆に描画するタイプ :

元ネタは以下。「反転描画ってどうすればいいの?」→「1ラインずつ逆に描画すればいいだろ」というやり取り。

_flipping graphics - freebasic.net

自分も書いてみた。左右反転、上下反転の時は1ラインずつ処理してるけど、上下左右反転/180度回転の時は1ドットずつPutしてるので、なんだかアホな処理をしている気もする…。

_obj.bmp

_put_flip2.bas
' put flip test 2
' 1 line 毎にループ処理して反転描画する。

#ifdef __FB_WIN32__
' Windowsの場合、mmsystemを利用
#include "windows.bi"
#include "win/mmsystem.bi"
#endif

' fbgfxモードを使う
#Include "fbgfx.bi"
Using fb

' Draw h v flip image
'
' x, y : Position, img : Bitmap image pointer
' sx, sy : Source position, sw, sh : Source width, height
' flag : 0 (normal), 1 (H Flip), 2 (V Flip), 3 (HV Flip)
Sub PutEx( _
      ByVal x As Integer, ByVal y As Integer, _
      ByVal img As Any Ptr, _
      ByVal sx As Integer, ByVal sy As Integer, _
      ByVal sw As Integer, ByVal sh As Integer, _
      ByVal flag As Integer )

    sw -= 1
    sh -= 1
    
    Select Case flag
    Case 0
        ' draw normal
        Put (x, y), img, (sx, sy) - Step(sw, sh), TRANS
    Case 1
        ' draw H flip image
        Dim As Integer xx = sx + sw
        For i As Integer = 0 To sw
            Put (x, y), img, (xx, sy) - step(0, sh), TRANS
            x += 1
            xx -= 1
        Next i
    Case 2
        ' draw V flip image
        Dim As Integer yy = sy + sh
        For i As Integer = 0 To sh
            Put (x, y), img, (sx, yy) - step(sw, 0), TRANS
            y += 1
            yy -= 1
        Next i
    Case 3
        ' draw HV flip
        Dim As Integer yy = sy + sh
        For yi As Integer = 0 To sh
            Dim As Integer xx = sx + sw
            For xi As Integer = 0 To sw
                Put (x + xi, y + yi), img, (xx, yy) - step(0, 0), TRANS
                xx -= 1
            Next xi
            yy -= 1
        Next yi
    End Select
End Sub

' 時間計測用の変数
Dim As Double start_time, prev_time, now_time, delta, next_time
Dim As Integer frame_count
Dim As String fps_text = "FPS"

Dim As Integer scrw, scrh  ' ウインドウサイズ
Dim As Integer imgw, imgh  ' 画像サイズ

chdir exepath()  ' カレントディレクトリを exeファイルのある場所にする

' ウインドウサイズと色深度を指定
scrw = 512
scrh = 288
Screenres scrw, scrh, 32

' 画像読み込み。FreeBASIC標準のbmp読み込みを使う場合
Dim img As any ptr = ImageCreate(128, 64)
Bload "obj.bmp", img
imageinfo img, imgw, imgh  ' 画像の幅と高さを取得

#ifdef __FB_WIN32__
timeBeginPeriod(1)         ' タイマー精度を1msecに向上
#endif

Dim As Double MAX_FPS = 60.0  ' FPS
start_time = Timer            ' 開始時間を取得
prev_time = start_time
frame_count = 0

Dim As Boolean running = True
Dim As Double anime_t = 0.0
Dim As Double x, y
x = scrw / 2
y = scrh / 2

' メインループ
While (running)

    ' 前回フレームから何秒経過したか取得。単位は秒(小数点以下有り)
    now_time = Timer
    delta = now_time - prev_time
    prev_time = now_time
    next_time = now_time + (1.0 / MAX_FPS)
    If delta < 0 Then delta = (1.0 / MAX_FPS)

    If now_time >= start_time Then
        If (now_time - start_time) >= 1.0 Then
            ' 1秒経過したのでFPSを取得
            fps_text = "FPS: " & frame_count
            start_time += 1.0
            frame_count = 0
        End If
    Else
        start_time = now_time
    End If
    frame_count += 1

    ' ESCキー、qキー、ウインドウの閉じるボタンを検出
    Dim As String k = inkey$
    If k = Chr$(27) Or k = "q" Or k = Chr$(255) + "k" Then
        running = False  ' メインループ終了
    End If

    anime_t += delta  ' アニメ表示用カウンタを更新

    ScreenLock  ' 描画開始
    color RGB(255, 255, 255), RGB(52, 164, 255)
    cls         ' 画面クリア

    ' 画像を描画
    Dim As Integer n, sx, sy, sw, sh, px, py
    n = Int(anime_t / 0.5) Mod 4  ' value = 0 - 6
    sw = (imgw / 2)  ' 幅
    sh = imgh        ' 高さ
    sx = 0           ' 描画元 x
    sy = 0           ' 描画元 y
    px = x - (sw / 2)
    py = y - (sh / 2)

    Select Case n
    Case 0 : PutEx px, py, img, sx, sy, sw, sh, 0
    Case 1 : PutEx px, py, img, sx, sy, sw, sh, 1
    Case 2 : PutEx px, py, img, sx, sy, sw, sh, 3
    Case 3 : PutEx px, py, img, sx, sy, sw, sh, 2
    End Select

    ' 文字列を描画
    Draw String (10, 10), fps_text
    Draw String (scrw / 2 - (8 * 6), scrh * 0.8), "HELLO WORLD"

    ScreenUnlock ' 描画終了

    If Timer < next_time Then
        ' 本来の1フレーム時間がまだ経過してないので sleep させる
        Dim As Double wait_ms = (next_time - Timer) * 1000.0
        If wait_ms > 0.0 Then sleep wait_ms
    End If
Wend

While Inkey <> "": Wend  ' キーバッファを空にする
ImageDestroy img  ' 画像を使い終わったので破棄

#ifdef __FB_WIN32__
timeEndPeriod(1)  ' タイマー精度を本来のスペックに戻す
#endif

fbc put_flip2.bas でコンパイル。put_flip2.exe を実行。

put_flip2_ss.gif


まあ、たしかに、できなくはない。たぶん処理速度は遅いだろうけど…。

余談。Put に与えるソース領域のサイズについて、例えば 8x8ドットを描画したかったら、-step(8, 8) と書けばいいのかなと思ってたけど、そうではなかった。-step(7, 7)にしないといけない。

FreeBASIC の For - Next 文も、For i As Integer = 0 To 10 と書いたら、0 から 9 までではなく、0 から 10 まで処理されるので…。基本的に、様々な場面で、「終了値も処理に含まれることが多い」と思っておいたほうがいいのかもしれない。

メモリ内容をコピーして実現するタイプ :

元ネタは以下。

_Fast Put() Alternative with Horizontal / Vertical Flipping - freebasic.net

FreeBASICのグラフィック描画は、メインメモリ上のバッファに対して、CPUのソフトウェア処理でガシガシ書いていくので、つまるところ、ここからここまでのメモリ内容をどこにどうやってコピーするか、という処理でしかない。

上記ページで紹介されているのは、反転描画について、「グラフィック関係の機能を呼び出さずに、実際にメモリ内容をコピーするだけの処理として書くとこうなりますよ」という事例。

オリジナル版は透明色を扱ってなかったけど、RGB = (255, 0, 255) は透明色として扱うように少し修正してみた。ループの中で &H00FF00FF が出てきたらコピーしないようにしただけなので、ウインドウ画面は32bitモード限定。

実処理を担当する部分は以下。

_putflipped.bi


putflipped.bi の使用サンプル。

_putflipped_test.bas
' Put Flipped! v1.0
' (C) 2008 Innova and Kristopher Windsor
'
' Fast Put() Alternative with Horizontal / Vertical Flipping - freebasic.net
' https://www.freebasic.net/forum/viewtopic.php?t=11374

#include once "fbgfx.bi"
#include once "putflipped.bi"

Screenres 800, 600, 32

' Create sample image
Function CreateSampleImage() As fb.image Ptr
    Dim As fb.image Ptr graphic = ImageCreate(320, 240, &HFFFFFFFF)
    For i As Integer = 1 To 10000
        Dim As Integer x = Rnd * 320
        Dim As Integer y = Rnd * 240
        Dim As Integer r = Rnd * 32
        Dim As ulong col = Rgb(Rnd * 256, Rnd * 256, Rnd * 256)
        If Rnd < 0.5 Then col = RGB(255, 0, 255)
        Circle graphic, (x, y), r, col, , , , F
    Next i
    Line graphic, (0, 0) - (319, 239), &HFF88FF88, B '1px border for clipping tests
    Return graphic
End Function

' main
Dim As fb.image Ptr graphic = CreateSampleImage()
Dim As Integer mx, my, mb

' main loop
Do
    Getmouse mx, my,, mb

    Screenlock
    color RGB(40, 80, 160), RGB(40, 80, 160)
    Cls
    putflipped(graphic, mx - 160, my - 120, (mb And 1) > 0, (mb And 2) > 0)
    Screenunlock

    Sleep 10
Loop Until Inkey = Chr(27)

ImageDestroy(graphic)

putflipped() というサブルーチンが、反転描画処理をしている。


fbc putflipped_test.bas でコンパイル。putflipped_test.exe を実行。
  • マウスを動かすと、その位置に画像が表示される。
  • 左ボタンで水平反転描画。
  • 右ボタンで垂直反転描画。
  • ESCキーで終了。




たしかに反転描画ができてる。

ただ、渡した画像全体を描画してるな…。画像の一部だけを描画できるように修正したら、使い勝手が少し変わるだろうか。

今気づいたけど、ウインドウの閉じるボタンを押してもウインドウが閉じない…。FreeBASIC において、ウインドウの閉じるボタンをクリックした時は、特定のキー入力( chr(255) & "k" / chr(255, 107) / chr(0) & "k" ) があったものとして処理されるので、ESCキー( Chr(27) )以外に、そのキーもチェックするようにしておけば対応できそう。

_mieki256's diary - FreeBASICでゲームのメインループ相当を書きたい

90度、180度、270度回転対応版 :

元ネタは以下。水平反転、垂直反転の他に、90度、180度、270度回転もサポートした実装事例。

_Put function that does simple transformations - freebasic.net

実際の処理部分。imgput() というサブルーチンが実処理をしている。

_imgput.bi


使用サンプル。

_imgput_test3.bas
' imgput() sample

#include "imgput.bi"

screenres 512, 288, 32

Dim As Any Ptr p = ImageCreate(101, 101)
Bload "sprite.bmp", p

imgput( p, , 10 + 120 * 0, 10, 0   )
imgput( p, , 10 + 120 * 1, 10, 90  )
imgput( p, , 10 + 120 * 2, 10, 180 )
imgput( p, , 10 + 120 * 3, 10, 270 )

imgput( p, , 10 + 120 * 0, 130, TRANSFORM_HFLIP  )
imgput( p, , 10 + 120 * 1, 130, TRANSFORM_VFLIP  )
imgput( p, , 10 + 120 * 2, 130, TRANSFORM_D1FLIP )
imgput( p, , 10 + 120 * 3, 130, TRANSFORM_D2FLIP )

imagedestroy p
sleep


サンプルの使用画像。

_sprite.bmp

fbc imgput_test3.bas でコンパイル。imgput_test3.exe を実行。

imgput_test3_ss.png


たしかに、90度、180度、270度回転もできている。ただ、透明色を扱ってないな…。画像全体を描画してるあたりも気になる…。

回転拡大縮小描画をするタイプ :

公式掲示板上では、MultiPut なる、回転拡大縮小描画をするサブルーチンが人気(?)のようにも見えた。

以下は、回転拡大縮小描画はするけど反転描画はサポートしてないタイプ。作成時期が2016年なので、比較的新しい。

_MultiPut V2.0 :-) - freebasic.net

以下は、回転拡大縮小描画に加えて、反転描画もサポートするタイプ。ただ、作成時期が 2008年なので、上記の版より古い。

_flipping graphics - freebasic.net

MultiPut V2.0 のほうを試してみた。

実処理部分。MultiPut() というサブルーチンが処理をしている。

_multiput.bi


MultiPut() の使用サンプル。メインループだけ抜き出して引用しておく。

_multiput_test.bas
#include "MultiPut.bi"
'
' main
'
screenres 1024, 480, 32
dim as integer w, h
screeninfo w, h

' ...

' main

Dim As Any Ptr img = CreateSampleImage()

dim as single rotation(5)
dim as boolean transparent
dim as integer frames

while inkey()=""
    screenlock
    line (0, 0) - step(w - 1, h - 1), 0, BF
    draw string (32, 0), "Original"
    put (0,8), img, PSET

    dim as single x
    for i as integer = 1 to 6
        dim as single scale = i * .5
        x += scale * 100
        MultiPut ,x, 240, img, scale, scale, rotation(i - 1), transparent
        rotation(6 - i) += i * .25
    next
    screenunlock
    
    frames += 1
    if frames mod 60 = 0 then transparent = not transparent
    sleep 10
Wend

ImageDestroy(img)

fbc multiput_test.bas でコンパイル。multiput_test.exe を実行。




たしかに回転拡大縮小描画ができている。透明色も扱えている。画像全体を描画しているのが少し気になるけど…。

ちなみに、拡大縮小率にマイナス値を入れたら反転描画してくれないかなと試してみたけど、変な描画になった。そんな旨い話は無いらしい。


反転描画をサポートしている版のほうも試してみた。

_multiput_v1f.bi
_multiput_v1f_test.bas
_sprite.bmp

fbc multiput_v1f_test.bas でコンパイル。multiput_v1f_test.exe を実行。




ちょっと分かりづらいけれど、「Press a key」の文字が左右逆に描画されていて、水平反転描画ができている。また、透明色(RGB = (255, 0, 255))もサポートされている。

ちなみに、MultiPut V2.0 は、固定小数点を使って計算するようにしてあるらしい。

雑感 :

そんなわけで、各サンプルを眺めた感じでは、自分で処理を書けば反転描画もできそうだと分かった。

とは言うものの、CPUによるソフトウェア処理では、処理速度面は期待できない気もする…。

もっとも、ハードウェア支援が欲しければ、FreeBASIC の場合、OpenGLモードによる描画もサポートされているので、そちらを使ってしまうのもアリなんだろうなと…。何もかも OpenGL で描画することになるから OpenGL の知識が必要になるけれど。

_Screenres
_OpenGL, The Open Graphics Language - FreeBASIC Wiki Manual | FBWiki

あるいは、ハードウェアで描画する SDL2 を FreeBASIC から利用する手もあるなと。

_fbc/examples/graphics/SDL/sdl2-hello.bas at master - freebasic/fbc

もしくは、OpenGL を呼び出して描画をしてくれるゲーム制作用ライブラリ、raylib を使う手もありそう。

_glasyalabolas/fb-raylib: Port of raylib 3.5 headers and examples for FreeBasic

ただ、ハードウェア支援が得られる描画方法は、初期設定その他が面倒臭いので…。bmp画像を読み込んでポンと表示できればひとまずOK、ぐらいの時は、FreeBASICの標準描画機能+今回メモした処理で済ませてしまうのも全然アリだろうなと…。おそらく速度は遅いのだろうけど、お手軽に使えるというメリットはあるし。

余談。富豪的解決策。 :

公式掲示板のどこかで目にしたけれど、「速度が欲しかったら最初から反転した画像を持ってしまってPut()で描画すればいいのでは」という意見もあって。たしかにそれも全然アリなんだよな…。

元々、反転描画機能って、「使えるメモリ(RAM/ROM)は少ないけれど、少しでもリッチな画面に見せたい」という需要があって盛り込まれていったはずの機能なので、メモリを贅沢に使えるならそんな機能要らない、と考えるのも妥当というか…。

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

#1 [basic][xyzzy] FreeBASIC用エディタについて少し試用中

今現在、FreeBASIC用のソースを、xyzzy というエディタで書いている。Visual BASICモードにすれば、そこそこ使えなくもない感じ。ただ、一部の命令が入るとインデントがおかしくなる。「end」が入ると、そこからずれる…。

もうちょっと便利なエディタは無いものか…。

xyzzy関係の設定をメモ :

xyzzy の basic-mode 関係の設定を、一応バックアップ的にメモ。
;; ----------------------------------------
;; VBScript (basic-mode)
(pushnew '("\\.vbs$" . basic-mode) *auto-mode-alist*)

;; ----------------------------------------
;; basicモードの設定
;; C-. でキーワード補完
;; タブをスペースに

(require "basic-mode")

(add-hook 'ed::*basic-mode-hook*
          #'(lambda ()
              (set-tab-columns 4 (selected-buffer))
              (setq ed::*basic-indent-level* 4)
              (make-local-variable 'indent-tabs-mode)
              (setq indent-tabs-mode nil)
              ))

(define-key *basic-mode-map* #\C-. 'ed::basic-completion)

(pushnew '("\\.bi$" . basic-mode) *auto-mode-alist*)

Unicode対応問題 :

FreeBASICのソース内に Unicode(utf8n)で日本語文字列を書くと、エディタによっては正常に表示されない。

EditorUnicodeSJIS日本語入力note
IUP_FB_EDITORGOODGOODGOOD文字コード判別はOS機能に任せてるのでどうしようもないらしい
FbEdit 1.0.7.4BADBADBAD表示も入力も一切できない
WinFBE 3.1.0 x86BADGOODBAD日本語入力できない
FBIde 0.4.6r4BADGOODBADインライン日本語入力できない
Geany 2.0GOODGOOD?インライン入力できるけど変換中の文字が小さくて見辛い
Notepad++ 8.6 32bitGOODGOODGOOD表示も入力も問題無し
Microsoft Visual Studio Code 1.86.0GOODBAD?Unicodeなら入力できる

FreeBASIC用を謳っているエディタの中で、Unicodeも表示できるのは IUP_FB_EDITOR ぐらいのようだなと…。

それぞれの入手先は以下。

_Main - IUP_FB_EDITOR
_FbEdit
_PaulSquires/WinFBE: FreeBASIC Editor for Windows
_FBIde - #1 editor for FreeBASIC
_Home | Geany
_Geany Portable (text editor and basic IDE) | PortableApps.com
_Notepad++

FBIde の「language file missing」 :

FBIde は、コマンドラインオプションでファイル名を渡しても、「language file missing」と警告が表示される。一応ファイルを開いてはいるけど…。

_fb024 Patcher Warning - freebasic.net

FBIde 0.4.6r2 を入手して、FbIdeFix.dll を 0.4.6r2版で置き換えれば、警告が出なくなるらしい。試してみたら、たしかに警告は出なくなった。

#2 [basic] FreeBASICで反転描画。その2

Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit で、標準機能のグラフィック描画で反転描画する方法について調べてる。

_昨日 は、公式掲示板で紹介されていた、色々な実装を動作確認していたわけだけど。その中の imgput() がイイ感じだなと…。

_Put function that does simple transformations - freebasic.net

ただ、透明色を扱わない仕様だったので、透明色有りで動くようにできないかと修正を試みた。

ソース :

実処理部分。オリジナル版と混ざるとアレなので、サブルーチン名は imgput() から imgputt() にしておいた。

_imgputt.bi


imgputt()の使用サンプル。

_imgputt_test.bas
' imgput() sample. RGB = (255, 0, 255) is transparent

#include "imgputt.bi"

#define BPP  32
'#define BPP  16
'#define BPP  8

screenres 512, 288, BPP
Dim As Any Ptr p = ImageCreate(101, 101)
Select Case BPP
Case 8 : Bload "sprite_8bit.bmp", p : color 16, 16 : cls
Case 16 : Bload "sprite_16bit.bmp", p
Case 32 : Bload "sprite_24bit.bmp", p
End Select

Dim As Integer mode = 0  ' 0 Trans, 1 Pset

' Draw
imgputt( p, , 10 + 120 * 0, 10, 0,   mode)
imgputt( p, , 10 + 120 * 1, 10, 90,  mode)
imgputt( p, , 10 + 120 * 2, 10, 180, mode)
imgputt( p, , 10 + 120 * 3, 10, 270, mode)

imgputt( p, , 10 + 120 * 0, 130, TRANSFORM_HFLIP,  mode )
imgputt( p, , 10 + 120 * 1, 130, TRANSFORM_VFLIP,  mode )
imgputt( p, , 10 + 120 * 2, 130, TRANSFORM_D1FLIP, mode )
imgputt( p, , 10 + 120 * 3, 130, TRANSFORM_D2FLIP, mode )

imagedestroy p
sleep


使用画像。

_sprite_24bit.bmp
_sprite_16bit.bmp
_sprite_8bit.bmp

fbc imgputt_test.bas でコンパイル。imgputt_test.exe を実行。

imgputt_test_ss1.png

RGB = (255, 0, 255) の部分が透明になっている。

ベンチマークを取ってみた :

せっかくだから、Put() と比べてどのくらい遅いのか調べてみた。

また、ついでなので、昨日自分が書いた、縦1ライン、もしくは横1ライン毎に Put() を使って逆方向に描画する版( PutEx() )も測ってみた。

_putex.bi

_imgputt_test2.bas
#include "imgputt.bi"
#include "putex.bi"

#define BPP  32
'#define BPP  16
'#define BPP  8

screenres 512, 288, BPP
Dim As Any Ptr p = ImageCreate(101, 101)
Select Case BPP
Case 32 : Bload "sprite_24bit.bmp", p
Case 16 : Bload "sprite_16bit.bmp", p
Case 8 : Bload "sprite_8bit.bmp", p : color 16, 16 : cls
End Select

Dim As Double starttime, t0, t1, t2
Dim As Integer max_count = 3000

Sub sub1(ByVal max_count As Integer, ByVal p As Any Ptr)
    For i As Integer = 0 To max_count
        Put (10, 10), p, Trans
        ' Put (10, 10), p, Pset
    Next i
End Sub

Sub sub2(ByVal max_count As Integer, ByVal p As Any Ptr)
    For i As Integer = 0 To max_count
        'imgputt( p, , 130, 10, TRANSFORM_HFLIP, 0 )
        'imgputt( p, , 130, 10, TRANSFORM_VFLIP, 0 )
        imgputt( p, , 130, 10, TRANSFORM_HFLIP Or TRANSFORM_VFLIP, 0 )
    Next i
End Sub

Sub sub3(ByVal max_count As Integer, ByVal p As Any Ptr)
    For i As Integer = 0 To max_count
        'PutEx(10, 130, p, 0, 0, 101, 101, 1)
        'PutEx(10, 130, p, 0, 0, 101, 101, 2)
        PutEx(10, 130, p, 0, 0, 101, 101, 3)
    Next i
End Sub

starttime = Timer
ScreenLock
sub1(max_count, p)
ScreenUnlock
t0 = Timer - starttime

sleep 1000

starttime = Timer
ScreenLock
sub2(max_count, p)
ScreenUnlock
t1 = Timer - starttime

sleep 1000

starttime = Timer
ScreenLock
sub3(max_count, p)
ScreenUnlock
t2 = Timer - starttime

sleep 1000

Print "Put     : " & t0 & " sec (100%)"
Print "imgputt : " & t1 & " sec (" & int((t1 / t0) * 100) & "%)"
Print "PutEx   : " & t2 & " sec (" & int((t2 / t0) * 100) & "%)"

imagedestroy p
sleep

fbc imgputt_test2.bas でコンパイル。imgputt_test2.exe を実行。

  • imgputt() は、メモリからメモリに値をコピーするものとして処理をしている。
  • PutEx() は、横1ライン、もしくは縦1ライン毎に、Put() を使って逆方向に描画していく。水平垂直反転の時だけ、1ドット単位で Put() を呼んで描画する。

水平反転のみ、垂直反転のみ、水平垂直反転、の3つを測定。

imgputt_test2_ss1.png

imgputt_test2_ss2.png

imgputt_test2_ss3.png


フォントが小さくて読めないかもしれないけれど、大体以下のような結果になった。

  • 水平反転のみ、かつ、透明色有効の場合、Put() と比べて、imgputt() は5倍遅い。PutEx() は6倍遅い。
  • 垂直反転のみ、かつ、透明色有効の場合、Put() と比べて、imgputt() は5倍遅い。PutEx() は2倍遅い。
  • 水平垂直反転、かつ、透明色有効の場合、Put() と比べて、imgputt() は5倍遅い。PutEX() は135倍遅い。

imgputt() は、大体5倍ぐらい遅い。どの反転描画も似たような処理をするので、処理速度は安定している。

ライン単位で Put() を呼んでいく PutEx() はかなり遅いのではないかと思ってたけど、意外とそうでもなかった。imgputt() は5倍遅いけど、PutEx() の水平反転は6倍ちょっと程度。そんなに悪くない。垂直反転に至っては Put() の2倍程度で処理できてるので、5倍遅い imgputt() よりも処理が速かった。これは意外だった。

ただ、PutEx() で水平垂直反転をすると、最悪な結果になる。135倍遅いのは、さすがに…。おそらく、Put() を1回呼ぶたびに、画面をオーバーしてないか等を調べていくのだろうけど、それを1ドットずつやってたら話にならないよなと…。

Put()のソースを眺めてみた :

それにしても、標準描画機能の Put() は異様に速い。どういう処理をしているのだろう。github にC言語で書かれたソースがあったので眺めてみた。

_fbc/src/gfxlib2/gfx_put_trans.c at master - freebasic/fbc

fb_hPutTrans1C()、fb_hPutTrans2C()、fb_hPutTrans4C() があるけれど、それぞれ、色深度8bitモード(パレットモード)、16bitモード(RRRRRGGGGGGBBBB bit)、32bitモード(AARRGGBB byte)なのだろう。

やってることは、imgputt() とさほど変わらない印象。y と x でループを回して、1ドットずつ見ていって、値をコピーするかしないかを決めて、ポインタをしかるべき値で進める。まあ、そうなるよなあ…。

もしかして、+= ではなくて、++ をループ内で使ってるあたりが効いてるのだろうか?

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

#1 [basic] FreeBASICで反転描画。その3

Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit で、標準機能のグラフィック描画で反転描画する方法について調べてる。

_昨日_imgput() を改造して、透明色を有効にする版にしてみたけれど、もう少し高速化できないものかと思えてきた。

試しに、おそらくは横一列を描画するループの中で、ポインタに += n をしていたあたりを += 1 になるように修正してみたのだけど…。Put() に比べて5倍遅かったのが、4倍ちょっとぐらいにはなった。

ただ、元画像を参照するポインタも += 1, -= 1 にしてみたところ、90度、270度回転の結果がグチャグチャになってしまった。そこは変更できないようだな…。書き込む側は横方向/右側に進んでいくけれど、参照する側は左右だけではなく縦方向に進んでいく場合もあるのだろう…。

回転処理は諦めて、水平垂直反転のみサポートということにすれば、もう少し高速化できたりするのだろうか。いや、焼け石に水かな…。

ソース :

現状のソースは以下。

_imgputt_new.bi
_putex.bi
_imgputt_test.bas
_imgputt_test2.bas


使用画像は以下。

_sprite_8bit.bmp
_sprite_16bit.bmp
_sprite_24bit.bmp


以下でコンパイルできる。
fbc imgputt_test.bas
fbc imgputt_test2.bas

実行は以下。コマンドラインオプションで、8, 16, 32 のどれかを指定すれば、ウインドウを 8bit, 16bit, 32bitモードで作るようにしてみた。
imgputt_test.exe 32
imgputt_test2.exe 32

imgputt_test.exe 8
imgputt_test2.exe 8

imgputt_test.exe 16
imgputt_test2.exe 16


imgputt_test.exe, imgputt_test2.exe の実行結果は以下。

imgputt_test_ss2.png

imgputt_test2_ss4.png

gccを使ったら速くなった :

ふと、gcc の最適化を指定したらどうなるかなと思いついた。通常、FreeBASIC 32bit版は、gas (as.exe, GNUアセンブラ)を利用してコンパイルしてるらしいけど…。

MinGW (gcc 6.3.0) を使える状態にして、FreeBASIC のソースのコンパイルに gcc を使いつつ、かつ、最適化をかけるように指定して exe を生成してみた。
fbc imgputt_test2.bas -gen gcc -O 3
  • -gen gcc ... gcc を使って exe を生成
  • -O 3 ... 最適化レベル 3 を指定。

_コンパイラ・オプション: -gen - CompilerOptgen
_コンパイラ・オプション: -O - CompilerOptoptimization

何と言うことでしょう。ソースは前と変わってないのに、gcc でコンパイルさせただけで、4倍ちょっとの遅さが、1.8倍程度の遅さになった。いやはや、gcc の最適化処理は優秀なんだなあ…。

しかしこうなると、高速化できないかとチマチマ修正してコンパイルして動作確認するのも馬鹿馬鹿しいというか…。まずは gcc でコンパイルしようぜ、話はそれからだ。みたいな。もっとも、高速化したいならまずはアルゴリズムをどうにかできないか考えるのが先だろうけど。

その後も試していたら、-O 3 をつけると 1.8倍だったのが、-O 2 にすると 1.5倍になった。必ずしも -O 3 のほうが良いというわけではないらしい。

8bitモードや16bitモードは遅い :

今までウインドウの色深度について32bitモードで動作確認していたけれど、8bitモードや16bitモードにして動作確認したら、同じような処理をしているのに処理時間が10倍以上に増えてしまった。扱うデータのバイト数は1/4、1/2になっているはずなのに、どうしてそんなことになるのだろう…。

もしかして、バイト単位で処理をする時は、4バイト読み込んで、そのうち1バイトを取り出して、みたいな処理になってしまうのだろうか…? 4バイト単位で処理したほうがシンプルになって、処理時間は速くなる?

MMX機能の有無で使うルーチンが違うのかもしれない :

FreeBASIC の Put() 関連ソースを眺めてたら、CPUがMMX機能を持ってる場合、アセンブラで書いたルーチンを利用してるように見えた。

_fbc/src/gfxlib2/x86/gfx_put_trans_mmx.s at master - freebasic/fbc

だから Put() は速いのかな…。だとしたら、アセンブラで書かないと Put() の速度に近づけないのかもしれない…。

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

#1 [basic] アセンブラを少しだけ勉強中

FreeBASIC の Put() について、アセンブラで書かれてる部分がどういう処理をしているのか気になったので、少しだけx86アセンブラについて勉強中。

_fbc/src/gfxlib2/x86/gfx_put_trans_mmx.s at master - freebasic/fbc

C言語版のソースでは、画像バッファを1ドットずつ見ていって、透明色と遭遇したら書き込み処理をしない、といった処理をしていたけれど。アセンブラ版では条件分岐が見当たらなくて変だなと…。どうやら、二つのレジスタを比較して、値が一致したら片方のレジスタの全bitを1に、値が一致しなかったら全bitを0にする命令(pcmpeqd)を使っている模様。そこから先は、AND、NAND、OR を使って書き込むべき値を求めているのだろう。たぶん。よく分からんけど。

昔、OpenGL 関係で GLSL を勉強した際、「条件分岐が入るとそれだけで処理が遅くなる」「できるだけ数値計算で値を求めるようにせよ」という話を見かけた記憶があるのだけど。FreeBASIC の Put() もそういうことをしているのだろうか。命令数は増えるけど、条件分岐が入るルーチンより、まだ速いのかもしれない。たしか今時のCPUは、条件分岐が入って実際に分岐すると、片方の分岐先の処理結果を捨てるので無駄になる、という話をどこかで聞いた記憶もあるし…。

C言語で書きたくなってきた :

C/C++ で書いた処理を即座にアセンブラで表示してくれるページを利用して勉強中。

_Compiler Explorer

x86 gcc を選んで、最適化オプションの -O 3 等をつけて結果が変わる様子を眺めていたけれど、かなり変わるものなのだなと感心してしまった。最適化をしないと、なんでもかんでもスタックに置いて、スタック上の値を使おうとするので、絶えずメモリアクセスが入って遅くなりそうだけど、最適化を有効にすると極力レジスタに入れてどうにかしようとしてくれる。また、ループ内の命令数も激減するように見える。たしかに速くなりそう…。

コンパイル結果を眺めているうちに、FreeBASIC のライブラリをC言語を使って書けないものかと思えてきた。C言語で書けたら gcc の最適化も使えるだろうから、アセンブラで書かずにC言語で書いたとしても、そこそこの速度が出てくれるのではないかしらん。

それはさておき。C言語のソース上でポインタの型をちゃんと指定しても、アセンブラ上では結局のところ、2だの4だのを足してアドレスを変化させてることにも気づいた。+= N を ++ にしても、出てくるアセンブラの結果は同じ。FreeBASIC のソース上で、どうにかして += 1 と書かないと速くならないのかなと思ってたけど、あまり関係ないのかもしれない。…いや、関係あるか。+= xd といった感じで、変数を足していたところが定数になってくれるのだから、変数1つ分 ―― スタックだかレジスタだかを1つ使わずに済むよな…。実際に試したら少しは速くなったし…。

参考ページ :

#2 [basic] FreeBASICでライブラリファイルを作成する方法を調べていた

FreeBASICのライブラリをC言語で書きたいと思ったものの、解説ページが見当たらなくて。まずはその前に、FreeBASICでライブラリファイル(lib*.a)を作成する手順について試してみようかなと思い立った。

手順については以下のページが参考になった。ありがたや。

_静的ライブラリ - ProPgStaticLibraries
_Static Libraries - FreeBASIC Wiki Manual | FBWiki

FreeBASICコンパイラ(fbc)に、「-lib」をつけるだけでいいらしい。圧倒的に簡単…。

  1. 関数/サブルーチンだけを書いた .bas を用意する。
  2. fbc -lib hoge.bas でコンパイル。
  3. libhoge.a が作成される。
これだけ。マジで簡単。

後は、その関数/サブルーチンがどんな引数を要求して、どんな値を返すのか、そのあたりを書いたヘッダーファイル(.bi)を書いて、実際に使いたいファイルの最初のほうで #include "hoge.bi" を書いてやればいい。簡単過ぎる…。

gccでライブラリファイル化したい :

どうしてライブラリファイルの形にしたいのかと言うと、そのライブラリファイルだけでも gcc でコンパイルしたいから。

FreeBASIC 32bit版は、gas と呼ばれる、GNUアセンブラ(as.exe)を使って実行形式を作るのだけど、最適化はかからないから処理が遅い実行形式が生成されてしまう。しかし、gcc の最適化を有効にしてコンパイルするだけで、同じソースでも数倍の速さになってくれる。

しかし、gcc はどの環境にも入ってるわけではないので、gccが入ってる環境でライブラリファイルだけでも作ることができれば、そのライブラリファイルを配布することで処理が速い版を使えるのではないかなと…。

ただ、Windows 32bit版、64bit版、Linux版 の lib*.a を用意しなきゃいけないあたりが少し面倒臭いかも。

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

#1 [basic] FreeBASICで反転描画。その4

Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit で、標準機能のグラフィック描画で反転描画する方法について調べてる。

imgput() を改造して、透明色を有効にしたり、描画元領域を指定できるようにしてみたけれど、ウインドウ外に描画すると見た目がおかしくなることに気づいて修正していた。水平垂直反転の描画時は動作するようになったと思ったら、回転描画時にクラッシュしたりして…。まあ、どうにか動く状態になった気がする。

実処理部分をライブラリファイル(lib*.a)化して、ヘッダファイル(.bi)を include すれば使えるようにしてみた。ライブラリファイルは gas ではなく gcc + 最適化有効でコンパイルしたので、Put() と比べて4倍遅かったのが、1.5〜2倍程度の遅さに改善された。

せっかくだから、現状の成果を置いときます。

_imgputt2_20240209.zip

ただ、 _元ファイル(imgput()) のライセンスが不明で…。「改良する元ソースにはなるだろう」とか書いてあった気もするから、自由に使っていいのではと勝手に思ってるけど…。

サンプルのスクリーンショット :

同梱のサンプルファイルを実行した際の結果は以下。

ss_test_imgputt.png

ss_bench_imgputt.png

ss_simple_sample.png

ss_move_sample.png

ライブラリのソースとヘッダーファイル :

ライブラリのソースとヘッダーファイルは以下。

_imgputt.bas
_imgputt.bi

簡単な使い方 :

簡単なサンプル。imgputt.bi を include すれば、imgputt() が使えるようになる。

_simple_sample.bas
#include once "imgputt.bi"

screenres 512, 288, 32
Dim As Any Ptr p = ImageCreate(288, 288)
Bload "obj_24bit.bmp", p

Put (10, 10), p, (0, 96) - Step(96, 96), Trans

imgputt(p, , 10, 10 + 100, TRANSFORM_VFLIP, IMGPUT_TRANS, 0, 96, 96, 96)

imagedestroy p
sleep

#2 [basic] FreeBASICでbmp画像のサイズを調べたい

FreeBASIC を使って画像の反転描画の実験をしていた際、指定したサイズで画像が描画されなくて、数時間悩んでしまった。

原因はしょーもないことだった。bload でbmp画像を読み込むその前に、ImageCreate() を使って画像と同じサイズの画像バッファを用意しなきゃいけないのだけど。以前の実験で使ってた、小さい画像のサイズのままだった、というオチで…。

でも、そのあたりは自動でやってほしいよなあ…。フツー(?)のライブラリなら、画像ファイル名を指定するだけで、サイズ取得、バッファ確保、ファイル読み込みを全部やってくれるものではないか…。

ということで、bmp画像のファイル名を指定するだけで、サイズ取得、バッファ確保、画像をロードする処理を書いてみた。

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

ソース :

実処理部分。

_loadbitmapimage.bi
' load bitmap image file

#ifndef __LOADBITMAPIMAGE__
#define __LOADBITMAPIMAGE__

Function LoadBitmapImage(ByRef fname As Const string) As Any Ptr
    Dim As Integer f
    Dim As Long w, h

    ' open bitmap file
    f = FreeFile()
    Open fname For Binary Access Read As #f
    If Err > 0 Then Print "Error: Can not open file" : Return 0
    Get #f, 19, w    ' get width
    Get #f,   , h    ' get height
    Close #f

    ' Print "Bitmap : " & fname & "  (w,h)=(" & w & "," & h & ")"

    ' create image
    Dim As Any Ptr img = ImageCreate(w, h, RGB(0, 0, 0))
    If img = 0 Then Print "Error: Can not ImageCreate()" : Return 0

    ' load bitmap to image
    bload fname, img
    Return img
End Function

#endif


使用サンプル。

_getbitmapsize.bas
' get bitmap image size

#include "loadbitmapimage.bi"

ScreenRes 512, 288, 32

Dim As String fname = "obj_8bit.bmp"
'Dim As String fname = "obj_16bit.bmp"
'Dim As String fname = "obj_24bit.bmp"

' load bitmap image
Dim img As Any Ptr = LoadBitmapImage(fname)

' get image width, height, bpp
Dim As Integer w, h, bpp
ImageInfo(img, w, h, bpp)

Put (0, 0), img

Print "Load: " & fname & " (" & w & ", " & h & ")"
Print "Push Any Key"

ImageDestroy(img)
sleep


使用画像は以下。

_obj_24bit.bmp
_obj_16bit.bmp
_obj_8bit.bmp

雑感 :

一応動作はしたのだけど、この処理、bmp画像をサイズ取得のためだけに1度オープンしてからクローズして、bload でまたオープンするという、実にトンチキな作りで…。

本来なら、1度ファイルをオープンしてメモリ上に読み込んでから、サイズ取得、バッファ確保、バッファ上に展開、とかするのが妥当なのだろうな…。画像ファイルをオープンする処理と、メモリ上に存在する画像データを展開する処理は、別れてたほうが都合がいいはず。

参考ページ :

_BMP ファイルフォーマット
_BMP画像のファイル構造(ヘッダ部・データ部) | 西住工房

bmp画像の横幅、縦幅は、ファイル先頭を0バイト目とした場合、18バイト目から4バイトずつ並んでる。

FreeBASIC で get を使って、ファイルから値を読み込む際、一番最初のバイトにアクセスするなら1を指定するので…。前述のソースでは以下のような記述になっている、とメモ。
    Get #f, 19, w    ' get width
    Get #f,   , h    ' get height

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

#1 [prog][editor] Geany Portableのメニューが日本語で表示されない

Windows10 x64 22H2上で Geany Portable 2.0 をインストールしてみたのだけど、起動してもメニューその他が日本語化されなくて悩んだ。

_Geany Portable (text editor and basic IDE) | PortableApps.com
_Home | Geany

App\Geany64\share\ の中に、各国語の表示に対応させるための locale\ja\LC_MESSAGES\*.mo は入っているように見えるのだけどな…。

ググってみたら、
「環境変数 LANG か LANGUAGE を変更すればその言語に変わる」
「GTKの仕様だからどうしようもない」
という話を見かけた。

_Selecting Interface language - Issue #1757 - geany/geany

GTKを使っているはずの GIMP などは、設定で使用したい言語UIを選択できるのに、変な話だな…。もっとも、GIMPも何か変わったことをして無理矢理実現してる可能性もあるのかな。知らんけど。

とりあえずDOS窓上で、環境変数を変更してから Geany Portable を起動して実験してみた。set LANG=ja を打ってから試しても変化しなかったけど、set LANGUAGE=ja なら日本語メニューになる模様。

batファイルで、環境変数を変更してから Geany Portable を起動するようにしてみた。

GeanyPortable.bat
set GEANYPATH=D:\Prog\GeanyPortable-2.0\GeanyPortable.exe
set LANGUAGE=ja
%GEANYPATH% %1 %2 %3 %4 %5 %6 %7 %8 %9

これならメニューが日本語化された。

しかし、これだと、batファイルをダブルクリックして起動した直後、デスクトップ上にDOS窓がドーンと表示されてしまう。

batファイルのショートカットを作成して、プロパティで、最小化するように指定。タスクバー上にDOS窓のアイコンが一つ無駄に表示されるけれど、まあ、そのくらいはいいだろう…。

ちなみに、Geany Portable版ではなく、公式のインストール版なら、特に何もしなくてもメニューは日本語表示になった。

#2 [basic] FreeBASICで画像を拡大描画する方法を勉強中

FreeBASICで画像を拡大描画したい。QVGA(320x240)ぐらいの小さいサイズの画像バッファにCPUでアレコレを描画して、それをデスクトップ画面全体に拡大表示してみたい。

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

拡大時だけOpenGLを使う方法 :

公式掲示板(?)を眺めていたら、画像バッファにはCPUで描画しつつ、その画像バッファを拡大表示する時だけ OpenGL を使う方法が紹介されていた。そんなこともできるのか…。拡大表示だけでもハードウェアで処理してくれるならありがたい。CPUでデスクトップ全体にドットを打っていくのはそこそこ処理が重そうだし。

_Modern GL 2D game lib needed - freebasic.net

紹介されているソースの動作確認をしてみた。fbc gl2dscale_orig.bas でコンパイル。

_gl2dscale_orig.bas

ss_gl2dscale_orig.png

ちょっと分かりづらいけれど、320x200の画面を、4倍のウインドウサイズ、1280x800 の大きさに拡大表示できている。

ScreenControl() で、SET_GL_2D_MODE と SET_GL_SCALE を指定して、ScreenRes() で、GFX_OPENGL を指定するのが肝らしい。
screencontrol(SET_GL_2D_MODE, OGL_2D_MANUAL_SYNC)
screencontrol(SET_GL_SCALE, 4)

screenres scrw, scrh, 32, , GFX_OPENGL

_SCREENCONTROL
_SCREENRES


注意点。FreeBASIC + OpenGL で描画する際は、最後に flip() を呼ぶことでダブルバッファを切り替えて、そこで描画結果が画面に表示されるのだけど、ループの中で flip() を呼んでやらないとハングアップするらしい。

_Modern GL 2D game lib needed - Page 2 - freebasic.net


さておき。これを使えば、デスクトップ画面全体に拡大表示してフルスクリーン表示をしているかのように見せられるのではないか。と思ったけれど甘かった。

_gl2dscale.bas

ss_gl2dscale.png

screencontrol(SET_GL_SCALE, n) で指定できる拡大率は、Integer(整数)しか受け付けないらしい…。Double で変数を用意して渡したらエラーになった。

拡大率を整数で指定することしかできないとなると、結構な領域がデスクトップ画面外にはみ出してしてまう状態もフツーにありそうだなと…。元画面をドット単位できっちり調整しながら作っても、ごっそり表示されない領域が出てきてしまうのでは…。もっとも、整数倍の表示なら、各ドットは綺麗にクッキリと等間隔で表示されるというメリットはありそう。

何か上手い方法はないものか。例えば、整数倍で拡大した際にできてしまう未使用領域を埋めてくれるような元画面サイズを計算で求めて、ウインドウサイズはそのサイズで指定するけど、描画範囲を本来想定していたサイズに限定して処理してやればいいのだろうか。デスクトップ画面の中に一回り小さな描画領域が表示されている状態になりそうではあるけれど…。

CPUで拡大縮小 :

以下のやり取りで、CPUで計算して拡大縮小する方法が紹介されていた。途中から回転描画処理や3D描画処理に話が変わっていってしまうけど、最初のあたりは参考になりそう。

_Mini Image Scaler Demo - freebasic.net

固定小数点を使って処理してる事例が分かりやすくて参考になりそう。32bitのうち、整数部を12bit、小数部を20bitにして…。描画先のアドレスは1ドットずつ進めて、描画元の参照アドレスは固定小数点計算で求めていく模様。

デスクトップ画面全体に拡大表示できるか試してみた。fbc miniimagescalerdemo4.bas でコンパイル。

_imagescale2.bi (拡大縮小実処理を担当する ImageScale() )
_createsampleimage.bi (サンプル画像作成部分)
_miniimagescalerdemo4.bas ( ImageScale() を呼び出すサンプル)




AMD Ryzen 5 5600X上で、1920x1080 のデスクトップ画面に拡大表示する分には、CPU負荷はそれほどかかってないように見えた。非力なCPUで動かすとどうなるのかちょっと分からないけど…。

それでも、小さい画像バッファに色々書き込んでおいて、最後にデスクトップ全体に拡大表示する、といった使い方なら、1フレームに1回しか処理しないからどうにか使えるのではないかという気もする…。

このあたり、C言語で書いて FreeBASICのライブラリにできたら、もうちょっと処理速度を追求できるのではないかな…。もっとも、描画を速くしたいなら、ハードウェア描画ができる何かしらのライブラリを使えないか検討するのが妥当だろうか…。

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

#1 [basic] FreeBASICで画像を拡大描画する方法を勉強中。その2

FreeBASICで、QVGA(320x240)サイズの画像バッファにCPUで描画して、それをデスクトップ画面全体に拡大表示したい。

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

まずは画像バッファに描画する方法を調べないといかんのだけど、ざっとググっても方法が見つからなくて、さては FreeBASICは画像バッファに描画できないのかなと…。だとしたら、画面表示で使ってるバッファに一旦描画して、それを拡大するしかないのかなと、そのあたりを試し始めた。

ちなみに後で再度調べたら、各描画命令の最初の引数に書き込みたい画像バッファのポインタを渡すだけで目的は果たせる模様。調べ方が足りなかった。

OpenGLで画面全体に拡大表示 :

OpenGL で画面全体に拡大表示する方法でどうにかできないか試してみた。線、円、画像を描画するところはCPUで処理して、最後の拡大表示だけ OpenGL を使う。

この方法は整数倍しかできないので、元解像度とデスクトップ解像度の組み合わせによっては、画面全体に表示できない状態になる。

そこで、元解像度とは別に、デスクトップ画面の1/Nのサイズになる解像度を決めてしまって、そのサイズ内で、描画範囲を指定する view() を使って、実際に描画する範囲を元解像度で固定してしまう方法でどうにかしようと試みた。

ソースは以下。元解像度は320x240にして、それをデスクトップ解像度に拡大する。

_gl_scaling.bas
#define NO_BLUR 1  'change to 1
'Const SCRW = 320 : Const SCRH = 180
Const SCRW = 320 : Const SCRH = 240

#include "fbgfx.bi"
Using fb

' set screen depth
Dim As Integer sdepth = 32
Select Case Command(1)
Case "8"  : sdepth = 8
Case "16" : sdepth = 16
Case "32" : sdepth = 32
End Select

' get desktop size
Dim As Integer dispw, disph
ScreenInfo dispw, disph

' get scale (Integer)
Dim As Integer dscale = Int(disph / SCRH)
If dscale > (dispw / SCRW) Then dscale = Int(dispw / SCRW)

' ge true screen size
Dim As Integer tscrw, tscrh
tscrw = dispw / dscale
tscrh = disph / dscale

If (tscrw * dscale) <> dispw Then tscrw += (dispw - (tscrw * dscale))
If (tscrh * dscale) <> disph Then tscrh += (disph - (tscrh * dscale))

screencontrol(SET_GL_2D_MODE, OGL_2D_MANUAL_SYNC)
screencontrol(SET_GL_SCALE, dscale)

ScreenRes tscrw, tscrh, sdepth, , GFX_OPENGL Or GFX_NO_FRAME

#if (NO_BLUR = 1)
#include "GL/gl.bi"
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
#endif

Dim As Double angle = 0.0
Dim As Boolean running = True

Const PI = 3.1415926535897932

' Main Loop

Do
    If Inkey <> "" Then running = False

    ' set true screen view size
    view (0, 0) - (tscrw - 1, tscrh - 1), RGB(0, 0, 0)

    ' set draw area size
    Dim As Integer ox, oy
    ox = (tscrw - SCRW) * 0.5
    oy = (tscrh - SCRH) * 0.5
    view (ox, oy) - (SCRW - 1 + ox, SCRH - 1 + oy), RGB(30, 60, 120)

    ' clear screen
    Color RGB(255, 255, 255), RGB(30, 60, 120)
    cls
    
    Circle (SCRW \ 2, SCRH \ 2), (SCRH \ 2 - 1), RGB(0, 255, 0)

    Dim As Integer x, y, r
    r = SCRH / 4
    x = (SCRW / 2 - r - 1) * Cos(angle * PI / 180.0) + (SCRW / 2)
    y = (SCRH / 2 - r - 1) * Sin(angle * PI / 180.0) + (SCRH / 2)
    Circle (x, y), r, RGB(0, 255, 255)

    Draw String (0, 0), Str(SCRW) & "x" & SCRH & " (" & tscrw & "x" & tscrh & ")x" & dscale, RGB(255, 255, 255)

    angle += 1.0

    sleep 10
    flip()  ' change double buffer
Loop While running

fbc gl_scaling.bas でコンパイル。gl_scaling.exe を実行した結果は以下。




OpenGL を使っているからキャプチャ時に黒画面が入ってしまっているけれど、実際の動作画面では瞬時に切り替わってる。デスクトップ画面全体に表示することはできてないけれど、元解像度がそれなりの大きさで拡大表示されてることぐらいは分かるかなと…。

昨日もメモしたけれど、肝は以下。
#include "fbgfx.bi"
Using fb

' ...

screencontrol(SET_GL_2D_MODE, OGL_2D_MANUAL_SYNC)
screencontrol(SET_GL_SCALE, dscale)

ScreenRes tscrw, tscrh, sdepth, , GFX_OPENGL Or GFX_NO_FRAME

#include "GL/gl.bi"
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

Do
    ' ...

    sleep 10
    flip()  ' change double buffer
Loop While running

  • ScreenControl() で、SET_GL_2D_MODE や SET_GL_SCALE を設定。
  • SET_GL_SCALE で拡大率を整数で指定。
  • ScreenRes の最後で、GFX_OPENGL を指定。
  • OpenGL利用時は flip() でダブルバッファを切り替えるけど、必ずループの中で呼ばないとハングアップする。

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

#1 [basic] FreeBASICで画像を拡大描画する方法を勉強中。その3

FreeBASICで、QVGA(320x240)サイズの画像バッファにCPUで描画して、それをデスクトップ画面全体に拡大表示したい。

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

FreeBASICの各グラフィックス描画命令は、一番最初の引数で画像バッファを指定すると、スクリーンではなく画像バッファに描画してくれることが分かった。今回は、QVGA程度の画像バッファを作成してそこに色々描画して、その画像バッファをデスクトップ全体に、CPUで計算/ソフトウェア処理で拡大描画する方法を試してみた。

画像バッファに描き込めるかどうか :

まず、本当に画像バッファに対して描画できるのか試してみた。

_draw2image.bas
'Const SCRW = 320 : Const SCRH = 180
Const SCRW = 320 : Const SCRH = 240

#include "fbgfx.bi"
Using fb

' set screen depth
Dim As Integer sdepth = 32
Select Case Command(1)
Case "8"  : sdepth = 8
Case "16" : sdepth = 16
Case "32" : sdepth = 32
End Select

' get desktop size
Dim As Integer dispw, disph
ScreenInfo dispw, disph

' get scale (Integer)
Dim As Double dscale = dispw / SCRW
If dscale > (disph / SCRH) Then dscale = disph / SCRH

ScreenRes dispw, disph, sdepth, , GFX_NO_FRAME
Dim As Any Ptr gbuf = ImageCreate(SCRW, SCRH)

Color RGB(255, 255, 255), RGB(0, 0, 0)
cls

Const PI = 3.1415926535897932
Dim As Double angle = 0.0
Dim As Boolean running = True

' Main Loop

While running
    If Inkey <> "" Then running = False

    ' draw to image buffer
    line gbuf, (0, 0) - (SCRW - 1, SCRH - 1), RGB(30, 60, 120), BF ' clear image buffer
    
    Circle gbuf, (SCRW \ 2, SCRH \ 2), (SCRH \ 2 - 1), RGB(0, 255, 0)

    Dim As Integer x, y, r
    r = SCRH / 4
    x = (SCRW / 2 - r - 1) * Cos(angle * PI / 180.0) + (SCRW / 2)
    y = (SCRH / 2 - r - 1) * Sin(angle * PI / 180.0) + (SCRH / 2)
    Circle gbuf, (x, y), r, RGB(0, 255, 255)

    Draw String gbuf, (0, 0), Str(SCRW) & "x" & SCRH, RGB(255, 255, 255)

    ' draw to screen
    ScreenLock
    Put (0, 0), gbuf, Pset
    ScreenUnlock
    
    angle += 1.0

    sleep 10
Wend

ソース内に出てくる gbuf が、画像バッファのポインタ。円を描画する Circle、テキストを描画する Draw String で gbuf を指定してるので、画像バッファに描画することになる。


fbc draw2image.bas でコンパイル。実行結果は以下。




デスクトップの左上のほうに、画像バッファを Put() している。

これで、画像バッファに対して描画できることが分かった。

画面全体に拡大描画してみる :

この小さい画像を画面全体に拡大描画してみる。拡大描画処理は、 _先日試したImageScale() を ―― 画像から画像に拡大する処理を少し改造して、画像からスクリーンに拡大するように変更して使ってみた。画像から画像に変換する処理のままだと、拡大時に1回、拡大した画像をスクリーンに転送する際に1回と、デスクトップ画面の解像度 1920x1080 で無意味に数回描画することになってしまうので…。


拡大描画の実処理部分。ImageScale2Screen()。

_imagescale2screen.bi


拡大描画を使うサンプル。

_draw2image2.bas
'Const SCRW = 320 : Const SCRH = 180
Const SCRW = 320 : Const SCRH = 240
'Const SCRW = 640 : Const SCRH = 480

#ifdef __FB_WIN32__
' Windows : use  mmsystem
#include "windows.bi"
#include "win/mmsystem.bi"
#endif

#include "fbgfx.bi"
Using fb

#include "imagescale2screen.bi"

#ifdef __FB_WIN32__
' Timer accuracy 1ms
timeBeginPeriod(1)
#endif

' set screen depth
Dim As Integer sdepth = 32
Select Case Command(1)
Case "8"  : sdepth = 8
Case "16" : sdepth = 16
Case "32" : sdepth = 32
End Select

' get desktop size
Dim As Integer dispw, disph
ScreenInfo dispw, disph

' get scale (Integer)
Dim As Double dscale = dispw / SCRW
If dscale > (disph / SCRH) Then dscale = disph / SCRH

' get dest image size
Dim As Integer tgtscrw, tgtscrh
tgtscrw = Int(SCRW * dscale)
tgtscrh = Int(SCRH * dscale)
If tgtscrw > dispw Then tgtscrw = dispw
If tgtscrh > disph Then tgtscrh = disph

' set screen size. like fullscreen
ScreenRes dispw, disph, sdepth, 2, GFX_NO_FRAME
Dim As Any Ptr gbuf = ImageCreate(SCRW, SCRH)

Dim As Double angle = 0.0
Dim As Boolean running = True

Const PI = 3.1415926535897932

#define MAX_FPS   60.0
Dim As Double start_time, prev_time, now_time, delta, next_time
Dim As Integer frame_count, fps_count

' save start time
start_time = Timer
prev_time = start_time
frame_count = 0
fps_count = 0

Dim As Integer workpage = 1
ScreenSet workpage, (workpage + 1) And &H01

' Main Loop

While running
    ' get delta time
    now_time = Timer
    delta = now_time - prev_time
    If delta < 0 Then delta = (1.0 / MAX_FPS)
    prev_time = now_time
    next_time = now_time + (1.0 / MAX_FPS)

    ' fps count
    If now_time >= start_time Then
        If (now_time - start_time) >= 1.0 Then
            ' get FPS
            fps_count = frame_count
            start_time += 1.0
            frame_count = 0
        End If
    Else
        start_time = now_time
    End If
    frame_count += 1

    If Inkey <> "" Then running = False

    angle += (1.0 * MAX_FPS) * delta

    ' draw to image buffer
    
    ' clear screen
    line gbuf, (0, 0) - (SCRW - 1, SCRH - 1), RGB(30, 60, 120), BF
    ' cls

    Circle gbuf, (SCRW \ 2, SCRH \ 2), (SCRH \ 2 - 1), RGB(0, 255, 0)

    Dim As Integer x, y, r
    r = SCRH / 4
    x = (SCRW / 2 - r - 1) * Cos(angle * PI / 180.0) + (SCRW / 2)
    y = (SCRH / 2 - r - 1) * Sin(angle * PI / 180.0) + (SCRH / 2)
    Circle gbuf, (x, y), r, RGB(0, 255, 255)

    Draw String gbuf, (0, 0), Str(SCRW) & "x" & SCRH & " (" & tgtscrw & "x" & tgtscrh & ") FPS:" & fps_count, RGB(255, 255, 255)
    
    ' draw to screen
    ScreenLock
    
    Color RGB(255, 255, 255), RGB(0, 0, 0)
    cls

    Dim As Integer ox, oy
    ox = (dispw - tgtscrw) / 2
    oy = (disph - tgtscrh) / 2
    ImageScale2screen(gbuf, ox, oy, tgtscrw, tgtscrh)
    
    ScreenUnlock

    workpage = (workpage + 1) And &H01
    ScreenSet workpage, (workpage + 1) And &H01
    
    If Timer < next_time Then
        ' sleep
        Dim As long wait_ms = (next_time - Timer) * 1000
        If wait_ms > 0 Then sleep wait_ms
    End If
Wend

#ifdef __FB_WIN32__
' reset timer accuracy
timeEndPeriod(1)
#endif

正確に時間待ちをするために、ソースがちょっと長くなってしまっている。

fbc draw2image2.bas でコンパイル。実行結果は以下。




それらしい描画になった。

ただ、60FPS前後で描画してるはずなのに、動きの滑らかさが無いような気もする…。なんだかガクガクしながら動いてるというか…。

当初、ScreenLock, ScreenUnlock を使うだけではダメなのかなと、ScreenRes() で描画ページと表示ページの2ページを用意して、ダブルバッファを切り替える感じに変更してみた。しかし、改善されたようには見えなかった。

もしかすると、ScreenRes で用意した表示用バッファから実際のデスクトップに転送する処理がCPUで行われていて、その処理が行われるタイミングが60FPSになってないのかもしれない…。

でもまあ、当初の目的通り、小さい画面を拡大している見た目にはなってるから、これはこれで。

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

#1 [basic] FreeBASICでCustom fontを使ってみたい

FreeBASICは、Draw String() を使うと、グラフィックス画面にテキストを描画することができる。ただ、使われるフォントが豆粒みたいに小さくて読みづらい…。もう少し大きいフォントを使いたい。

一応、Draw String はカスタムフォントを利用できるらしいので、そのあたりを試してみた。

ファイル一式は以下。

_fbcustomfont_20240213.zip

カスタムフォントの仕様 :

bitmap font が敷き詰められたフォント画像(.bmp)を与えればすぐさま利用できるのかなと思ったけれど、どうやらそんな単純な仕様では無さそうで…。

_DrawString
_FBgfx Image and Font Buffers - FreeBASIC Wiki Manual | FBWiki

bitmap font画像を渡すまでは予想通りだったけど、そのbmp画像の中にフォント情報(対象文字コードや各文字の横幅)も含ませておかないといかんらしい。

まず、bmp画像の最初の1ラスター目が、font header になっていて、そこにフォント情報を byte 単位で書き込まないといけないらしい。実際の bitmap font部分は、2ラスター目から置かれている。

about_fbfont.png


font header の内容は以下。

about_fbfont_header.png

1 byte目 : Version情報。これは現在 0 しか用意されてないらしい。
2 byte目 : 開始文字コード。例えば ASCIIコードの「空白」から始まるなら、0x20になる。
3 byte目 : 終了文字コード。例えば ASCIIコードの「DEL」で終わるなら、0x7f になる。
4 byte目 : 開始文字 + 0 の横幅ドット数
5 byte目 : 開始文字 + 1 の横幅ドット数
6 byte目 : 開始文字 + 2 の横幅ドット数
...
...
N byte目 : 終了文字の横幅ドット数

個人的に、この仕様はどうなんだと思えた。例えば画像編集ソフトでフォント画像を開いて、色調補正をしただけで、フォント情報が一発で破壊されてしまう。よろしくない気がする。いやまあ、フォント画像一つでカスタムフォントを扱いたい、フォント画像の中にフォント情報まで含めてしまいたい、と考えたらこうなったのだろうとは想像できるけど…。

変換ツールを書いてみた :

とりあえず、使ってみよう…。

等幅フォント、色深度32bitに限定して、読み込んだ bitmapフォント画像(.bmp)に Font header情報を追加/書き込んでから、bmp画像として保存するツールを書いてみた。

例えば、以下のように、ASCIIコード 0x20 - 0x7f の文字が、横一列に並んでいるbitmapフォント画像を用意して…。

_fbfont_bs_pet2015_16x16.bmp


このbmp画像を読み込んで、Font header 情報を追加して、以下のようなカスタムフォント用画像として出力する。

_fbfont_pet2015_16x16.bmp

見た目で違いは分からないだろうけど、一番上に1ラスター分追加されていて、そこに Font header情報が書き込まれている。


変換ツールのソースは以下。

_make_mono_font.bas
Const START_CHR = &H20
Const END_CHR   = &H7f

Const SCRW = 800
Const SCRH = 600
Dim As Integer sdepth = 32

If Command(1) = "" And Command(2) = "" Then
    Print "Usage: " & Command(0) & " INPUT.bmp OUTPUT.bmp" : End
End If

Dim As String infilename  = Command(1)
Dim As String outfilename = Command(2)

Dim As Integer f
Dim As Long w, h

' open bitmap file
f = FreeFile()
Open infilename For Binary Access Read As #f
If Err > 0 Then Print "Error: Can not open file" : End

' get bitmap width and height
Get #f, 19, w    ' get width
Get #f,   , h    ' get height
Close #f

ScreenRes SCRW, SCRH, sdepth

' load base font image
Dim As Any Ptr base_font = ImageCreate(w, h, RGB(0, 0, 0))
bload infilename, base_font

Print "Load : " & infilename

' create new font image
Dim As Any Ptr out_font = ImageCreate(w, h + 1, RGB(255, 0, 255))
Put out_font, (0, 1), base_font, PSet

' get custom font header address
Dim As ubyte Ptr headerp
ImageInfo out_font, , , , , headerp

' set header
headerp[0] = 0
headerp[1] = START_CHR  ' first character code
headerp[2] = END_CHR    ' end character code

' get 1 character Width
Dim As Integer cw = Int(w / (END_CHR - START_CHR + 1))
For i As Integer = 0 To (END_CHR - START_CHR)
    headerp[3 + i] = cw
Next i

bsave outfilename, out_font

color RGB(255, 255, 255), RGB(0, 0, 0)
Print "Save : " & outfilename
Print "Push Any Key"
sleep

ImageDestroy out_font
ImageDestroy base_font

fbc make_mono_font.bas でコンパイル。

使い方は以下。
Usage: make_mono_font.exe INPUT_FONT.bmp OUTPUT_FONT.bmp

テスト表示してみる :

変換して生成されたフォント画像(.bmp)を読み込んで、カスタムフォントとして利用してみる。

_test_fbfont.bas
Const SCRW = 512
Const SCRH = 288
Dim As Integer sdepth = 32

If Command(1) = "" Then
    Print "Usage : " & Command(0) & " INPUT_FONT.bmp" : end
End If

ScreenRes SCRW, SCRH, sdepth

Dim As String infilename  = Command(1)

Dim As Integer f
Dim As Long w, h

' open bitmap file
f = FreeFile()
Open infilename For Binary Access Read As #f
If Err > 0 Then Print "Error: Can not open file" : End

' get bitmap width and height
Get #f, 19, w    ' get width
Get #f,   , h    ' get height
Close #f

Dim As Any Ptr img = ImageCreate(w, h)
bload infilename, img

Color RGB(255, 255, 255), RGB(0, 0, 0)
cls

h = 34
Draw String (0, h * 0), "Hello World !", , img
Draw String (0, h * 1), infilename, , img
Draw String (0, h * 2), "0123456789",    , img
Draw String (0, h * 3), "@ABCDEFGHIJKLMNO", , img
Draw String (0, h * 4), "PQRSTUVWXYZ[\]^_", , img
Draw String (0, h * 5), "`abcdefghijklmno", , img
Draw String (0, h * 6), "pqrstuvwxyz{|}~ ", , img

sleep
ImageDestroy img

fbc test_fbfont.bas でコンパイル。

使い方は以下。表示してみたいフォント画像をコマンドラインオプションで指定する。
Usage: test_fbfont.exe INPUT_FONT.bmp

ss_fbfont_pet2015_16x16.png

たしかに、カスタムフォントが利用できた。

再度メモ。ファイル一式は以下。

_fbcustomfont_20240213.zip

他のフォントを利用した際のスクリーンショット :

以前、 _OpenGLでビットマップフォントを描画した際 のフォントデータを利用して試してみた。

courR18 (16x26)

ss_fbfont_courR18_16x26.png


profont (12x22)

ss_fbfont_profont_12x22.png


東雲フォント shnm8x16r (8x16)

ss_fbfont_shnm8x16r_8x16.png


東雲フォント2倍拡大 shnm8x16r x 2 (16x32)
ss_fbfont_shnm8x16rx2_16x32.png


Terminus font (12x24)

ss_fbfont_ter-u24b_12x24.png


各フォントの入手先やライセンスは以下を参照のこと。

_readme.md

#2 [basic] FreeBASICで等幅ビットマップフォントのみを使って描画

FreeBASIC の Draw string でカスタムフォントを使ってみたけれど、フォント画像を用意するのが面倒臭いだろうなと思えてきた。これがもし、等幅フォントのみを扱うことを前提にするなら、もっと簡単にフォント画像を用意できそうだし、描画処理も簡単にできそうな気がする。試してみた。

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

フォント画像の用意 :

まずは、以下のような文字が並んでいる画像を、等幅フォントで作成。

 !"#$%&'()*+,-./
0123456789:;<=>?
@ABCDEFGHIJKLMNO
PQRSTUVWXYZ[\]^_
`abcdefghijklmno
pqrstuvwxyz{|}~ 


透明色=RGB(255, 0, 255) として扱う。以下のような画像になる。

font_pet2015_16x16.bmp


これなら、1文字当たりの横幅、縦幅は、(画像横幅 / 16), (画像縦幅 / 6) で求められる。

後は、与えらえた文字列の、各文字に対応する領域を Put() で描画していけばいい。

描画処理 :

そんな感じで、描画処理は以下のようになった。


画像の読み込み、テキスト描画をする部分。

_bitmapasciifont.bi


簡単な使用サンプル。

_simple_sample.bas
#include "bitmapasciifont.bi"
ScreenRes 320, 240, 32

Dim fnt As BitmapAsciiFont
fnt.load_image("font_profont_12x22.bmp")

fnt.draw_string(10, 10, "Hello World !")

sleep
fnt.destroy()


フォント画像を渡して描画するサンプル。

_test_bitmapasciifont.bas
#include "bitmapasciifont.bi"

If Command(1) = "" Then
    Print "Usage: " & Command(0) & " INPUT_FONT.bmp" : end
End If

Dim As String fontfile = Command(1)

Const SCRW = 640
Const SCRH = 480
Dim As Integer sdepth = 32

ScreenRes SCRW, SCRH, sdepth

Dim fnt As BitmapAsciiFont
fnt.load_image(fontfile)

Dim As Integer h = fnt.cheight + 1
fnt.draw_string(0, h * 0, "Load: " & fontfile)
fnt.draw_string(0, h * 1, "Hello World ! 0123456789")
fnt.draw_string(0, h * 2, "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_")
fnt.draw_string(0, h * 3, "`abcdefghijklmnopqrstuvwxyz{|}~ ")
fnt.draw_string(0, h * 4, "Push any key to exit.")

sleep
fnt.destroy()


スクリーンショットは以下。

ss_font_courr18_16x26.png

ss_font_pet2015_16x16.png

ss_font_profont_12x22.png

ss_font_shnm8x16r_8x16.png

ss_font_shnm8x16rx2_16x32.png

ss_font_ter-u24b_12x24.png


ファイル一式を置いておきます。御自由にどうぞ。

_bitmapasciifont_20240213.zip


これなら、フォント画像を作るのも簡単なのではないかな…。

フォントの入手先は以下を参照のこと。

_readme.md

#3 [basic] FreeBASICでテキストファイル読み込みをしたい

FreeBASIC で、テキストファイルを行単位で読み込みたい。環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。

以下が参考になった。

_ファイル入出力関連
_Freefile
_Open
_Eof
_LineinputPp

  1. FreeFile() で空いているファイル番号を取得して、
  2. Open でファイルを開いて、
  3. EOF() でファイルの終端かどうかをチェックしつつ、
  4. Line Input で1行読み込んで、
  5. 処理が終わったら Close
そんな感じらしい。

動作確認するために、サンプルソースを書いてみた。

_lineinput.bas
Const infile = "test.txt"

' Open file
Dim As Long f = FreeFile()
If (Open(infile For Input As #f)) Then
    Print "Error: Cano not open " & infile : End -1
End If

' Read lines
Dim As Integer count = 0
Do until EOF(f)
    count += 1
    Dim As String s
    Line Input #f, s  ' Read 1 line
    Print Str(count) & ": " & s
Loop

' Close file
Close #f

fbc lineinput.bas でコンパイル。

_test.txt を用意して読み込ませてみた。

_test.txt
Lorem ipsum dolor sit amet,
consectetur adipiscing elit,

sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.

> lineinput.exe
1: Lorem ipsum dolor sit amet,
2: consectetur adipiscing elit,
3:
4: sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
5:
6: Ut enim ad minim veniam, quis nostrud exercitation ullamco
7: laboris nisi ut aliquip ex ea commodo consequat.

たしかに、1行ずつ読み込みができた。

行末については、LF か CRLF があれば行末/改行コードとして扱う、と書いてある。本当にそうなっているのか、読み込むテキストファイルの改行コードを LF もしくは CRLF にして試してみたけど、どちらも行単位で読み込んでくれた。

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

#1 [basic] FreeBASICでsplit()を使いたい

プログラミング言語の Perl や Ruby や Python には split() というメソッドがある。これを使うと、指定した区切り文字で、文字列を分割して配列にしてくれる。かなり便利。

FreeBASIC でもsplit() を使いたい。しかし、標準では持ってない。どうにかできんのか。いや、自分で書くしかないだろうけど。ということで、そのあたりをググって調べてみた。

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

FreeBASICでsplit()を使いたいという需要はちょくちょくあるようで、公式掲示板でも色々な実装事例が紹介されてた。

_function Split - freebasic.net
_function Split - Page 2 - freebasic.net
_Split String Algorithm for FreeBasic - freebasic.net
_Sugesstion String Split in FB - freebasic.net
_Easy Split Function - freebasic.net
_I need someone to defeat my string splitting algo - freebasic.net

色々なやり方はあるけれど、個人的には、C言語の strtok() を利用して実装してしまうのが簡単そうに見えた。

_C Standard Library Functions - FreeBASIC Wiki Manual | FBWiki
_strtokによるトークンの切り出し ・ ソフトウェアII
_C/C++にsplit関数はなく、代わりにstrtok関数を使う|情報科学を学ぶ大学生のブログ
_<紙>さんLoG FreeBASIC 学習:strtok, 文字列を指定文字で分割

ただ、strtok() は、文字列の中で区切り文字が見つかったら、そこに文字列の終端記号である 0 を書き込んで処理をするらしい。なんだか怖い…。元の文字列のあちこちに終端記号が書き込まれて、元文字列がズタズタになりそうな…。

strtok()で試してみた :

本当に strtok() を使って split() 相当を実装できるのか試してみた。

_strtok_test.bas
#include "crt.bi"

Sub Split(byref text As String, byref delim As String, result() as String)
    var p = strtok(strptr(text), strptr(delim))
    While (p)
        ReDim preserve result(UBound(result) + 1)
        result(UBound(result)) = *p
        p = strtok(NULL, strptr(delim))
    Wend
End Sub

' main

Dim As String text = "this   is   a     test"
Dim words() As String

split(text, " ", words())

Print text
For i As Integer = 0 To UBound(words)
    Print "[" & words(i) & "]"
Next i

Print "    ...success."

fbc strtok_test.bas でコンパイル。実行結果は以下。
> strtok_test.exe
this   is   a     test
[this]
[is]
[a]
[test]
    ...success.

ちゃんと分割できているように見える。区切り文字として扱う空白が連続して入っていても、イイ感じに処理してくれた。

少し解説。FreeBASIC でC言語が持ってる関数を使いたい場合は、#include "crt.bi" を最初のほうで入れておく。

配列の長さ(数?)を可変にしたい時は、ReDim を使う。

ReDim を使う際の注意。ReDim preserve とすれば、それまで格納した値を保持したまま長さを変更してくれるけど、preserve をつけないとそれまで格納した値を全部クリアした状態で配列の長さを変更してしまう。自分、格納した値が全部消えてるから変だなおかしいなと悩んでたら、単に preserve をつけるのを忘れてたという…。

UBound() を使うと、その配列変数の最後の添え字(?)、配列のインデックスの最大値を取得できる。

split()では解決しなかった :

ひとまず split() を使えばどうにかなるだろうと思ってたけど、ちと考えが甘かった。

例えば、以下のような文字列なら、split() で十分処理できそうなのだけど…。
char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15
> strtok_test3.exe
char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15
[char]
[id=32]
[x=250]
[y=22]
[width=5]
[height=5]
[xoffset=-2]
[yoffset=21]
[xadvance=7]
[page=0]
[chnl=15]
    ...success.

以下のような文字列ではよろしくない。
info face="DejaVu Sans" size=24 bold=1 italic=0 charset="ANSI" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2
> strtok_test3.exe
info face="DejaVu Sans" size=24 bold=1 italic=0 charset="ANSI" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2
[info]
[face="DejaVu]
[Sans"]
[size=24]
[bold=1]
[italic=0]
[charset="ANSI"]
[unicode=0]
[stretchH=100]
[smooth=1]
[aa=4]
[padding=0,0,0,0]
[spacing=1,1]
[outline=2]
    ...success.

お判りいただけるだろうか。「face="DejaVu Sans"」と区切ってほしいけれど、「face="DejaVu」と「Sans"」になってしまっている。間に区切り文字の空白があるから当たり前だけど。

こういう事例にも対応できるような処理を書くしかないか…。

split()ぐらい標準でつけてほしい :

思考メモ。

FreeBASICの公式掲示板を眺めてたら、「split()が欲しい。標準でつけてくれ」という要望に対して、「俺なら数行のコードで実現できるぞ。だからそんなもん要らねえ」と言い出した人を見かけた。個人的にかなり呆れてしまった。お前のマウンティングで皆が楽になるルートを潰してんじゃねえよ…みたいな。

こんな処理は各人が毎回独自に実装して用意するより標準で持ってたほうがいいに決まってる。どうして他の言語が組み込みで持ってるのかを考えたら…。Perl や Ruby や Python や Java や JavaScript や Go言語が「split」のたった5文字で済ませてることを、FreeBASIC では毎回実処理コードをどこかに書かなきゃいけないとか、そしてそれを当たり前のこととして利用者全員に強制するとかクソ過ぎる…。そういうところが「塵も積もれば」的に効いてきて最後には無駄に長くてゲンナリするソースばかりが目につくようになってプログラミング言語としての魅力も薄れて誰も触らなくなるわけで…。

既に標準状態で山のような .bi が inc/ の中に入ってるんだから、「文字列処理をしたいならコレを include すると楽だよ。ただし速度は期待するな。もっと速いのが欲しかったらその時は自分で書け」ぐらいの感覚で .bi を一つ追加すれば済む話だろうに。いやまあ、テストがどうとか速度がどうとか言い出して標準ライブラリとして加えることをとにかく面倒臭がってる気配も感じたのでその手の .bi が追加されるのは今後も望み薄かもしれないけれど。

そのあたりを考えてるうちに、Python の pip、Ruby の gem、Perl のCPAN等々はよく考えられているなと思えてきた。「こういうライブラリが必要だな」となったらコマンド一つでライブラリをインストールできてしまう。もし、そういう状況が用意されているなら、標準ライブラリで持たなくてもまあいいかな、とも思えてくる。しかし、FreeBASIC の場合はそういうツールが無いのだから、最初から標準で色々入れておくしかないだろうと思うのだけど。

まあ、今の時代にBASIC言語を名乗るソレにどうこう言ってみても、というところもあるのだけど…。

一発で exe が作れて、しかも処理速度がそこそこ速い点に魅力を感じたから FreeBASIC を触ってみているけれど、フツーは Python あたりを使うよな…。あらゆるOSにPythonが標準でインストールされてたらいいのに…。いや、それはそれでPythonのバージョンの違いで余計なトラブルが起きそう…。

思考メモです。オチは無いです。

2024/02/15追記。安易に「.biを1つ追加すれば」などと書いてしまったけれど。考えてみたらマルチバイト文字列への対応が面倒なのではと思えてきた…。

#2 [digital] 親父さんがワイヤレススピーカーを購入

親父さんが、首に引っ掛けるタイプのワイヤレススピーカーを購入したらしい。しかし、「音が全然聞こえない」「TVの音が聞こえない」と騒ぎ始めた。

購入した製品の型番は、audio-technica AT-NSP300BT。

_AT-NSP300BT|AVアクセサリー:スピーカー|オーディオテクニカ

説明書を見たら、予想通り、Bluetooth接続のワイヤレススピーカーだった。ウチの茶の間に置いてあるTVは、TOSHIBA REGZA Z9000 という古い製品なので、Bluetooth送信機能なんてついてるはずもなく。親父さんに、「貴方は送信機を持ってないのに、今回受信機だけを買ってしまったんですよ」と説明したところ、ようやく状況が理解できた模様。というか、「Bluetooth」という単語すら知らないのにいきなり思い付きでワイヤレス製品を購入とか…。なんだかな…。

ウチの中にあるデジタル機器で、Bluetoothを持ってる機器と言えば、スマホかノートPCぐらいしかない。親父さんのスマホとワイヤレススピーカーをペアリングして、たしかに音が鳴ることは確認できた。

しかし、親父さんは、「スマホの音が聞けても意味がない」「TVの音が聞きたい」と言い出した。となると、Bluetooth送信機(トランスミッター)を購入しないといけない…。

件のワイヤレススピーカーは、対応コーデックが aptX、aptX Low Latency (aptX LL)、SBC。さて、コレって何だろう…。自分、Bluetooth関連機器なんてほとんど持ってないから、何が何やら。

_【iPhone 15で高音質&低遅延!】Bluetoothイヤホンの「aptX LL」って何?コーデックとは?|radius|ラディウス株式会社 オーディオ・デジタル音響機器・Lightning製品メーカー
_Bluetoothオーディオの品質向上に貢献するQualcomm aptX - 半導体事業 - マクニカ
_【検証】Bluetoothイヤホンはゲームに使える?遅延を測定・比較してみた!【aptXLL】 | いろいろてすと中

上記のページによると、SBCが昔からあるコーデックだけど、人間の耳では聞こえづらい部分をマスキングしてるそうで、音源が mp3だったりした場合は二重にマスキングされることになって音質的によろしくないらしい。aptX はマスキングしていないので音質が良いのだとか。

更に、aptX に対して遅延を少なくしたのが aptX LL らしいので、TVの音を聞くには aptX LL 対応品を買っておけば良い…のかな? それでも数フレームは遅れて聞こえるらしいけど…。

音楽だけ聴く分には、Bluetooth の遅延云々はどうでもいいのだろうけど、TVを見ながら音声を聞く場合は、遅延云々は無視できない。画面の中の人が口をパクパクしてしばらくしてから音声が聞こえてきたら使い物にならない。全ての番組の全ての出演者がプチいっこく堂さんになってしまう。

そんなわけで、aptX LL対応の Bluetoothトランスミッター(送信機)をググってみたのだけど、種類が少ない…。

考えてみれば、当然かもしれない。例えばスマホの場合は Bluetooth機能を最初から持ってるし、今時店頭で販売されてる高機能TV(REGZA等)も Bluetooth機能を持っていたりするようなので、今から新規にその手の機器を購入して使う場合、別途 Bluetoothトランスミッターの購入が必要になることはまずないのだろう。最初からその機器に入っちゃってるから。

つまるところ、Bluetoothなんて持ってない昔のデジタル機器をどうにかしてワイヤレス対応にしたい、という場面でのみ、Bluetoothトランスミッターが必要になるわけだから、そりゃ種類も少なくなるよなと…。製品ジャンル自体がマイナーになってしまったのだろう…。

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

#1 [basic] FreeBASICでsplit()を使いたい。その2

_昨日、 FreeBASIC で split() を ―― 文字列を区切り文字で分割して配列にする関数の実装を試してみたけれど。ダブルクオーティション(")が入ってくる場合に望んだ形にならなかったので、そのあたりをどうにかできないかと試していた。

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

ソース :

一応処理は書けたような気がする。もはや split() の動作ではなくなっているけれど…。

実処理は以下。

_splitdq.bi
' License: CC0 / Public Domain

#ifndef __SPLITDQ__
#define __SPLITDQ__

Sub Splitdq(byref text As String, byref delim As String, result() as String)
    If text = "" Then Return
        
    Dim As ubyte ptr cp = strptr(text)
    Dim As ubyte ptr dp = strptr(delim)
    Dim As Integer slen = Len(text)
    Dim As Integer pi = 0
    Dim As Integer i = 0
    Dim As Boolean dqfg = False
    Dim As String s = ""
    
    Do
        If dqfg Then
            ' check double quotation pair
            If (*cp = 34) Then dqfg = False
        ElseIf (*cp = 34) Then
            ' found double quotation
            dqfg = True
        ElseIf (*cp = *dp) Then
            ' found delimiter
            s = Mid(text, pi + 1, (i - pi))
            pi = i + 1
        End If

        cp += 1
        i += 1

        If i >= slen Then
            s = Mid(text, pi + 1, (i - pi))
            pi = i + 1
        End If
        
        If s <> "" Then
            ReDim preserve result(UBound(result) + 1)
            result(UBound(result)) = s
            s = ""
        End If
    Loop While (i < slen)
End Sub

#endif


テストサンプルは以下。

_test_splitdq.bas
#include "splitdq.bi"

Dim As String text
'text = "char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15"
text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2"

Dim words() As String
splitdq(text, " ", words())

Print text
For i As Integer = 0 To UBound(words)
    Print "[" & words(i) & "]"
Next i
Print "    ...success."

fbc test_splitdq.bas でコンパイル。実行結果は以下。

> test_splitdq.exe
info face="DejaVu Sans" size=24 bold=1 italic=0 charset="ANSI" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2
[info]
[face="DejaVu Sans"]
[size=24]
[bold=1]
[italic=0]
[charset="ANSI"]
[unicode=0]
[stretchH=100]
[smooth=1]
[aa=4]
[padding=0,0,0,0]
[spacing=1,1]
[outline=2]
    ...success.

ダブルクオーティションで挟まれた部分については区切り文字を無視するようにできた。

ただ、バグがありそうな気がする…。こういう文字列を渡すとおかしくなる、という場面がありそうな…。

処理の仕方も無駄がありそう。少なくとも処理速度は期待できない予感。

でもまあ、今回やりたいことはできそうだから、これでもいいか…。

split()の実装が気になってきた :

一般的に split() ってどんな感じに実装されたりするのか気になってググってみた。

以下の事例を見て目ウロコ。元文字列の最後に区切り文字を追加してから処理を始めることで、ループがスッキリしてる…。その手があったか…。

_Split the string into substrings using delimiter - GeeksforGeeks


FreeBASIC版を書いてみた。ダブルクオーティションに対応させようとして、記述が長くなってしまったけれど…。

_splitstringa.bi
#ifndef __SPLITSTRINGA__
#define __SPLITSTRINGA__

Sub splitStringA(ByVal text As String, ByVal delim As String, result() as String, ByVal chk_dbqt As Boolean = False)
    If text = "" Then Return
    Dim As String src = text & delim
    Dim As String word = ""
    Dim As Boolean founded_dbqt = False

    For i As Integer = 0 To Len(src) - 1
        If founded_dbqt Then
            word &= Chr(src[i])
            If src[i] = 34 Then founded_dbqt = False  ' found double quote
        Else
            If src[i] <> delim[0] Then
                ' not delimiter
                word &= Chr(src[i])
                If chk_dbqt And src[i] = 34 Then founded_dbqt = True  ' found double quote
            Else
                ' found delimiter
                If word <> "" Then
                    ' word is not empty. save word to array
                    ReDim preserve result(UBound(result) + 1)
                    result(UBound(result)) = word
                    word = ""
                End If
            End If
        End If
    Next i
    If word <> "" Then
        ' word is not empty. save word to array
        ReDim preserve result(UBound(result) + 1)
        result(UBound(result)) = word
    End If
End Sub

#endif


使用サンプルは以下。fbc test_splitstringa.bas でコンパイル。

_test_splitstringa.bas
#include "splitstringa.bi"

Dim As String text
text = "char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15"
'text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2"

Dim words() As String
splitStringA(text, " ", words())
'splitStringA(text, " ", words(), True)  ' check double quote

Print "[" & text & "]"
For i As Integer = 0 To UBound(words)
    Print i & ": [" & words(i) & "]"
Next i
Print "end."


test_splitstringa.exe を実行。たしかに分割できている。

> test_splitstringa.exe
[char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15]
0: [char]
1: [id=32]
2: [x=250]
3: [y=22]
4: [width=5]
5: [height=5]
6: [xoffset=-2]
7: [yoffset=21]
8: [xadvance=7]
9: [page=0]
10: [chnl=15]
end.


それとは別の実装事例。FreeBASICのWikiを眺めてたら、そもそも split() の実装サンプルが紹介されてた。まさかそんなページで紹介されていたとは…。

_Passing Arrays to Procedures - FreeBASIC Wiki Manual | FBWiki

内容的には、instr() で区切り文字を探して位置を求めて、頭からそこまでの文字列を配列に記録したら、その文字列分を元文字列から削除して処理を続けていた。サブルーチンに引数として元文字列を渡す際、値渡しで渡してるから、元文字列を破壊しても大丈夫ということなのだろう…。もしかすると strtok() を使った事例と考え方は似ているのかもしれない。

これも手元で動作確認してみた。

_splitstringb.bi
#ifndef __SPLITSTRINGB__
#define __SPLITSTRINGB__

Sub splitStringB(ByVal src As String, ByVal delim As String, result(Any) As String)
    Do
        Dim As Integer i = InStr(1, src, Chr(delim[0])) ' search delimiter
        ReDim Preserve result(UBound(result) + 1)
        If i = 0 Then
            ' not found delimiter. exit loop
            result(UBound(result)) = src
            Exit Do
        End If
        result(UBound(result)) = Left(src, i - 1)  ' save word to array
        src = Mid(src, i + 1)  ' delete word
    Loop
End Sub

#endif


使用サンプルは以下。fbc test_splitstringb.bas でコンパイル。

_test_splitstringb.bas
#include "splitstringb.bi"

Dim As String text
text = "char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15"
'text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2"

Dim words() As String
splitStringB(text, " ", words())

Print "[" & text & "]"
For i As Integer = 0 To UBound(words)
    Print i & ": [" & words(i) & "]"
Next i
Print "end."


ただ、この版は、区切り文字が連続して並んでる際、それぞれを配列変数に入れてしまう。

> test_splitstringb.exe
[char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15]
0: [char]
1: [id=32]
2: []
3: []
4: [x=250]
5: []
6: []
...
34: [page=0]
35: []
36: [chnl=15]
end.

もしかすると文字列を切り出した後、文字列が空かどうかを調べて、空じゃなければ配列に格納するようにすればいいのだろうか…?

マルチバイト文字列対応は大変そう :

1文字=1byteの文字列なら処理が書けたので、せっかくだからマルチバイト文字列にも対応させられないものかと思い立ったのだけど。String を、マルチバイト文字列を格納する WString に書き換えれば済むかなと思ったらコンパイルすら通らず。

WString や ZString は、扱いが面倒らしい…。

_FreeBASICのワイド文字列が使いにくい件 - 飴屋ぷろじぇくと
_FreeBASIC Compiler - 飴屋ぷろじぇくと
_Problem passing and copying fixed length WSTRING array - freebasic.net

あらかじめ領域を固定で確保しておかないといけないとか、サブルーチンや関数に WString の配列変数を渡せないとか…。これはなかなか大変…。

区切り"文字列"にすると大変 :

区切り"文字"で分割する分にはまだこうして書けるけど、区切り"文字列"で分割できるように対応しようとするとちょっと大変そう。文字列検索/文字列探索アルゴリズムの話になって、KMP法とかBM法とかが出てくる…。

_文字列探索アルゴリズムとは?KMP法やBM法について解説
_文字列検索アルゴリズムについて #アルゴリズム - Qiita

#2 [basic] FreeBASICでBMFontを描画したい

先日、FreeBASICを使って、カスタムフォントや等幅ビットマップフォントを利用してみたけれど。

_FreeBASICでCustom fontを使ってみたい - mieki256's diary
_FreeBASICで等幅ビットマップフォントのみを使って描画 - mieki256's diary

この際せっかくだから BMFont も利用できるようにしてみたいと思い立ってしまって、そのあたりを試してた。

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

BMFontについて :

BMFont というのは、ビットマップフォントのフォーマットの一種というか…。ビットマップフォントがギッチリと敷き詰められた画像と、どこに何の文字があるのか記述したテキストファイル(.fnt)を利用して、文字を描画する仕組み。

_BMFont - AngelCode.com

以下の画像とテキストファイルを見てもらえばなんとなく分かるだろうか。

bmfont_dejavu_0.png

_bmfont_dejavu.fnt
info face="DejaVu Sans" size=24 bold=1 italic=0 charset="ANSI" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2
common lineHeight=24 base=20 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
page id=0 file="bmfont_dejavu_0.png"
chars count=95
char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15
char id=33   x=71    y=89    width=8     height=20    xoffset=0     yoffset=2     xadvance=9     page=0  chnl=15
...
char id=125  x=51    y=0     width=14    height=24    xoffset=0     yoffset=1     xadvance=14    page=0  chnl=15
char id=126  x=208   y=105   width=17    height=9     xoffset=0     yoffset=8     xadvance=17    page=0  chnl=15
kernings count=86
kerning first=45  second=84  amount=-3  
kerning first=45  second=86  amount=-1  
...
kerning first=89  second=46  amount=-3  
kerning first=118 second=44  amount=-1  

.fntファイルには、各文字について、x, y, w, h 等々が列挙されてる。各パラメータがどこに絡んでいるかは、以下のドキュメントが参考になりそう。

_How to render text - Bitmap Font Generator - Documentation
_File format - Bitmap Font Generator - Documentation
_Bitmap Font Generator - Documentation

スクリーンショット :

まずは実験結果のスクリーンショットを。

ss_bmfont_dejavu.png

ss_bmfont_roboto.png

ss_bmfont_vegur.png


等幅フォントと比べると、それらしい見た目になっている。

ただ、実装してみたものの、カーニングが妙な感じで悩んでしまって…。カーニングというのは、特定の文字と文字が並んだ時に間隔を調整して見た目の不自然さを減らす方法のこと。自分が仕様を正しく解釈できてない気配がしてきたので、今回はデフォルトでカーニング情報を利用しない状態にしておいた。一応、bmfont.drawstring() にフラグを指定すれば有効になるようにはしてある。

一部の文字が重なってしまっているのは、BMFont作成に使ったツール上で各パラメータの指定が適切ではなかったか、もしくは今回書いた処理にバグがあるのかもしれない。自分がBMFontの仕様を勘違いしている可能性は否定できない。

ちなみに、BMFont作成に使えるツールについては以前の日記にメモしてあった。

_mieki256's diary - ビットマップフォント画像を作れるツールについてメモ

ソース :

ソースは以下。

実処理部分。

_bmfont.bi


テストサンプルその1。

_simple_sample.bas
#include "bmfont.bi"

Dim As bmfont bmfnt
bmfnt.loadInfo("bmfont_dejavu.fnt")

ScreenRes 320, 240, 32
bmfnt.loadImage()

bmfnt.drawstring(10, 10, "Hello World !")

sleep
bmfnt.destroy()
  • #include "bmfont.bi" で bmfont というクラス相当(構造体 + 関数)が使えるようになる。
  • bmfont.loadInfo() で .fntファイルを読み込んで解析して記録。
  • bmfont.loadImage() で .fntファイルに書かれた画像ファイル(拡張子を .bmp に変更)をロード。
  • bmfont.drawstring() でスクリーンに文字を描画。
  • bmfont.destroy() でBMFont画像を破棄。


テストサンプルその2。コマンドラインオプションで .fntファイルを指定したり、.fntファイルの解析結果が上手くできているか確認するためのダンプ処理をつけてみた。

_test_bmfont.bas
Usage: test_bmfont.exe [INPUT.fnt] [--dump]


以下でコンパイル。
fbc simple_sample.bas
fbc test_bmfont.bas


今回利用したBMFontは以下。

_bmfont_dejavu.fnt
_bmfont_dejavu_0.bmp

_bmfont_roboto.fnt
_bmfont_roboto.bmp

_bmfont_vegur.fnt
_bmfont_vegur_0.bmp

各フォントの入手先と、フォントのライセンスについては、以下に書いておいた。

_readme.md


ファイル一式もzipにして置いておきます。自分が書いた部分は CC0 / Public Domain にしておくので使えそうなら自由に使ってください。

_fb_bmfont_20240215.zip

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

#1 [basic] FreeBASICでsplit()を使いたい。その3

_昨日、 FreeBASIC上で split() 相当の処理を書いて実験していたけど、今日も少し実験。環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。

フツーの言語なら組み込みで持ってるメソッドをわざわざ実装するとか、これは車輪の再発明に近いのではないか、無駄なことをしているのではないかと思ったりもするのだけど、そこからの流れで FreeBASIC の WString の不便さや文字列検索アルゴリズムのアレコレを知れたので、一応少しは勉強になったからまあいいか、と…。

空文字を除外するように修正 :

FreeBASICの公式Wikiで紹介されていた実装事例は、区切り文字が連続していた際に、空文字まで配列に登録されてしまっていた。

_Passing Arrays to Procedures - FreeBASIC Wiki Manual | FBWiki
_配列を手続きに渡す - ProPgPassingArrays

そのあたりをちょっとだけ修正してみた。

_splitstringb2.bi
#ifndef __SPLITSTRINGB2__
#define __SPLITSTRINGB2__

Sub splitStringB2(ByVal src As String, ByVal delim As String, result(Any) As String)
    Do
        Dim As Integer i = InStr(1, src, Chr(delim[0])) ' search delimiter
        If i = 0 Then
            ' not found delimiter. exit loop
            ReDim Preserve result(UBound(result) + 1)
            result(UBound(result)) = src
            Exit Do
        End If

        Dim As String word = Left(src, i - 1)
        If word <> "" Then
            ReDim Preserve result(UBound(result) + 1)
            result(UBound(result)) = word ' save word to array
        End If
        src = Mid(src, i + 1)  ' delete word
    Loop
End Sub

#endif


以下、使用サンプル。fbc test_splitstringb2.bas でコンパイル。

_test_splitstringb2.bas
#include "splitstringb2.bi"

Dim As String text
text = "char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15"
'text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2"

Dim words() As String
splitStringB2(text, " ", words())

Print "[" & text & "]"
For i As Integer = 0 To UBound(words)
    Print i & ": [" & words(i) & "]"
Next i
Print "end."

test_splitstringb2.exe の実行結果。
> test_splitstringb2.exe
[char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15]
0: [char]
1: [id=32]
2: [x=250]
3: [y=22]
4: [width=5]
5: [height=5]
6: [xoffset=-2]
7: [yoffset=21]
8: [xadvance=7]
9: [page=0]
10: [chnl=15]
end.

空文字を除外できている。

ちなみに、この処理の中で使ってる instr() は処理が遅いから、もし高速化を試みるなら使わないようにするべき、という主張も見かけたのだけど。どんな処理をしているのかソースが分かりやすくなるし、マルチバイト文字列への対応も楽になりそうだから、使ってしまってもいいのではないかと思ったりもした。膨大な長さの文字列を大量に処理しなければいけない場面が出てきたら、その時にまた考えよう…。

WStringで同じ処理をしてみる :

前述の処理は、1文字=1byteとして扱う String を使った時の事例だけど。マルチバイト文字列を扱える WString を使って同じことをしてみたい。

ただ、FreeBASIC の WString は以下の制限があるので、String を WString で置換すれば済むわけでもなく。
  • 可変長文字列は使えない。固定長文字列(事前に確保した領域サイズから変更できない)になる。
  • サブルーチンや関数に配列変数を渡せない。

とりあえず、以下の方針で書いてみた。
  • 事前に確保する領域は、処理をするにあたって十分足りるであろう大きな領域にしておく。今回の場合、配列で、256文字 x 256個あれば大丈夫かな…と…。
  • サブルーチンに入れずに マクロ機能 (#macro - #endmacro) で代替してみる。

.basファイルは SJIS で書いてみた。

_test_splitw.bas
Dim As WString * 1024 text, delim
text = "新しい     朝が来た   希望の    朝だ"
delim = " "
Dim result(256) As WString * 256  ' max 256 length x 256

' ----------------------------------------
#macro splitW( s, d, a )
Dim As WString * 1024 src = s
Dim As Integer n = 0
Do
    Dim As Integer i = InStr(1, src, WChr(d[0])) ' search delimiter
    If i = 0 Then
        ' not found delimiter. exit Loop
        If src <> "" Then
            a(n) = src
            n += 1
        End If
        Exit Do
    End If

    Dim As WString * 256 word
    word = Left(src, i - 1)
    If word <> "" Then
        a(n) = word ' save word to Array
        n += 1
    End If
    src = Mid(src, i + 1)  ' delete word
Loop
#endmacro
' ----------------------------------------

splitW(text, delim, result)

Print "[" & text & "]"
For i As Integer = 0 To UBound(result)
    If result(i) = "" Then Exit For
    Print i & ": [" & result(i) & "]"
Next i
Print "end."

fbc test_splitw.bas でコンパイル。実行結果は以下。

> test_splitw.exe
[新しい     朝が来た   希望の    朝だ]
0: [新しい]
1: [朝が来た]
2: [希望の]
3: [朝だ]
end.

区切り文字は半角空白。分割できている。区切り文字が連続しているところも除外できている。

ただ、これはマクロなので…。サブルーチン化できたらいいのだけどな…。

ユーザ定義型を使うのも手だろうか :

FreeBASIC にはユーザ定義型という機能もある。C言語で言うところの構造体だろうか。

そのユーザ定義型の中に、WString を格納してしまえば、String のような感覚で使える状態にできなくもないらしい。

おそらくはそういう方法で用意された、DWString という型が紹介されていた。

_DWSTRING.bi - Dynamic null terminated unicode string data type - freebasic.net

これを使えば、マルチバイト文字列に対して処理する際も、String のような感覚で書ける可能性がありそう。たぶん。ただ、これは Windows限定のライブラリに見える…。Linuxの場合はどうすればいいのか…。

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

#1 [windows] 録画用PCにNTPクライアントをインストールした

Windows7で動かしている録画用PCの日付と時刻が、いつの間にか約1日半ほど遅れていて、予約録画がかなり失敗していた。見てみたかったアニメ映画(かがみの孤城)が録画できてなくて、結構ショック。

またこんな目に合うのは悲しいので、NTPクライアント設定を見直したいけれど、Windowsに標準で入っているNTPクライアントは各設定項目が一体何と関係しているのかよく分からなくて…。機能はあるのに設定方法がチンプンカンプン。大体にして、レジストリを弄れだの、サービス設定を変更せよだの、タスクスケジューラを弄れだの…。

いっそのこと、別途、NTPクライアントソフトをインストールしてしまったほうが楽だったりしないかと思えてきた。

昔は桜時計というNTPクライアントソフトが有名だったけど、古いソフトなのでWindows7以降で動くのか不安。他のソフトを探してみたら、Windows7にも対応と明記されている NetTime というNTPクライアントを見かけた。これにしてみよう…。

NetTimeについて :

_NetTime - Network Time Synchronization Tool
_Windows7時刻合わせにフリーソフトNetTimeを使ってみる:メールのプロの独り言
_正確な時刻を表示できるフリーソフトのSNTPクライアント/サーバ「NetTime」 - GIGAZINE

NetTimeSetup-314.exe をDLして実行してインストール。今回は、C:\Tools\NetTime\ にインストールしておいた。途中で、「サービスとして登録するか」と尋ねてきたのでチェックを入れておいた。

実行するとタスクトレイにアイコンが表示されるので、右クリックして Property を選択。Settings を選んで設定。

NTPサーバは以下にした。ウチで使っているプロバイダはぷらら(東日本)なので、plalaのNTPサーバも設定。
ntp.nict.jp
ntp1.plala.or.jp
ntp2.plala.or.jp

_NICT 公開 NTP サービス
_各種設定項目一覧|インターネットの接続方法|ぷらら

他の設定は以下。
  • 10分毎に時間合わせ。
  • 失敗時は1分毎に4回再挑戦。
  • Max Free Run は 24年(years)に。
  • 2秒(seconds)ずれてたら調整する。(Adjust System Time)

nettime_on_windows7.png

電池は切れてなかった :

こんなに時間がずれるのは、M/B上のコイン型電池(CR2032)が切れてるのではないかという疑念が湧いた。交換しておいたほうがいいのかもしれない…。

PCケースを開いて電池交換してみたけれど、元々入っていた CR2032 をテスターで測ってみたら全然電池切れしてなかった。+3V どころか +3.5Vぐらいはあるように見えた。

となると、電池切れでこうなってしまったわけではないのだな…。何かの拍子に Windows7 のNTPクライアント機能がずっと呼ばれないか、もしくは呼んでも働いてくれない状態に陥っていて、こんな事故が起きてしまったのだろう…。

スリープ解除時に時刻合わせ :

スリープから復帰/スリープ解除時に、NTPで時間合わせ/時刻同期することはできないのだろうか。

ググってみたら、できるっぽい。

_スリープ解除時に時刻が合うようにタスクで設定してみた! - A2-blog

上記のページを参考にして、一応念のため、スリープ解除時に時刻合わせが実行されるように、タスクスケジューラでトリガーを追加してみた。

  1. タスクスケジューラ起動。
  2. Microsoft → Windows → Time Synchronization
  3. SynchronizeTime を選択 → プロパティ。
  4. トリガー → 新規。

新規作成したトリガーの設定は以下。
  • タスクの開始 : イベント時
  • ログ : システム
  • ソース : Power-Troubleshooter
  • イベントID : 1
  • 遅延時間 : 30秒

また、スタートアップ時にも時刻合わせするようにトリガーを追加してみた。
  • トリガー → 新規。
  • スクの開始 : スタートアップ時
  • 遅延時間 : 1分

これで効果は出てくるのかどうか…。さてはて…。

#2 [pc] ワイヤレスキーボードが反応しなくて悩んだ

録画用PCに繋いでいた、トラックボール付きワイヤレスキーボード ENERMAX AURORA Micro Wireless KB008W-S が反応してくれなくて結構悩んだ。電池をテスターで測っても切れてないのに…。ずっと放置してたから壊れてしまったのかと…。

_ENERMAX アルミ製ワイヤレスキーボード AURORA Micro Wireless トップ
_価格.com - ENERMAX AURORA Micro Wireless KB008W-S 価格比較
_KB008W-JP_20091005.pdf

マニュアルを眺めると、受信機側の横のボタンを押して、受信機が青く点滅しているうちに、キーボード側の connectボタンを押す、と書いてあるのだけど、そんなボタンは無い…。

_KB008W_FAQ
Q : 取扱説明書のコネクト(connect)設定方法にある【点滅中にキーボードの「connect」ボタンを押します。】とありますが、キーボードには、リセット(RESET)ボタンしかありません。コネクト(connect)ボタンはどこにありますか?

A : こちらについては、リセット(RESET)ボタン=コネクト(connect)ボタンとなります。リセット(RESET)ボタンをご使用いただきコネクト設定を行ってください。

KB008W_FAQ より


そういうオチか…。

受信機側のボタンを押してからキーボード側のRESETを押したり、順番を逆にしたりして何度も試していたら、そのうち反応してくれた。良かった。壊れてなかった。

ただ、このキーボード、ゴツイのだよな…。もっと小さいキーボードが欲しい気もしてきた…。

#3 [digital] 親父さんがBluetoothトランスミッターを購入

先日親父さんが、Bluetooth接続のワイヤレススピーカー audio-technica AT-NSP300BT を購入して「音が鳴らない」「TVの音を聞きたい」と騒いでたけど、その後、Bluetoothトランスミッター(送信機)を注文して、昨日届いたそうで。

メーカ名/型番は、Twelve South AirFly SE。ヨドバシカメラで5,400円。

_Twelve South AirFly SE | FOCALPOINT DIRECT _AirFly SE Quick Start Guide _AirFlySE_ProductManual - AirFlySE_ProductManual.pdf _twelveSouth AirFly SE Bluetooth トランスミッター ユーザーガイド

一応スペックを引用してメモ。
aptX-LL対応だから、遅延は少ない、はず。たぶん。

ペアリングはできた :

トランスミッター側の電源を入れると、LEDが白とオレンジで交互に点滅する。その間にペアリングをするらしい。

ワイヤレススピーカ AT-NSP300BT 側は、スマホと接続した状態を覚えてしまっていたようで…。強制ペアリングモードとやらを試してみた。音量調整ボタン(「+」と「−」)を両方6秒間押し続けることで強制ペアリングモードになる。この状態でBluetoothトランスミッターの電源を入れたら、ワイヤレススピーカーから「接続できました」と音声が聞こえた。その状態でトランスミッターをTVのステレオイヤホンジャックに差し込んだ。

最初は音が聞こえなかったけど、トランスミッター側、ワイヤレススピーカ側、両方の音量ボタンを操作して音量を上げていったらTVの音が鳴り始めた。

ただ、最大音量にしても、さほど大きな音にならなくて…。耳が遠くなったお年寄りも使うのであれば、もうちょっと大きい音が欲しいかも…。

その他の操作をメモ :

おそらく親父さんのことだからそのうち操作方法を忘れて「これどうやって使うんだ」と尋ねてくるだろうから自分のほうでもメモしておく…。というか自分も操作方法忘れそうだし…。
  • 電源残量は、音量ボタンの「+」をダブルクリック。4回点滅=100%、3回点滅=75%、2回点滅=50%、1回点滅=25%。
  • 充電は、USB-C - USB-Aケーブルを使う。USB-C側を本体に差す。

audio-technica AT-NSP300BT 側の操作方法もメモしておく。
  • 電源ONは、電源ボタンを1秒押し続ける。
  • 電源OFFは、電源ボタンを3秒押し続ける。
  • 強制ペアリングモードにするには、音量ボタン「+」「−」を両方共6秒間押し続ける。

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

#1 [linux] 200円PCケース機のメンテナンス中

100円ショップで購入したプラスチックのケース x 2 で作ったPCケースに入れてあるPCのメンテナンスをしていた。スペックは以下。


ずっと放置していたからM/B上のコイン型電池(CR2032)が切れているんじゃないかと交換してみたけど、交換前の電池はまだ3.0Vを少し下回る程度だった。

それはともかく。何故かデスクトップ画面が表示されない…。sshでログインはできるのだけど…。

別のOSを入れ直すことにした :

デスクトップ画面が出せないのでは色々困る場面もあるかもしれない。こんな古いハードに Ubuntu Linux 22.04 LTS をインストールしているのが良くないのかなと思えてきたので、Debian Linux 12 Bookworm をインストールすることにした。

debian-12.5.0-amd64-netinst.iso (629MB) を入手して、Windows10 x64 22H2 + UNetbootin 702 で 8GBのUSBメモリに iso を書き込んで、USBメモリから起動。HDDのパーティションを全部削除してインストール。

しかし、Debian 12 も、デスクトップ画面が出ない…。いや、640x480 の画面解像度なら表示されるのだけど、その解像度以外は選べない。この M/B はVIAの統合ビデオチップが載っているので、xserver-xorg-video-openchrome が入れば画面解像度も変更できるようになるだろうとインストールしてみたのだけど。

_Debian -- bookworm の xserver-xorg-video-openchrome パッケージに関する詳細

このパッケージをインストールしてしまうと、DM(デスクトップマネージャ)の lightdm すら起動しない状態になってしまう。アンインストールすれば 640x480 の画面が表示される。それ以外の解像度はやはり選べないけど。

原因を調べないと…。

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

#1 [linux] 200円PCケース機のメンテナンス中。その2

_昨日 に続いて、200円PCケース機を ―― M/B : MSI K8MM-V のPCのメンテナンスをしている。Ubuntu Linux 22.04 LTS や Debian Linux 12 をインストールしてもデスクトップ画面が表示されない問題で悩んでた。

VIAビデオチップ搭載M/BはDebian 11までにとどめておいたほうがいいのかも :

MSI K8MM-V は、VIAの統合ビデオチップ S3 UniChome Pro が載っている。めっちゃ古い。

Ubuntu Linux 22.04 LTS は昔のハードを切り捨てているのではないかと疑って、Debian Linux 12 (debian-12.5.0-amd64-netinst.iso) をインストールしてみたけれど、そちらもデスクトップ画面が出てこない。正確には、xserver-xorg-video-openchrome をインストールしてしまうと、デスクトップマネージャ lightdm も gdm3 も表示できない状態になって、そこから先のデスクトップ画面も当然出てこない。xserver-xorg-video-openchrome を入れなければ640x480の解像度で表示される。でも、解像度は変更できない。

ググっているうちに、Debian 12 をベースにしている antiX 23 でもデスクトップ画面が出ないという事例を見かけた。その中で、「Debian 12はVIAのビデオチップ群を切り捨てた/サポート対象外にしたとしか思えない」との推測を目にした。もしそうならば、Debian 12以外を試してみれば何か分かるだろうか。

_antiX-23 installation image boot problem

試しに、KNOPPIX 9.1 をUSBメモリから起動したところ、そちらはデスクトップ画面が表示された。

KNOPPIX 9.1 は Debian 11 bullseye をベースにしているディストリビューション。つまり、Debian 12 はダメでも、Debian 11 なら画面が出る可能性が高い…? Debian 11 x64 (amd64) をインストールすれば状況が変わる?

_Index of /cdimage/archive

debian-11.9.0-amd64-netinst.iso (389MB) を入手して、Rufus 4.4 Portable で8GBのUSBメモリに書き込み。USBメモリから起動してインストール。

Debian 11 (Kernel: 5.10.0-28-amd64) なら、xserver-xorg-video-openchrome 1:0.6.0-4 をインストールしても、画面が出るし、解像度も選べる。

どうやら、Debian 12 からVIAのビデオチップ群がサポート対象外になったというのは本当らしい…。VIAのビデオチップが載ってるM/Bでは、Debian 11 が、最後に動かせるOSということになるのだろうか…?

いやまあ、詳しい人なら適切な設定をして問題を回避できるのかもしれないけれど、少なくとも今までのように、OSをインストールすればすぐに画面が出る状況ではないようだなと…。

VIAビデオチップ使用時の各種設定 :

VIAのビデオチップ利用時、試せる設定がいくつかあるらしいのでメモ。

_Via Technologies - ArchWiki
_Via Technologies - ArchWiki
_Booting Lubuntu/Ubuntu from USB on an HP 2133 Netbook results in weird screen

tightvncserverをインストール :

VNCを使いたくて tigervnc-standalone-server (1.11.0+dfsg-2+deb11u1) をインストール(sudo apt install tigervnc-standalone-server)したけれど、利用しようとすると segmentation fault が発生してしまう。

tigervnc-standalone-server をアンインストール(sudo apt purge tigervnc-standalone-server)して、tightvncserver (1:1.3.10-3) をインストールしたところ、まだそちらのほうが安定して動いてくれた。一般的には、tightvnc のほうが不安定で、tigervnc のほうがまだ安定しているという評を多く見かけたのだけど…。もしかすると VIAビデオチップを使っているので、OpenGLを使うという tigervnc は問題が起きやすいのだろうか。

ちなみに、tigervnc も、tightvnc も、インストールすると vncserver というコマンドが使えるようになった。

tigervnc をインストールした際は、vncserver を実行しても ~/.vnc/xstartup を用意してくれなかったけど、tightvnc なら ~/.vnc/xstartup を用意してくれた。

Lightdmが起動しないときの対処 :

_LightDMが正常に起動しない時の対処: カタカナクライマ
_Ubuntu: LightDM Black Screen When Using SSD Workaround ~ Web Upd8: Ubuntu / Linux blog

ビデオドライバが読み込まれる前に lightdm が起動してしまう時もあるらしい。sleep を入れておくと改善する可能性があるとのこと。

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

#1 [nitijyou] ネズミに悩まされてる

ここ1〜2週間、家の壁の中でねずみが動き回っているようで悩んでる。電線をかじられてるのではないか…火事になるのではないかと不安で仕方ない。しかし一体どこから潜り込んだのか。えてして経路のどこかに糞などの証拠物件を残していくらしいのだけど、家の周りを観察しても見当たらなくて…。

しかし、妹が、「以前は雨戸の内側に巣を作ってたこともあった」と言い出したのがヒントになった。まさかと思って自分の部屋の雨戸をチェックしたら痕跡を大量に発見。糞だらけ…。ということは、ここをうろついている可能性が高いのか…。もっとも、妹曰く、「昔うろついていたヤツが残していったものではないか」という話もあって。もしそうだとしたら今回はここではないということになるのかもしれないけれど。

とりあえず、雨戸の周辺に殺鼠剤を2つほど置いておいた。何度も食べさせないと効果が出てこない殺鼠剤なので、まずはどうにか齧ってほしいものだけど…。

その後、家の裏をチェックしていたら、床下にスイスイと潜り込めそうな穴も発見。フツーは、コンクリートに四角い空気穴(?)が開いていて、そこに金属の柵が埋め込まれていて、ねずみが入れないようにしてあるけれど、その穴だけは何の対策もしていない。猫すら通れそうなぐらいの大きさ。おそらく家を建てた当初から何もつけてなかったのではないか…。もしやここから出入りしてるのでは、と思ったけれど、痕跡は見つからない。とりあえず、バネで扉が閉まるネズミ捕りを置いておいた。ただ、ネズミ捕りに仕掛ける餌が無い…。

別の穴にも不備があった。金属の柵は一応ついているけれど、右端がずれていて、5〜6cmの隙間ができている。ここも通り抜けできそうだよな…。何か策を打たないと…。

ピーナッツを固定する方法が知りたい :

ネズミ捕りの餌としてピーナッツが有効らしいのだけど、ピーナッツは丸くて小さいから、ネズミ捕りにそのままぶら下げるわけにはいかないだろうなと。昔、クリップを加工してピーナッツを固定している画像を見た記憶があるのだけど、ググってみてもそんな画像は出てこない。Googleの検索精度が落ちたせいなのか、元サイトが消滅してしまったのか…。

YouTubeで「mouse trap」のワードで検索したら、餌としてピーナッツバターを使っている事例を多数見かけた。なるほど、それもたしかにピーナッツか…。何かしらにべったりと塗れば餌として利用できそう。ただ、その何かしらは、ある程度の固さが必要だろうけど。引っ張ってポロリと千切れるようでは、罠が発動しないだろうし…。

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

#1 [anime] 動物キャラを人間キャラにすると印象が変わる気がする

思考メモ。というか妄想メモ。

数年前に、「境界戦機」というロボットアニメが放送されていたのです。ロボットのコクピットの中がVR空間になっていて、ロボットのAIが動物キャラになってぷかぷかと空中に浮きながら、パイロットをサポートしていくという。

なかなか面白い設定を考えるものだなと感心していたのだけど。そういえば、その動物キャラが美少女キャラやイケメンキャラだったら印象は変わるのかなあ、などと当時妄想していたことを何故か急に思い出したわけで。

と、そこまで思い出して、これまたふと気が付いた。今現在放送中の「SYNDUALITY Noir」というロボットアニメが、まさにソレじゃないのかと…。コクピットの中はVR空間で、そこに美少女やダンディなおじさんのAIキャラがぷかぷか浮かんでるという…。なるほど、印象が結構変わるなあ、というかやってることが「境界戦機」と全く同じなことに今頃になって気づくぐらい、キャラの見た目が変わることで違う印象になるんだなと今更ながら認識した次第。

たぶんこのあたり、他にも応用できたりするのかなと。既存作品内で、動物キャラとして出ている何かを、人間のような見た目のキャラに置き換えるとどうなるか。あるいは逆に、人間のような見た目のキャラを、動物キャラに置き換えてみるとどうなるか。

例えば。「刀剣乱舞」は日本刀をイケメンキャラに置き換えて見せているけれど。もしもアレが、動物キャラだったら、印象はどのように変わるのか。みたいな。

「ジョジョ」シリーズにはスタンドが出てくるし、そのほとんどは人間の見た目をしているけれど。もし、そこに、必ず動物キャラにしないといけないという縛りがあったとしたら、印象はどのように変わるのか。とか。いやまあ、アレは動物っぽいデザインのスタンドも登場するから、あまり印象は変わらないかも…。

ガンダムが全部動物キャラの見た目だったらどうなるだろう。とか。いや、それたぶんZOIDシリーズになるだけか…。

「鬼滅の刃」の登場人物が全部動物だったらどうなるだろう。とか。印象は変わるかもしれないけれど、あそこまでヒットはしないかな…。

などと考えてみたものの。今時のアニメーターさんって、たぶん動物を描けと言われてもなかなか描けないのではないかという気もしてきた…。大塚康生さん世代じゃないとそういうのは描けないんじゃないか…。でもないか。妖怪ウォッチとかああいうノリで描けなくもないか。ちゃんとした動物をきっちり描けというわけでもないし。

思考メモです。オチはないです。

#2 [nitijyou][neta] 記憶力が怪しくなってきて怖い

今朝、家族と雑談していた際に。自分、何故か「サロンパス」の5文字が思い出せなくて…。15分ぐらい「アレってなんだっけ。何て呼ぶんだっけ」と頑張って「そうだ。サロンパスだ」となんとか思い出せた…。怖い。もうダメかもしれん。

もっとも、自分だけじゃなく、親父さんも怪しい。夕方頃、友人から受け取ったUSBメモリを持ってきて、「これは何だ?」「PCのどこに入れればいいんだ?」「どうやって中身を見るんだ?」と言い出して…。貴方、PCデスクの柱に何個もUSBメモリぶら下げてますやん…。しかも、数日前にお袋さんの妹が家に来て、USBメモリの開き方を教わった時に、その場に居ましたやん…。とうとうUSBメモリが何なのかすら分からなくなってきたのか…。参ったなあ…。いや、自分だって「サロンパス」を思い出せなかったからヤバいんだけど…。

脳にくっつける補助AIが発明されないものかな…。もっとも、AIも困るだろうけど。

人間「ほら、アレだよアレ。四角くて白いやつ。数cmぐらいの大きさで体に貼るアレ」
AI「もしかして…豆腐ですか?」
人間「違う! なんで豆腐を体に貼るんや!」

そんな光景があちらこちらで見られるのかもしれない。AI虐待が問題になりそう。こっちも虐待したくてしてるわけじゃないんだけどな…。

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

#1 [prog] Premakeをインストールした

OpenGL利用時に、テクスチャの読み込みを簡単にしてくれる、soil(Simple OpenGL Image Library)というライブラリがある。

_lonesock.net: SOIL (WebArchive)
_littlstar/soil: Simple OpenGL Image Library
_SpartanJ/SOIL2: SOIL2 is a tiny C library used primarily for uploading textures into OpenGL.


この soil2 をビルドする際に premake なるツールが必要になるらしいので、Windows10 x64 22H2上でインストールしてみた。

_What is Premake? | Premake
_Download | Premake

premake-5.0.0-beta2-windows.zip を入手して解凍。中には複数のファイルが入っていた。
example.dll
example.exp
example.lib
example.pdb
luasocket.dll
luasocket.exp
luasocket.lib
luasocket.pdb
premake5.exe
premake5.pdb

今回は、D:\Dev\premake\ に置いてみた。

premakeadd.bat というbatファイルを作って、使いたい時だけ環境変数PATHに追加するようにしておいた。
@echo off
set PREMAKEPATH=D:\Dev\premake
set PATH=%PREMAKEPATH%;%PATH%
echo Premake enable.
echo Add Path [%PREMAKEPATH%]

さておき。premake を使う時は、premake.exe ではなくて、premake5.exe というファイル名で呼び出せばいいのだろうか。
> premakeadd.bat
Premake enable.
Add Path [D:\Dev\premake]

> premake5 --version
premake5 (Premake Build Script Generator) 5.0.0-beta2

MSYS2のパッケージがあった :

インストールした後で気づいたけど、MSYS2 でパッケージが用意されていた…。そっちをインストールすれば済んだかも…。
$ pacman -S mingw-w64-i686-premake mingw-w64-i686-premake4 mingw-w64-x86_64-premake mingw-w64-x86_64-premake4

$ which premake
which: no premake in 

$ which premake5
/mingw64/bin/premake5

$ which premake4
/mingw64/bin/premake4

$ premake5 --version
premake5 (Premake Build Script Generator) 5.0.0-dev

$ premake4 --version
premake4 (Premake Build Script Generator) 4.3

#2 [prog] soil2をビルド

OpenGLで、テクスチャ画像のロードを楽にする、soli (または soil2) というライブラリがある。

_lonesock.net: SOIL (WebArchive)
_littlstar/soil: Simple OpenGL Image Library
_SpartanJ/SOIL2: SOIL2 is a tiny C library used primarily for uploading textures into OpenGL.

soil2 を Windows10 x64 22H2 + MSYS2 + mingw64 でビルドできるか試してみた。

ビルド :

README.md 通りに作業していけば問題無いけど一応メモ。

Makefile を作るために Premake なるツールが必要になる。MSYS2の場合はパッケージが用意されているのでそれをインストールすればいい。ちなみに、MSYS2 上では pacman -Ss hoge でパッケージの検索ができて、pacman -S hoge でインストールできる。
$ pacman -Ss premake

$ pacman -S mingw-w64-i686-premake mingw-w64-i686-premake4 mingw-w64-x86_64-premake mingw-w64-x86_64-premake4

git clone を使って、github から soil2 のソース一式を入手する。任意のフォルダで以下を実行。
git clone https://github.com/SpartanJ/SOIL2.git
cd SOIL2

premake5を使って、Makefile を作る。
premake5 gmake2

make/windows/ というフォルダが作成されて、その中に色んな Makefile が作成された。中に入る。
cd make/windows

makeを呼び出す際、config=xxxx と指定することで、以下の版の .dll、.lib が作れるらしい。
release_x86
release_x86_64
debug_x86
debug_x86_64

makeと打ってビルドする。MinGW64-x85_64 で作業していたので、x86_64 を選んでみた。
make config=release_x86_64

../../lib/windows/ 内に、以下の2つのファイルが生成された。
soil2.dll
soil2.lib

更に、../../bin/ 内に、以下の2つの実行サンプルも生成された。動作には SDL2 が必要らしい。
soil2-perf-test-release.exe
soil2-test-release.exe

SDL2 は、MSYS2ならパッケージで用意されているのでインストールできる。
$ pacman -Ss SDL2

$ pacman -S mingw-w64-i686-SDL2 mingw-w64-x86_64-SDL2

先ほど生成した 実行サンプルを実行してみる。色々なdllを要求されるけど、ひとまず、先ほど生成した soil2.dll を bin/ にコピー。他のdllは、MinGW64 の bin/ にパスが通っている状態なら、そちらから読み込んでくれる。
cd ../../bin/
./soil2-test-release.exe
./soil2-perf-test-release.exe
  • soil2-test-release.exe ... ウインドウが表示されて画像が描画された。
  • soil2-perf-test-release.exe ... 画像群を読み込んで、かかった時間が表示された。




ということで、Windows10 x64 22H2 + MSYS2 で soil2 をビルドすることができると分かった。

MinGW64-x86_64 (64bit版) ではなく、MinGW64-i686 (32bit版) でも試してみた。そちらも同様に動いてくれた。.dll と .lib も生成された。

実行サンプルに必要なdll :

soil2-test-release.exe が要求するdllは以下。
$ objdump -p soil2-test-release.exe | grep dll
        DLL Name: soil2.dll
        DLL Name: libgcc_s_dw2-1.dll
        DLL Name: KERNEL32.dll
        DLL Name: msvcrt.dll
        DLL Name: SDL2.dll
        DLL Name: SHELL32.dll
        DLL Name: libstdc++-6.dll

つまり、以下のdllが別途必要になる。
libgcc_s_dw2-1.dll
libstdc++-6.dll
SDL2.dll
soil2.dll

soil2.dll 以外は、msys64/mingw64/bin/ の中からコピーしてくれば良さそう。bin/ に各dllをコピーしてみたら、MSYS2等にパスが通ってない状態でも soil2-test-release.exe が実行できた。

#3 [basic] FreeBASICでsoilを使ってみる

FreeBASIC は、CPUによるソフトウェア処理の描画とは別に、OpenGLを使ったハードウェア処理による描画もできる。しかし、OpenGL を使う際にネックになるのが、テクスチャ画像の読み込みと変換。OpenGL自体には、テクスチャ画像ファイルを読み込んで、テクスチャとして使える形に変換してくれる機能が無い。そのあたりは自分で処理を書かないといけない。

FreeBASICの公式掲示板を眺めていたら、soil (libsoil) を使って、このあたりを楽に行う方法が紹介されていた。

_Simple OpenGL Image Library Windows/Linux 32/64-bit - freebasic.net

興味が湧いたので試用してみることにした。環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。

関連ファイルを入手 :

前述のページから、libsoil.zip を入手する。解凍すると、中には以下のファイルが入っている。
libsoil32.so
libsoil64.so
OpenGL_RGB.png
OpenGL_RGBA.png
soil.bi
soil32.dll
soil64.dll
test01.bas
test02.bas
  • *.so は Linux で使うファイル。
  • *.dll は Windows で使うファイル。動的リンクライブラリ。
  • soil.bi は、FreeBASICのソースからインポートするヘッダファイル。
  • *.bas は、FreeBASIC のソースファイル。今回のサンプルファイル。

コンパイルする :

fbc を使ってコンパイル。
fbc test01.bas
fbc test02.bas

test01.exe と test02.exe が生成された。

実行してみる :

test01.exe と test02.exe を実行してみる。

test01.exe の結果。

test01_ss.png


test02.exe の結果。

test02_ss.png


どちらも画像ファイルを表示できている。test01 のほうは不透明画像(RGB画像)、test02 のほうは半透明画像(RGBA画像)を描画している模様。

ソースを見てみる :

test01.bas を眺めてみる。

#include once "GL/gl.bi"
#include once "GL/glu.bi"
#include once "soil.bi"

' test of RGB texture

ChDir ExePath()

ScreenRes 640,480,32,,2

var texture = SOIL_load_OGL_texture("OpenGL_RGB.png", SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_POWER_OF_TWO)

dim as integer w,h
screeninfo w,h
glViewport 0,0,w,h
glMatrixMode GL_PROJECTION
glLoadIdentity
gluPerspective 45.0, w/h, 0.1, 100.0
glMatrixMode GL_MODELVIEW
glLoadIdentity

glClearColor 1,0,0,1
glShadeModel GL_SMOOTH

glDisable GL_DEPTH_TEST
glDepthFunc GL_LEQUAL
glHint GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST

glEnable GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D, texture

do
    glClear GL_COLOR_BUFFER_BIT
    
    glBegin GL_QUADS
    glTexCoord2f 0,0:glVertex3f -.5, .25,-5
    glTexCoord2f 1,0:glVertex3f  .5, .25,-5
    glTexCoord2f 1,1:glVertex3f  .5,-.25,-5
    glTexCoord2f 0,1:glVertex3f -.5,-.25,-5
    glEnd
    
    flip
loop while inkey=""

最初のほうで、OpenGL関係のヘッダファイル(GL/gl.bi, GL/glu.bi)と、soil のヘッダファイル(soil.bi)を読み込んでいる。

テクスチャ画像ファイルの読み込みは、SOIL_load_OGL_texture() で行うようだなと…。たった1行で、画像ファイルの読み込み、OpenGLのテクスチャ用データへの変換、OpenGLのテクスチャ確保を行ってくれている模様。

その後は、screeninfo() でウインドウの横幅と縦幅を取得してから、OpenGL関係のいつもの長々とした初期化処理をして、ループ内で四角形を描画しつつテクスチャを画面に出している。ループの最後に flip を呼ぶことで、FreeBASIC は OpenGLのダブルバッファ切り替えをしてくれる。

テクスチャ画像の読み込み、変換、確保を、たった1行で済ませてくれるのはありがたいなと…。

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

#1 [basic] FreeBASICでOpenGLを使ってみる

FreeBASICは、標準状態だとCPUによるソフトウェア処理でグラフィック画面を描画するけれど、OpenGLを使ってハードウェア処理で描画することもできる。と思う。たぶん。どの程度のことができそうなのか少し試してみた。

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

スクリーンショット :

実行すると以下のようになる。

ソース :

今回書いたソースは、soil.bi という、テクスチャ画像ファイル読み込み用のライブラリを使っている。以下から libsoil.zip を入手できる。

_Simple OpenGL Image Library Windows/Linux 32/64-bit - freebasic.net

Windowsの場合、解凍して中に入っている soil.bi と *.dll をプロジェクトフォルダにコピーすれば利用できる。


ソースは以下。かなり長くなってしまったけど…。

_opengl_sample1.bas
#ifdef __FB_WIN32__
' use mmsystem. Windows only
#include "windows.bi"
#include "win/mmsystem.bi"
#endif

#include once "fbgfx.bi"
Using FB
#include once "GL/gl.bi"
#include once "GL/glu.bi"
#include once "soil.bi"

Const IMG_FILE = "ufo.png"

Const OBJ_MAX = 10000
Const SPRITE_SIZE = 128
Const MAX_FPS = 60.0
Const PI = 3.1415926535897932

Dim Shared As Integer obj_num = OBJ_MAX  ' Shared ... global variable

ChDir ExePath()

' get desktop size
Dim shared As Integer scr_w, scr_h, sdepth
ScreenInfo scr_w, scr_h, sdepth

' set window screen size
If Command(1) = "/s" Or Command(2) = "/s" Then
    ' like fullscreen
    ScreenRes scr_w, scr_h, sdepth, ,GFX_OPENGL Or GFX_NO_FRAME
Else
    ' window style
    scr_w *= 0.8
    scr_h *= 0.8
    ScreenRes scr_w, scr_h, sdepth, ,GFX_OPENGL
End If

' obj work type
Type obj
    x As double
    y As double
    dx As double
    dy As Double
    w As Integer
    h As Integer

    Declare Sub init(ByVal x As Double, ByVal y As Double, ByVal dx As Double, ByVal dy As Double, ByVal w As Integer, ByVal h As Integer)
    Declare Sub update(ByVal delta As Double)

End Type

' obj init
Sub obj.init(ByVal x As Double, ByVal y As Double, ByVal dx As Double, ByVal dy As Double, ByVal w As Integer, ByVal h As Integer)
    this.x = x
    this.y = y
    this.dx = dx
    this.dy = dy
    this.w = w
    this.h = h
End Sub

' obj update
Sub obj.update(ByVal delta As double)
    this.x += (this.dx * MAX_FPS) * delta
    this.y += (this.dy * MAX_FPS) * delta
    Dim As Integer wh = this.w / 2
    Dim As Integer hh = this.h / 2
    If this.dx < 0.0 And this.x - wh <= 0     Then this.dx *= -1.0
    If this.dx > 0.0 And this.x + wh >= scr_w Then this.dx *= -1.0
    If this.dy < 0.0 And this.y - hh <= 0     Then this.dy *= -1.0
    If this.dy > 0.0 And this.y + hh >= scr_h Then this.dy *= -1.0
End Sub

#ifdef __FB_WIN32__
' Changed timer precision to 1 msec
timeBeginPeriod(1)
#endif

' init objs work
Dim objs(0 To OBJ_MAX) As obj

For i As Integer = 0 To UBound(objs)
    Dim As Double a, spd, dx, dy
    a = (Rnd * 360.0) * PI / 180.0
    spd = (scr_h / (MAX_FPS * 2)) * (Rnd + 0.25)
    dx = spd * Cos(a)
    dy = spd * Sin(a)
    objs(i).init(scr_w / 2, scr_h / 2, dx, dy, SPRITE_SIZE, SPRITE_SIZE)
Next i

obj_num = UBound(objs) / 2

For i As Integer = 1 To 2
    If Command(i) <> "" And Command(i) <> "/s" Then
        obj_num = Valint(Command(i))
        If obj_num < 0 Then obj_num = 0
        If obj_num > UBound(objs) Then obj_num = UBound(objs)
    End If
Next i

' load texture image file
var texture = SOIL_load_OGL_texture(IMG_FILE, SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_POWER_OF_TWO)

' OpenGL Config
glViewport 0, 0, scr_w, scr_h
glMatrixMode(GL_PROJECTION)
glLoadIdentity()

glOrtho(0, scr_w, scr_h, 0, 5.0, -5.0)
' gluPerspective 45.0, w/h, 0.1, 100.0

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()

glClearColor(0.15, 0.3, 0.6, 1)
glShadeModel(GL_SMOOTH)

glDisable(GL_DEPTH_TEST)
' glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)

glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, texture)

Dim As Double start_time, prev_time, now_time, next_time, delta
Dim As Integer frame_count, fps_count
Dim As Boolean running

start_time = Timer
prev_time = start_time
next_time = start_time + 1.0 / MAX_FPS
frame_count = 0
fps_count = 0
running = True

' main loop
While running
    ' get delta time
    now_time = Timer
    delta = now_time - prev_time
    If delta < 0 Then delta = 1.0 / MAX_FPS
    prev_time = now_time
    next_time = now_time + 1.0 / MAX_FPS

    ' count fps
    If now_time < start_time Then start_time = now_time
    If (now_time - start_time) >= 1.0 Then
        fps_count = frame_count  ' get FPS
        start_time += 1.0
        frame_count = 0
    End If
    frame_count += 1

    Dim As String k = inkey$
    If k = Chr$(27) Or k = "q" Or k = Chr$(255) + "k" Then
        running = False
    End If

    If MultiKey(SC_UP  ) Then obj_num += 20
    If MultiKey(SC_DOWN) Then obj_num -= 20
    If obj_num < 0 Then obj_num = 0
    If obj_num > UBound(objs) Then obj_num = UBound(objs)

    ' update objs
    For i As Integer = 0 To obj_num
        objs(i).update(delta)
    Next i

    ' draw
    glClear GL_COLOR_BUFFER_BIT

    For i As Integer = 0 To obj_num
        Dim As Integer swh = objs(i).w / 2, shh = objs(i).h / 2

        glLoadIdentity()
        glTranslatef(objs(i).x, objs(i).y, -1)

        glBegin GL_QUADS
        glTexCoord2f 0, 0 : glVertex3f -swh, -shh, 0
        glTexCoord2f 1, 0 : glVertex3f  swh, -shh, 0
        glTexCoord2f 1, 1 : glVertex3f  swh,  shh, 0
        glTexCoord2f 0, 1 : glVertex3f -swh,  shh, 0
        glEnd
    Next i

    ' draw String (10, 10), "FPS " & fps_count, RGB(255, 255, 255)
    WindowTitle("FPS:" & fps_count & " obj:" & obj_num)

    flip  ' double buffer flip

    ' wait
    If Timer < next_time Then
        Dim As Double wait_msec = cast( Integer, (next_time - Timer) * 1000.0)
        If wait_msec > 0 Then sleep wait_msec
    End If
Wend

#ifdef __FB_WIN32__
timeEndPeriod(1)
#endif


使用画像は以下。

_ufo.png


fbc opengl_sample1.bas でコンパイル。opengl_sample1.exe が生成される。

使い方は以下。
Usage: 
    opengl_sample1.exe [/s] [OBJ_NUM]

      * /s : Full screen
      * OBJ_NUM : objects number. 0 - 10000

動作確認してみた :

以下の環境で動作確認してみた。
  • メインPC、AMD Ryzen 5 5600X (6C12T, 3.7 - 4.6GHz) + GeForec GTX 1060 6GB, デスクトップ解像度 1920x1080
  • サブPC、Athlon 5350 (4C4T, 2GHz, TDP 25W, Jaguar Core) + On-board VGA (Radeon R3, GCN), デスクトップ解像度 1920x1200

結果としては…。
  • メインPC上では、128x128のスプライト相当を4,000個ほど出しても60FPSが出た。10,000個出すと50FPS台になった。
  • サブPC上では、128x128個のスプライト相当を50個までなら60FPSが出たけれど、60個になると50FPS台になってしまった。100個ほど出すと30FPS台。

サブPCの結果が予想以上に酷かった。ここまで差が出るとは思ってなかった…。

タスクマネージャで確認したところ、CPU負荷はそれほどでもなかったけれど、GPU負荷がすぐに80%を超え始めて60FPSが出ない状態になっているように見えた。

opengl_sample1_ss_akatsudumi_trim.png


つまり、それなりのスペックのGPUを積んでいるPCなら、OpenGLを使うことで描画速度も改善されるけれど、非力な内蔵GPUでは期待した効果は得られないようだなと…。 OpenGLを使ってもソフトウェア描画と五十歩百歩になる場面もありそうな気配がする…。

ただ、今回、OBS Studio 30.0.2 64bit で画面をキャプチャした際、OpenGL を使ってるはずなのに画面が一時的に暗くなっていない点が気になった。今まで OpenGL を使ったプログラムをキャプチャした際は、最初の数秒はキャプチャできなかったのに…。

そこからの邪推だけど、FreeBASIC で OpenGL を使う場合、OpenGL でVRAM上に描画した結果を、メインメモリ上の画像バッファにキャプチャして、その画像バッファをデスクトップに表示していたりしないかと…。そして、VRAM からメインメモリに持ってくるあたりで負荷がかかってるのではないかと。いや、自信無いけど。ただ、C言語等でOpenGLを利用する時とはちょっと違う仕組みになっていてもおかしくなさそうな気もする。

flipが時間待ちをしてそう :

当初、メインループの最後でうっかり sleep を入れ忘れてしまったのだけど、その状態でも60FPS前後で動いてしまった。sleep を入れてもその状態からさほど変わらず。

もしかすると、ダブルバッファを切り替える flip の中で、独自に時間待ち処理をして 60FPS になるようにしてあるのかも。ひょっとして、処理内容としては vsync をチェックしてるつもり、だったりするのかな…。

文字描画が悩ましい :

FreeBASIC は、標準グラフィック画面上で文字を描画する際に、 draw string を使える。今回も draw string で画面に文字描画できないかなと期待したけれど、OpenGL利用時はそういうことはできないっぽい。仕方ないので、ウインドウタイトルにFPS等を表示して誤魔化したけど…。

となると、FreeBASIC で OpenGL を利用する時は、文字描画処理を自前で書かないといけないようだなと…。

平行投影で2D描画っぽく使う :

OpenGLに、glOrtho() を使って平行投影を指示する際、画面の左上が (0, 0)、画面の右下が (w, h) になるように値を指定すれば、ウインドウサイズの2D描画面がそこにあるように見せかけられると知ったのでメモ。

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

#1 [basic] FreeBASICでOpenGLのglBitmap()を利用してみる

OpenGLにはglBitmap()という、二値画像相当を描画する機能がある。

_ビットマップ

これを FreeBASIC から利用できるのかどうか試してみた。環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。

ビットマップデータを用意する :

まず、以下の画像を、FreeBASIC から利用できる形に ―― 例えば配列変数の形に変換しなけれないけない。

_image_32x32.png
_image_lena.png

変換するための Pythonスクリプトを書いてみた。

_png2bitsbas.py
  • 動作には Pillow が必要。
  • Python 3.10.10 + Pillow 10.2.0 で動作確認した。
  • License: CC0 / Public Domain

使い方は以下。標準出力に結果を出力するので、「>」を使ってファイルに保存する。
python png2bitsbas.py -i image.png

python png2bitsbas.py -i image.png > image.bi

変換すると以下のような出力結果が得られる。

_image_32x32.bi
_image_lena.bi

これを FreeBASIC のソース(.bas) で include すれば、バイナリデータに対して配列変数の形でアクセスできるようになる。

注意点。現状の FreeBASIC 1.10.1 は巨大な配列を作るとコンパイルに失敗する。今回は 1bit = 1dot のデータなのでそこまで大きくならなかったけど、もし大き目なバイナリデータを含めたい場合は、DATA文を利用するとか、バイナリデータをオブジェクトファイルに変換してリンクする、といった手段を使うことになる。

ソース :

FreeBASIC で glBitmap() を利用するソースは以下。

_glbitmap_test01.bas
#include once "fbgfx.bi"
Using FB
#include once "GL/gl.bi"
#include once "GL/glu.bi"

' include glbitmap binary data
#include "image_32x32.bi"
#include "image_lena.bi"

ChDir ExePath()

Const SCRW = 480
Const SCRH = 270
ScreenRes SCRW, SCRH, 32, ,GFX_OPENGL

' get window size
Dim shared As Integer scr_w, scr_h, sdepth
ScreenInfo scr_w, scr_h, sdepth

' OpenGL Config
glViewport 0, 0, scr_w, scr_h
glMatrixMode(GL_PROJECTION)
glLoadIdentity()

glOrtho(0, scr_w, scr_h, 0, 5.0, -5.0)
' gluPerspective 45.0, w/h, 0.1, 100.0

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()

glClearColor(0, 0, 0, 1)
glShadeModel(GL_SMOOTH)

glDisable(GL_DEPTH_TEST)
' glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)

Dim As Boolean running = True

' main loop
While running
    
    ' ESC or Q key to exit
    Dim As String k = inkey$
    If k = Chr$(27) Or k = "q" Or k = Chr$(255) + "k" Then
        running = False
    End If

    ' draw start

    ' clear screen
    glClear(GL_COLOR_BUFFER_BIT)
    
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1)

    Dim As GLsizei w, h
    Dim As GLfloat xorig, yorig, xmove, ymove

    ' draw arrow bitmap
    w = IMAGE_32X32_PNG_WIDTH
    h = IMAGE_32X32_PNG_HEIGHT
    xorig = 0.0
    yorig = 0.0
    xmove = 0.0
    ymove = 0.0
    glColor4f(1, 1, 1, 1)  ' set color
    glRasterPos2f(32, 32 + h)  ' set position
    glBitmap(w, h, xorig, yorig, xmove, ymove, @image_32x32_png(0))

    ' draw lena bitmap
    w = IMAGE_LENA_PNG_WIDTH
    h = IMAGE_LENA_PNG_HEIGHT
    xorig = 0.0
    yorig = 0.0
    xmove = 0.0
    ymove = 0.0
    glColor4f(1, 1, 1, 1)  ' set color
    glRasterPos2f(100, 32 + h)  ' set position
    glBitmap(w, h, xorig, yorig, xmove, ymove, @image_lena_png(0))

    flip  ' double buffer flip
    sleep 10
Wend

fbc glbitmap_test01.bas でコンパイル。実行結果は以下。

glbitmap_test01_ss.png


ということで、FreeBASIC から OpenGL の glBitmap() を利用することができると分かった。

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

#1 [basic] FreeBASICとOpenGLで文字描画をしてみる

_昨日、 FreeBASIC + OpenGL で、二値のbitmapを描画する glBitmap() を利用してみた。その glBitmap() を使ってビットマップフォントの文字描画をしてみたい。

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

文字は等幅フォント、かつ、0/1の二値とする。ASCIIコード 0x20 - 0x7f まで対応。

動作結果 :

先に動作結果を載せておく。

glbitmapfont_test1_ss.gif


ということで、FreeBASIC + OpenGL で、ビットマップフォントを使って文字描画できると分かった。

ソース :

先にファイル一式を置いておく。

_glbitmap_font_20240225.zip
  • プログラム部分の License は CC0 / Public Domain。
  • 内包しているフォントデータについては各フォントのライセンスを確認のこと。


各ソースも一応載せておく。

以下は、文字描画を担当する部分。

_glbitmapfont.bi

ビットマップフォントデータが配列の形で入っていて、文字描画を担当する glDrawBitmapText() というサブルーチンも入ってる。

処理をしているところだけ抜き出して貼っておく。
#include once "fbgfx.bi"
Using FB
#include once "GL/gl.bi"
#include once "GL/glu.bi"

' ...

Type glbitmapfontinfo
    w As GLsizei
    h As GLsizei
    clen As Integer
    adrs As UByte Ptr
End Type

Const GLBITMAPFONT_COURR18     = 0
Const GLBITMAPFONT_PET2015     = 1
Const GLBITMAPFONT_PROFONT     = 2
Const GLBITMAPFONT_SHNM8X16R   = 3
Const GLBITMAPFONT_SHNM8X16RX2 = 4
Const GLBITMAPFONT_TER_U24B    = 5

Const GLBITMAPFONT_MAX = 6

Dim Shared glBitmapFontInfoTbl( GLBITMAPFONT_MAX - 1 ) As glbitmapfontinfo => { _
  ( FONT_COURR18_PNG_WIDTH, FONT_COURR18_PNG_HEIGHT, FONT_COURR18_PNG_CHR_LEN, @font_courR18_png(0, 0)), _
  ( FONT_PET2015_PNG_WIDTH, FONT_PET2015_PNG_HEIGHT, FONT_PET2015_PNG_CHR_LEN, @font_pet2015_png(0, 0)), _
  ( FONT_PROFONT_PNG_WIDTH, FONT_PROFONT_PNG_HEIGHT, FONT_PROFONT_PNG_CHR_LEN, @font_profont_png(0, 0)), _
  ( FONT_SHNM8X16R_PNG_WIDTH, FONT_SHNM8X16R_PNG_HEIGHT, FONT_SHNM8X16R_PNG_CHR_LEN, @font_shnm8x16r_png(0, 0)), _
  ( FONT_SHNM8X16RX2_PNG_WIDTH, FONT_SHNM8X16RX2_PNG_HEIGHT, FONT_SHNM8X16RX2_PNG_CHR_LEN, @font_shnm8x16rx2_png(0, 0)), _
  ( FONT_TER_U24B_PNG_WIDTH, FONT_TER_U24B_PNG_HEIGHT, FONT_TER_U24B_PNG_CHR_LEN, @font_ter_u24b_png(0, 0)) _
  }

Sub glDrawBitmapText(ByVal text As string, ByVal kind As Integer)
    Dim As GLfloat xorig, yorig, xmove, ymove
    Dim As GLsizei w, h
    Dim As Integer clen
    Dim As ubyte ptr adrs

    If kind < 0 Then kind = 0
    If kind > GLBITMAPFONT_MAX - 1 Then kind = GLBITMAPFONT_MAX - 1

    w = glBitmapFontInfoTbl(kind).w
    h = glBitmapFontInfoTbl(kind).h
    clen = glBitmapFontInfoTbl(kind).clen
    adrs = glBitmapFontInfoTbl(kind).adrs

    xorig = 0
    yorig = 0
    xmove = w
    ymove = 0

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1)

    Dim As Integer slen = Len(text)
    For i As Integer = 0 To slen - 1
        Dim As Integer c = text[i]
        If c = 0 Then Exit For

        If c < &H20 Or c > &H7f Then c = &H20
        c -= &H20
        glBitmap(w, h, xorig, yorig, xmove, ymove, adrs + clen * c)
    Next i
End Sub


使用サンプルは以下。カーソルキーの上下でフォント種類を切り替えることができるようにしておいた。

_glbitmapfont_test1.bas
#include once "fbgfx.bi"
Using FB
#include once "GL/gl.bi"
#include once "GL/glu.bi"

#include "glbitmapfont.bi"

ChDir ExePath()

Const SCRW = 480
Const SCRH = 270
ScreenRes SCRW, SCRH, 32, ,GFX_OPENGL

' get window size
Dim shared As Integer scr_w, scr_h, sdepth
ScreenInfo scr_w, scr_h, sdepth

' OpenGL Config
glViewport 0, 0, scr_w, scr_h
glMatrixMode(GL_PROJECTION)
glLoadIdentity()

glOrtho(0, scr_w, scr_h, 0, 5.0, -5.0)
' gluPerspective 45.0, w/h, 0.1, 100.0

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()

glClearColor(0, 0, 0, 1)
glShadeModel(GL_SMOOTH)

glDisable(GL_DEPTH_TEST)
' glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)

Dim As Boolean running = True

Dim fontlist(GLBITMAPFONT_MAX - 1) As Integer => { _
  GLBITMAPFONT_COURR18, _
  GLBITMAPFONT_PET2015, _
  GLBITMAPFONT_PROFONT, _
  GLBITMAPFONT_SHNM8X16R, _
  GLBITMAPFONT_SHNM8X16RX2, _
  GLBITMAPFONT_TER_U24B _
  }

Dim As Integer fontkind = 0
Dim As Integer fontmax = UBound(fontlist) + 1

' main loop
While running

    ' ESC or Q key to exit
    Dim As String k = inkey$
    If k = Chr$(27) Or k = "q" Or k = Chr$(255) + "k" Then
        running = False
    ElseIf k = Chr$(255) + "H" Then
        ' up key
        fontkind = (fontkind + 1) Mod fontmax
    ElseIf k = Chr$(255) + "P" Then
        ' down key
        fontkind = (fontkind + fontmax - 1) Mod fontmax
    End If

    ' clear screen
    glClear(GL_COLOR_BUFFER_BIT)

    ' draw text
    glColor4f(1, 1, 1, 1)  ' set color
    glRasterPos2f(32, 64)  ' set position
    glDrawBitmapText("Hello World", fontlist(fontkind))

    flip  ' double buffer flip
    sleep 10
Wend

  • #include "glbitmapfont.bi" をして、glDrawBitmapText() を呼び出せば使える。
  • glColor4f() で色を、glRasterPos2f() で描画位置を指定して、glDrawBitmapText() で文字列を描画する。

フォントデータの作り方 :

今回は、以下のビットマップフォント画像を元にしてフォントデータを作ってる。それぞれ、ASCIIコード 0x20 - 0x7f を、16 x 6 個並べた画像になっている。

_font_courR18.png
_font_pet2015.png
_font_profont.png
_font_shnm8x16r.png
_font_shnm8x16rx2.png
_font_ter-u24b.png

この画像を、FreeBASIC の配列の形に変換する。Pythonスクリプトを書いて変換した。

_fontpng2bitsbas.py
  • 動作には Pillow が必要。
  • Python 3.10.10 + Pillow 10.2.0 で動作確認した。
  • License : CC0 / Public Domain

使い方は以下。
python fontpng2bitsbas.py -i image.png

python fontpng2bitsbas.py -i image.png > image.bi

以下のような変換結果が得られる。UByte の二次元配列の中に、ASCIIコード 0x20 - 0x7f 分の bitmap font データが、1bit = 1dot の状態で入っている。

_font_courR18.bi
_font_pet2015.bi
_font_profont.bi
_font_shnm8x16r.bi
_font_shnm8x16rx2.bi
_font_ter-u24b.bi

これらをコピペして、glbitmapfont.bi を作成した。

各フォントの入手先やライセンスは、以下を参照のこと。

_readme.md

余談 :

glut (freeglut) を使える環境なら、glut にビットマップフォント描画機能( glutBitmapCharacter() ) があるので、そちらを使えば済む。

_グラフィックス科学演習 / Graphics Science Seminar

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

#1 [anime] 「王様戦隊キングオージャー」最終回を視聴

録画していたソレを視聴。「キングオージャー」は異世界を舞台にした戦隊モノ。という説明でいいのだろうか。

凄い。とうとう最終回までこの映像スタイルを貫いてしまった。LEDウォールやライブ合成を活用して実現したらしいけど、まさか実写で、しかもスケジュールの厳しいTVシリーズで、1年間、4クールにも渡って、見事に異世界風景を映像化してしまうとは。てっきり「ジャスピオン」みたいに途中で地球に引っ越してきてそこからずっと地球編になるのではないか、1年間もこのスタイルを続けるのは無理だろうなあ、と思ってたけど、やり遂げてしまうとはなあ…。これが偉業ではないとしたら一体何をそう呼べばいいのだろう、とすら思えてくる。

もちろん、TVシリーズの厳しいスケジュール故か、カメラ移動とオブジェクトや背景の位置合わせが時々ずれちゃったり等々、合成してます感が強いカットもチラホラあったりもしたのだけど。映画ならまだしも、これはTVシリーズなわけで。特撮ヒーロー番組でありながら、まるで大河ドラマのような合戦シーンまで実現しちゃっていたし。「こういう画を見せたいんだ」という姿勢が伝わってきて、なんだか「サイバーコップ」を思い出してしまったりもして。見た瞬間に「うわあ。このスタッフ、無茶するなあ…」と思うカットが目白押し。素晴らしい。

東映の特撮ヒーロー番組のスタッフは、また一つ実写の枠を壊したというか、ハードルを飛び越えて見せたというか。昔なら「実写でそんなの無理無理」「アニメでやれ」と言われるであろう設定やシーンを、実写でやってみせたわけだから…。 *1

今後こういった技術を使って、一体どんな内容の作品を見せてくれるのか、ますます期待してしまうなと。公式サイトを眺めた感じでは、こういう作り方を試したことで様々な知見が得られたようでもあるし。

脚本面では、メインキャラ達が最後まで協調性ゼロだったあたりが興味深かった。1話からずっと喧嘩腰だし、うっかり油断してると騙し合いや化かし合いを始めるし。君達本当に戦隊メンバーなの? と言いたくなるレベル。ただ、「皆バラバラでもいざという時には一致団結して事に当たるならそれでいいじゃんよ」みたいなことをナレーション(?)で言っていて、これはこれでイイ感じのメッセージかもしれないなと。まあ、常に「みんななかよく」と子供達に諭してる大人の方々は困っちゃうかもしれないけれど…。

それにしても、「ゼンカイジャー」「ドンブラザーズ」と来て、この「キングオージャー」。後々、「ディズニー・ルネサンス」みたいなノリで、「戦隊ルネサンス三部作」とか呼ばれちゃってもおかしくないのかもしれないなと…。

余談。LEDウォールについて :

余談。公式サイトに掲載されていた話が ―― 当初は東映に新設されたLEDウォールを使う予定だったけど、実際試してみたら問題が出てきて、急遽ソニーPCLの清澄白河BASEで撮影することにしたらするすると作業が進んだ、という話が興味深かった。ノウハウの有無って大きいのだなと。というか東映のLEDウォールはどうなってしまったの…。そっちもガンガン使える状態になってほしいよなあ…。

それはともかく、役者さんの後ろに映像を投影してそれをカメラで撮影するというのは大昔からある撮影技法なので、LEDウォールは温故知新というか、バージョンアップ版みたいなものだろうと思うわけで。

_スクリーン・プロセス - Wikipedia

もしかすると、他にも似た感じのネタがあったりしないか。昔の技法だから今は使われてないけれど、現代のアレやコレを盛り込めば、実はバージョンアップができてしまう、そんな技法が他にもあるのかもしれない。

*1: もっとも、実写、かつ、TVシリーズで、完全な異世界を表現した作品は、コレが日本初というわけでもなさそうだけど。NHK教育/Eテレの子供向け番組では、バーチャルスタジオを活用して異世界を舞台にしてみせた実写作品があったりするし、戦隊シリーズでも宇宙=ほぼ異世界を舞台にしていた「宇宙戦隊キュウレンジャー」のような作品もあったし。そもそも考えてみたら時代劇だって今となってはほぼ異世界モノと言えなくもない。とはいえ、Eテレのそれは本格的なアクションなどは無いし、「キューレンジャー」は現実の風景にちょっと何かを付け足して異世界風に仕立てるやり方だったので、この内容+映像スタイルはおそらく初じゃないのかなと。

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

#1 [prog] GLFWを使ったC言語のソースをコンパイルしようとして少しハマった

OpenGLの学習をする際に、ウインドウの生成処理その他を簡単にしてくれる GLFW というライブラリ(ツールキット?)があって、少し勉強しようとしていたのだけど。C言語で使おうとしたところコンパイル時にリンクエラーが出て少しハマってしまった。

環境は Windows10 x64 22H2 + MinGW gcc 6.3.0。

エラー内容は以下と同じ。

_c++ - "undefined reference to __mingw_free" when linking GLFW - Stack Overflow _c - GLFW with GCC Mingw flags for static doesn't work as documented - Stack Overflow

gcc 6.3.0利用時の注意点 :

一般的には、gcc で glfw3 を使ったソースをコンパイルする場合、-lglfw3 を指定すればリンクができるはずなのだけど…。

SourceForge から入手できる MinGW gcc 6.3.0 を使って、GLFW を利用しているC言語のソースをコンパイルする場合は、以下のように `-lglfw3dll` を指定しないとリンクエラーが出てしまう。

gcc 01_helloglfw.c -o 01_helloglfw.exe -static -lglfw3dll -lopengl32 -lwinmm -lgdi32 -mwindows

この場合、生成した .exe を動作させるためには、別途 glfw3.dll が必要になる。objdump を使って、生成された exe が要求するdllの一覧を出してみると、glfw3.dll が入ってることが分かる。
> objdump -p 01_helloglfw.exe | grep dll
        DLL Name: glfw3.dll
        DLL Name: KERNEL32.dll
        DLL Name: msvcrt.dll
        DLL Name: msvcrt.dll

gcc 9.2.0なら問題は起きない :

SourceForge から入手できる MinGW gcc 6.3.0 ではなく、OSDN から入手できる MinGW gcc 9.2.0 を使えば、-static -lglfw3 を指定することで、glfw3.dll を必要としない実行形式を作れる。

そもそも、GLFWの公式配布版バイナリに同梱されている README.md には、「MinGW (built with GCC 9.2.0)」という記述があった。gcc 6.3.0 ではなく gcc 9.2.0 の利用を前提としたバイナリだったらしい…。

しかし、MinGW gcc 9.2.0 が入手可能な OSDN は、サービス終了するという話があったはずで…。サービス終了が中止になったという報道も見かけたけれど、今までの不安定な状況を鑑みれば、今後どうなるのか予測がつかない。将来的には Windows上で動作する MinGW gcc 9.2.0 が入手できなくなる可能性が高いだろうなと。

MSYS2を使えばハマらずに済む :

GLFW を使って実験するなら、MSYS2 + gcc 13.2.0 を使ったほうがいいのかもしれない。そちらなら、-static -lglfw3 の指定で、glfw3.dll を必要としない実行形式を生成できる。
$ objdump -p 01_helloglfw.exe | grep dll
        DLL Name: GDI32.dll
        DLL Name: KERNEL32.dll
        DLL Name: msvcrt.dll
        DLL Name: OPENGL32.dll
        DLL Name: SHELL32.dll
        DLL Name: USER32.dll

また、MSYS2用パッケージとして GLFW も用意されているので、公式サイトからバイナリをDLして手作業でコピーして、といったこともしなくて済む。
$ pacman -Ss glfw
...
mingw32/mingw-w64-i686-glfw 3.3.9-2 [インストール済み]
    A free, open source, portable framework for OpenGL application development (mingw-w64)
mingw64/mingw-w64-x86_64-glfw 3.3.9-2 [インストール済み]
    A free, open source, portable framework for OpenGL application development (mingw-w64)
...

公式配布版 GLFW のバイナリについて :

glfw-3.3.9.bin.WIN32.zip までは MinGW 9.2.0 用のライブラリファイル (lib-mingw/) が入っているが、glfw-3.3.10.bin.WIN32.zip 以降は、lib-mingw-w64 しか入っていない。

テクスチャ画像読み込み機能が無くなっていた :

GLFWはテクスチャ画像の読み込み機能があるという話を見かけて気になっていたのだけど、ググってみたらそれは昔の話のようで、glfw3 では削除されていたらしい。

_GLFW: Moving from GLFW 2 to 3
Removed functions
glfwReadImage, glfwReadMemoryImage, glfwFreeImage, glfwLoadTexture2D, glfwLoadMemoryTexture2D and glfwLoadTextureImage2D.

GLFW: Moving from GLFW 2 to 3 より


ごっそり無くなってるなあ…。

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

#1 [prog] SOILのインストールと使い方をメモ

OpenGLの利用時、テクスチャ画像ファイルの読み込み等を簡単にしてくれる、SOIL (Simple OpenGL Image Library) というライブラリがある。試用してみた。

環境は、Windows10 x64 22H2 + MinGW (gcc 9.2.0、OSDN版), MSYS2 (gcc 13.2.0)。

MSYS2上でインストール :

MSYS2 は SOIL のパッケージも用意されている。ありがたや。pacman でインストールできる。
  • pacman -Ss hoge でパッケージの検索。
  • pacman -S hoge でパッケージのインストール。
$ pacman -Ss soil
mingw32/mingw-w64-i686-soil 1.16.0-2 [インストール済み]
    C library used for loading image files into OpenGL (mingw-w64)
mingw64/mingw-w64-x86_64-soil 1.16.0-2 [インストール済み]
    C library used for loading image files into OpenGL (mingw-w64)
...

$ pacman -S mingw-w64-i686-soil mingw-w64-x86_64-soil

MinGW上でインストール :

MinGW (gcc 9.2.0、OSDN版)の場合は、公式サイトで公開されてるzipを入手して、中に入っている libsoil.a と SOIL.h を使う。ただ、公式サイトは消滅してしまっているので、WebArchive から入手した。

_lonesock.net: SOIL (WebArchive)

「here」と書かれてるところのリンクをクリックすれば、soil.zip が入手できる。解凍すると、lib/ に libSOIL.a が、src/ に SOIL.h が入ってる。
  • libSOIL.a を、MinGWインストールフォルダ/lib/ にコピー。
  • SOIL.h を、MinGWインストールフォルダ/include/ 内に、SOIL/ というディレクトリを作ってから、その中にコピー。

SOILの使い方 :

C言語のソースの最初のほうで、以下を記述。
#include <SOIL/SOIL.h>

例えば logo.png という画像をテクスチャ画像として読み込むなら、以下のように記述。正常に読み込めたら 0 より大きい値が返ってくる。
  // load texture image file. use SOIL
  tex_id = SOIL_load_OGL_texture("logo.png", SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_POWER_OF_TWO);
  if (tex_id > 0)
  {
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, tex_id);
  }
  else
  {
    glDisable(GL_TEXTURE_2D);
  }
  
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);

gcc や g++ にリンク指定をする際は、-lSOIL と記述する。以下は一例。
gcc 01_helloglfw.c -o 01_helloglfw.exe -static -lSOIL -lglfw3 -lopengl32 -lwinmm -lgdi32 -mwindows

ただ、-lSOIL を最初のほうに書いておかないと、何故かリンクエラーが出てしまう。

SOIL/SOIL.h を眺めると、memory がどうとかの関数もあるので、おそらくメモリ上に存在する画像ファイルのバイナリを利用してテクスチャにすることもできたりするのではないかと…。また、OpenGLの描画画面を画像ファイルとして保存する機能もあるように見えた。

サンプルソース :

png画像をテクスチャ画像として読み込んで、OpenGL + glfw3 で描画するサンプルを書いてみた。MinGW (gcc 9.2.0、OSDN版)、MSYS2 gcc 13.2.0 でコンパイルできることを確認した。

_01_helloglfw.c
#include <stdlib.h>
#include <stdio.h>
#include <GLFW/glfw3.h>
#include <SOIL/SOIL.h>

void error_callback(int error, const char *description)
{
  fprintf(stderr, "Error: %s\n", description);
}

static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
  if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
  {
    glfwSetWindowShouldClose(window, GLFW_TRUE);
  }
}

int main(void)
{
  GLFWwindow *window;
  GLuint tex_id;

  glfwSetErrorCallback(error_callback);

  if (!glfwInit())
  {
    // Initialization failed
    exit(EXIT_FAILURE);
  }

  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1); // set OpenGL 1.1
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);

  // create window
  window = glfwCreateWindow(1280, 720, "Hello GLFW", NULL, NULL);
  if (!window)
  {
    // Window or OpenGL context creation failed
    glfwTerminate();
    exit(EXIT_FAILURE);
  }

  glfwSetKeyCallback(window, key_callback);

  glfwMakeContextCurrent(window);

  glfwSwapInterval(1);

  // load texture image file. use SOIL
  tex_id = SOIL_load_OGL_texture("logo.png", SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_POWER_OF_TWO);
  if (tex_id > 0)
  {
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, tex_id);
  }
  else
  {
    glDisable(GL_TEXTURE_2D);
  }

  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);

  int scrw, scrh;
  glfwGetFramebufferSize(window, &scrw, &scrh);
  glViewport(0, 0, scrw, scrh);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  double a = (double)scrw / (double)scrh;
  glOrtho(-a, a, -1.0, 1.0, 1.0, -1.0);
  
  double angle = 0.0;

  // main loop
  while (!glfwWindowShouldClose(window))
  {
    angle += (45.0 / 60.0);

    glClearColor(0.1, 0.2, 0.4, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(angle, 0, 0, 1);

    float w = 0.8;
    float h = 0.8;
    glColor4f(1.0, 1.0, 1.0, 1.0);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-w, +h, -0.5);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(+w, +h, -0.5);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(+w, -h, -0.5);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-w, -h, -0.5);
    glEnd();

    glfwSwapBuffers(window);
    glfwPollEvents();
  }

  glfwDestroyWindow(window);
  glfwTerminate();
  exit(EXIT_SUCCESS);
}


使用画像は以下。512x512、RGBAのpng画像。

_logo.png


Makefile は以下。

_Makefile
01_helloglfw.exe: 01_helloglfw.c Makefile
    gcc 01_helloglfw.c -o 01_helloglfw.exe -static -lSOIL -lglfw3 -lopengl32 -lwinmm -lgdi32 -mwindows
#   gcc 01_helloglfw.c -o 01_helloglfw.exe -static -lSOIL -lglfw3dll -lopengl32 -lwinmm -lgdi32 -mwindows

.PHONY: clean
clean:
    rm -f *.exe
    rm -f *.o

もし、MinGW gcc 9.2.0 (OSDN版)ではなくて、MinGW gcc 6.3.0 (SourceForge版) を使う時は、-lglfw3 を -lglfw3dll にして、glfw3.dll をプロジェクトフォルダにコピーしておく必要がある。

make と打ってコンパイルして、01_helloglfw.exe を生成。

実行結果。




png画像を読み込んで、テクスチャ画像として利用できていることが分かる。

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

#1 [prog] Makefile内でgccのバージョンを取得して処理を分けたい

Windows10 x64 22H2 + MinGW (gcc 6.3.0, gcc 9.2.0) の環境で、Makefile内でgccのバージョンを取得して処理を分けたいと思った。

gccのバージョンを取得するのは、以下が参考になった。

_シェルスクリプトやmakefileでのバージョン番号 x.y.z の比較 ShellScript - Qiita

gcc -dumpversion で、バージョン部分のみが取得できるらしい。
> gcc -dumpversion
6.3.0

なので、Makefile内で以下を記述しておけば、gccのバージョンを GCC_VERSION という変数に入れられるはず。
GCC_VERSION=$(shell gcc -dumpversion)

ここまでできれば、ifeq - else - endif で処理を分けられそう。
GCC_VERSION=$(shell gcc -dumpversion)

ifeq ($(GCC_VERSION),6.3.0)
# ----------------------------------------
# MinGW gcc 6.3.0 (SourceForge)
glbitmfont_sample.exe: glbitmfont_sample.c glbitmfont.h Makefile
    gcc $< -o $@ -static -lSOIL -lglfw3dll -lopengl32 -lwinmm -lgdi32 -mwindows
else
# ----------------------------------------
# MinGW gcc 9.2.0 (OSDN), MSYS2
glbitmfont_sample.exe: glbitmfont_sample.c glbitmfont.h Makefile
    gcc $< -o $@ -static -lSOIL -lglfw3 -lopengl32 -lwinmm -lgdi32 -mwindows
endif

.PHONY: clean
clean:
    rm -f *.exe *.o

#2 [prog] SOILのビルド手順をメモ

OpenGLのテクスチャ画像ファイル読み込みを簡単にしてくれる、SOIL (Simple OpenGL Image Library)のビルド手順を一応メモしておく。

環境は、Windows10 x64 22H2 + MinGW gcc 6.3.0 SourceForge版。

オリジナル版 soil.zp (2008/07/07版) が対象。

  1. soil.zip を解凍。
  2. projects/makefile/ をカレントディレクトリにする。
  3. obj というディレクトリを作成。
  4. Makefile を使って make。
  5. ../../lib/ の中に libSOIL.a が生成される。
  6. ../../src/ の SOIL.h を利用する。

MSYS2ならパッケージがある :

念のために書いておくけど、MSYS2 を使っているならパッケージが用意されているので自分でビルドする必要は無い。pacman でパッケージをインストールすれば使えるようになる。
$ pacman -S mingw-w64-i686-soil mingw-w64-x86_64-soil

#3 [prog] GLFW利用時にビットマップフォントを描画したい

C言語 + OpenGL + GLFW (GLFW3)利用時に、ビットマップフォントを描画したい。とりあえず今回は、等幅ビットマップフォント、ASCIIコード 0x20 - 0x7f が描画できれば十分なのだけど…。

せっかくだから、ヘッダーファイル(.h) を1つ include すれば使える状態にしてみたい。ということで書いてみた。一応 github に置いておきます。

_mieki256/glbitmfont: Use glbitmap() to draw bitmap fonts in OpenGL. It can be used by simply including a single header file.

_glbitmfont.h をダウンロードして、#include "glbitmfont.h" を記述すれば、ビットマップフォントを描画する glBitmapFontDrawString(char *str, int kind) という関数が使えるようになる。フォント種類は5種類の中から選べるようにしておいた。

_glbitmfont_sample.c が使用サンプル。最後のほうで「// draw text」と書いてあるあたりでビットマップフォントを描画している。

ただ、今回描画に使った glBitmap() は、OpenGL 3.x から非推奨になってしまったらしいので…。OpenGL 1.x を使って何かをしたい時ぐらいしか、今回書いたソレは使えない気もする…。

ディスプレイリストを使ったほうがいいのかもしれない :

以下のページで紹介されているソースを眺めたところ、こういった処理をするならディスプレイリストなるものを利用するのが一般的らしいなと…。

_グラフィックス科学演習 / Graphics Science Seminar

ただ、そのディスプレイリストも、OpenGL 3.x で非推奨になってる模様。

#4 [nitijyou] ネズミが出没して困ってる

数日前に親父さんが、親父さん達の寝室の床の上を走っていくネズミの姿を見かけたそうで。とうとう直接目撃、か…。

寝室の隣の部屋には米袋が数袋置いてあって、よくよく見ると袋の下の端に穴が開いているようにも見えた。おそらくネズミが米を齧りに来ていて、それを親父さんが目撃したのではないかと…。

ネズミ捕りシートを数枚置いてみたところ、一昨日、昨日、今日と、1日に1匹ずつ子ネズミが引っ掛かっていた。

また、お袋さんが米袋を部屋の奥から引っ張り出してみたら、見事に穴が開いていた。やはりネズミが食べに来ていたようだなと…。また、米袋の陰になっていた壁の部分に、約2cmほどの穴が開いていた。1.5cmの穴があればネズミは侵入してくるという話を見かけていたけど本当らしい。

ネズミは1度に6〜7匹の子供を産むらしいので、子ネズミが見つかったら、親ネズミと合わせて最低でも9匹は家の中をウロウロしてることになる。今回3匹は捕まえたから、少なく見積もっても残り6匹。どうしたもんか。いやまあ、出没しそうなところにネズミ捕りシートを置いておくしかないのだろうけど。

ピーナッツバターは効果無しだった :

家の外に置いてある、金網の籠の蓋が閉まるタイプのネズミ捕りに、生のさつまいも+ピーナッツバターを仕掛けてみたけれど、全くネズミが捕まらない。YouTubeの動画を眺めた感じでは、外国のネズミは吸い寄せられるようにピーナッツバターに集まってくるようだけど。どうやら日本のネズミはピーナッツバターが合わないのかもしれない。

一体何を使えばおびき寄せることができるのだろう…。米は食べていた実績があるけれど、あんな小さいものをぶら下げるのは難しいだろうし。

以上、29 日分です。

過去ログ表示

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