TBitmapFont
BlitzMax Forums/BlitzMax Programming/TBitmapFont
| ||
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 ;-) |
| ||
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. |
| ||
The TAnim code would be ideal for this. Isn't there already TBitmapFont source available. |
| ||
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 :) |
| ||
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). |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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? |
| ||
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. |
| ||
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. |
| ||
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...;-) |
| ||
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)! |
| ||
Now all I need to make my empire complete is some working Mesh code (for scrolling tile maps)! If you ask nicely. |
| ||
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? |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
Looking forward to your next update already Grey. @Grey, Indie & Gfk:- I kneel at your feet. |
| ||
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. |
| ||
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.... |
| ||
Don't work with latest version of BitMap Font Editor from www.AngelCode.com :( |
| ||
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 |
| ||
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. |
| ||
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. |
| ||
@ziggy: thx. looks cool too. work unde linux? |
| ||
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. |
| ||
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) ;) |