dotLottie
원래 LottieFiles의 dotLottie에서 thorvg를 활용한다는 얘기를 알고 있어서 이번 기회에 조사해봤습니다.
Lottie는 웹, 데스크톱 및 모바일 애플리케이션에서 고품질 모션을 구현하는 데 사용되는 경량 JSON 기반 애니메이션 형식이며, Lottie 사용량이 증가함에 따라 다양한 플랫폼에서 애니메이션을 패키징, 전달 및 관리하는 보다 표준화된 방법을 필요로 하게 되면서 dotLottie가 탄생하게 되었습니다. (참고 dotLottie)
오픈소스로 제공되고 있어서 LottieFiles 조직을 방문하니, dotlottie-rs가 실제 thorvg를 활용하고 있고 dotlottie-web 등의 프로젝트에서 이 프로젝트를 코어로 활용하는 플레이어 입니다. (참고 dotlottie-rs)
flowchart TD
A[dotLottie-web] --> WASM[WASM / wasm-bindgen]
B[dotLottie-ios] --> CAPI[C API]
C[dotLottie-android] --> CAPI
D[dotLottie-flutter] --> CAPI
E[dotLottie-react-native] --> CAPI
WASM --> dotlottiers[dotlottie-rs]
CAPI --> dotlottiers
dotlottiers --> Thorvg[Thorvg\Lottie renderer]
Renderer
dotLottie 자체는 다양한 역할을 포함하고 있고 thorvg를 렌더러 중 하나로 사용하고 있다. 실제로 dotlottie-rs/src/renderer/backend.rs를 보면 다음과 같이 구현되어 있다.
pub trait Renderer: Sized + 'static {
type Shape: Shape<Error = Self::Error>;
type Animation: Animation<Error = Self::Error>;
type Error: error::Error + 'static;
...
fn clear(&self) -> Result<(), Self::Error>;
fn push(&mut self, drawable: Drawable<Self>) -> Result<(), Self::Error>;
/// Insert `drawable` immediately before `at` in the scene.
fn insert(&mut self, drawable: Drawable<Self>, at: Drawable<Self>) -> Result<(), Self::Error>;
fn draw(&mut self, clear_buffer: bool) -> Result<(), Self::Error>;
fn sync(&mut self) -> Result<(), Self::Error>;
fn update(&mut self) -> Result<(), Self::Error>;
fn load_font(font_name: &str, font_data: &[u8]) -> Result<(), Self::Error>;
fn unload_font(font_name: &str) -> Result<(), Self::Error>;
}
그리고 이 Renderer를 thorvg를 사용해서 구현하고 있다. dotlottie-rs/src/renderer/thovg.rs
impl Renderer for TvgRenderer {
type Animation = TvgAnimation;
type Shape = TvgShape;
type Error = TvgError;
...
fn draw(&mut self, clear_buffer: bool) -> Result<(), TvgError> {
if let Some(raw_canvas) = self.raw_canvas {
unsafe { tvg::tvg_canvas_draw(raw_canvas, clear_buffer).into_result() }
} else {
Err(TvgError::InvalidArgument)
}
}
fn sync(&mut self) -> Result<(), TvgError> {
if let Some(raw_canvas) = self.raw_canvas {
unsafe { tvg::tvg_canvas_sync(raw_canvas).into_result() }
} else {
Err(TvgError::InvalidArgument)
}
}
fn update(&mut self) -> Result<(), TvgError> {
if let Some(raw_canvas) = self.raw_canvas {
unsafe {
let res = tvg::tvg_canvas_update(raw_canvas);
res.into_result()
}
} else {
Err(TvgError::InvalidArgument)
}
}
}
그래서 다른 라이브러리를 렌더러로 사용하고 싶다면 Renderer 부분만 구현해서 붙이면 된다. 그래서 실제로 dotlottie는 렌더러가 멀 쓰는지 신경 안쓰고 기능 구현에만 집중하면 된다. thorvg 활용한 부분이 적어서 아쉬우니 렌더러 코드를 쓰는 쪽을 하나 보고 끝내려고 한다.
대충 렌더러를 사용하는 Player는 다음과 같다. dotlittie-rs/src/player.rs
impl Player {
#[cfg(feature = "tvg")]
pub fn new() -> Self {
Self::with_renderer(crate::TvgRenderer::new(0))
}
#[cfg(feature = "tvg")]
pub fn with_threads(threads: u32) -> Self {
Self::with_renderer(crate::TvgRenderer::new(threads))
}
#[cfg(feature = "tvg")]
pub fn load_font(name: &str, data: &[u8]) -> Result<()> {
use crate::renderer::Renderer;
crate::TvgRenderer::load_font(name, data).map_err(|_| Error::Unknown)
}
#[cfg(feature = "tvg")]
pub fn unload_font(name: &str) -> Result<()> {
use crate::renderer::Renderer;
crate::TvgRenderer::unload_font(name).map_err(|_| Error::Unknown)
}
...
}
후기
LottieFiles 행사에 몇번 가면서 dotLottie에 대해서 들었는데 실제로 코드를 분석하니 좀 재밌었다. dotLottie의 state machine이 좀 재밌어 보였는데 다음번에 그 부분을 한번 보고 싶다.
댓글
Discussion 원문