Faster vector graphics

Just starting out? Need help? Post your questions and find answers here.
Fred
Administrator
Administrator
Posts: 18175
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Faster vector graphics

Post 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
coco2
Enthusiast
Enthusiast
Posts: 461
Joined: Mon Nov 25, 2013 5:38 am
Location: Australia

Re: Faster vector graphics

Post 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.
coco2
Enthusiast
Enthusiast
Posts: 461
Joined: Mon Nov 25, 2013 5:38 am
Location: Australia

Re: Faster vector graphics

Post 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.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Faster vector graphics

Post 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)
Last edited by netmaestro on Sun Dec 03, 2023 10:50 pm, edited 1 time in total.
BERESHEIT
coco2
Enthusiast
Enthusiast
Posts: 461
Joined: Mon Nov 25, 2013 5:38 am
Location: Australia

Re: Faster vector graphics

Post 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.
firace
Addict
Addict
Posts: 946
Joined: Wed Nov 09, 2011 8:58 am

Re: Faster vector graphics

Post 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
pjay
Enthusiast
Enthusiast
Posts: 251
Joined: Thu Mar 30, 2006 11:14 am

Re: Faster vector graphics

Post 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.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Faster vector graphics

Post by netmaestro »

Edited post above to include Vector Drawing version of the ImageStripCreate program.
BERESHEIT
pjay
Enthusiast
Enthusiast
Posts: 251
Joined: Thu Mar 30, 2006 11:14 am

Re: Faster vector graphics

Post 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
Fred
Administrator
Administrator
Posts: 18175
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Faster vector graphics

Post by Fred »

You shouldn't need EncodeImage(), just use DrawImage() on your SpriteOutput() and it should be fine.
User avatar
pf shadoko
Enthusiast
Enthusiast
Posts: 386
Joined: Thu Jul 09, 2015 9:07 am

Re: Faster vector graphics

Post 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
pjay
Enthusiast
Enthusiast
Posts: 251
Joined: Thu Mar 30, 2006 11:14 am

Re: Faster vector graphics

Post 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().
Fred
Administrator
Administrator
Posts: 18175
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Faster vector graphics

Post 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.
coco2
Enthusiast
Enthusiast
Posts: 461
Joined: Mon Nov 25, 2013 5:38 am
Location: Australia

Re: Faster vector graphics

Post 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
pjay
Enthusiast
Enthusiast
Posts: 251
Joined: Thu Mar 30, 2006 11:14 am

Re: Faster vector graphics

Post 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.
Post Reply