Page 1 of 1

Any advice on centering bitmap text inside a screen?

Posted: Sun Jan 26, 2020 1:02 am
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!

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

Posted: Sun Jan 26, 2020 2:31 am
by BarryG
Not sure what you mean. Are you creating an image made of text, and want that image centered inside another image?

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

Posted: Sun Jan 26, 2020 2:55 am
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.

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

Posted: Sun Jan 26, 2020 11:12 am
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:

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

Posted: Mon Jan 27, 2020 3:17 pm
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()?

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

Posted: Wed Jan 29, 2020 1:54 am
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

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

Posted: Wed Jan 29, 2020 2:19 am
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

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

Posted: Sun Feb 02, 2020 2:58 am
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

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

Posted: Mon Feb 10, 2020 10:23 pm
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

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

Posted: Tue Feb 11, 2020 2:33 pm
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

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

Posted: Tue Feb 11, 2020 9:39 pm
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

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

Posted: Tue Feb 11, 2020 10:24 pm
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.