Page 1 of 1

DrawText() with border?

Posted: Sun Sep 19, 2010 4:45 pm
by c4s
How can draw a text with a border around it (with alpha support)?

Well, I had the idea of loading a relatively bigger font and drawing this one first and then the smaller one in the center of it. But then the padding between each character won't be correct and alpha won't work either.
Can anyone help me out? I already searched in the forum and even the whole internet but didn't found a solution...

Re: DrawText() with border?

Posted: Sun Sep 19, 2010 5:06 pm
by kenmo
One classic way is to draw the same exact text, in a different color, around it.

This is what I use:

Code: Select all

Procedure Write(x.l, y.l, Message.s, Color.i, Center.i = #False, Outline.l = -1)
  Static dx.l
  If (Message)
    If (Center)
      dx = x - TextWidth(Message)/2
    Else
      dx = x
    EndIf
    If (Outline >= 0)
      DrawText(dx - 1, y, Message, Outline)
      DrawText(dx + 1, y, Message, Outline)
      DrawText(dx, y - 1, Message, Outline)
      DrawText(dx, y + 1, Message, Outline)
    EndIf
    DrawText(dx, y, Message, Color)
  EndIf
EndProcedure
It looks fine. However it can only do a single fixed-width border, and it calls DrawText() 5 times... which has actually caused frame slowdown for me sometimes.

Re: DrawText() with border?

Posted: Sun Sep 19, 2010 5:09 pm
by srod
I'm very rushed and am not sure if the following is what you are after? If it is then you can probably do the same sort of thing using the drawing lib and the alpha channels etc.

Code: Select all

text$ = "Hello Stinky!"
LoadFont(1, "Arial", 60)
pen = CreatePen_(#PS_SOLID, 2, #Blue)

If OpenWindow(0, 100, 100, 440, 300, "PureBasic - Image")
  If CreateImage(0, 440, 300)
    hdc = StartDrawing(ImageOutput(0))
    If hdc    
      Box(0, 0, 440, 300, #White)
      oldPen = SelectObject_(hdc, pen)
      SetBkMode_(hdc, #TRANSPARENT)
      oldFont = SelectObject_(hdc, FontID(1))
      SetTextAlign_(hdc, #TA_LEFT|#TA_TOP)
      BeginPath_(hdc)    
        TextOut_(hdc, 0, 0, text$, Len(text$))
      EndPath_(hdc)
      SelectObject_(hdc, oldFont)
      StrokeAndFillPath_(hdc) 
      SelectObject_(hdc, oldPen) 
      StopDrawing() ; This is absolutely needed when the drawing operations are finished !!! Never forget it !
    EndIf
  EndIf
  ImageGadget(1, 0, 0, 0, 0, ImageID(0))
  Repeat
    EventID = WaitWindowEvent()
  Until EventID = #PB_Event_CloseWindow  ; If the user has pressed on the close button
EndIf
DeleteObject_(pen)

Re: DrawText() with border?

Posted: Sun Sep 19, 2010 5:54 pm
by c4s
@kenmo
I also thought of this but it's a very unpleasant way of doing it. Also it doesn't allow making thicker borders and alpha doesn't work because DrawText() lays over DrawText() etc.

@srod
Kind of. But I don't see where I could change the thickness of the border and the color inside the characters.

Re: DrawText() with border?

Posted: Sun Sep 19, 2010 7:17 pm
by srod

Code: Select all

text$ = "Hello Stinky!"
LoadFont(1, "Arial", 70)


Procedure BorderedText(hdc, x, y, text$, borderWidth, borderColor, fillColor)
  Protected pen, oldPen, brush, oldBrush
  pen = CreatePen_(#PS_SOLID, borderWidth, borderColor)
  oldPen = SelectObject_(hdc, pen)
  brush = CreateSolidBrush_(fillColor)
  oldBRush = SelectObject_(hdc, brush)
  SetBkMode_(hdc, #TRANSPARENT)
  SetTextAlign_(hdc, #TA_LEFT|#TA_TOP)
  BeginPath_(hdc)    
    TextOut_(hdc, x, y, text$, Len(text$))
  EndPath_(hdc)
  StrokeAndFillPath_(hdc) 
  SelectObject_(hdc, oldBrush)
  SelectObject_(hdc, oldPen) 
  DeleteObject_(brush)
  DeleteObject_(pen)
EndProcedure

If OpenWindow(0, 100, 100, 500, 300, "PureBasic - Image")
  If CreateImage(0, 500, 300)
    hdc = StartDrawing(ImageOutput(0))
    If hdc    
      Box(0, 0, 500, 300, #White)
      oldFont = SelectObject_(hdc, FontID(1))
      BorderedText(hdc, 0, 0, text$, 1, #Blue, #White)
      BorderedText(hdc, 0, 80, text$, 4, #Blue, #Red)
      BorderedText(hdc, 0, 160, text$, 2, #Red, #Gray)
      SelectObject_(hdc, oldFont)
      StopDrawing()
    EndIf
  EndIf
  ImageGadget(1, 0, 0, 0, 0, ImageID(0))
  Repeat
    EventID = WaitWindowEvent()
  Until EventID = #PB_Event_CloseWindow
EndIf

Re: DrawText() with border?

Posted: Sun Sep 19, 2010 7:47 pm
by c4s
Too bad there isn't a cross-platform solution. Anyway thank you srod!

Now I'll just have to try to get alpha involved. ;)

Re: DrawText() with border?

Posted: Sun Sep 19, 2010 8:59 pm
by srod
c4s wrote:Now I'll just have to try to get alpha involved. ;)
Probably best doing this on a pixel by pixel basis (use the drawing buffer). I hacked up some alpha support using a combination of the above code and PB's DrawText() on the alpha-channel, but the effects were relatively poor due to anti-aliasing effects etc.

Re: DrawText() with border?

Posted: Sun Sep 19, 2010 10:27 pm
by c4s
srod wrote:Probably best doing this on a pixel by pixel basis (use the drawing buffer). I hacked up some alpha support using a combination of the above code and PB's DrawText() on the alpha-channel, but the effects were relatively poor due to anti-aliasing effects etc.
I'm testing again and this stuff seems complicated to me.
Could you please share your code?

Re: DrawText() with border?

Posted: Mon Sep 20, 2010 9:29 am
by srod
I haven't got any code for the pixel by pixel stuff though it should be easy enough. :)

All I did was test some code using a combination of the above and PB's 2d drawing lib to give some alpha support and whilst it worked, it wasn't good enough because of the text anti-alias.

Re: DrawText() with border?

Posted: Mon Sep 20, 2010 11:56 am
by c4s
So you would draw the API stuff on a separate 24bit image with a color like $FF00FF as the background, then create a 32bit alpha image containing the API image and replace each $FF00FF pixel with full alpha, then draw the real things (unfortunately the API image creation must be before the actual StartDrawing() block...not cool) and the outlined "text" with DrawAlphaImage()?

This is really complicated to me and yes, I already tried it but didn't work. Looks like I didn't try hard enough though. What makes me think is your comment about anti-alias. Does that mean it could look strange on some systems?
So again I searched through the net using terms like "text border", "draw text outline" but it seems that all I get is something like kenmo's idea and another approach using .NET... This is really disappointing. :(

Re: DrawText() with border?

Posted: Mon Sep 20, 2010 12:23 pm
by c4s
This is my current solution and I don't like that I have to create the image before the actual Drawing block also a thickness small than 10 doesn't look good and finally I can't handle it as a normal DrawText() function. :?

Code: Select all

EnableExplicit



Enumeration
    #Font
    #Window
    #Image
    #ImageGadget
EndEnumeration


Procedure ImageDrawTextBordered(Text.s, TextFontNr, TextColor=$000000, BorderColor=$FFFFFF, BorderThickness=1)
    Protected ImageNr, Image1Nr, Image2Nr, hDC
    Protected Width, Height, iX, iY
    Protected Pen, PenOld, FontOld

    ; Get width & height of the text+border
    ImageNr = CreateImage(#PB_Any, 1, 1)
    If ImageNr
        If StartDrawing(ImageOutput(ImageNr))
            DrawingFont(FontID(TextFontNr))
            Width = TextWidth(Text) + (BorderThickness * 2)
            Height = TextHeight(Text) + (BorderThickness * 2)
            StopDrawing()
        EndIf
        FreeImage(ImageNr)
    EndIf


    ; Draw bordered text on Image1
    Image1Nr = CreateImage(#PB_Any, Width, Height, 24)
    If Image1Nr
        hDC = StartDrawing(ImageOutput(Image1Nr))
        If hDC
            Box(0, 0, Width, Height, $FF00FF)

            FontOld = SelectObject_(hDC, FontID(TextFontNr))
            Pen = CreatePen_(#PS_SOLID, BorderThickness, BorderColor)
            PenOld = SelectObject_(hDC, Pen)
            SetBkMode_(hDC, #TRANSPARENT)
            BeginPath_(hDC)   
            TextOut_(hDC, BorderThickness, BorderThickness, Text, Len(Text))
            EndPath_(hDC)
            StrokeAndFillPath_(hDC)
            SelectObject_(hDC, PenOld)
            DeleteObject_(Pen)
            SelectObject_(hDC, FontOld)

            DrawingMode(#PB_2DDrawing_Transparent)
            DrawingFont(FontID(TextFontNr))
            DrawText(BorderThickness, BorderThickness, Text, TextColor)

            StopDrawing()
        EndIf

        ; Draw Image1 on Image2, make $FF00FF parts transparent
        Image2Nr = CreateImage(#PB_Any, Width, Height, 32)
        If Image2Nr
            If StartDrawing(ImageOutput(Image2Nr))
                DrawImage(ImageID(Image1Nr), 0, 0)

                DrawingMode(#PB_2DDrawing_AlphaChannel)
                For iY = 0 To Height - 1
                    For iX = 0 To Width - 1
                        If Point(iX, iY) & $FFFFFF = $FF00FF
                            Plot(iX, iY, $00000000)
                        EndIf
                    Next
                Next

                StopDrawing()
            EndIf
        EndIf

        FreeImage(Image1Nr)
    EndIf

    ProcedureReturn Image2Nr
EndProcedure



Define Text.s, Width, Height
Define ImageNr

Text = "Test"
Width = 330
Height = 150


LoadFont(#Font, "", 90, #PB_Font_HighQuality)

ImageNr = ImageDrawTextBordered(Text, #Font, $F0F0F0, $FFFF00, 10)  ; StartDrawing in a Drawing block isn't allowed

If CreateImage(#Image, Width, Height)
    If StartDrawing(ImageOutput(#Image))
        DrawingMode(#PB_2DDrawing_Gradient)      
        BackColor($00FFFF) : FrontColor($FF0000)
        LinearGradient(0, Height, Width, Height)
        Box(0, 0, Width, Height)

        DrawAlphaImage(ImageID(ImageNr), 10, 10)  ; Draw the text image
        FreeImage(ImageNr)

        StopDrawing()
    EndIf
EndIf


If OpenWindow(#Window, #PB_Any, #PB_Any, Width, Height, "DrawTextBordered() Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

    ImageGadget(#ImageGadget, 0, 0, Width, Height, ImageID(#Image))

    Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf

Re: DrawText() with border?

Posted: Mon Sep 20, 2010 12:43 pm
by srod
Well the text itself doesn't have an alpha-channel and that particular example wouldn't require any additional images at all in that you could easily render the bordered text directly onto a window (because you are not using alpha values with the text itself).

Here's my quick hack which renders the text with alpha. I use your gradient filled image from above.

Code: Select all

text$ = "Heyho!"
LoadFont(1, "Arial", 90)

;Create a background brush.
  If CreateImage(1, 450, 400)
    If StartDrawing(ImageOutput(1))
      DrawingMode(#PB_2DDrawing_Gradient)      
      BackColor($00FFFF) : FrontColor($FF0000)
      LinearGradient(0, 400, 450, 400)
      Box(0, 0, 450, 400)
      StopDrawing()
    EndIf
  EndIf
  backBrush = CreatePatternBrush_(ImageID(1))


Procedure GetAlphaBorderedText(image, x, y, text$, borderWidth, borderColor, fillColor, alpha)
  Protected pen, oldPen, brush, oldBrush, oldFont, *mem.RGBQUAD, pxHeight, pxWidth, i , j
  pen = CreatePen_(#PS_SOLID, borderWidth, borderColor)
  If IsImage(image)
    hdc = StartDrawing(ImageOutput(image))
    If hdc
      oldFont = SelectObject_(hdc, FontID(1))
      oldPen = SelectObject_(hdc, pen)
      brush = CreateSolidBrush_(fillColor)
      oldBRush = SelectObject_(hdc, brush)
      SetBkMode_(hdc, #TRANSPARENT)
      SetTextAlign_(hdc, #TA_LEFT|#TA_TOP)
      BeginPath_(hdc)    
        TextOut_(hdc, x, y, text$, Len(text$))
      EndPath_(hdc)
      StrokeAndFillPath_(hdc) 
      SelectObject_(hdc, oldBrush)
      SelectObject_(hdc, oldPen) 
      DeleteObject_(brush)
      DeleteObject_(pen)
      ;Apply per-pixel alpha values.
      ;We change all oqaque pixels to transparent and transparent ones are given the specified alpha value.
        *mem = DrawingBuffer()
        If DrawingBufferPixelFormat() & (#PB_PixelFormat_32Bits_RGB | #PB_PixelFormat_32Bits_BGR)
          pxHeight = ImageHeight(image)
          pxWidth = ImageWidth(image)
          For i = 1 To pxHeight 
            For j = 1 To pxWidth
              If *mem\rgbReserved = 0
                *mem\rgbReserved = alpha
              Else
                *mem\rgbReserved = 0
              EndIf
              *mem + SizeOf(RGBQUAD)
            Next 
          Next
        EndIf
      StopDrawing()
    EndIf
  EndIf
EndProcedure


hWnd = OpenWindow(0, 100, 100, 450, 400, "PureBasic - Image")
If hWnd
  SetClassLongPtr_(hWnd, #GCL_HBRBACKGROUND, backBrush)
  InvalidateRect_(hWnd, 0, 1)
  If CreateImage(0, 450, 140, 32)
    GetAlphaBorderedText(0, 20, 0, text$, 6, #Red, #White, 80)
  EndIf
  ImageGadget(1, 0, 0, 0, 0, ImageID(0))
  Repeat
    EventID = WaitWindowEvent()
  Until EventID = #PB_Event_CloseWindow
EndIf
You could easily modify this to simulate a DrawAlphaText() kind of function.

Re: DrawText() with border?

Posted: Mon Sep 20, 2010 8:53 pm
by Demivec
Here is code by Trond that outlines text.

Code: Select all

h = 500
w = 500
CreateImage(0, h, w, 24)
LoadFont(0, "Impact", 40)
text.s = "In my pants!"
tcol = #Red
ocol = #White ; can't be the same as tcol!
owidth = 2

StartDrawing(ImageOutput(0))
  DrawingFont(FontID(0))
  FrontColor(tcol)
  DrawRotatedText(100, 350, text, 45)
  For y = owidth To h-owidth-1
    For x = owidth To w-owidth-1
      c1 = Point(x, y)
      If c1 <> tcol
        For y2 = -owidth To owidth
          For x2 = -owidth To owidth
            If Point(x+x2, y+y2) = tcol
              Plot(x, y, ocol)
              Break 2
            EndIf
          Next
        Next
      EndIf
    Next
  Next
StopDrawing()


OpenWindow(0, 0, 0, 500, 500, "", #PB_Window_ScreenCentered | #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
ImageGadget(0, 0, 0, 0, 0, ImageID(0))

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
  EndSelect
ForEver
Here's the source link where this and related code were presented in relationship to producing a watermark.

Re: DrawText() with border?

Posted: Mon Sep 20, 2010 9:42 pm
by c4s
Thank you for that link. I probably was to focused on "border" instead of "outline" when searching the forum.