EriKWDev / nanim

Nanim is an easy-to-use framework to create smooth GPU-accelerated animations that can be previewed live inside a glfw window and, when ready, rendered to videos at an arbitrary resolution and framerate.
MIT License
119 stars 3 forks source link

issue with size and positioning of canvas (not expected and inconsistent behavior) #16

Open pietroppeter opened 2 years ago

pietroppeter commented 2 years ago

hi, nanim looks great, thanks for making this!

I am having trouble understanding the size and positioning of the canvas and it seems to me it is not what I would expect and inconsistent between what is shown in the rendering video and the final result in mp4. I might be missing something.

as an ad hoc example that allows me to understand better the issue (the issue presents itself for me also on the examples in repo), here is what I am running:

import nanim

let
  colors = colorsFromCoolors("https://coolors.co/202b38-009900-ffff66-dea584-b5453a")
  dark = colors[0]
  green = colors[1]
  yellow = colors[2]
  rust = colors[3]
  crab = colors[4]

let scene = newScene() # a ref object, let would be also fine
scene.setBackgroundColor(dark)
scene.width = 500
scene.height = 500

var circle = newCircle(radius=100)
circle.fill(yellow)
scene.add(circle)

scene.wait 500
scene.showAllEntities()
scene.wait 500
scene.play circle.move(500, 0)
scene.wait 500
scene.play circle.move(0, 500)
scene.wait 500
scene.play circle.move(-500, 0)
scene.wait 500
scene.play circle.move(0, -500)
scene.wait 500

render(scene)

I run with nim c -r example.nim --video --debug:false and output in terminal is:

[Info]: Width: 500, Height: 500 (ratio: 1.00:1.00), FPS: 60.0
[Info]: Launching FFmpeg subprocess with: ffmpeg -y -f rawvideo -pix_fmt rgba -s 500x500 -r 60.0 -i - -vf vflip -an -c:v libx264 -preset medium -profile:v high -crf 17 -coder 1 -tune animation -pix_fmt yuv420p -movflags +faststart -g 30 -bf 2 <mypath>/renders/final_17_46_28.mp4

here are two screenshots that I take when the circle has moved in the bottom right corner. Note that in both screenshots the behavior is not as expected above:

when rendering plays in a window:

rendering

when reproducing the mp4 file:

final_video

this is on a recent Mac Air M1 with Nim 1.6 and latest nanim

pietroppeter commented 2 years ago

I went looking in the code and found that if in this line: https://github.com/EriKWDev/nanim/blob/eb039590379d9130cecb2a94129e4dbc2b09e35f/src/nanim/rendering.nim#L262

I substitute scene.window.getWindowSize(width.addr, height.addr) with the call to getFramebufferSize (I google stuff to find this gflw reference), then at least get consistent behavior between final video and rendering (the canvas is still sized 1000x1000 instead of 500x500). I see those two functions appear in many places, so it is possible that this is not the correct place to make the fix.

EriKWDev commented 2 years ago

Hi! Thanks for taking interest in nanim! :)

I looked at your PR quickly and love the RenderingOptions, make much more sense than what I was doing. Will take a closer look later and might merge :)

It is deffinately a bug if the rendered video is different from the window animation. I only have access to test on an intel mac, so thanks for reporting behaviour on the M1!

When I render your example with the command you provided, I get the same expected result in both the live window and the video:

image

I'll boot up in MacOS later and see what the behaviour is like over there. In the meantime, here is some info about the coordinate system:

The Coordinate System of Nanim

The coordinate system of Nanim is a bit "weird", but it is intentional and has its logic. I have not yet documented it, so here's a start:

No matter what size of canvas you use, coordinates will always map from 0 to 1000 in a square with 500, 500 in the center. If the canvas is not a perfect square, (500, 500) will still remain the center of the canvas, and unit "1000" will represent pixel position min(width, height) + offset as in the picture below:

image

The reason I did this was that I usually render videos at many different resolutions and want them all to "look the same" - even if the ratio changes!

Here is an example:

import nanim

proc myScene(): Scene =
  let
    scene = newScene()
    square = newSquare(1000) # Fills the height/width of the inner square, no matter canvas size

  square.moveTo(500, 500) # Puts square in center, no matter the canvas  size

  square.stroke(newColor("#db235d"), 10)
  square.fill(newColor("#00000000"))

  scene.add(square)

  return scene

when isMainModule:
  render(myScene)

Notice now, when I resize the canvas (either by dragging the window or by passing --height:XX, --width:XX and/or --ratio:XX), the square will always fill the smallest of the width and the height and remain in the center:

Coordinates

This all stems from the fact that I scale the canvas using this function: https://github.com/EriKWDev/nanim/blob/eb039590379d9130cecb2a94129e4dbc2b09e35f/src/nanim/core.nim#L361

I should probably both document this behaviour and also add an option to use exact pixels as unit instead.

BTW: Sorry for the circles looking a bit weird with dots and artefacts.. I'm working on fixing that. You can fix it in the meantime by using newDot() instead of newCircle() since Dots are perfect circles.

EriKWDev commented 2 years ago

Screenshot 2021-12-21 at 10 44 30

Here is the video rendered using your command next to the live rendering on my mac.

Sadly, I don't get the same behaviour on my Intel Macbook Air from 2016 in Big Sur (11.1) :/

That being said, I tried your "Wrong Fix", and it doesn't change anything for me, so I would be OK with applying it to the rendering procs in rendering.nim