Page 2 of 2

Re: Faster vector graphics

Posted: Sat Dec 02, 2023 9:02 pm
by Fred
No startdrawing is needed to print text or display a score, you can use a bitmap with all letters inside and clip sprite. No game (in general, not just in PB) use startdrawing () in the render loop, it just kill the performance. You can use it to prepare your sprites if you need to, that's why it's available

Re: Faster vector graphics

Posted: Sat Dec 02, 2023 11:31 pm
by coco2
The problem I'm having is that I did precalculate all the graphics into sprites, but rotating a sprite is not as good quality as drawing something directly with the vector graphics tools. The anti-aliasing doesn't look as good even with SpriteQuality(#PB_Sprite_BilinearFiltering). And when the rest of the tachometer dial is prerendered with vector graphics, and the needle is a rotated sprite, it stands out. It is OK for now, I'm happy to leave it, but I was looking for other solutions in PureBasic for graphics.

Re: Faster vector graphics

Posted: Sat Dec 02, 2023 11:32 pm
by coco2
Fred wrote: Sat Dec 02, 2023 9:02 pm No startdrawing is needed to print text or display a score, you can use a bitmap with all letters inside and clip sprite
True, I have made the font module to convert fonts into sprites, it works well.

Re: Faster vector graphics

Posted: Sun Dec 03, 2023 4:15 am
by netmaestro
This is how I would go about it. You sacrifice a bit of memory for big gains in processing time. Here (i5 on win11), the time taken to replace the old image with a new one is under 1 millisecond.

There are two codes you need to run this example. The first code downloads the 2 needed images from my website and creates the imagestrip in your temporary directory. This only needs to be done once, and will not be part of the final program. I'm using gdiplus for the rotations but of course you can use the vector library if you are more comfortable with that. In fact, the vector library on Windows is built around gdiplus, so there shouldn't be much difference in quality of output. Here is the first code:

Code: Select all

UsePNGImageDecoder()

*Buffer = ReceiveHTTPMemory("https://lloydsplace.com/face-132.png")
If *Buffer
  CatchImage(0, *buffer)
  FreeMemory(*Buffer)
Else
  Debug "Failed"
EndIf

*Buffer = ReceiveHTTPMemory("https://lloydsplace.com/needle-132.png")
If *Buffer
  CatchImage(1, *buffer)
  FreeMemory(*Buffer)
Else
  Debug "Failed"
EndIf

CreateImage(3,2112,1980,32,#PB_Image_Transparent)

#MatrixOrderPrepend          = 0 
#MatrixOrderAppend           = 1 

#PixelFormat32bppARGB        = $26200A

Import "gdiplus.lib"
  GdiplusStartup(a,b,c)
  GdiplusShutdown(a)
  GdipDrawImageRectI(a,b,c,d,e,f)
  GdipDisposeImage(a) 
  GdipCreateFromHDC(a,b)
  GdipDeleteGraphics(a)
  GdipReleaseDC(a,b)
  GdipCreateBitmapFromScan0(a,b,c,d,e,f)
  GdipRotateWorldTransform(a, b.f, c)
  GdipTranslateWorldTransform(a, b.f, c.f, d)
  GdipResetWorldTransform(a)
EndImport

Structure GdiplusStartupInput
  GdiPlusVersion.l
  *DebugEventCallback.DEBUG_EVENT
  SuppressBackgroundThread.l
  SuppressExternalCodecs.l
EndStructure

input.GdiplusStartupInput\GdiPlusVersion = 1
GdiplusStartup(@*token, @input, #Null)

GetObject_(ImageID(1), SizeOf(BITMAP), @bmp.BITMAP)
GdipCreateBitmapFromScan0(132, 132, bmp\bmWidthBytes, #PixelFormat32bppARGB, bmp\bmBits, @*gdip_bitmap)

this.i=60

For j=0 To 1848 Step 132
  For i=0 To 1980 Step 132
    angle.f = this
    CreateImage(4,132,132,32,#PB_Image_Transparent)
    
    hDC = StartDrawing(ImageOutput(4))
      GdipCreateFromHDC(hDC, @*gfxcontext)
      GdipRotateWorldTransform(*gfxcontext, angle, #MatrixOrderPrepend)
      GdipTranslateWorldTransform(*gfxcontext, 66, 66, #MatrixOrderAppend)
      GdipDrawImageRectI(*gfxcontext, *gdip_bitmap, 66.0, 66.0, -132, -132)
      GdipReleaseDC(*gfxcontext, hDC)
      GdipDeleteGraphics(*gfxcontext)
    StopDrawing()

    StartDrawing(ImageOutput(3))
      DrawAlphaImage(ImageID(0),i,j)
      DrawAlphaImage(ImageID(4),i,j)
    StopDrawing()
    
    this+1
  Next   
Next 

GdiplusShutdown(*token)

UsePNGImageEncoder()
SaveImage(3, GetTemporaryDirectory()+"\imagestrip.png",#PB_ImagePlugin_PNG)
The second code snippet loads the imagestrip and draws it to a sprite strip of the same dimensions. ClipSprite() is then used to display the relevant section of the sprite strip in the main loop. A TrackBarGadget is used to select the desired speed. Slamming back and forth very quickly on the trackbar has no effect on the used CPU here. It stays around 2% all the time, which is what it does at idle without running any programs.

Code: Select all

UsePNGImageDecoder()
LoadImage(0,GetTemporaryDirectory()+"\imagestrip.png")

InitSprite()
OpenWindow(0,0,0,320,240,"",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)
TrackBarGadget(0,0,210,320,30,60,299)
OpenWindowedScreen(WindowID(0),90,30,132,132)

CreateSprite(0,2112,1980,#PB_Sprite_AlphaBlending)
StartDrawing(SpriteOutput(0))
Box(0,0,2112,1980, GetSysColor_(#COLOR_BTNFACE))
  DrawAlphaImage(ImageID(0),0,0)
StopDrawing()

Dim a.POINT(300)

this=60

For j=0 To 1848 Step 132
  For i=0 To 1980 Step 132
    a(this)\x=i
    a(this)\y=j
    this+1
  Next   
Next 

Repeat
  EventID = WindowEvent()
  speed=GetGadgetState(0)
  ClipSprite(0, a(speed)\x, a(speed)\y, 132, 132)
  DisplaySprite(0,0,0)
  FlipBuffers()
Until EventID = #PB_Event_CloseWindow
One final note, if DirectX9 is selected as subsystem, this code will not work.
This is quite a simple approach, as I said deliberately using a bit of memory (in this case around 700k) in order to display the goods in a fraction of the time it would take to draw it on the fly. 700k well spent imho.

Here is the ImageStripCreate program written with the PB Vector library instead of gdiplus. I must confess it's much simpler (and just as good)

Code: Select all

UsePNGImageDecoder()

*Buffer = ReceiveHTTPMemory("https://lloydsplace.com/face-132.png")
If *Buffer
  CatchImage(0, *buffer)
  FreeMemory(*Buffer)
Else
  Debug "Failed"
EndIf

*Buffer = ReceiveHTTPMemory("https://lloydsplace.com/needle-132.png")
If *Buffer
  CatchImage(1, *buffer)
  FreeMemory(*Buffer)
Else
  Debug "Failed"
EndIf

CreateImage(3,2112,1980,32,#PB_Image_Transparent)

this.d=60

For j=0 To 1848 Step 132
  For i=0 To 1980 Step 132
    angle.f = this
    CreateImage(4,132,132,32,#PB_Image_Transparent)
    StartVectorDrawing(ImageVectorOutput(4))
    RotateCoordinates(66, 66, this)
    MovePathCursor(0,0)
    DrawVectorImage(ImageID(1))
    StopVectorDrawing()
    StartDrawing(ImageOutput(3))
      DrawAlphaImage(ImageID(0),i,j)
      DrawAlphaImage(ImageID(4),i,j)
    StopDrawing()
    this+1
  Next   
Next 

UsePNGImageEncoder()
SaveImage(3, GetTemporaryDirectory()+"\imagestrip.png",#PB_ImagePlugin_PNG)

Re: Faster vector graphics

Posted: Sun Dec 03, 2023 8:49 am
by coco2
Thanks for the code netmaestro it works very well. You have given me an idea, rather than make the whole dial just prerender the needle because that is the only think I'm having trouble with. Just doing the needle would give me the vector quality I wanted as well as the speed. I also have numbers that change on the dial like speed and gear so I wouldn't be able to prerender the whole thing.

Re: Faster vector graphics

Posted: Sun Dec 03, 2023 4:18 pm
by firace
pf shadoko wrote: Sat Dec 02, 2023 6:40 pm ok with Pjay
I don't think it would be difficult to do the basic graphics functions in openGL
however I couldn't make anti-aliased polygons (for lines it's ok), if someone has the solution

@ fred :
in a game you always have to update a score, and other info (speed needle for coco2)
and then you have to go through startdrawing
I could be wrong but perhaps this shows how to do smooth polygons in openGL?
https://www.purebasic.fr/english/viewtopic.php?t=82915

Also this could be interesting (haven't tested)
https://community.khronos.org/t/multisa ... g/14967/17

Re: Faster vector graphics

Posted: Sun Dec 03, 2023 10:33 pm
by pjay
To get smooth polygons you need to have an opengl context that has multisampling enabled during creation. PBs OpenGLGadget doesn't give end users the option to enable it as far as I can see.

Thankfully, many gpu drivers have the ability to override the default context setup to force-enable multisampling which can enable anti-aliasing on the OpenGLGadgets context - look in your gpu control panel for any Anti-aliasing overrides that will enable it.

You can see if you have anti-aliasing enabled in opengl with this snippet, a MultiSample count > 0 means anti-aliasing is enabled:

Code: Select all

OpenWindow(0,0,0,640,480,"")
OpenGLGadget(0,0,0,640,480)

Define.l Multisamples
#GL_SAMPLES = $80A9
glGetIntegerv_(#GL_SAMPLES,@Multisamples)
MessageRequester("Info", "MultiSample count: " + Multisamples)
There are alternative methods to enable it, but they're more complex & involved - you can generate your own opengl context with API (but will need to handle event messages manually), or through using Multisample FrameBuffer Objects.

Re: Faster vector graphics

Posted: Sun Dec 03, 2023 10:52 pm
by netmaestro
Edited post above to include Vector Drawing version of the ImageStripCreate program.

Re: Faster vector graphics

Posted: Mon Dec 04, 2023 11:06 am
by pjay
@coco2
As you never posted any example code, I put together a small demo for myself (it's the first time I think i've used the Sprite library).

I'll be honest with you, I couldn't see too much jaggedness with the sprite rendering, but I do have natures anti-aliasing algorithm running (poor eyesight :D ).

SpriteOutput was slow as you described & for reasons already explained - I couldn't find a way to use VectorDrawing library with it either, or find a command that creates a sprite from an existing image, which was surprising.

As always there are workarounds for these limitations - The workround I found was ~50% faster than SpriteOutput() on my PC; draw to an image, EncodeImage() to a bitmap buffer and then Catchsprite from that buffer:

Code: Select all

; Sprite test - PJ 23.

EnableExplicit
Define event, exit, frames, nextSecond.q, max, Rotation.f

#Size = 768
Enumeration 
  #MySpr_BG
  #MySpr_Needle
EndEnumeration

Procedure CatchSpriteFromImage(Sprite, Image) 
  Protected *mem = EncodeImage(Image,#PB_ImagePlugin_BMP)
  CatchSprite(Sprite,*mem,#PB_Sprite_AlphaBlending)
  FreeMemory(*mem)
EndProcedure
Procedure DrawSpriteImages()
  Protected ang, hx = #Size / 2, hy = #Size / 2, txt.s, shadow = hy * 0.01
  CreateImage(#MySpr_BG,#Size,#Size,32) : CreateImage(#MySpr_Needle,#Size,#Size,32)
  LoadFont(0,"Arial",32,#PB_Font_HighQuality|#PB_Font_Bold)
  
  ; face
  StartVectorDrawing(ImageVectorOutput(#MySpr_BG))
  VectorSourceColor(RGBA(60,70,80,255)) : FillVectorOutput()
  VectorSourceColor(RGBA(30,50,70,240)) : AddPathCircle(hx,hy,hy * 0.99) : FillPath()
  VectorSourceColor(RGBA(30,30,30,255)) : AddPathCircle(hx,hy,hy * 0.99,149,391) : StrokePath(hy * 0.04)
  VectorSourceColor(RGBA(200,220,240,255)) : AddPathCircle(hx,hy,hy * 0.99,149,391) : StrokePath(hy * 0.02)
  VectorFont(FontID(0),hx / 5.5)

  For ang = 150 To 400 Step 24
    If ang > 310 : VectorSourceColor(RGBA(230,40,70,255)) : EndIf
    MovePathCursor(hx + (hy * 0.97) * Cos(Radian(ang)),hy + (hy*0.97) * Sin(Radian(ang)))
    AddPathLine(hx + (hy * 0.825) * Cos(Radian(ang)),hy + (hy * 0.825) * Sin(Radian(ang))) : StrokePath(7.5)
    
    txt = Str((Ang - 150) / 24)
    VectorSourceColor(RGBA(30,30,30,255))
    MovePathCursor((hx + shadow + (hy * 0.7) * Cos(Radian(ang))) - (VectorTextWidth(txt)/2), (hy + shadow + (hy * 0.7) * Sin(Radian(ang))) - (VectorTextHeight(txt)/2))
    DrawVectorText(txt)
    VectorSourceColor(RGBA(220,225,240,255)) : If ang > 310 : VectorSourceColor(RGBA(230,30,60,255)) : EndIf
    MovePathCursor((hx + (hy * 0.7) * Cos(Radian(ang))) - (VectorTextWidth(txt)/2), (hy + (hy * 0.7) * Sin(Radian(ang))) - (VectorTextHeight(txt)/2))
    DrawVectorText(txt)
  Next
  VectorSourceColor(RGBA(200,220,240,255))
  For ang = 150 To 390 Step 4
    If ang > 314 : VectorSourceColor(RGBA(230,40,70,255)) : EndIf
    MovePathCursor(hx + (hy * 0.97) * Cos(Radian(ang)),hy + (hy * 0.97) * Sin(Radian(ang)))
    AddPathLine(hx + (hy * 0.88) * Cos(Radian(ang)),hy + (hy * 0.88) * Sin(Radian(ang)))
    StrokePath(2.5)
  Next
  StopVectorDrawing()
  
  ; needle
  StartDrawing(ImageOutput(#MySpr_Needle))
  DrawingMode(#PB_2DDrawing_AllChannels) : Box(0,0,OutputWidth(),OutputHeight(),RGBA(0,0,0,0))
  StopDrawing()
  StartVectorDrawing(ImageVectorOutput(#MySpr_Needle))
  VectorSourceColor(RGBA(230,50,80,255))
  MovePathCursor(hx + (hy * 0.825) * Cos(Radian(225)),hy + (hy * 0.825) * Sin(Radian(225)))
  AddPathLine(hx + (hy * 0.2) * Cos(Radian(45)),hy + (hy * 0.2) * Sin(Radian(45)))
  StrokePath(hy * 0.075, #PB_Path_RoundEnd)
  VectorSourceColor(RGBA(0,0,0,255)) : AddPathCircle(hx,hy,48) : FillPath()
  VectorSourceColor(RGBA(80,80,80,255)) : AddPathCircle(hx,hy,32) : FillPath()
  VectorSourceColor(RGBA(24,20,24,255)) : AddPathCircle(hx,hy,8) : FillPath()
  StopVectorDrawing()
  
  CatchSpriteFromImage(#MySpr_BG,#MySpr_BG)
  CatchSpriteFromImage(#MySpr_Needle,#MySpr_Needle)  
EndProcedure  
Procedure Init_Main()
  InitSprite()
  OpenWindow(0, 0,0,#Size / DesktopResolutionX(),#Size / DesktopResolutionY(),"Tacho - standard sprite",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)
  OpenWindowedScreen(WindowID(0), 0, 0, #Size, #Size, 0, 0, 0,#PB_Screen_SmartSynchronization)
  SpriteQuality(#PB_Sprite_BilinearFiltering)
  DrawSpriteImages()
EndProcedure
Init_Main()

nextSecond = ElapsedMilliseconds() + 1000
Repeat
  Repeat
    event = WindowEvent()
    Select event
      Case #PB_Event_CloseWindow : exit = #True
    EndSelect
  Until Event = 0
  
  ClearScreen(0)
  DisplaySprite(#MySpr_BG,0,0)
  
  Rotation = Cos(Radian((ElapsedMilliseconds() + 1400) / 8.0)) * 150
  If Rotation > 90 : Rotation = 90 : Rotation - Abs(Sin(Radian((ElapsedMilliseconds() + 60))) * 50) : EndIf
  RotateSprite(#MySpr_Needle,Rotation + 75,#PB_Absolute)
  DisplayTransparentSprite(#MySpr_Needle,0,0)
  
  FlipBuffers()
  
  frames + 1
  If ElapsedMilliseconds() >= nextSecond
    If frames > max : max = frames : EndIf
    nextSecond = ElapsedMilliseconds() + 1000
    SetWindowTitle(0,Str(frames)+ " fps - Max: "+Str(max))
    frames = 0
  EndIf
Until exit = #True

Re: Faster vector graphics

Posted: Mon Dec 04, 2023 11:13 am
by Fred
You shouldn't need EncodeImage(), just use DrawImage() on your SpriteOutput() and it should be fine.

Re: Faster vector graphics

Posted: Mon Dec 04, 2023 12:07 pm
by pf shadoko
@pjay
no, no multisampling for me
but anyway, that's not what I want, I'd like a non-alias plot at the base
(note: the SPH example is not anti-aliased)

regarding your last example, it's clear that this is the solution I'll use

@coco2, you can still improve the rendering by creating the needle in size X 2 (and displaying it /2)

concerning netmaestro method: using clipsprite would seem more suitable for displaying the letters of a sprite containing all the characters
I could see functions like this:
- initscreenfont (possibly with a brush (see https://www.purebasic.fr/french/viewtop ... =6&t=16385 )
- screendrawtext

Re: Faster vector graphics

Posted: Mon Dec 04, 2023 12:16 pm
by pjay
Fred wrote: Mon Dec 04, 2023 11:13 am You shouldn't need EncodeImage(), just use DrawImage() on your SpriteOutput() and it should be fine.
Yeah, that's understood. My point was more that it was 50% quicker for me to to use EncodeImage()/CatchSprite() than it was to use SpriteOutput()/DrawImage().

Re: Faster vector graphics

Posted: Mon Dec 04, 2023 1:41 pm
by Fred
pjay wrote: Mon Dec 04, 2023 12:16 pm
Fred wrote: Mon Dec 04, 2023 11:13 am You shouldn't need EncodeImage(), just use DrawImage() on your SpriteOutput() and it should be fine.
Yeah, that's understood. My point was more that it was 50% quicker for me to to use EncodeImage()/CatchSprite() than it was to use SpriteOutput()/DrawImage().
That's a bit unexpected for sure.

Re: Faster vector graphics

Posted: Tue Dec 05, 2023 4:33 am
by coco2
I don't have any code but here is a screenshot of my tachometer I've been working on.

On the left is the tachometer created with sprites. The needle is the most difficult part and it a 4x size sprite resized and rotated to fit into the tachometer. The tachometer on the right is the tachometer created with vector tools. As you can see the needle looks a lot better on the vector version.

https://ibb.co/k09pZj2

Re: Faster vector graphics

Posted: Tue Dec 05, 2023 7:48 am
by pjay
That's pretty clear to see when I magnify it - I think the scaling is what's causing most of this issue.

Try the following:
- Draw the sprite to the correct 1:1 scale.
- Ensure the needle isn't being drawn to the very edge of the image, leave a couple of pixels spare for the vector librarys AA to work.
- Try drawing the source needle image at a 45 degree angle instead of vertical or horizontal.