basic svg animation with fb and sdl2

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
thrive4
Posts: 74
Joined: Jun 25, 2021 15:32

basic svg animation with fb and sdl2

Post by thrive4 »

description
Very basic animation with svg (Scalable Vector Graphics)
using sdl2, sdl_image and fb.

usage:
arrow left / right or numpad 4 / 5 to switch svg's
arrow up / down or numpad plus / minus to increase / decrease
home first svg
end last svg
F11 fullscreen
ESC quit app

Code: Select all

' svg animation with fb and sdl image
' note animation, filters (gaussian, etc) and text are not supported
' yet by sdl_image (sdl_image uses nanosvg https://github.com/memononen/nanosvg) 
' tweaked for fb and sdl2 jan 2024 by thrive4

#include once "SDL2/SDL.bi"
#include once "SDL2/SDL_image.bi"
#include once "vbcompat.bi"

' setup generic 
dim running         as boolean = True
dim renderpass      as boolean = True
dim screenwidth     As integer = 1280
dim screenheight    As integer = 720
dim fullscreen      as boolean = false
dim fps             as ulong  = 30
dim fpscurrent      as ulong
dim curscreenw      as integer
dim curscreenh      as integer
' get desktop info
ScreenInfo curscreenw, curscreenh

' setup svg
dim svganimation as integer = 1
dim svgframe     as integer = 1
' correlates to size of svg
dim svg          as zstring * 2048 
dim svgrw        as SDL_RWops ptr
dim svgviewport  as string
dim svgtitle     as string

' sdl colors
#define sdl_rgba(r, g, b, a) type<sdl_color>(r, g, b, a)
Dim As SDL_Color backgroundcolor        = (75, 85, 95, 0)

' init sdl rects and other sdl screen metrics
dim event           as SDL_Event
Dim                 As SDL_Texture Ptr texture
dim rotateimage     as SDL_RendererFlip = SDL_FLIP_NONE
dim rotateangle     as double = 0
Dim As SDL_Texture Ptr background_texture
dim as SDL_Rect desktopplate, srcrect

' Get the texture w/h so we can center it in the screen
Dim As Integer iW, iH
Dim As integer posx, sposx
Dim As integer posy, sposy

' via https://www.freebasic.net/forum/viewtopic.php?t=32323 by fxm
function syncfps(byval myfps as ulong, byval skipimage as boolean = true, byval restart as boolean = false, byref imageskipped as boolean = false) as ulong
    '' 'myfps'          : requested fps value, in frames per second
    '' 'skipimage'      : optional parameter to activate the image skipping (true by default)
    '' 'restart'        : optional parameter to force the resolution acquisition, to reset to false on the next call (false by default)
    '' 'imageskipped'   : optional parameter to inform the user that the image has been skipped (if image skipping is activated)
    '' function return  : applied fps value (true or apparent), in frames per second
    static as single tos, bias, sum
    static as long count
    ' initialization calibration
    if tos = 0 or restart = true then
        dim as double t = timer
        for i as integer = 1 to 10
            sleep 1, 1
        next i
        dim as double tt = timer
        #if not defined(__fb_win32__) and not defined(__fb_linux__)
        if tt < t then t -= 24 * 60 * 60
        #endif
        tos   = (tt - t) * 0.00001f
        bias  = 0
        count = 0
        sum   = 0
    end if
    static as double t1
    static as long n = 1
    static as ulong fps
    static as single tf
    ' delay generation
    dim as double t2 = timer
    #if not defined(__fb_win32__) and not defined(__fb_linux__)
    if t2 < t1 then t1 -= 24 * 60 * 60
    #endif
    dim as double t3 = t2
    dim as single dt = (n * tf - (t2 - t1)) * 1000 - bias
    if (dt >= 3 * tos / 2) or (skipimage = false) or (n >= 20) or (fps / n <= 10) then
        if dt <= tos then dt = tos / 2
        sleep dt, 1
        t2 = timer
        #if not defined(__fb_win32__) and not defined(__fb_linux__)
        if t2 < t1 then t1 -= 24 * 60 * 60 : t3 -= 24 * 60 * 60
        #endif
        fps = n / (t2 - t1)
        tf = 1 / myfps
        t1 = t2
        ' automatic test and regulation
        dim as single delta = (t2 - t3) * 1000 - (dt + bias)
        if abs(delta) > 3 * tos then
            tos = 0
        else
            bias += 0.1 * sgn(delta)
        end if
        ' automatic calibation
        if dt < tos then
            if count  = 100 then
                tos   = sum * 0.00001f
                bias  = 0
                sum   = 0
                count = 0
            else
                sum   += (t2 - t3)
                count += 1
            end if
        end if
        imageskipped = false
        n = 1
    else
        imageskipped = true
        n += 1
    end if
    return fps
end function

initsdl:

' init window and render
If (SDL_Init(SDL_INIT_VIDEO) = not NULL) Then
    'logentry("error", "sdl2 video could not be initlized error: " + *SDL_GetError())
    SDL_Quit()
else
    ' no audio needed
    SDL_QuitSubSystem(SDL_INIT_AUDIO)
    SDL_QuitSubSystem(SDL_INIT_HAPTIC)
    ' render scale quality: 0 point, 1 linear, 2 anisotropic
    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")
    ' filter non used events
    SDL_EventState(SDL_FINGERMOTION,    SDL_IGNORE)
    SDL_EventState(SDL_FINGERDOWN,      SDL_IGNORE)
    SDL_EventState(SDL_FINGERUP,        SDL_IGNORE)
    SDL_EventState(SDL_MULTIGESTURE,    SDL_IGNORE)
    SDL_EventState(SDL_DOLLARGESTURE,   SDL_IGNORE)
    SDL_EventState(SDL_JOYBALLMOTION,   SDL_IGNORE)
    SDL_EventState(SDL_DROPFILE,        SDL_IGNORE)
End If

' setup glass aka window
Dim glass      As SDL_Window Ptr
if screenwidth < curscreenw then
    SDL_ShowCursor(SDL_ENABLE)
    glass = SDL_CreateWindow( "sdl svg demo", 100, 100, screenwidth, screenheight, SDL_WINDOW_RESIZABLE)
else
    SDL_ShowCursor(SDL_DISABLE)
    glass = SDL_CreateWindow( "sdl svg demo", null, null, screenwidth, screenheight, SDL_WINDOW_BORDERLESS)
end if
if (glass = NULL) Then
    'logentry("error", "sdl2 could not create window")
	SDL_Quit()
EndIf
Dim As SDL_Renderer Ptr renderer = SDL_CreateRenderer(glass, -1, SDL_RENDERER_ACCELERATED Or SDL_RENDERER_PRESENTVSYNC)
if (renderer = NULL) Then	
    'logentry("error", "sdl2 could not create renderer")
	SDL_Quit()
EndIf

desktopplate.x = 0
desktopplate.y = 0
desktopplate.w = screenwidth
desktopplate.h = screenheight

' setup background image or color
srcrect.x = iW
srcrect.y = iH
srcrect.w = screenwidth
srcrect.h = screenheight

' absoulte cordinates x, y svg todo still clunky 
' origin -100 -100
' coordinates 0 ~ 1  
' x y screen width / height - 100
svgviewport = "<svg width='" & screenwidth & "' height='" & screenheight & _
              " viewbox='-" & screenwidth & "' -" & screenheight & "' " & screenwidth & " " & screenheight & "'" _
              "' preserveAspectRatio='xMinYMin' fill='none'><g transform='translate(" & -100 + screenwidth * 0.5 & ", " & -100 + screenheight * 0.5 & ")'>"

print svgviewport
renderpass = true

' main
while running

    while SDL_PollEvent(@event) <> 0
        ' basic window interaction
        select case event.type
            case SDL_KEYDOWN and event.key.keysym.sym = SDLK_ESCAPE
                running = False
                exit while
            case SDL_WINDOWEVENT and event.window.event = SDL_WINDOWEVENT_CLOSE
                running = False
                exit while
            case SDL_KEYDOWN and event.key.keysym.sym = SDLK_F11
                SDL_DestroyRenderer(renderer)
                SDL_DestroyWindow(glass)
                select case fullscreen
                    case true
                        screenwidth  = 1280
                        screenheight = 720
                        fullscreen = false
                        goto initsdl
                    case false
                        screenwidth  = curscreenw
                        screenheight = curscreenh
                        fullscreen = true
                        goto initsdl
                end select
            case SDL_KEYDOWN and event.key.keysym.sym = SDLK_HOME
                svganimation = 1
                svgframe = 1
                renderpass = true
            case SDL_KEYDOWN and event.key.keysym.sym = SDLK_END
                svganimation = 13
                svgframe = 1
                renderpass = true
            ' simple animation increment / decrease
            case SDL_KEYDOWN and event.key.keysym.sym = SDLK_LEFT, SDL_KEYDOWN and event.key.keysym.sym = SDLK_KP_4
                if svganimation > 1 then
                    svganimation -= 1
                end if
                svgframe = 1
                renderpass = true
            case SDL_KEYDOWN and event.key.keysym.sym = SDLK_RIGHT, SDL_KEYDOWN and event.key.keysym.sym = SDLK_KP_6
                if svganimation < 13 then
                    svganimation += 1
                else
                    svganimation = 1
                end if
                svgframe = 1
                renderpass = true
            case SDL_KEYDOWN and event.key.keysym.sym = SDLK_UP, SDL_KEYDOWN and event.key.keysym.sym = SDLK_KP_PLUS
                svgframe += 1
                renderpass = true
            case SDL_KEYDOWN and event.key.keysym.sym = SDLK_DOWN, SDL_KEYDOWN and event.key.keysym.sym = SDLK_KP_MINUS
                svgframe -= 1
                renderpass = true
            case SDL_MOUSEWHEEL
                if event.wheel.y > 0 then  
                    svgframe += 1
                elseif event.wheel.y < 0 then
                    svgframe -= 1
                end if
                renderpass = true
            case SDL_MOUSEBUTTONDOWN
                select case event.button.button
                    case SDL_BUTTON_LEFT
                        svgframe -= 1
                    case SDL_BUTTON_MIDDLE
                        ' nop
                    case SDL_BUTTON_RIGHT
                        svgframe += 1
                end select
                renderpass = true
        end select
    wend

    if renderpass then
        SDL_RenderClear(renderer)
            ' background
            SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g, backgroundcolor.b, backgroundcolor.a )
            SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE)
            SDL_RenderFillRect(renderer, @desktopplate)

            ' sample svg
            ' courtesy https://www.w3schools.com/graphics/svg_examples.asp
            select case svganimation
            case 1
                svgtitle = "circle"
                svg = svgviewport + "<circle cx='100' cy='100' r='" & svgframe * 5 & "' stroke='white' stroke-width='4' fill='green'/></svg>"
            case 2
                svgtitle = "rectangle"
                svg = svgviewport + "<rect x='0' y='0' width='190' height='190' style='fill:rgb(0,0," & svgframe * 2 & ");stroke:black;stroke-width:2;opacity:1.0'/></svg>"
            case 3
                svgtitle = "rectangle rounded corners"
                svg = svgviewport + "<rect x='65' y='55' rx='10' ry='10' width='90' height='90' style='fill:rgb(" & svgframe * 2 & ",0,0);stroke:black;stroke-width:5;opacity:1.0'/></svg>"
            case 4
                svgtitle = "polyline staircase"
                svg = svgviewport + "<polyline points='0,40 40,40 40,80 80,80 80,120 120,120 120,160' style='fill:white;stroke:red;stroke-width:" & svgframe & "' /></svg>"
            case 5
                svgtitle = "audio volume icon" ' set coord 0 0
                svg = svgviewport + "<path d='M6 14V12C6 8.68629 8.68629 6 12 6H36C39.3137 6 42 8.68629 42 12V14' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/><path d='M" & 31 + svgframe & " 18V30' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/><path d='M40 20V28' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/><path d='M24 15V33' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/><path d='M16 18V30' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/><path d='M8 20V28' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/><path d='M6 34V36C6 39.3137 8.68629 42 12 42H36C39.3137 42 42 39.3137 42 36V34' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/></svg>"
            case 6
                svgtitle = "star"
                svg = svgviewport + "<polygon points='100,10 40,198 190,78 10,78 160,198' style='fill:black;stroke:grey;stroke-width:" & svgframe & ";fill-rule:evenodd;'/></svg>"
            case 7
                svgtitle = "triangle"
                svg = svgviewport + "<polygon points='200, 10 250,190 " & svgframe * 10 & ", 210' style='fill:blue;stroke:black;stroke-width:1' /></svg>"
            case 8
                ' issues with proportial scale and posistion when using svgviewport    
                svgtitle = "linetypes"
                svg = svgviewport + "<svg height='80' width='" & svgframe * 10 & "'><g fill='none' stroke='black' stroke-width='2'><path stroke-linecap='butt' d='M5 20 1215 0' /><path stroke-linecap='round' d='M5 40 l215 0' /><path stroke-linecap='square' d='M5 60 l215 0' /></g></svg>"
            ' fullscreen
            case 9
                svgtitle = "corner"
                svg = "<svg viewBox='0 0 1600 800'><rect fill='#000000' width='1600' height='800'/><g fill-opacity='1'><polygon fill='#222222' points='800 100 0 200 0 800 1600 800 1600 200'/><polygon fill='#" & svgframe & "44444' points='800 200 0 400 0 800 1600 800 1600 400'/><polygon  fill='#666666' points='800 300 0 600 0 800 1600 800 1600 600'/><polygon  fill='#888888' points='1600 800 800 400 0 800'/><polygon  fill='#aaaaaa' points='1280 800 800 500 320 800'/><polygon  fill='#cccccc' points='533.3 800 1066.7 800 800 600'/><polygon fill='#EEE' points='684.1 800 914.3 800 800 700'/></g></svg>"
            case 10
                svgtitle = "wave"
                svg = "<svg width='1920' height='1080' preserveAspectRatio='none' viewBox='0 0 1920 1080'><g mask='url(&quot;#SvgjsMask1013&quot;)' fill='none'><rect width='1920' height='1080' x='0' y='0' fill='#0e2a47'></rect><path d='M 0," & svgframe * 5 & " C 76.8,262.6 230.4,538.2 384,513 C 537.6,487.8 614.4,70.6 768,74 C 921.6,77.4 998.4,501.8 1152,530 C 1305.6,558.2 1382.4,250.2 1536,215 C 1689.6,179.8 1843.2,326.2 1920,354L1920 1080L0 1080z' fill='#184a7e'></path><path d='M 0," & svgframe * 10 & " C 128,689 384,937.6 640,925 C 896,912.4 1024,556.6 1280,567 C 1536,577.4 1792,895 1920,977L1920 1080L0 1080z' fill='#2264ab'></path></g><defs><mask id='SvgjsMask1013'><rect width='1920' height='1080' fill='#ffffff'></rect></mask></defs></svg>"
                'svg = "<svg width='1920' height='1080' preserveAspectRatio='none' viewBox='0 0 1920 1080'><g mask='url(&quot;#SvgjsMask1013&quot;)' fill='none'><rect width='1920' height='1080' x='0' y='0' fill='#0e2a47'></rect><path d='M 0,200 C 76.8,262.6 230.4,538.2 384,513 C 537.6,487.8 614.4,70.6 768,74 C 921.6,77.4 998.4,501.8 1152,530 C 1305.6,558.2 1382.4,250.2 1536,215 C 1689.6,179.8 1843.2,326.2 1920,354L1920 1080L0 1080z' fill='#184a7e'></path><path d='M 0,630 C 128,689 384,937.6 640,925 C 896,912.4 1024,556.6 1280,567 C 1536,577.4 1792,895 1920,977L1920 1080L0 1080z' fill='#2264ab'></path></g><defs><mask id='SvgjsMask1013'><rect width='1920' height='1080' fill='#ffffff'></rect></mask></defs></svg>"
             ' gradients
            case 11
                svgtitle = "radiant"
                svg = "<svg width='400' height='200' viewBox='0 0 800 400'><rect fill='#330000' width='800' height='400'/><defs><radialGradient id='a' cx='396' cy='281' r='514' gradientUnits='userSpaceOnUse'><stop  offset='0' stop-color='#D18'/><stop  offset='1' stop-color='#330000'/></radialGradient><linearGradient id='b' gradientUnits='userSpaceOnUse' x1='400' y1='148' x2='400' y2='333'><stop offset='0'  stop-color='#FA3' stop-opacity='0'/><stop offset='1'  stop-color='#FA3' stop-opacity='0.5'/></linearGradient></defs><rect fill='url(#a)' width='800' height='400'/><g fill-opacity='0.4'><circle fill='url(#b)' cx='267.5' cy='61' r='300'/><circle fill='url(#b)' cx='532.5' cy='61' r='300'/><circle fill='url(#b)' cx='400' cy='30' r='300'/></g></svg>"
            case 12
                svgtitle = "gradient ellipse"
                svg = svgviewport + "<defs><linearGradient id='grad1' x1='0%' y1='0%' x2='100%' y2='0%'><stop offset='0%' style='stop-color:rgb(255," & svgframe * 5 & ",0);stop-opacity:1' /><stop offset='100%' style='stop-color:rgb(" & svganimation * 2 & ",0,0);stop-opacity:1' /></linearGradient></defs><ellipse cx='200' cy='70' rx='295' ry='155' fill='url(#grad1)' /></svg>"
            case 13
                svgtitle = "radial gradient circle"
                svg = svgviewport + "<defs><radialGradient id='grad1' cx='" & svgframe & "%' cy='30%' r='" & svgframe & "%' fx='50%' fy='50%'><stop offset='0%' style='stop-color:rgb(255,255,255);stop-opacity:2' /><stop offset='100%' style='stop-color:rgb(0,0,255);stop-opacity:1' /></radialGradient></defs><ellipse cx='200' cy='70' rx='185' ry='185' fill='url(#grad1)' /></svg>"
            end select
            ' text not working
            'svg = "<svg height='30' width='200'><text x='0' y='15' fill='red'>I love SVG!</text></svg>"
            'svg = svgviewport + "<path id='lineAB' d='M 100 350 l 150 -300' stroke='red' stroke-width='3' fill='none' /><path id='lineBC' d='M 250 50 l 150 300' stroke='red' stroke-width='3' fill='none' /><path d='M 175 200 l 150 0' stroke='green' stroke-width='3' fill='none'/><path d='M 100 350 q 150 -300 300 0' stroke='blue' stroke-width='5' fill='none' /><!-- Mark relevant points --><g stroke='black' stroke-width='3' fill='black'><circle id='pointA' cx='100' cy='350' r='3' /><circle id='pointB' cx='250' cy='50' r='3' /><circle id='pointC' cx='400' cy='350' r='3'/></g><!-- Label the points --><g font-size='30' font-family='sans-serif' fill='black' stroke='none' text-anchor='middle'><text x='100' y='350' dx='-30'>A</text><text x='250' y='50' dy='-10'>B</text><text x='400' y='350' dx='30'>C</text></g></svg>"

            ' convert svg xml to SDL_RWops structure
            ' courtesy https://stackoverflow.com/questions/64838044/how-i-can-create-an-sdl-texture-by-loading-an-svg-from-memory
            svgrw = SDL_RWFromConstMem(@svg, sizeof(svg))
            SDL_DestroyTexture(background_texture)
            'background_texture = SDL_CreateTextureFromSurface(renderer, IMG_Load_RW(svgrw, 1)) ' causes memory leak
            background_texture = IMG_LoadTexture_RW(renderer, svgrw, 1)
            SDL_RenderCopyEx(renderer, background_texture, null, @srcrect, rotateangle, null, rotateimage)
        SDL_RenderPresent(renderer)
        renderpass = false
    end if

    ' sync fps and decrease cpu usage
    fpscurrent = syncfps(fps)
    if fullscreen then
        ' nop
    else
        SDL_SetWindowTitle(glass, "svg animation - " + svgtitle + " - " & fpscurrent & " fps")
    end if
wend

' cleanup
SDL_DestroyTexture(texture)
SDL_DestroyRenderer(renderer)
SDL_DestroyWindow(glass)
SDL_Quit()
end
compile with
sdl https://github.com/libsdl-org/SDL/releases
sdl_image https://github.com/libsdl-org/SDL_image/releases

place:
SDL2.dll
SDL2_image.dll
in the same folder as the compiled exe
(tested with 32 bit version)

Snags and other issues

One caveat is cpu usage which, pending on the size
and or complexity of the svg can be quite high.
This might be due the implementation still something
to keep an eye out for.

The second 'issue' is the methods used for
proportional scaling and placing of the svg.
I really struggled with this I have little doubt
that this can be done more efficient and practical.

For now the coordinates for x and y are
0 - 0 to 1 - 1 (see code comments for more detail)

More info on
svg attributes:
https://oreillymedia.github.io/Using_SV ... arkup.html
https://developer.mozilla.org/en-US/doc ... /Attribute
scaling:
https://css-tricks.com/scale-svg/
viewbox (scaling):
https://stackoverflow.com/questions/153 ... -attribute
https://stackoverflow.com/questions/145 ... height-etc
resources:
https://www.svgrepo.com/
https://www.w3schools.com/graphics/svg_examples.asp

If somebody is tempted to do a native fb implementation
a) use / port nanosvg https://github.com/memononen/nanosvg
b) insight rasterize vector https://github.com/libsdl-org/SDL_image ... /IMG_svg.c

Finally a bit about the history of svg from 1999 ....
https://www.siliconpublishing.com/blog/ ... se-of-svg/
A cautionary, but optimistic, tale of how turbulent
tech development can be ...

Special thanks to fxm
viewtopic.php?t=32323
Post Reply