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

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 ULong
         End Union
         bpp As Long
         Width As ULong
         height As ULong
         pitch As ULong
         _reserved(1 To 12) As UByte
         
   '		'' properties
   '		declare property pixels() as ubyte ptr
      End Type
      
      '' This is a trick to obtain a pointer to the pixels data area
      ''
   '	property IMAGE.pixels() as ubyte ptr
   '		return cast(ubyte ptr, @this) + sizeof(IMAGE)
   '	end property

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.  A ulong 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 cast 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 Byte 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, cast, 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

   ImageDestroy( myBuf )

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

