← 블로그 목록

GIF saver 동작방식

@chae-dahee
목차

GIF saver는 Animation을 프레임별로 소프트웨어 렌더링한 뒤 하나의 애니메이션 GIF 파일로 인코딩한다. 일반 Paint 저장은 지원하지 않는다.

전체 처리 흐름

Saver::save(Animation*, "*.gif")은 확장자로 GifSaver를 선택하고 저장 작업을 TaskScheduler에 요청한다.

  • 애니메이션의 바운드로 출력 크기를 정하고 ABGR8888 버퍼를 할당한다.
  • SwCanvas에 배경과 애니메이션의 Picture를 추가한다.
  • FPS 간격으로 프레임을 이동하며 Canvas에 렌더링한다.
    • FPS를 지정하지 않으면 totalFrame / duration으로 계산한다.
    • 최대 FPS 60
    • 프레임 지연 시간을 1/100초 단위 정수로 기록하므로 실제 재생 속도와 요청한 FPS가 정확히 일치하지 않을 수 있다.
  • 렌더링한 픽셀을 GIF encoder에 전달한다.
    • 각 프레임을 최대 256색의 로컬 팔레트로 변환하고 LZW 방식으로 압축한다.
    • 팔레트 인덱스 0은 투명색으로 예약하며 디더링은 적용하지 않는다.
    • 배경이 없으면 알파 값이 127 미만인 픽셀을 투명하게 처리한다.
    • 배경이 있으면 이전 프레임과 같은 픽셀을 투명 인덱스로 기록한다.
  • GIF를 무한 반복으로 설정하고 파일 기록을 마친다.
  • Saver::sync()에서 작업 완료를 기다리고 자원을 정리한다.

프레임 렌더링과 encoder 전달은 GifSaver::run()의 다음 루프에서 수행한다.

for (auto p = 0.0f; p < duration; p += delay) {
    auto frameNo = animation->totalFrame() * (p / duration);
    animation->frame(frameNo);
    canvas->update();
    if (canvas->draw(true) == tvg::Result::Success) {
        canvas->sync();
    }
    if (!gifWriteFrame(&writer, reinterpret_cast<uint8_t*>(buffer), w, h, uint32_t(delay * 100.0f), transparent)) {
        TVGERR("GIF_SAVER", "Failed gif encoding");
        break;
    }
}

GIF saver는 Animation만 지원하며 일반 Paint 저장, 반복 횟수 설정과 대문자 .GIF 확장자를 지원하지 않는다.

quality 인자 사용하지 않는다.

PNG saver를 구현한다면?

문제

core에는 PNG saver가 없어 Saver::save(Paint*, ...)로 정적 이미지를 저장할 수 없다. 현재 loader용 LodePNG는 디코딩 기능만 포함하므로 PNG 인코딩 경로도 없다.

해결 방향

LodePNG를 공용 PNG codec으로 구성해 loader와 saver가 함께 사용한다.

  • common에 PNG codec을 두고 loader용 디코드와 saver용 인코드를 빌드 옵션으로 구분한다.
  • PngSaver를 SaveModule과 Task 기반으로 구현한다.
    • Paint의 바운드로 출력 크기를 정하고 빈 영역을 검사한다.
    • 배경과 Paint를 SwCanvas에 한 번 렌더링한다.
    • RGBA 버퍼를 PNG codec으로 인코딩한다.
  • Meson의 saver 옵션과 Saver의 .png 분기를 추가한다.
  • 첫 구현은 8비트 RGBA와 알파 보존만 지원하며 quality는 사용하지 않는다.

#3874 에서 구현되었다.

댓글

Discussion 원문