FBgfx Image and Font Buffers

FreeBASIC

FBgfx Image and Font Buffers
 
Creating and understanding your FBgfx image and font buffers

The FBgfx Image Buffer
Creating Buffers
Buffer Format
Getting Pixels
The FBgfx Font Header
Header Details
Creating a Font Buffer
Assigning Font Characters
Tips & Tricks
Coloring your Custom Fonts
ScrPtr vs ImgBuf

Download Accompanying Tutorial Files: FreeBASIC Font Tutorial.7z

The FBgfx Image Buffer


FBgfx has a new data type in .17 and above. This type is called IMAGE. You can use it by including the FBgfx Header in your program (#include "fbgfx.bi") and then accessing the namespace for FBgfx, via FB.IMAGE. When we create buffers in this tutorial, we're going to be using the fb.Image Ptr type. A pointer, because it's dynamic memory which we can resize.

To use an image in the FBgfx Library, you have to create it via image buffer. Your buffer is an area of memory allocated (created, made available) for your image. You have to deallocate (free, make available to other programs) the buffer when you are done using it at the end of your program. FBgfx has its own internal pixel format, as well as an image header at the beginning of every buffer created. The image header contains information about your image. Things like its width, height, bit depth, etc., while the pixel Buffer contains the actual colors for each individual pixel in RGB (red, blue, green) format.

Creating Buffers

The size of the buffer you create will vary depending on screen depth. Your bytes-per-pixel are the number of bytes needed to store individual pixels. Thus, a 32-bit pixel depth screen will need 4 bytes per pixel (8 bits in a byte). You don't need to worry about this, however, as using the fb.Image Ptr setup to create your buffer makes it very easy to get the information we need from our buffers. You only need to know this information to understand how much size a buffer may take up total, for memory usage information.

Actually creating the buffer is very simple. It's just a simple creation of an fb.Image Ptr, and a call to 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


Code Dissection

#include "fbgfx.bi"


This includes the header file which contains the definition for the fb.Image type.

  '' Our image width/height
Const ImgW = 64
Const ImgH = 64


This creates constants which will be used to decide the size of our image. FBgfx doesn't know about these. We'll have to pass them to ImageCreate when we use it.

  '' Screens have to be created before a call to imagecreate
ScreenRes 640, 480, 32


This creates our FBgfx screen. ImageCreate needs to know our bit depth beforehand. However, FBgfx's ImageCreate now has an extra parameter allowing you to set the depth yourself.

  '' Create our buffer
Dim As FB.Image Ptr myBuf = ImageCreate(ImgW, ImgH)


This first of all creates a pointer that is of the fb.Image type. It's just a location of memory. We haven't filled it with anything yet. In fact, right now it equals zero, and could not be used. That's considered to be null.

The ImageCreate call returns the address of an area in memory of a newly created fb.Image which we initialize our pointer with. The size of this buffer depends on the bit depth, but the width/height of the image contained in the buffer is going to be the ones we set earlier. ImageCreate can also take a fill color and depth as the third and fourth arguments, respectively; if not specified, the image will be created filled with the transparent color and match the current screen color depth.

We now have allocated a space in memory. It's enough space to hold an ImgWxImgH image, along with the data FBgfx holds within its fb.Image type. We'll need to destroy it later for proper memory management.

  '' Print the address of our buffer.
Print "Buffer created at: " & myBuf
Sleep


This is just there to let you know what we've done. We print the address of myBuf. If it's not 0, we can assume that ImageCreate had worked.

  '' Destroy our buffer.  Always DESTROY buffers you CREATE
ImageDestroy( myBuf )
Print "Our buffer was destroyed."
Sleep


Here we destroy our buffer with a call to ImageDestroy. We don't have to use ImageDestroy to deallocate our buffer, but it's best to use it for consistency and clarity.

Buffer Format

Now that we know how to create buffers, we might want to know more information about what's being held inside of them. You can open up the fbgfx.bi header file and find the fb.Image type, and you can see all of this cool stuff inside of it.

We actually don't need to know much about the format itself. The reason for this is, we used an fb.Image Ptr. Everything after the Buf + SizeOf(fb.Image) in memory belongs to pixels. Everything before that is the header. The header can be accessed very easily because we used the fb.Image Ptr. All you have to know is what you want to look for.

FB.IMAGE Data Type

  '' 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


This same information can be found in fbgfx.bi. As you can see, this data type saves a *lot* of neat information about your buffer. The Width, Height, Pitch (bytes per row), and Bit Depth (bytes per pixel) are all contained. In the union is included the type of header, and the old header itself within the same space. The new header format is indicated by a type value of 7. The old header format is not used in the default dialect in the newer versions of FB, so we're not going to cover it here.

How do we access that information within the header? If you're familiar with pointers (which you should be, we used a pointer for our buffer in the first example), then all you have to do is access your buffer like a pointer, and directly access the data within. This may leave you to believe that all that's contained in your buffer is the fb.Image type itself, but that's just not true. Using a fb.Image Ptr allows the compiler to think that's what's contained in the buffer, even though only the first part does so.

Getting Pixels

The first section of our buffer which FreeBASIC helps us out with contains the header information. Add the size of the fb.Image to our address, and the rest of our buffer contains pixels (Example2.bas).

  '' We have to include this to use our FB.IMAGE datatype, remember.
#include "fbgfx.bi"


Remember to include our fb.Image data type!

  '' 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 uLong ptr, simply to avoid "suspicious assignment"
  '' warnings.
Dim As uLong Ptr myPix = Cast( uLong Ptr, ( Cast( UByte Ptr, myBuf ) + SizeOf(FB.Image) ) )


Phew. Alright. We have to make sure we get the exact address of our pixels. An integer contains 4 bytes. 3 of these are used for our RGB, and the extra is generally used for alpha when you need it (some people are very resourceful and will use the alpha byte - or channel - to store all kinds of data). If we're even ONE BYTE off, your Red can become your Green, and your Blue into your Red! So we have to cast to a UByte Ptr first.

You probably also noticed that we simply added sizeof(fb.Image) to our address. That's another perk of using fb.Image! If you add its size to the start of the buffer, we have just skipped all the memory addresses relating to the header and are now at our pixels.

Finally, we cast it all to a Ulong Ptr, mainly for safety. We're in 32 bit depth mode, so we need 4 bytes per pixels. A Ulong has that.

Here's a small line if you still don't understand how this works. Here is our buffer: |FB.IMAGE Header|Pixels|

If what's contained in the first section of our buffer is the fb.Image Header, it's obviously going to be that big in size. So, we can get our address for the pixels, simply by adding the size of the fb.Image datatype onto our original address.

One problem though! If we add that size to our buffer address, to try and get a new one, we end up with strange results. This is because our datatype isn't one byte long. We have to cast to a UByte Ptr first, then add the address. A UByte is one byte long, so we'll get the exact byte we need in memory to work with.

Finally, we're in 32-bits. We just casted to a UByte Ptr. Although we *can* just assign the uLong ptr the address of the UByte, it's best practice to cast it to a Ulong Ptr first. We finally have the address of our pixels, in the right datatype (one per pixel!). We could manipulate those pixels directly now, if we'd like.


  '' 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 ""


This is what I was talking about earlier. FB will treat your pointer as if it's an fb.Image Ptr, so you can access the data in the header directly. Since we have the size of the image as well as its pixels address now, we could edit and manipulate them as if they were a pointer to our screen buffer! See ScrPtr vs ImgBuf.bas for an example on this.

FBGfx Font Header


Header Details

The first row of an image buffer that will be used as a font contains the header information for your font, on a byte by byte basis (remember that the first row of pixels are going to be the first byes since it's stored in row->column).

The very first byte tells us what version of the header we're using. Currently, only 0 is supported, as only one header version has been released. The second byte tells us the first character supported in our font, and the third byte tells us the last.

0; Byte; Header Version
1; Byte; First Character Supported
2; Byte; Last Character Supported
3 to (3 + LastChar - FirstChar); Byte; Width of each Character in our font.

Creating a Font Buffer

If you had a font that supported character 37 as the first, and character 200 as the last, your bytes would contain:

0 for the header version. It's the current only version supported.
37 for the first character supported.
200 for the last character supported.
94 bytes containing the widths of each character.

Since the first row is taken up for header data, the font buffer will be an image buffer whose height is the font height plus one. That is, if you have a font height of 8, you need a buffer height of 9. You'll be putting the font in the second row of your buffer, rather than the first as you usually would.

Here's an example (Example3.bas), which creates a font buffer. It only creates it and assigns header data right now, not the actual font:

  '' The first supported character
Const FirstChar = 32
  '' Last supported character
Const LastChar = 190
  '' Number of characters total.
Const NumChar = (LastChar - FirstChar) + 1


These constants help us. It makes the code cleaner and faster.


  '' 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 )


Create our font buffer. Remember, we need to add horizontal space for each character in the font (8 pixels wide). We also need to add an extra row for our font header information.


  '' Our font header information.
  '' Cast to uByte ptr for safety and consistency, remember.
Dim As UByte Ptr myHeader = Cast(UByte Ptr, myFont )


Get the exact, casted, and having no warnings address of our font buffer. The header goes on a byte by byte basis, so we can't work on this with an fb.Image type.


  '' Assign font buffer header.
  '' Header version
myHeader[0] = 0
  '' First supported character
myHeader[1] = FirstChar
  '' Last supported character
myHeader[2] = LastChar


Assign the header information described above, into the first three bytes. The header version, the first supported character, and the last supported character.


  '' 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


Each character in our font can have its own width, so we have to assign these. The 3 + skips the header information. ##DoVar## starts at 0, so the first time it runs through that code, we'll be at index 3. Give all supported characters a width of 8.


  '' Remember to destroy our image buffer.
ImageDestroy( myFont )


Just reminding you :D

Assigning Font Characters

This is fairly simple. We'll use FreeBASIC's default font to draw onto our buffer. Remember to draw starting at column 1, rather than column 0, as the very first column is reserved for header data. Start the character you're drawing at your first supported character, and give it the color you want. Be warned, you can't have custom colors when drawing your font. When you add a character to our buffer, it's stuck the color you draw it as! See the tips & tricks section on how to get around this, however.

Here's the modified code (Example4.bas), where we'll add the font drawing via FreeBASIC's default font onto our buffer.

  '' NEW!!!
  '' Our current font character.
Dim As UByte CurChar


Just to have a quick index of the current ASCII character we're drawing onto our font.


Draw String myFont, ( DoVar * 8, 1 ), Chr(CurChar), RGB(Rnd * 255, Rnd * 255, Rnd * 255)


Skip the first row of our image buffer, as that contains font buffer information. Draw our font using FBgfx's font as our custom font. Fill it with a random color. You should note that we're drawing right into our buffer, with "Draw String myFont...".


Print Chr(CurChar);


Just for clarity, so you can see the characters we're drawing into the buffer.


  '' 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


Test out our new font. Of course, it's the same one we're used to. You could have created your own from your own custom font buffer somewhere.

Tips & Tricks


Coloring Your Custom Fonts

Alright, so by now you have realized that once you color a custom font, you can't use Draw String to change that color. Well, no fear, we can get around that (CustFontCol.bas). It might be a bit slow, however.

We can create a font object, which has a function to return a font buffer. What the code does is it redraws the font buffer every time we change color, and returns the font buffer stored in the object. This *could* in theory be sped up if we knew the range of characters to redraw, so we could only redraw from the lowest to the highest. Figuring out that range in itself, could also be slow.

#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

Comparison of how to draw onto image buffer pixels, versus how to draw on the screen's buffer (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 uLong Ptr myScrPix = ScreenPtr
  '' Get the address of our pixel's buffer.
Dim As uLong Ptr myBufPix = Cast( uLong 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
 
'/