← 블로그 목록

[Study Note] 리소스 로드 : Picture::load()에서 SVG Loader

@jeongsunyong
  • #Study
목차

[Study Note] 리소스 로드 : Picture::load()에서 SVG Loader

0. Picture

ThorVG에서 화면에 그릴 수 있는 그래픽 요소의 공통 기반은 Paint다. (복습)

Paint
ㄴShape    직접 만든 path, 사각형, 원 등의 vector 도형
ㄴ Picture  파일이나 메모리에서 불러온 이미지 리소스
ㄴ Text     글자
ㄴ Scene    여러 Paint를 묶는 그룹

Shape은 사용자가 직접 geometry와 색을 구성할 때 사용한다.

auto shape = tvg::Shape::gen();
shape->appendRect(0, 0, 100, 100);
shape->fill(255, 0, 0);

Picture는 이미 만들어진 외부 리소스를 ThorVG로 가져올 때 사용한다.

auto picture = tvg::Picture::gen();
picture->load("image.svg");

이 때, Picture가 SVG 파일 자체인 것은 아니고. SVG, PNG, JPEG, Lottie 같은 여러 포맷의 리소스를 받아 ThorVG가 그릴 수 있도록 연결하는 Paint 객체다.

결국, 저번에 살짝 봤던 Paint는 Shape에만 해당하는 것이 아니고, ShapePicture 모두 Paint이므로 최종적으로 Canvas::add()에 전달할 수 있다.

Picture::gen(), Picture Public API

1. 리소스 로딩의 Public API는 Picture

ThorVG에서 외부 이미지 리소스를 불러올 때는 Picture를 사용한다. 3주차 과제 조사 차 타이젠 플랫폼에서의 ThorVG 사용 확인 시에도,

ImageVisual
 ㄴ SvgLoader / VectorImageRenderer
    ㄴ ThorVG Picture

위와 같이 SVG, Lottie 같은 리소스들을 쓸 때, 복잡한 벡터 콘텐츠를 픽셀 텍스처로 만드는데 사용하고있었고, 이 때 Picutre::load 를 사용했다.

auto picture = tvg::Picture::gen();
picture->load("image.svg");

참고로 파일이 아니라 메모리에 있는 데이터를 직접 전달할 수도 있다.

picture->load(data, size, "svg");

SVG와 Lottie 같은 vector 리소스뿐 아니라 PNG, JPEG, WebP 같은 bitmap 리소스도 같은 Public API로 불러올 수 있다. 이 때 Public Picture::load()이 입력을 직접 해석하지 않고 Shape때와 같이 내부 구현인 PictureImpl::load()로 요청을 넘긴다는 점.

사용자 코드
  → Picture::load()
  → PictureImpl::load()

따라서 loader 동작을 추적할 때는 tvgPicture.cpp의 Public API를 확인한 뒤 tvgPicture.hPictureImpl를 확인. Picture::load(filename), Picture::load(data, ...)

사용자가 Picture::load()에 SVG 리소스를 전달하면, ThorVG 내부에서는 어떤 과정을 거쳐 SVG loader와 parser에 도달할지.

image

2. LoaderMgr : 입력에 맞는 loader를 선택한다

PictureImpl은 SVG loader를 직접 생성하지 않는다. 입력 데이터와 부가 정보를 LoaderMgr에 전달한다.

이 때 LoaderMgr는 입력 형태에 따라 적절한 loader를 찾는다.

  • 파일 경로를 받았다면 .svg, .png, .jpg 같은 확장자를 확인한다.
  • 메모리 데이터를 받았다면 "svg", "png" 같은 mimeType을 우선 확인한다.
  • 타입을 확실히 판단할 수 없다면 지원하는 loader를 차례로 시도한다.

SVG 입력이라면 최종적으로 SvgLoader가 선택된다.

"image.svg"                 data + mimeType "svg"
  → 확장자 "svg"              → FileType::Svg
  → FileType::Svg             → SvgLoader
  → SvgLoader

3. 공통 Loader와 SVG Loader의 역할

ThorVG의 각 loader는 공통 Loader 인터페이스를 기반으로 동작한다. 전체 흐름은 대충 아래 3단계가 큰 흐름이다.

open() : 이 loader가 입력을 처리할 수 있는지 확인하고 데이터를 준비
read() : 실제 parsing 또는 decoding 작업 시작
paint() / bitmap() : 처리한 결과를 Picture에 전달

이미지 loader는 처리 결과에 따라 크게 두 종류로 볼 수 있는데,

Vector Loader
  → SVG, Lottie
  → Paint 또는 Scene 생성

Bitmap Loader
  → PNG, JPEG, WebP, Raw
  → RenderSurface 생성

loader는 원본 리소스를 읽고 해석하는 객체, vector는 SVG처럼 Paint와 Scene으로 변환된 결과, (pixel로 바로 변환 x) bitmap은 PNG처럼 pixel surface로 변환된 결과 이렇게 보면 될 것 같다.

PictureImpl은 loader와 변환 결과를 관리한다. 코드: PictureImpl의 loader/vector/bitmap 상태, 메모리 입력을 LoaderMgr로 넘기는 지점

4. SVG Loader는 헤더 확인과 전체 파싱을 나누어 처리한다

image
  • open : SvgLoader가 선택되면 먼저 open()이 호출된다.
  • header : header()는 입력에서 <svg> 태그를 찾고 width, height, viewBox 같은 기본 정보를 확인한다.
  • read : 그다음 read()가 전체 SVG 파싱 작업을 요청한다.
SvgLoader::read()
  → TaskScheduler::request()
  → SvgLoader::run()

open()에서 전체 SVG를 모두 해석하는 것은 아니다.

  • open()header()는 입력이 SVG인지 확인하고 기본 정보를 얻는다.
  • read()run()은 전체 문서를 파싱하고 실제 그래픽 객체를 준비한다.

SVG 전체 파싱은 task를 통해 진행되므로 loader 코드를 볼 때 작업 완료를 기다리는 done() 또는 sync() 지점도 함께 확인할 필요가 있다.

SvgLoader.h, 메모리 입력 open(), 파일 입력 open() SvgLoader::header() SvgLoader::read(), SvgLoader::run()

5. SVG 문자열에서 ThorVG Scene까지

image

간략하게 말하면 문자열의 문법을 나누고 의미를 해석한 뒤 렌더링 가능한 Paint 그래프로 변환한다. 이 과정은 역할에 따라 세 부분으로 나누어서 보면 될 것 같다.

문자열을 나누는 parser

tvgXmlParser.cpp는 XML 태그, CSS block, key:value 형태의 속성 문자열을 분리하고 callback을 호출한다.

xmlParse()
  → XML event 생성

xmlParseCSSAttribute()
  → CSS selector와 선언 block 분리

xmlParseW3CAttribute()
  → key:value 선언 분리

xmlParse(), xmlParseCSSAttribute(), xmlParseW3CAttribute()

SVG 의미를 해석하는 loader

tvgSvgLoader.cpp는 parser가 전달한 태그와 속성이 SVG에서 무엇을 의미하는지 해석한다.

<rect>          → 사각형 node
<path>          → path node
<style>         → CSS style 정보
@font-face      → font 정보

파싱하는 세부 로직은 로더를 학습할 때 따로 더 봐야할 것 같고 일단 전체 구조 위주로..

살짝 상세

@font-face {
  font-family: Ext;
  src: url('font.svg')
}

위와 같은 svg가 있을 때, ThorVG에서는 바로 svg파일을 여는게 아니고, 아래와 같이

"font-family: Ext"
→ key: font-family
→ value: Ext

"src: url('font.svg')"
→ key: src
→ value: url('font.svg')

나누는 것. 이렇게 분리된 keyvalue는 callback을 통해 SVG loader로 전달되고 SVG loader는 font-family를 폰트 이름으로, src를 폰트 리소스 정보로 해석해 데이터로 저장한다. 이렇게 추출한 폰트 정보는 이후 SVG의 Text Paint를 구성할 때 활용된다. ( 데이터가 Text Paint에 그대로 들어가는 것은 아니고, SVG에 포함된 폰트를 등록한 뒤 Text Paint가 그 폰트를 사용할 수 있도록 연결하는 중간 정보로 사용된다. )

ThorVG 그래픽 객체를 만드는 builder

파싱과 후처리가 끝나면 svgSceneBuild()가 SVG 문서 모델을 ThorVG의 Scene, Shape, Text, Picture 같은 Paint 객체로 변환한다. 만들어진 root SceneSvgLoader::paint()를 통해 PictureImpl로 전달되고, 이후 renderer가 이 vector scene을 처리.

정리

이번에는 loader 내부 구현을 모두 따라가기보다 Public API에서 SVG parser까지 이어지는 주요 경계를 확인.

  • Picture : 사용자에게 리소스 로딩 API 제공
  • PictureImp : loader와 vector/bitmap 결과 관리
  • LoaderMgr : 확장자 또는 mimeType에 맞는 loader 선택
  • SvgLoader : SVG 입력 확인, 파싱 작업 수행
  • XML/CSS Parser : 문자열을 element와 attribute 단위로 분리
  • SVG Builder : SVG 문서 모델을 ThorVG Scene으로 변환

이 큰 흐름을 기준으로 보면 loader 관련 코드를 확인할 때 다음 단계로 범위를 나눌 수 있다.

다음에는 svgSceneBuild()에서 SVG node 하나가 실제 ThorVG ShapeText로 변환되는 과정을 따라가 보면 좋을 것 같다.

댓글

Discussion 원문