TBitmapFont

BlitzMax Forums/BlitzMax Programming/TBitmapFont

Grey Alien(Posted 2007) [#1]
I'm finally getting round to making one, but I wondering about the old pow2 VRAM use.

Basically I'm pretty sure that when BMax reads in a ttf font, it converts each letter into a separate image which of course needs to be rounded up to the next pow2 size in VRAM.

This would also seem the most obvious and easy (yet VRAM hungry) way to make a TBitmapFont type. Basically read in one large image as a TPixmap, use x,y,w,h values to grab the portions I need (or should I draw it to the screen (without CLS) and grab it from there?) and thus each letter is a separate image.

However, I keep reading about this fancy "single surface" stuff and using UV coords etc. This would seem a more efficient way to make a bitmap font because it would consume less VRAM PLUS you would't need to go through the grabbing stage, you could just draw the correct area.

Does single surface stuff work in OpenGL though?

Any thoughts on the whole matter are appreciated, thanks.

I'll code something else until I get some (useful) feedback ;-)


GfK(Posted 2007) [#2]
Use a pixmap/PixmapWindow - pretty sure thats how I did it.

You can't draw it to the screen and grab it from there - not if you want to retain the alpha channel.


tonyg(Posted 2007) [#3]
The TAnim code would be ideal for this.
Isn't there already TBitmapFont source available.


Dreamora(Posted 2007) [#4]
Or using one of the DrawImageRectEx / DrawImageArea resources from the code archives that do what the "old" DrawImageRect did, should do the trick as well :)


Grey Alien(Posted 2007) [#5]
GfK: OK thanks, yeah may do that, depends on the other options. I had some weird understanding that the screen retained the alpha channel for grabbing later...

tonyg: There may be source out there but I'm planning to add this to my framework so I feel like making it myself, but there could be some good example code! I made one for BPlus, so the basics are easy enough, it's just the grabbing and drawing I need to sort for BMax.

Dreamora: Yeah I have that (Ian Duff made DrawImageArea), and it works in OpenGL, so I could try that compared to drawing a normal image in a speed test. I'll have to make sure the bitmap font source file is within a reasonable power of 2 size though, like not going over 1024 I guess in case some graphics cards mess up (I've seen some not displaying over 1024).


TartanTangerine (was Indiepath)(Posted 2007) [#6]
You might need to change some stuff to work on the latest version of BMAX but here is my module that you are more than welcome to include in your framework as long as you make reference to me. Oh and it works on all platforms.

Compiled example : http://www.pjio.com/temp/bmaptext.zip

Module:
Strict

Rem
bbdoc: BitMap Text Lib - Single Surface
End Rem
Module Indiepath.bmaptext

ModuleInfo "Version: 1.0"
ModuleInfo "Author: Tim Fisher"
ModuleInfo "License: Indiepath License for non warranted software"
ModuleInfo "Modserver:indiepath"
?Win32
Import BRL.D3D7Max2D
?
Import BRL.GLMax2D
Import BRL.PNGLoader
Import BRL.Retro


Type bMapText
	Field Image		:TImage
	Field Texturesize	:Double
	Field ScaleFactor	:Double
	Field NumChars	:Short
	Field ID			:Byte[96]
	Field xPos		:Double[96]
	Field yPos		:Double[96]
	Field width		:Double[96]
	Field height		:Double[96]
	Field xOffset		:Double[96]
	Field yOffset		:Double[96]
	Field xAdvance	:Double[96]
Rem
bbdoc: Free the Object
End Rem		
		Function Destroy(b:bMapText)
				b = Null
        	End Function

		'-----------------------------------------------------------------
Rem
bbdoc: Create a New bitMapped Text object by loading info from file
returns: Handle to new Bit Map Text Object
about: Use this command exactly as you would the Standard BLitz Command
End Rem			
		Function Create:bMapText(url:Object,flags:Int=-1)
				Local b:bMapText
				b:bMapText = New bMapText
				b.Image = LoadImage(String(url)+".png",flags)
				b.NumChars = 0
				Local myfile:TStream = ReadStream("littleendian::" + String(url)+".fnt")
				Local temp:String = ReadLine(myfile)
				Local pos1:Byte = temp.Find("scaleF=")
				b.ScaleFactor = Double(Mid(temp,pos1+8,4))
				temp:String = ReadLine(myfile)
				pos1:Byte = temp.Find("scaleW=")
				b.TextureSize = Double(Mid(temp,pos1+8,4))
				While Not Eof(myfile)
					temp:String = ReadLine(myfile)
					pos1:Byte = temp.Find("id=")
					b.ID[b.NumChars] = Byte(Mid(temp,pos1+4,3))
					pos1:Byte = temp.find("x=")
					b.xPos[b.NumChars] = Double(Mid(temp,pos1+3,3)) / b.TextureSize
					pos1:Byte = temp.find("y=")
					b.yPos[b.NumChars] = Double(Mid(temp,pos1+3,3)) / b.TextureSize
					pos1:Byte = temp.find("width=")
					b.Width[b.NumChars] = Double(Mid(temp,pos1+7,3)) / b.TextureSize
					pos1:Byte = temp.find("height=")
					b.Height[b.NumChars] = Double(Mid(temp,pos1+8,3)) / b.TextureSize
					pos1:Byte = temp.find("xoffset=")
					b.xOffset[b.NumChars] = Double(Mid(temp,pos1+9,3))
					pos1:Byte = temp.find("yoffset=")
					b.yOffset[b.NumChars] = Double(Mid(temp,pos1+9,3))
					pos1:Byte = temp.find("xadvance=")
					b.xadvance[b.NumChars] = Double(Mid(temp,pos1+10,3))
					b.Numchars:+1	
				Wend
				CloseStream(myfile)
				Return b		
		End Function
		
		'----------------------------------------------------------------
Rem
bbdoc: Get width of a string 
returns: Width of String as Double
End Rem			
		Method StringWidth:Double(text:String)
				Local a:Int
				Local char:Byte
				Local width1:Double
				For a = 0 To Len(text) -1
					char = FindChar(Mid(text,a+1,1))
					width1:+ self.xAdvance[char]
				Next
				Return width1
		End Method
		
			
		'----------------------------------------------------------------
Rem
bbdoc:Get height of a string
returns: Returns height of string as Double
End Rem		
		Method StringHeight:Double(text:String)
				Local a:Int
				Local char:Byte
				Local Height1:Double
				For a = 0 To Len(text) -1
					char = FindChar(Mid(text,a+1,1))
					Height1:+ self.height[char] * self.TextureSize
				Next
				Return (height1 / Double (Len(text)-1)) 
		End Method
		
		'----------------------------------------------------------------
Rem
bbdoc: Draw string to Screen using BmapText object
about: Pass text, positon and justification.
End Rem			
		Method Draw(text:String,x:Double,y:Double,center:Byte=False,rght:Byte=False)
			'	SetBlend(SOLIDBLEND)
				Local char:Int
				Local scale_x:Float
				Local scale_y:Float
				GetScale(scale_x,scale_y)
				
			 	scale_x :* self.ScaleFactor
			 	scale_y :* self.ScaleFactor
								
				If center 	Then x = x - (Self.StringWidth(text) / 2 * scale_x)
				If Rght 	Then x = x - (Self.StringWidth(text) * scale_x)
				
				If Len(text) < 1 Then Return
					
				Local a:Byte
				For a = 0 To Len(text) -1
			
					char = FindChar(Mid(text,a+1,1))
	
					If char >= 0 Then
						Local xpos		:Double = self.xPos[char]
						Local ypos		:Double = self.yPos[char]
						Local width		:Double = self.width[char]
						Local height	:Double = self.height[char]
?Win32
						Local DXFrame:TD3D7ImageFrame = TD3D7ImageFrame (self.Image.frame(0))
						If DXFrame
                			  	 DXFrame.setUV(xpos,ypos,xpos + width,ypos + height)
            				Else
?
                   			 	Local GLFrame:TGLImageFrame = TGLImageFrame(self.Image.frame(0))
                  					GLFrame.u0 = xpos
                    				GLFrame.v0 = ypos
                    				GLFrame.u1 = xpos + width
                    				GLFrame.v1 = ypos + height
?Win32
                			EndIf
?
						Local x1:Double = x +  self.xOffset[char] * scale_x
						Local y1:Double = y +  self.yOffset[char] * scale_y
					'	SetBlend(ALPHABLEND)
						DrawImageRect(self.Image,x1,y1,width * self.TextureSize * self.scalefactor,height * self.TextureSize * self.scalefactor)
						x :+ (self.xAdvance[char] * scale_x)
					EndIf
				Next
		End Method
		
		'----------------------------------------------------------------
		
		Method FindChar(digit:String)
			Local b:Int
			For b = 0 To self.NumChars
				If Asc(digit) = self.ID[b] Then Return b
			Next
		EndMethod
	
End Type


This is how you would use it:
Strict
Import Indiepath.bMapText

Graphics 640,480,0
SetClsColor(80,80,80)
Cls
SetBlend(alphablend)
Local text1:bMapText = bMapText.Create("fatbot",FILTEREDIMAGE|MIPMAPPEDIMAGE)
SetScale(0.4,0.4)
text1.Draw("Indiepath Ltd",320,280,True,False)
SetScale(0.35,0.35)
SetColor(255,255,155)
text1.Draw("Hit ESC to Exit",320,330,True,False)
Flip
WaitKey() 


And here is some stuff you really do need to read:
Generate Fonts using the BitMap Font Editor from www.AngelCode.com

You Must do the following to the generated .fnt file!

1) Replace the very first line with a Scale Factor, this is the scale factor that the module will reference

The First line will look something like :-
info face="Arial" size=32 bold=0 italic=0 charset="ANSI" stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1

Replace it with :-
scaleF=1.3

It will now look something like :-
scaleF=1.3
common lineHeight=64 base=57 scaleW=512 scaleH=512 pages=1
char id=32   x=0     y=0     width=1     height=0     xoffset=0     yoffset=64    xadvance=14    page=0 

2) Delete All references to Kerning, they appear after char id=255 ( we don't need these)


Finally *********************************************

***THE FONT IMAGE FILE MUST BE A .PNG**********	

That's all folks.



Grey Alien(Posted 2007) [#7]
Thanks Tim, nice one! :-D. I will study this then I probably copy the code into my framework and change it a bit but with a reference right at the top to Tim Fisher of Indiepath.


Grey Alien(Posted 2007) [#8]
btw, that Destroy function won't actually do anything. It will null the pointer passed into the function, but the original external pointer will still point to the bitmap font.


Grey Alien(Posted 2007) [#9]
Hmm, interesting. To avoid multiple calls to FindChar when drawing the text, I'll think I'll fill the array slot specified by ID (the character code) instead of filling the array from slot 0, then I can just reference the correct array slot based on the character code without any searching. This should be a lot faster. Is there any reason you did it the other way that I'm not seeing?


Grey Alien(Posted 2007) [#10]
Tim, why is TextureSize important? Wouldn't it work without it?

Also is there a reason why you chose to include a scalefactor as part of the type when you can just use SetScale externally? Was it for some kind of increased flexibility because I can't see it at the moment...;-)

Anyway integration is going well.


GfK(Posted 2007) [#11]
I used an array of tImages. If a character exists in my bitmap image, its loaded into the relevant element of the array. All the others are left at Null.

Doing it this way means a little wasteage on unused array elements, but at least the ASCII codes and array indices are consistent, so, no searching. More speed that way.

My text class renders text faster than DrawText does. Plus I added in support for left/right/center justification, which I don't think DrawText has.


TartanTangerine (was Indiepath)(Posted 2007) [#12]
Hmm, interesting. To avoid multiple calls to FindChar when drawing the text, I'll think I'll fill the array slot specified by ID (the character code) instead of filling the array from slot 0, then I can just reference the correct array slot based on the character code without any searching. This should be a lot faster. Is there any reason you did it the other way that I'm not seeing?
Na just bad programming on my part.

Tim, why is TextureSize important? Wouldn't it work without it?
Can't remember, something to do with calculating the correct width of the characters?!?!


Also is there a reason why you chose to include a scalefactor as part of the type when you can just use SetScale externally? Was it for some kind of increased flexibility because I can't see it at the moment...;-)
Ummn.. Ah it was because nn point fonts may be different sizes depending on the font used - this allows you to adjust the scale externally so they all match.


Grey Alien(Posted 2007) [#13]
GfK: Yeah I made a load of DrawText wrapper functions for various different things like justification, shadows etc. I'm gonna go with the wasted array slots method.

Tim: OK cool, thanks for the answers. I'll probably remove texture size as it just complicate things, I may keep scale factor but it will slow everything down (a teeny weeny bit) due to having an extra multiplication to do.

Got it working now, neat! I can see how to build an TAnimImage type using UV now :-)

I've added in some extra functionality like a Char Gap and optional rounded draw coords for crisp drawing if you are drawing at scale 1 (which I'll be doing a lot).

Now all I need to make my empire complete is some working Mesh code (for scrolling tile maps)!


TartanTangerine (was Indiepath)(Posted 2007) [#14]
Now all I need to make my empire complete is some working Mesh code (for scrolling tile maps)!

If you ask nicely.


Grey Alien(Posted 2007) [#15]
I kneel at your feet... Pretty please I need some "mesh" code in Bmax (or so I've been lead to believe) e.g. you instruct DX or GL to draw a load of tiles with perfect anti-aliased joins. It doesn't matter if the source tiles are separate images or one big image and the data is obtained with UV coords, but basically they must all output on a pefect single image (same size as the screen, well maybe a tiny bit larger) which I can then draw at floating point coords to avoid horrid line artifacts between the tiles which you can see when they are drawn separately with DrawImage and the screen is scrolling slowly. Make sense?


Grey Alien(Posted 2007) [#16]
OK you do need texture size as the UV coords and width/height evidently have to be in the range 0 to 1, so they are basically a % of the texture size, you can't just supply integer x and y coords.


TartanTangerine (was Indiepath)(Posted 2007) [#17]
Now all I need to make my empire complete is some working Mesh code (for scrolling tile maps)!
That would be it then :) I knew there was a reason.


ImaginaryHuman(Posted 2007) [#18]
You can draw to the backbuffer and grab and it will grab the alpha so long as you're drawing to an RGBA backbuffer - ie a 32-bit mode not a 24-bit one.

Also I would use only one image or as few images as possible for storing the text since swapping to new textures/images is a significant overhead. If you can draw all your text from one image that will be much faster.

I would also use UV (texture coordinates) to draw custom Quads with OpenGL, it's pretty easy. Texture coords go from 0 to 1 for X and Y, while quad coords are usually pixel coords.

Regarding VRAM being square, each dimension must individually be a power of 2, but the two dimensions don't have to be the same. You can have 64x64, 64x128, 128x64 etc.


Grey Alien(Posted 2007) [#19]
AngelDaniel: Yep thanks, got all that. It's working very nicely now thanks.

Tim: You'll see the new one in my next framework update. In the end I used TextureSize but actually split it into TextureW and TextureH so I could have non-square textures and save on VRAM. Also got rid of Find() although it may be useful to have a different setup method (like your one) for Unicide character codes in foreign languages that go into silly high numbers like 8000 to avoid having a massive empty array.


Grey Alien(Posted 2007) [#20]
Oh yes! Did a combo of a fixed array of 256 standard chars + extra array slots for Unicode chars (e.g. the Euro sign or Cyrillic etc.). The extra slots use a find method but the standard slots are direct access to the array.


Blitzplotter(Posted 2007) [#21]
Looking forward to your next update already Grey.
@Grey, Indie & Gfk:- I kneel at your feet.


Grey Alien(Posted 2007) [#22]
I tempted to release it now as the update is so cool, but making a release takes several hours that I simply don't have right now.


Blitzplotter(Posted 2007) [#23]
I know what you mean, I should be packing to go away for the next three weeks right about now, instead I've resolved one of the issues you picked up in my alfa. Not in the online trial version yet, but I've got it into the full version.

Thanks again for testing the alfa, it's made me address some things before I reach my beta{;-)

Right gotta pack....


AndyGFX(Posted 2008) [#24]
Don't work with latest version of BitMap Font Editor from www.AngelCode.com :(


d-bug(Posted 2008) [#25]
hmm, I don't know, if someone is interested in one of those "resource hungry" BitmapFont-Classes, but I've made a module to register bitmapfonts as TImageFont. This will provide you full funtionality like LoadImageFont loaded fonts have...

BitmapFont 1.13

cheers


Grey Alien(Posted 2008) [#26]
AndyGFX: I'm using 1.8c so maybe some changes since then have altered the file format. I presume you are using the code above which is pretty old because you are not one of my Framework customers. Double check the file format against what the code above is looking for and hopefully you'll find the problem.


ziggy(Posted 2008) [#27]
I've already made a bitmapfont module, with tutorial and free sample fonts here: http://www.blide.org/index.php?section=fontmachine

The module is open source and free to be distributed or even sold.


AndyGFX(Posted 2008) [#28]
@ziggy:

thx. looks cool too. work unde linux?


ziggy(Posted 2008) [#29]
Yes, it's 100% written on pure Max2d commands, so it works nativelly on Linux and MacOs too. The Bitmap Font Editor, wich is a separated application is developed on Mono. It is windows only at the moment becouse the mono guys don't have yet a proper GDI+ counterpart for MacOsX and Linux, but somebody could easily create a compatible Bitmap Font Editor using Max. If anybody is interesting I'll send the file format used by the module.


AndyGFX(Posted 2008) [#30]
I have fixed TBMFont and work with BitMap Font Editor 1.10b from www.AngelCode.com now ;)

Only one problem i have now, when is with textured object (OpenGL) font is rewrited with used texture :(

EDIT:

Problem solved:
glActiveTextureARB(GL_TEXTURE0)
glClientActiveTextureARB(GL_TEXTURE0)


;)