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("#SvgjsMask1013")' 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("#SvgjsMask1013")' 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
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