[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에만 해당하는 것이 아니고,
Shape과 Picture 모두 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.h의 PictureImpl를 확인.
Picture::load(filename), Picture::load(data, ...)
사용자가 Picture::load()에 SVG 리소스를 전달하면, ThorVG 내부에서는 어떤 과정을 거쳐 SVG loader와 parser에 도달할지.
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는 헤더 확인과 전체 파싱을 나누어 처리한다
- 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까지
간략하게 말하면 문자열의 문법을 나누고 의미를 해석한 뒤 렌더링 가능한 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')
나누는 것.
이렇게 분리된 key와 value는 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 Scene은 SvgLoader::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 Shape나 Text로 변환되는 과정을 따라가 보면 좋을 것 같다.
댓글
Discussion 원문