创建和理解您的FBgfx图像和字体缓冲区
FBgfx图像缓冲区
创建缓冲区
缓冲格式
获取像素
FBgfx字体标题
标题细节
创建一个字体缓冲区
赋值字体字符
提示与技巧
着色您的自定义字体
ScrPtr vs ImgBuf
下载附件教程文件:FreeBASIC Font Tutorial.7z
FBgfx在.17及更高版本中有一个新的数据类型。这种类型称为
IMAGE.您可以通过在程序(
#include "fbgfx.bi")中包含FBgfx头文件,然后通过
FB.IMAGE访问FBgfx的命名空间来使用它。当我们在本教程中创建缓冲区时,我们将使用
fb.Image Ptr类型。一个指针,因为它是动态内存,我们可以调整大小。
要在FBgfx库中使用图像,必须通过图像缓冲区创建它。您的缓冲区是为您的图像赋值(创建,可用)的内存区域。当您在程序结束时完成使用缓冲区时,必须释放(免费,提供给其他程序)缓冲区。FBgfx具有自己的内部像素格式,以及每个创建的缓冲区开头的图像头。图像标题包含有关图像的信息。像宽度,高度,位深等等,像素缓冲区包含RGB(红色,蓝色,绿色)格式的每个像素的实际颜色。
创建缓冲区
您创建的缓冲区的大小将根据屏幕深度而有所不同。每像素的字节数是存储单个像素所需的字节数。因此,32位像素深度屏幕将需要每像素4字节(8位字节)。您不需要担心这一点,但是使用
fb.Image Ptr设置创建缓冲区可以很容易地从缓冲区获取我们需要的信息。您只需要知道这些信息,以了解缓冲区可能占用的总体大小,内存使用情况。
实际创建缓冲区非常简单。这只是一个
fb.Image Ptr的简单创作,并且调用
ImageCreate(Example1.bas):
#include "fbgfx.bi"
'' Our image width/height
Const ImgW = 64
Const ImgH = 64
'' Screens have to be created before a call to imagecreate
ScreenRes 640, 480, 32
'' Create our buffer
Dim As FB.Image Ptr myBuf = ImageCreate(ImgW, ImgH)
'' Print the address of our buffer.
Print "Buffer created at: " & myBuf
Sleep
'' Destroy our buffer. Always DESTROY buffers you CREATE
ImageDestroy( myBuf )
Print "Our buffer was destroyed."
Sleep
代码解剖
#include "fbgfx.bi"
这包括头文件,它包含
fb.Image类型的定义。
'' Our image width/height
Const ImgW = 64
Const ImgH = 64
这将创建常量,用于决定图像的大小。FBgfx不知道这些。当我们使用它时,我们必须将它们传递给
ImageCreate.
'' Screens have to be created before a call to imagecreate
ScreenRes 640, 480, 32
这将创建我们的FBgfx屏幕。
ImageCreate需要事先了解我们的深度。但是,FBgfx的
ImageCreate现在有一个额外的参数,可以自己设置深度。
'' Create our buffer
Dim As FB.Image Ptr myBuf = ImageCreate(ImgW, ImgH)
这首先创建一个指向
fb.Image类型的指针。这只是一个记忆的位置。我们还没有填补任何东西。事实上,现在它等于零,不能使用。这被认为是空的。
ImageCreate调用返回一个新创建的
fb.Image内存区域的地址,该地址用于初始化我们的指针。该缓冲区的大小取决于位深度,但缓冲区中包含的图像的宽度/高度将是我们之前设置的图像的宽度/高度。
ImageCreate也可以分别填充颜色和深度作为第三和第四个参数;如果未指定,将创建填充透明颜色的图像,并匹配当前屏幕颜色深度。
我们现在已经在内存中赋值了一个空间。拥有ImgWxImgH图像的足够空间,以及FBGfx在其
fb.Image类型中保存的数据。我们需要稍后摧毁它以进行适当的内存管理。
'' Print the address of our buffer.
Print "Buffer created at: " & myBuf
Sleep
这只是让你知道我们做了什么。我们打印地址
myBuf.如果不是
0,我们可以假设
ImageCreate已经工作了。
'' Destroy our buffer. Always DESTROY buffers you CREATE
ImageDestroy( myBuf )
Print "Our buffer was destroyed."
Sleep
这里我们用
ImageDestroy的方式销毁我们的缓冲区。我们不必使用
ImageDestroy来释放缓冲区,但最好使用它来保持一致性和清晰度。
缓冲格式
现在我们知道如何创建缓冲区,我们可能想要了解更多有关它们内部保持的内容的信息。您可以打开
fbgfx.bi头文件并找到
fb.Image类型,您可以看到所有这些酷的东西。
我们实际上不需要很多关于格式本身的知识。其原因是我们使用了
fb.Image Ptr.内存中
Buf + SizeOf(fb.Image)之后的所有内容都属于像素。之前的一切都是标题。因为我们使用了
fb.Image Ptr,所以可以非常容易地访问标题。所有你需要知道的是你想要的东西。
FB.IMAGE数据类型
'' Image buffer header, new style (incorporates old header)
Type IMAGE Field = 1
Union
old As _OLD_HEADER
Type As UInteger
End Union
bpp As Integer
Width As UInteger
height As UInteger
pitch As UInteger
_reserved(1 To 12) As UByte
End Type
相同的信息可以在
fbgfx.bi中找到。您可以看到,这种数据类型可以节省您的缓冲区的整洁信息* * *。宽度,高度,间距(每行字节数)和位深度(每像素的字节数)都包含在内。在联合中包含头类型,旧标头本身在同一空间内。新的标题格式由类型值
7表示。在较新版本的FB中,旧标题格式不会用于默认方言,因此我们不会在此处覆盖。
我们如何在标题中访问这些信息?如果你熟悉指针(在第一个例子中我们使用了一个指针来指定缓冲区),那么所有你需要做的就是像一个指针一样访问你的缓冲区,并直接访问数据。这可能会让您相信缓冲区中包含的所有内容都是
fb.Image类型,但这并非如此。使用
fb.Image Ptr允许编译器认为这是缓冲区中包含的内容,即使只有第一部分这样做。
获取像素
FreeBASIC帮助我们的缓冲区的第一部分包含头信息。将
fb.Image的大小添加到我们的地址,其余的缓冲区包含像素(Example2.bas)。
'' We have to include this to use our FB.IMAGE datatype, remember.
#include "fbgfx.bi"
请记住加入我们的
fb.Image数据类型!
'' This one is very important.
'' We cast to a ubyte ptr first off, to get the exact byte our pixels begin.
'' We then cast to a uInteger ptr, simply to avoid "suspicious assignment"
'' warnings.
Dim As UInteger Ptr myPix = Cast( UInteger Ptr, ( Cast( UByte Ptr, myBuf ) + SizeOf(FB.Image) ) )
唷。好的。我们必须确保我们得到我们像素的确切地址。整数包含4个字节。其中3个用于我们的RGB,并且额外的通常用于alpha,当你需要它(有些人非常有用,并将使用半透明字节或通道存储各种数据)。如果我们甚至一个BYTE关闭,你的红色可以成为你的绿色,你的蓝色变成你的红色!所以我们必须首先投入
UByte Ptr.
你可能也注意到我们只是在
sizeof(fb.Image)添加了我们的地址。这是使用
fb.Image的另一个原因!如果将其大小添加到缓冲区的开头,我们只是跳过与标题相关的所有内存地址,现在是我们的像素。
最后,我们将其全部归属于
UInteger Ptr,主要是为了安全。我们处于32位深度模式,因此我们需要每像素4个字节。A
UInteger有。
如果你仍然不明白这是如何工作的,这里有一条小线。这是我们的缓冲区:| FB.IMAGE Header | Pixels |
如果我们的缓冲区的第一部分中包含的是
fb.Image标题,那显然会变大。因此,我们可以通过将
fb.Image数据类型的大小添加到我们的原始地址来获取像素的地址。
一个问题呢!如果我们把这个大小添加到我们的缓冲区地址,尝试获得一个新的,我们最终会得到奇怪的结果。这是因为我们的数据类型不是一个字节长。我们必须首先转换为
UByte Ptr,然后添加地址。一个
UByte是一个字节长,所以我们将得到我们需要在内存中使用的确切字节。
最后,我们是32位。我们只是投了一个
UByte Ptr.虽然我们可以*只需将uInteger ptr赋值给
UByte的地址,最好先将其转换为
UInteger Ptr.我们终于在我们的像素的地址,在正确的数据类型(每像素一个!)。如果我们愿意,我们可以直接操纵那些像素。
'' Print information stored in our buffer.
Print "Image Width: " & myBuf->Width
Print "Image Height: " & myBuf->Height
Print "Image Bit Depth: " & myBuf->BPP
Print "Image Pitch: " & myBuf->Pitch
Print ""
这就是我刚才所说的话。FB会将您的指针视为
fb.Image Ptr,因此您可以直接访问头文件中的数据。既然我们现在拥有图像的大小和像素地址,我们可以编辑和操作它们,就像它们是指向我们的屏幕缓冲区一样!请参阅ScrPtr与ImgBuf.bas有关的示例。
标题细节
将用作字体的图像缓冲区的第一行包含字体的标题信息(以字节为单位)(请记住,第一行像素将成为第一个bye,因为它存储在row- >列)。
第一个字节告诉我们我们正在使用哪个版本的标题。目前只支持0,因为只有一个头版本已经被释放。第二个字节告诉我们字体支持的第一个字符,第三个字节告诉我们最后一个字符。
0;字节;标题版本
1;字节;第一个字符支持
2;字节;最后字符支持
3到(3 + LastChar - FirstChar);字节;我们的字体中每个字符的宽度。
创建一个字体缓冲区
如果您有一个字体支持字符37作为第一个,字符200作为最后,您的字节将包含:
0为标题版本。这是目前支持的唯一版本。
37支持第一个字符。
200支持最后一个字符。
包含每个字符宽度的94个字节。
由于第一行被占用头标数据,所以字体缓冲区将是高度为字体高度加一的图像缓冲区。也就是说,如果您的字体高度为8,则需要缓冲区高度为9。您将把字体放在缓冲区的第二行,而不是像通常那样的第一行。
这是一个例子(Example3.bas),它创建一个字体缓冲区。它只创建它并赋值头数据,而不是实际的字体:
'' The first supported character
Const FirstChar = 32
'' Last supported character
Const LastChar = 190
'' Number of characters total.
Const NumChar = (LastChar - FirstChar) + 1
这些常数帮助我们。它使代码更清洁和更快。
'' Create a font buffer large enough to hold 96 characters, with widths of 8.
'' Remember to make our buffer one height larger than the font itself.
Dim As FB.Image Ptr myFont = ImageCreate( ( NumChar * 8 ), 8 + 1 )
创建我们的字体缓冲区。请记住,我们需要为字体中的每个字符(8像素宽)添加水平空间。我们还需要为我们的字体标题信息添加一个额外的行。
'' Our font header information.
'' Cast to uByte ptr for safety and consistency, remember.
Dim As UByte Ptr myHeader = Cast(UByte Ptr, myFont )
获取准确的,被铸造的,没有我们的字体缓冲区的警告地址。标题是逐个字节的,所以我们无法使用
fb.Image类型进行处理。
'' Assign font buffer header.
'' Header version
myHeader[0] = 0
'' First supported character
myHeader[1] = FirstChar
'' Last supported character
myHeader[2] = LastChar
将上述标题信息赋值到前三个字节中。标题版本,第一个支持的字符和最后支持的字符。
'' Assign the widths of each character in the font.
For DoVar As Integer = 0 To NumChar - 1
'' Skip the header, if you recall
myHeader[3 + DoVar] = 8
Next
我们的字体中的每个字符都可以有自己的宽度,所以我们必须赋值这些。
3 +跳过标题信息。## DoVar ##从0开始,所以第一次运行该代码,我们将在索引3。给所有支持的字符宽度为8。
'' Remember to destroy our image buffer.
ImageDestroy( myFont )
只是提醒你:D
赋值字体字符
这很简单我们将使用
FreeBASIC的默认字体绘制到我们的缓冲区。记住从第1列开始绘制,而不是第0列,因为第一列保留给标题数据。开始您在第一个支持的角色中绘制的角色,并给出所需的颜色。请注意,绘制字体时不能有自定义颜色。当你添加一个字符到我们的缓冲区,它被卡住你画的颜色!但是,请参阅提示和技巧部分,了解如何解决此问题。
这是修改的代码(Example4.bas),我们将通过FreeBASIC的默认字体将字体绘图添加到我们的缓冲区。
'' NEW!!!
'' Our current font character.
Dim As UByte CurChar
只要快速索引当前的ASCII字符,我们将绘制到我们的字体上。
Draw String myFont, ( DoVar * 8, 1 ), Chr(CurChar), RGB(Rnd * 255, Rnd * 255, Rnd * 255)
跳过我们的图像缓冲区的第一行,因为它包含字体缓冲区信息。使用FBgfx的字体绘制我们的字体作为我们的自定义字体。用随机颜色填充。您应该注意到,我们正在绘制到我们的缓冲区,“
Draw String myFont...”。
Print Chr(CurChar);
为了清楚起见,您可以看到我们正在绘制到缓冲区中的字符。
'' Use our font buffer to draw some text!
Draw String (0, 80), "Hello!", , myFont
Draw String (0, 88), "HOW ARE ya DOIN Today?! YA DOIN FINE?!", , myFont
Sleep
测试我们的新字体。当然,这是我们习惯的一样。您可以从某个地方的自定义字体缓冲区创建自己的。
着色您的自定义字体
好的,所以现在你已经意识到,一旦你自定义字体颜色,你不能使用
Draw String来改变这种颜色。好吧,没有恐惧,我们可以绕过(CustFontCol.bas)。可能会有点慢,但是。
我们可以创建一个字体对象,它具有返回字体缓冲区的功能。代码的作用是每次更改颜色时重绘字体缓冲区,并返回存储在对象中的字体缓冲区。如果我们知道重绘字符的范围,这个*可以理论上加快,所以我们只能从最低到最高重绘。弄清楚这个范围本身,也可能很慢。
#include "fbgfx.bi"
Type Font
'' Our font buffer.
Buf As FB.Image Ptr
'' Font header.
Hdr As UByte Ptr
'' Current font color.
Col As UInteger
'' Make our font buffer.
Declare Sub Make( ByVal _Col_ As UInteger = RGB(255, 255, 255) )
'' Change the font color and edit the font buffer.
'' Return the new font.
Declare Function myFont( ByVal _Col_ As UInteger = RGB(255, 255, 255) ) As FB.Image Ptr
'' Create/Destroy our font.
'' Set a default color to it if you like.
Declare Constructor( ByVal _Col_ As UInteger = RGB(255, 255, 255) )
Declare Destructor()
End Type
'' Create our font's buffer.
Constructor Font( ByVal _Col_ As UInteger = RGB(255, 255, 255) )
This.Make( _Col_ )
End Constructor
'' Destroy font buffer.
Destructor Font()
ImageDestroy( Buf )
End Destructor
'' Assign the FBgfx font into our font buffer.
Sub Font.Make( ByVal _Col_ As UInteger = RGB(255, 255, 255) )
'' No image buffer data. Create it.
If This.Buf = 0 Then
'' No screen created yet.
If ScreenPtr = 0 Then Exit Sub
'' Support 256 characters, 8 in width.
'' Add the extra row for the font header.
This.Buf = ImageCreate( 256 * 8, 9 )
'' Get the address of the font header,
'' which is the same as getting our pixel address
'' Except that we always will use a ubyte.
This.Hdr = Cast(UByte Ptr, This.Buf) + SizeOf(FB.Image)
'' Assign header information.
This.Hdr[0] = 0
'' First supported character
This.Hdr[1] = 0
'' Last supported character
This.Hdr[2] = 255
Else
If This.Col = _Col_ Then Exit Sub
End If
'' Draw our font.
For DoVar As Integer = 0 To 255
'' Set font width information.
This.Hdr[3 + DoVar] = 8
Draw String This.Buf, (DoVar * 8, 1), Chr(DoVar), _Col_
Next
'' Remember our font color.
This.Col = _Col_
End Sub
'' Get the buffer for our font.
'' Remake the font if the color's different.
Function Font.myFont( ByVal _Col_ As UInteger = RGB(255, 255, 255) ) As FB.Image Ptr
'' If our colors match, just return the current buffer.
If _Col_ = Col Then
Return Buf
End If
'' Make the font with a new color.
This.Make( _Col_ )
'' Return out buffer.
Return This.Buf
End Function
'' MAIN CODE HERE!
ScreenRes 640, 480, 32
'' Create our font.
Dim As Font myFont = RGB(255, 255, 255)
'' Draw a string using our custom font.
Draw String (0,0), "Hello. I am the custom font.",, myFont.myFont()
'' Gasp. A new color!
Draw String (0,8), "Hello. I am the custom font.",, myFont.myFont(RGB(255, 0, 0))
Sleep
'' Speed test. Turns out it's quite slow.
Scope
Randomize Timer
'' Our timer.
Dim As Double T = Timer
'' Time how long it takes to make a new font this way.
For DoVar As Integer = 0 To 499
myFont.Make( RGB(Rnd * 255, Rnd * 255, Rnd * 255) )
Next
'' And we're all done. Print important data.
Locate 3, 1
Print "Time to Re-Draw font 499 times: " & ( Timer - T )
Print "Time per Re-Draw: " & ( Timer - T ) / 500
Sleep
End Scope
ScrPtr vs ImgBuf
比较如何绘制图像缓冲区像素,而不是绘制屏幕上的缓冲区(ScrPtr vs ImgBuf.bas):
#include "fbgfx.bi"
ScreenRes 640, 480, 32
'' Create a buffer the size of our screen.
Dim As FB.IMAGE Ptr myBuf = ImageCreate( 640, 480 )
'' Get the address of our screen's buffer.
Dim As UInteger Ptr myScrPix = ScreenPtr
'' Get the address of our pixel's buffer.
Dim As UInteger Ptr myBufPix = Cast( UInteger Ptr, Cast( UByte Ptr, myBuf ) + SizeOf(FB.IMAGE) )
'' Lock our page. Fill the entire page with white.
ScreenLock
'' Alternatively, if the screen resolution's unknown, use ScreenInfo to
'' make this more secure
'' Note: this code assumes no padding between rows. To prevent this,
'' you need to use ScreenInfo to get the screen's pitch, and calculate
'' row offsets using that instead.
For xVar As Integer = 0 To 639
For yVar As Integer = 0 To 479
myScrPix[ ( yVar * 640 ) + xVar ] = RGB(255, 255, 255)
Next
Next
ScreenUnlock
Sleep
'' Draw onto our image buffer all red.
For xVar As Integer = 0 To myBuf->Width - 1
For yVar As Integer = 0 To myBuf->Height - 1
myBufPix[ ( yVar * (myBuf->Pitch \ SizeOf(*myBufPix)) ) + xVar ] = RGB(255, 0, 0)
Next
Next
'' Put the red buffer on the screen.
Put (0,0), myBuf, PSet
Sleep
/'
ScreenPtr:
1) Get address of screen buffer
(remember that FBgfx uses a dummy buffer that it flips automatically)
2) Lock page
3) Draw onto screen address
4) Unlock page to show buffer
Image Buffer:
1) Create an image buffer
2) Get the address of image pixels
3) Draw onto image pixels
(you can use neat stuff like the buffer information to help you here)
4) Put down Image where you please
(another big plus!)
About Drawing:
cast(ubyte ptr, mybuff) + Y * Pitch + X * Bpp
Every Y contains PITCH number of bytes. In order to reach your new Y, you
have to skip an entire row.
It should be safe to do the pointer arithmetic in cases where the pointer's data
type is not one byte long, so you may find it easier to use a pointer type to
match your bit depth.
In these cases you should divide the Pitch and BPP by the size of the pointer type.
Conveniently, in this case the Pitch should always be divisible by the pixel's type
size. And, obviously, so will the BPP, which will just cancel to 1 :D
'/