2024/02/05(月) [n年前の日記]
#1 [basic] FreeBASICで反転描画
FreeBASICの標準機能によるグラフィック描画で、画像の反転描画はどうやるのかについて調べてる。
環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。
FreeBASICのグラフィック描画機能は、メインメモリ上にバッファを作って、そのバッファに、CPUによるソフトウェア処理でRGBA値を書き込んでいくものらしくて…。そういう仕組みなものだから、公式掲示板では、「反転描画ってどうすればいいの?」という問いに「自分で処理を書け」と言われちゃうのがお決まりの流れに見えた。そのくらいは用意しておいてくれてもいいのになあ、と少し思ってしまったりもする。
公式掲示板(?)で、いくつかの処理が紹介されてるようなので、それぞれ動作確認してみた。
環境は 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
fbc put_flip2.bas でコンパイル。put_flip2.exe を実行。
まあ、たしかに、できなくはない。たぶん処理速度は遅いだろうけど…。
余談。Put に与えるソース領域のサイズについて、例えば 8x8ドットを描画したかったら、-step(8, 8) と書けばいいのかなと思ってたけど、そうではなかった。-step(7, 7)にしないといけない。
FreeBASIC の For - Next 文も、For i As Integer = 0 To 10 と書いたら、0 から 9 までではなく、0 から 10 まで処理されるので…。基本的に、様々な場面で、「終了値も処理に含まれることが多い」と思っておいたほうがいいのかもしれない。
_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 に与えるソース領域のサイズについて、例えば 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
putflipped() というサブルーチンが、反転描画処理をしている。
fbc putflipped_test.bas でコンパイル。putflipped_test.exe を実行。
たしかに反転描画ができてる。
ただ、渡した画像全体を描画してるな…。画像の一部だけを描画できるように修正したら、使い勝手が少し変わるだろうか。
今気づいたけど、ウインドウの閉じるボタンを押してもウインドウが閉じない…。FreeBASIC において、ウインドウの閉じるボタンをクリックした時は、特定のキー入力( chr(255) & "k" / chr(255, 107) / chr(0) & "k" ) があったものとして処理されるので、ESCキー( Chr(27) )以外に、そのキーもチェックするようにしておけば対応できそう。
_mieki256's diary - FreeBASICでゲームのメインループ相当を書きたい
_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
サンプルの使用画像。
_sprite.bmp
fbc imgput_test3.bas でコンパイル。imgput_test3.exe を実行。
たしかに、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 を実行。
たしかに、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
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 は、固定小数点を使って計算するようにしてあるらしい。
以下は、回転拡大縮小描画はするけど反転描画はサポートしてないタイプ。作成時期が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の標準描画機能+今回メモした処理で済ませてしまうのも全然アリだろうなと…。おそらく速度は遅いのだろうけど、お手軽に使えるというメリットはあるし。
とは言うものの、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)は少ないけれど、少しでもリッチな画面に見せたい」という需要があって盛り込まれていったはずの機能なので、メモリを贅沢に使えるならそんな機能要らない、と考えるのも妥当というか…。
元々、反転描画機能って、「使えるメモリ(RAM/ROM)は少ないけれど、少しでもリッチな画面に見せたい」という需要があって盛り込まれていったはずの機能なので、メモリを贅沢に使えるならそんな機能要らない、と考えるのも妥当というか…。
[ ツッコむ ]
以上です。