← 과제 목록

[과제] 3주차) ThorVG 활용 사례 조사 이승원

@nowgnuesLeee
  • #과제
목차

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 원문