mieki256's diary



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)は少ないけれど、少しでもリッチな画面に見せたい」という需要があって盛り込まれていったはずの機能なので、メモリを贅沢に使えるならそんな機能要らない、と考えるのも妥当というか…。

以上、1 日分です。

過去ログ表示

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