Any advice on centering bitmap text inside a screen?

Just starting out? Need help? Post your questions and find answers here.
User avatar
Radical Raccoon
User
User
Posts: 11
Joined: Sat Feb 17, 2018 11:17 pm

Any advice on centering bitmap text inside a screen?

Post by Radical Raccoon »

I made a patcher a couple of years ago and am now working on an open source tool for creating patchers with a fair deal of customization, one of those being user defined bitmap fonts.
However, I never did get around to working out a proper solution for aligning bitmap font based text, so I figured I would go ahead and see if anyone here had any suggestions.

To compensate for this, I simply used spaces in my original patcher. Not very pretty, but it's one of the first things I ever made with PureBasic and I wanted to finish it quickly. :|
This piece of code was for optimization to avoid having to draw each character individually; on startup, this sprite is created so that only a single sprite needs to be drawn to the screen later.

Code: Select all

Procedure CreateHeader()
  Protected Header.s, Character.s, X.i = 11, OriginalX.i = X, Y.i = 16, OriginalY.i = Y
  
  Header = "                asio link pro patcher     #"
  Header + "<~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>      #"
  Header + "    brought to you by g.a. collective 2018#"
  Header + "<~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>"
  
  CreateSprite(#SpriteHeader, #ScreenWidth, #ScreenHeight)
  ClearScreen(FastRGB(255, 0, 255))
  
  For i = 1 To Len(Header)
    Character = LCase(Mid(Header, i, 1))
    If FindMapElement(Font(), Character)
      ClipSprite(#SpriteFont, Font(Character)\X, Font(Character)\Y, Font(Character)\W, Font(Character)\H)
      ZoomSprite(#SpriteFont, SpriteWidth(#SpriteFont) * 2, SpriteHeight(#SpriteFont) * 2)
      DisplayTransparentSprite(#SpriteFont, X, Y)
      X + ((Font(Character)\W * 2) - 2)
    Else
      If Character = "#"
        Y + 22
        X = OriginalX
      Else
        X + 8
      EndIf
    EndIf
  Next
  
  GrabSprite(#SpriteHeader, 0, 0, #ScreenWidth, #ScreenHeight, #PB_Sprite_AlphaBlending)
  TransparentSpriteColor(#SpriteHeader, FastRGB(255, 0, 255))
  ClearScreen(FastRGB(28, 28, 28))
EndProcedure
I've considered doing some calculations based on the screen width, character sizes, and space between characters in order to determine the center, left, right, etc. of the final text to be displayed, but I'm curious to know whether or not there's a better way.

For anyone that might be curious, this is the old patcher I made in use.

Thanks!
BarryG
Addict
Addict
Posts: 4226
Joined: Thu Apr 18, 2019 8:17 am

Re: Any advice on centering bitmap text inside a screen?

Post by BarryG »

Not sure what you mean. Are you creating an image made of text, and want that image centered inside another image?
User avatar
Radical Raccoon
User
User
Posts: 11
Joined: Sat Feb 17, 2018 11:17 pm

Re: Any advice on centering bitmap text inside a screen?

Post by Radical Raccoon »

BarryG wrote:Not sure what you mean. Are you creating an image made of text, and want that image centered inside another image?
Not exactly.

In the example from my old patcher provided above, I iterate over each character in a string, if it's found in the map (an index of each character's coordinates and size in the bitmap image) then I use ClipSprite() to get the relevant part of the following bitmap to draw using DisplayTransparentSprite(). Image

Once finished, I use GrabSprite() to create a sprite from the screen that all the drawing was done on for later use.
The issue is that I had to add a bunch of spaces to the string to get the alignment I wanted.
This is not quite as simple as centering an image inside another image.

That is done at startup so that it doesn't have to be done for every frame.

Code: Select all

; Font Sprite
  *Buffer = AllocateMemory(2606)
  UncompressMemory(?FontBitmap, ?FontBitmapEnd - ?FontBitmap, *Buffer, 2606, #PB_PackerPlugin_BriefLZ)
  If CatchSprite(#SpriteFont, *Buffer) <> #Null
    FreeMemory(*Buffer)
    TransparentSpriteColor(#SpriteFont, FastRGB(255, 0, 255))
    CreateHeader()
  Else
    FreeMemory(*Buffer)
    Error(#LoadFontError)
  EndIf
I know that I could calculate the overall size using the length of the input string, the individual sprite dimensions, etc. and then extrapolate from that the information I need in order to align everything without prefixing or appending strings with spaces, but I was curious as to how others might tackle this problem since there could be a better way.
User avatar
Demivec
Addict
Addict
Posts: 4282
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Any advice on centering bitmap text inside a screen?

Post by Demivec »

Radical Raccoon wrote:I know that I could calculate the overall size using the length of the input string, the individual sprite dimensions, etc. and then extrapolate from that the information I need in order to align everything without prefixing or appending strings with spaces, but I was curious as to how others might tackle this problem since there could be a better way.
I think the only way is to precalculate things to determine the positions to draw things centered.

Code: Select all

Procedure CreateHeader()
  Protected Header.s, Character.s, X.i = 0, OriginalX.i, Y.i = 0, OriginalY.i, Xmax.i = 0
 
  Header = "                asio link pro patcher     #"
  Header + "<~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>      #"
  Header + "    brought to you by g.a. collective 2018#"
  Header + "<~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>"
 
  For i = 1 To Len(Header)
    Character = LCase(Mid(Header, i, 1))
    If FindMapElement(Font(), Character)
      X + ((Font(Character)\W * 2) - 2)
    Else
      If Character = "#"
        Y + 22
        If X > XMax
          XMax = X
        EndIf
        X = OriginalX
      Else
        X + 8
      EndIf
    EndIf
  Next
  OriginalX = (#ScreenWidth - XMax) / 2
  OriginalY = (#ScreenHeight - Y) / 2
  X = OriginalX
  Y = OriginalY
  
  CreateSprite(#SpriteHeader, #ScreenWidth, #ScreenHeight)
  ClearScreen(RGB(255, 0, 255))
  For i = 1 To Len(Header)
    Character = LCase(Mid(Header, i, 1))
    If FindMapElement(Font(), Character)
      ClipSprite(#SpriteFont, Font(Character)\X, Font(Character)\Y, Font(Character)\W, Font(Character)\H)
      ZoomSprite(#SpriteFont, SpriteWidth(#SpriteFont) * 2, SpriteHeight(#SpriteFont) * 2)
      DisplayTransparentSprite(#SpriteFont, X, Y)
      X + ((Font(Character)\W * 2) - 2)
    Else
      If Character = "#"
        Y + 22
        X = OriginalX
      Else
        X + 8
      EndIf
    EndIf
  Next
  GrabSprite(#SpriteHeader, 0, 0, #ScreenWidth, #ScreenHeight, #PB_Sprite_AlphaBlending)
  TransparentSpriteColor(#SpriteHeader, RGB(255, 0, 255))
  ClearScreen(RGB(28, 28, 28))
EndProcedure
@Edit: removed a typo from the code, it should now execute :wink:
Last edited by Demivec on Wed Jan 29, 2020 2:08 am, edited 1 time in total.
PureLust
Enthusiast
Enthusiast
Posts: 478
Joined: Mon Apr 16, 2007 3:57 am
Location: Germany, NRW

Re: Any advice on centering bitmap text inside a screen?

Post by PureLust »

Radical Raccoon wrote:..., but I was curious as to how others might tackle this problem since there could be a better way.
I'm not sure if I understood your issue right, but have you thought of using TextWidth()?
[Dynamic-Dialogs] - create complex GUIs the easy way
[DeFlicker] - easily deflicker your resizeable Windows
[WinFX] - Window Effects (incl. 'click-through' Window)
User avatar
Radical Raccoon
User
User
Posts: 11
Joined: Sat Feb 17, 2018 11:17 pm

Re: Any advice on centering bitmap text inside a screen?

Post by Radical Raccoon »

PureLust wrote:I'm not sure if I understood your issue right, but have you thought of using TextWidth()?
TextWidth() is not applicable in this case. I'm not using any font.

I'm going to go ahead with my own solution since it seems to be the only real way forward. I will share what I end up with once it's ready. Might make more sense after seeing the code haha.

Sent from my F5121 using Tapatalk
User avatar
Demivec
Addict
Addict
Posts: 4282
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Any advice on centering bitmap text inside a screen?

Post by Demivec »

Here's a more compact version, similar in size to your original code:

Code: Select all

Procedure CreateHeader()
  Protected Header.s, Character.s, X.i = 0, OriginalX.i, Y.i = 0, OriginalY.i, Xmax.i = 0
 
  Header = "                asio link pro patcher     #"
  Header + "<~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>      #"
  Header + "    brought to you by g.a. collective 2018#"
  Header + "<~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>"
  
  CreateSprite(#SpriteHeader, #ScreenWidth, #ScreenHeight)
  ClearScreen(RGB(255, 0, 255))
  For i = 1 To Len(Header)
    Character = LCase(Mid(Header, i, 1))
    If FindMapElement(Font(), Character)
      ClipSprite(#SpriteFont, Font(Character)\X, Font(Character)\Y, Font(Character)\W, Font(Character)\H)
      ZoomSprite(#SpriteFont, SpriteWidth(#SpriteFont) * 2, SpriteHeight(#SpriteFont) * 2)
      DisplayTransparentSprite(#SpriteFont, X, Y)
      X + ((Font(Character)\W * 2) - 2)
    Else
      If Character = "#"
        Y + 22
        If X > XMax
          XMax = X
        EndIf
        X = OriginalX
      Else
        X + 8
      EndIf
    EndIf
  Next
  GrabSprite(#SpriteHeader, 0, 0, XMax, Y, #PB_Sprite_AlphaBlending)
  OriginalX = (#ScreenWidth - XMax) / 2
  OriginalY = (#ScreenHeight - Y) / 2
  
  ClearScreen(RGB(255, 0, 255))
  DisplaySprite(#SpriteFont, OriginalX, OriginalY)
  GrabSprite(#SpriteHeader, 0, 0, #ScreenWidth, #ScreenHeight, #PB_Sprite_AlphaBlending)
  TransparentSpriteColor(#SpriteHeader, RGB(255, 0, 255))
  ClearScreen(RGB(28, 28, 28))
EndProcedure
User avatar
Radical Raccoon
User
User
Posts: 11
Joined: Sat Feb 17, 2018 11:17 pm

Re: Any advice on centering bitmap text inside a screen?

Post by Radical Raccoon »

I went ahead with my own approach. Can't share my code right now since I'm not home, but I'll post it soon. Feel free to critique it when I do.

I appreciate the suggestions, though! Seems like I just didn't convey exactly what it was that I was trying to do clearly enough haha.
Image

Sent from my F5121 using Tapatalk
Last edited by Radical Raccoon on Tue Feb 11, 2020 9:25 pm, edited 1 time in total.
User avatar
Radical Raccoon
User
User
Posts: 11
Joined: Sat Feb 17, 2018 11:17 pm

Re: Any advice on centering bitmap text inside a screen?

Post by Radical Raccoon »

I'm on mobile now, so I can't easily post the source code on the forum itself, but here's a link to the code so that you can see what I ended up doing to horizontally align everything to the center of the screen. I'm open to any critique or advice anyone might have.

https://mega.nz/#!98tWWIBD!s2n_h2v8wPy7 ... UG7weWBog8

Sent from my F5121 using Tapatalk
User avatar
Mijikai
Addict
Addict
Posts: 1520
Joined: Sun Sep 11, 2016 2:17 pm

Re: Any advice on centering bitmap text inside a screen?

Post by Mijikai »

It really depends on what u plan to do :)

A json file is nice and it can be edited easily.
Theres no way around doing additional calculations for non fixed size bm fonts.

Here is an example without json and a more simple, faster mapper.
However u need to do the layout manually for each font!

Code:

Code: Select all

EnableExplicit

Structure BM_FONT_STRUCT
  sprite.i
  chr_width.i
  chr_height.i
  chr_map.a[256]
EndStructure

Procedure.i bmCreateFont(*Buffer,File.s,Layout.s,TransparentColor.i = #Null)
  Protected sprite.i
  Protected *bmf.BM_FONT_STRUCT
  Protected *chr.Ascii
  Protected index.i
  If Layout
    If *Buffer
      sprite = CatchSprite(#PB_Any,*Buffer,#PB_Sprite_AlphaBlending)
    ElseIf File
      sprite = LoadSprite(#PB_Any,File,#PB_Sprite_AlphaBlending)
    EndIf
    If sprite
      *bmf = AllocateStructure(BM_FONT_STRUCT)
      If *bmf
        *bmf\sprite = sprite
        *chr = @Layout
        Repeat
          *bmf\chr_map[*chr\a] = index
          index + 1
          *chr + SizeOf(Character)
        Until *chr\a = #Null
        *bmf\chr_width = SpriteWidth(sprite) / index
        *bmf\chr_height = SpriteHeight(sprite)
        TransparentSpriteColor(*bmf\sprite,TransparentColor)
        ProcedureReturn *bmf
      EndIf
      FreeSprite(sprite)
    EndIf
  EndIf
  ProcedureReturn #Null
EndProcedure

Procedure.i bmDestroyFont(*Font.BM_FONT_STRUCT)
  FreeSprite(*Font\sprite)
  FreeStructure(*Font)
  ProcedureReturn #Null
EndProcedure

Procedure.i bmRenderText(*Font.BM_FONT_STRUCT,X.i,Y.i,Text.s,Alpha.i = 255,Tint.i = $FFFFFFFF,Center.i = #False);<- cut out the sinus stuff ;)
  Static sin.i
  Protected *chr.Ascii
  If Center
    X - ((Len(Text) * *Font\chr_width) >> 1)
  EndIf
  *chr = @Text
  sin + 4
  If sin = 360
    sin = 0
  EndIf
  Repeat
    ClipSprite(*Font\sprite,*Font\chr_map[*chr\a] * *Font\chr_width,#Null,*Font\chr_width,*Font\chr_height)
    DisplayTransparentSprite(*Font\sprite,X + Y * Sin(Radian(sin)),Y - (X / 6) * Sin(Radian(sin)),Alpha,Tint)
    X + *Font\chr_width
    *chr + SizeOf(Character)
  Until *chr\a = #Null
  ProcedureReturn #Null
EndProcedure

Procedure.i Demo(Width.i = 320,Height.i = 200)
  Protected wnd.i
  Protected wnd_flags.i
  Protected wnd_event.i
  Protected font.i
  If InitSprite()
    wnd_flags = #PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_MinimizeGadget
    wnd = OpenWindow(#PB_Any,#Null,#Null,Width,Height,#Null$,wnd_flags)
    If wnd
      If OpenWindowedScreen(WindowID(wnd),#Null,#Null,Width,Height)
        font = bmCreateFont(#Null,"anomaly.bmp","abcdefghijklmnopqrstuvwxyz0123456789:;!?.,-'" + #DQUOTE$ + "_",$FF00FF);<- using the anomaly.bmp
        If font
          Repeat
            Repeat
              wnd_event = WindowEvent()
              If wnd_event = #PB_Event_CloseWindow
                Break 2
              EndIf
            Until wnd_event = #Null
            ClearScreen(#Null)
            bmRenderText(font,160,80,"hello",255,$FFFFFFFF,#True)
            FlipBuffers()
          ForEver
          bmDestroyFont(font)
        EndIf
      EndIf
      CloseWindow(wnd)  
    EndIf
  EndIf
  ProcedureReturn #Null
EndProcedure

Demo()

End
If this should work for non fixed size fonts add another entry to the mapper
that holds the offset to the base width (change the Layout accordingly) :D
User avatar
Radical Raccoon
User
User
Posts: 11
Joined: Sat Feb 17, 2018 11:17 pm

Re: Any advice on centering bitmap text inside a screen?

Post by Radical Raccoon »

Mijikai wrote:It really depends on what u plan to do :)

A json file is nice and it can be edited easily.
Theres no way around doing additional calculations for non fixed size bm fonts.

Here is an example without json and a more simple, faster mapper.
However u need to do the layout manually for each font!

Code:

Code: Select all

EnableExplicit

Structure BM_FONT_STRUCT
  sprite.i
  chr_width.i
  chr_height.i
  chr_map.a[256]
EndStructure

Procedure.i bmCreateFont(*Buffer,File.s,Layout.s,TransparentColor.i = #Null)
  Protected sprite.i
  Protected *bmf.BM_FONT_STRUCT
  Protected *chr.Ascii
  Protected index.i
  If Layout
    If *Buffer
      sprite = CatchSprite(#PB_Any,*Buffer,#PB_Sprite_AlphaBlending)
    ElseIf File
      sprite = LoadSprite(#PB_Any,File,#PB_Sprite_AlphaBlending)
    EndIf
    If sprite
      *bmf = AllocateStructure(BM_FONT_STRUCT)
      If *bmf
        *bmf\sprite = sprite
        *chr = @Layout
        Repeat
          *bmf\chr_map[*chr\a] = index
          index + 1
          *chr + SizeOf(Character)
        Until *chr\a = #Null
        *bmf\chr_width = SpriteWidth(sprite) / index
        *bmf\chr_height = SpriteHeight(sprite)
        TransparentSpriteColor(*bmf\sprite,TransparentColor)
        ProcedureReturn *bmf
      EndIf
      FreeSprite(sprite)
    EndIf
  EndIf
  ProcedureReturn #Null
EndProcedure

Procedure.i bmDestroyFont(*Font.BM_FONT_STRUCT)
  FreeSprite(*Font\sprite)
  FreeStructure(*Font)
  ProcedureReturn #Null
EndProcedure

Procedure.i bmRenderText(*Font.BM_FONT_STRUCT,X.i,Y.i,Text.s,Alpha.i = 255,Tint.i = $FFFFFFFF,Center.i = #False);<- cut out the sinus stuff ;)
  Static sin.i
  Protected *chr.Ascii
  If Center
    X - ((Len(Text) * *Font\chr_width) >> 1)
  EndIf
  *chr = @Text
  sin + 4
  If sin = 360
    sin = 0
  EndIf
  Repeat
    ClipSprite(*Font\sprite,*Font\chr_map[*chr\a] * *Font\chr_width,#Null,*Font\chr_width,*Font\chr_height)
    DisplayTransparentSprite(*Font\sprite,X + Y * Sin(Radian(sin)),Y - (X / 6) * Sin(Radian(sin)),Alpha,Tint)
    X + *Font\chr_width
    *chr + SizeOf(Character)
  Until *chr\a = #Null
  ProcedureReturn #Null
EndProcedure

Procedure.i Demo(Width.i = 320,Height.i = 200)
  Protected wnd.i
  Protected wnd_flags.i
  Protected wnd_event.i
  Protected font.i
  If InitSprite()
    wnd_flags = #PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_MinimizeGadget
    wnd = OpenWindow(#PB_Any,#Null,#Null,Width,Height,#Null$,wnd_flags)
    If wnd
      If OpenWindowedScreen(WindowID(wnd),#Null,#Null,Width,Height)
        font = bmCreateFont(#Null,"anomaly.bmp","abcdefghijklmnopqrstuvwxyz0123456789:;!?.,-'" + #DQUOTE$ + "_",$FF00FF);<- using the anomaly.bmp
        If font
          Repeat
            Repeat
              wnd_event = WindowEvent()
              If wnd_event = #PB_Event_CloseWindow
                Break 2
              EndIf
            Until wnd_event = #Null
            ClearScreen(#Null)
            bmRenderText(font,160,80,"hello",255,$FFFFFFFF,#True)
            FlipBuffers()
          ForEver
          bmDestroyFont(font)
        EndIf
      EndIf
      CloseWindow(wnd)  
    EndIf
  EndIf
  ProcedureReturn #Null
EndProcedure

Demo()

End
If this should work for non fixed size fonts add another entry to the mapper
that holds the offset to the base width (change the Layout accordingly) :D
That's actually really nice. I like that approach a lot, so I appreciate you sharing that with me! The reason for using JSON, though, is that users of the final tool should be able to change and add bitmap fonts freely.

The alignment isn't necessarily a requirement since users can freely move elements, such as the text displayed on the screen, wherever they like, but being able to snap it to the center is important as well, since that might be desired. Think form designer, but with something of a simple demo designer thrown in.

I think I have a better approach in mind, though. If I draw the text to a surface that is appropriately sized for that text and create sprites from that as opposed to drawing to the screen itself (or potentially just crop the excess?), then I could more readily perform any alignment I might need to do without having to iterate over the individual character sprites multiple times the way I am now. I'll have to work on that once I'm back home.

Sent from my F5121 using Tapatalk
User avatar
Demivec
Addict
Addict
Posts: 4282
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Any advice on centering bitmap text inside a screen?

Post by Demivec »

Radical Raccoon wrote:I think I have a better approach in mind, though. If I draw the text to a surface that is appropriately sized for that text and create sprites from that as opposed to drawing to the screen itself (or potentially just crop the excess?), then I could more readily perform any alignment I might need to do without having to iterate over the individual character sprites multiple times the way I am now. I'll have to work on that once I'm back home.
You won't be able to draw to an 'appropriately sized surface' without sizing up the text first, or in other words solving the original problem.

The approach I showed last draws the text with the font and tracks the size. It then clips the output (from the screen) and centers it on the same screen. After it is clipped though it can be positioned anywhere.
Post Reply