Bitmap Fonts - Speed up Trimming?

BlitzMax Forums/BlitzMax Programming/Bitmap Fonts - Speed up Trimming?

AndrewT(Posted 2009) [#1]
I'm writing a bitmap font library. It's fully-functional, but it needs a lot of optimization, specifically in the trimming of space off the sides of the character images.

How is this typically done? Currently I'm going through every pixel and finding the right- and left-most solid pixels in each image, then drawing the image to the backbuffer, then grabbing the image using the coordinates I found earlier. It works perfectly, but it's incredibly slow--it takes about 2 seconds to trim a font.

Any suggestions?


Bremer(Posted 2009) [#2]
If you are getting the font from a truetype and you are drawing the characters into images to create the bitmap font, which it kinda sounds like you are, then you can use the following blitzmax function to get the pixel width of the character. Just feed it one character at a time:

Function TextWidth( text$ )
Returns the width, in pixels, of text based on the current image font.
Description Get width of text
Information This command is useful for calculating horizontal alignment of text when using the DrawText command.

Its found in the Max2d module.


AndrewT(Posted 2009) [#3]
Unfortunately I'm not--I'm using the bitmap fonts found here.

Basically my problem was trimming off the empty space on the sides of each character efficiently--however I've found a different method and I've managed to cut the trimming time down to about 20 ms, and I've cut the loading time down to about 15 ms.

Anyways here's the finished library if anybody wants it:

'***********************************************************************************************************************************
'                                        B I T M A P   F O N T   L I B R A R Y
'***********************************************************************************************************************************
Rem

This library will allow you To draw bitmap fonts obtained from bitmap font image.

How To use it:

	-First, Load a font like so:
		
		Local Font:TBitmapFont = TBitmapFont.Load(FileName)
		
	where FileName is the filename of a bitmap font image.
	
	-Then draw text with the Draw() method:
	
		Font.Draw("Hello, world!", 100, 100)
		
	You can change the scale and rotation of the font as well, using SetScale() and SetAngle().
	
	You can also change the spacing of the font using SetSpacing(). You may need to set the spacing
	to a negative value in order to obtain the appearance you desire.
	
	You can also change the handle of the font with SetHandle(). The handle of the font is the center
	at which the font is rotated and scaled. Additionally you can center the font using SetCentered() and setting
	it to 1, which will automatically set the handle in the center of any text you draw.
		
EndRem
''***********************************************************************************************************************************

Type TBitmapFont

	Field Image:TImage
	
	Field Offset:Int[190]
	Field Spacing:Float
	
	Field CellWidth:Int
	Field CellHeight:Int
	
	Field HandleX:Float
	Field HandleY:Float
	Field Angle:Float
	Field ScaleX:Float
	Field ScaleY:Float
	Field Centered:Int
	
	Method GetWidth:Int(Text:String)
		Local CurIndex:Int
		Local CurWidth:Int
		
		For Local I:Int = 1 To Len(Text)
			CurIndex = Asc(Mid(Text, I, 1)) - 32
			CurWidth :+ (Offset[CurIndex * 2 + 1] - Offset[CurIndex * 2]) + Spacing
		Next
	
		Return CurWidth
	EndMethod

	Method GetHeight:Int()
		Return ImageHeight(Image)
	EndMethod
	
	Method SetSpacing(Space:Float)
		Spacing = Space
	EndMethod
	
	Method GetSpacing:Int()
		Return Spacing
	EndMethod
	
	Method SetHandle(X:Float, Y:Float)
		HandleX = X
		HandleY = Y
	EndMethod
	
	Method GetHandle(X:Float Var, Y:Float Var)
		X = HandleX
		Y = HandleY
	EndMethod
	
	Method SetCentered(IsCentered:Int)
		Centered = IsCentered
	EndMethod
		
	
	Method SetAngle(Ang:Float)
		Angle = Ang
	EndMethod
	
	Method GetAngle:Float()
		Return Angle
	EndMethod
	
	Method SetScale(X:Float, Y:Float)
		ScaleX = X
		ScaleY = Y
	EndMethod
	
	Method GetScale(X:Float Var, Y:Float Var)
		X = ScaleX
		Y = ScaleY
	EndMethod
	
	Method Draw(Text:String, X:Int, Y:Int)
		SetTransform(Angle, ScaleX, ScaleY)
	
		Local CurIndex:Int
		Local OrigX:Int = X
		
		Local OldHX:Float
		Local OldHY:Float
		
		If Centered
			OldHX = HandleX
			OldHY = HandleY
			HandleX = GetWidth(Text) / 2
			HandleY = GetHeight() / 2
		EndIf
		
		For Local I:Int = 1 To Len(Text)
			CurIndex = Asc(Mid(Text, I, 1)) - 32
			SetImageHandle(Image, -((X - Offset[CurIndex * 2]) - (OrigX + HandleX)),  HandleY)
			DrawImage(Image, OrigX, Y, CurIndex)
			X :+ Offset[CurIndex * 2 + 1] - Offset[CurIndex * 2]
			X :+ Spacing
		Next
		
		HandleX = OldHX
		HandleY = OldHY
		
	EndMethod
	
	Method Trim()
		Local Pixmap:TPixmap
	
		For Local I:Int = 0 To 94
		
			Pixmap = LockImage(Image, I)
                        Pixmap = Pixmap.Convert(PF_RGBA8888)
			
			Local LeftMax:Int = CellHeight - 1
			Local RightMax:Int = 0
			
			For Local Y:Int = 0 To CellHeight - 1
				For Local X:Int = 0 To CellWidth - 1
					Local Color:Int = Pixmap.ReadPixel(X, Y)
					If Color Shr 24 & $000000FF > 128
						If X < LeftMax
							LeftMax = X
						EndIf
						Exit
					EndIf
				Next
			Next
			
			For Local Y:Int = 0 To CellHeight - 1
				For Local X:Int = CellWidth - 1 To 0 Step -1
					Local Color:Int = Pixmap.ReadPixel(X, Y)
					If Color Shr 24 & $000000FF > 128
						If X > RightMax
							RightMax = X
						EndIf
						Exit
					EndIf
				Next
			Next
			
			If RightMax = 0
				RightMax = CellWidth - CellWidth / 3
			EndIf
			If LeftMax = CellWidth - 1
				LeftMax = CellWidth / 3
			EndIf
			
			LeftMax = LeftMax - Spacing / 2
			RightMax = RightMax + Spacing / 2
			
			If RightMax > CellWidth - 1
				RightMax = CellWidth - 1
			EndIf
			If LeftMax < 0
				LeftMax = 0
			EndIf
			
			Offset[I * 2] = LeftMax
			Offset[I * 2 + 1] = RightMax
			
			UnlockImage(Image, I)
		
		Next
	EndMethod

	Function Load:TBitmapFont(FileName:String)
		Local Font:TBitmapFont = New TBitmapFont
		
		Local FontImage:TImage = LoadImage(LoadBank(FileName))
		
		Local CellWidth:Int = ImageWidth(FontImage) / 10
		Local CellHeight:Int = ImageHeight(FontImage) / 10
		
		FontImage = LoadAnimImage(LoadBank(FileName), CellWidth, CellHeight, 0, 95)
		
		If Not FontImage
			Notify("The bitmap font image you specified does not exist, or is not of a supported file format.", True)
			End
		EndIf		
		Font.Image = FontImage
		Font.CellWidth = CellWidth
		Font.CellHeight = CellHeight
		Font.ScaleX = 1.0
		Font.ScaleY = 1.0
		
		Font.Trim()
		
		Return Font
	EndFunction
	
EndType


How To use it:

-First, Load a font like so:

Local Font:TBitmapFont = TBitmapFont.Load(FileName)


where FileName is the filename of a bitmap font image.

-Then draw text with the Draw() method:

Font.Draw("Hello, world!", 100, 100)


You can change the scale and rotation of the font as well, using Font.SetScale() and Font.SetAngle().

You can also change the spacing of the font using Font.SetSpacing(). You may need to set the spacing
to a negative value in order to obtain the appearance you desire.

You can also change the handle of the font with Font.SetHandle(). The handle of the font is the center
at which the font is rotated and scaled. Additionally you can center the font using Font.SetCentered() and setting
it to 1, which will automatically set the handle in the center of any text you draw.

Here's a little example that demonstrates loading, centering, scaling, rotating, and drawing a font:

SuperStrict

Include "TBitmapFont.bmx"

Graphics 1024, 768, 1
SetBlend(ALPHABLEND)
SetClsColor(0, 50, 200)

Local Font:TBitmapFont = TBitmapFont.Load("OrangeWithShadow.png")
Font.SetSpacing(-4)
Font.SetCentered(1)

Local Counter:Float
Local ScaleX:Float
Local ScaleY:Float
Local Ang:Float

Repeat

	Cls
	
	Font.SetAngle(0.0)
	Font.SetScale(1.0, 1.0)
	Font.Draw("Bitmap Font Library - Demo", GraphicsWidth() / 2, 20)
	
	Counter :+ 1.0
	
	ScaleX = Sin(Counter) * 3.0
	Font.SetScale(ScaleX, 3.0)
	Font.Draw("Hello, world!", MouseX(), MouseY())
	
	ScaleY = Sin(Counter) * 3.0
	Font.SetScale(3.0, ScaleY)
	Font.Draw("Hello, world!", MouseX(), MouseY() - 90)
	
	Ang = Sin(Counter) * 30.0
	Font.SetScale(2.0, 2.0)
	Font.SetAngle(Ang)
	Font.Draw("Hello, world!", MouseX(), MouseY() + 90)
	
	Flip
	
Until AppTerminate() Or KeyHit(KEY_ESCAPE)


Note: You'll need the 'Orange' font found here to run the demo.


_Skully(Posted 2009) [#4]
Are those bitmap fonts free to use? he mentions the games are free but i didn't see anything about the fonts


AndrewT(Posted 2009) [#5]
Yes, as he says here and here. :)


Jesse(Posted 2009) [#6]
you know you are not using this correctly:
Pixmap.Convert(PF_RGBA8888)

that is not going to convert the original pixmap "automatically".


Beaker(Posted 2009) [#7]
Fontext can be used to make fonts for this code.


Jesse(Posted 2009) [#8]
?


Beaker(Posted 2009) [#9]
Jesse - what is it that you don't understand?


Jesse(Posted 2009) [#10]
this:

Fontext can be used to make fonts for this code.


I didn't know where you were comming from. I got it now.
I just realized that you were the creator of Fontext.:)

http://www.blitzbasic.com/Community/posts.php?topic=65909


AndrewT(Posted 2009) [#11]
Jesse:

Ahh, I see--is it just a matter of using the Pixmap returned by Convert() or is the their extra work involved?

Beaker:

Good to know. :)


Jesse(Posted 2009) [#12]
it needs to be like this
pixmap = Pixmap.Convert(PF_RGBA8888)



AndrewT(Posted 2009) [#13]
Thank you, I've edited it.