Opkle(옵클) - 창작자를 위한 앱과 시스템
옵클(Opkle)은 창작자를 위한 다양한 앱과 시스템을 제공하는 개발사입니다. 전자책 에디터 앱 'Opkle editor'를 출시했고, 관련 전자책 클래스를 제공하고 있습니다.
EDITOR
CLASS
BLOG
LOGIN
표현한다는 것의
무한한 가능성,
새로운 형태로
담아내다.
새로운 형태의 콘텐츠
Opkle은 코드 없이 웹을 마음껏 만들고, 누구나 자기 화면을 그릴 수 있도록 에디터를 만들고, 그 결과물을 어디서나 즐길 수 있게 돕는 팀입니다.
텍스트와 화면과의 조화를 통해, 웹을 짓는다는 것이 그저 단순한 코딩이 아닌, 상상을 펼치고 감각을 깨우는 과정이 될 수 있도록 좋은 도구를 만들어 냅니다.
옵클 에디터 개발기: Valkey 큐 서버의 기본 구조
dev
7
옵클 에디터 개발기: Worker loop와 AI 결과 검증
dev
8
옵클 에디터 개발기: 미리보기가 그대로 코드로
dev
9
옵클 에디터 개발기: 그래픽 에디터도 결국 직접 만들기로 했다
dev
10
옵클 에디터 개발기: Rust 공장을 그래픽 엔진으로 다듬다
dev
11
7
8
9
10
11
...
10
옵클 에디터 개발기: 그래픽 에디터도 결국 직접 만들기로 했다
미리보기에서 고친 결과가 실제 EPUB 코드로 이어지고, AI까지 그 흐름 안에서 HTML과 CSS를 다루게 되자 다음 빈자리가 꽤 선명하게 보였습니다. EPUB은 텍스트만으로 끝나는 포맷이 아니었습니다. 이미지, 표, 레이아웃, 아이콘, 도형, 배경, 장식 요소가 계속 들어옵니다. 앞에서 미디어쿼리와 CSS animation, 이미지 시퀀스 영상까지 잡아두고 나니, 결국 책 안의 그래픽을 다루는 전용 에디터가 필요하다는 쪽으로 생각이 자연스럽게 기울었습니다.
처음에는 기존 그래픽 도구를 붙이는 방법도 생각할 수 있었습니다. 그런데 옵클 에디터에서 필요한 그래픽 도구는 일반 디자인 툴과 목적이 조금 달랐습니다. 웹에 올릴 이미지를 따로 만드는 도구가 아니라, EPUB 안에서 바로 쓰일 수 있는 그래픽을 만들고 편집해야 했습니다. 벡터 이미지, 래스터 이미지, SVG, JPEG, PNG, WebP, 폰트, 압축된 프로젝트 파일, 미리보기와 export pipeline이 한 흐름 안에서 움직여야 했습니다. 외부 툴을 열었다 닫는 방식으로는 이 workflow가 매끄럽게 이어지지 않았습니다.
그래서 Figma 같은 그래픽 에디터도 결국 직접 만들어야겠다고 생각했습니다. 더 정확히는 옵클 에디터 안에 붙어 있는 그래픽 에디터였습니다. EPUB 문서 안에서 이미지를 만들고, 벡터를 그리고, 이미지 필터를 적용하고, 필요하면 SVG로 바꾸고, 다시 EPUB 자산으로 저장할 수 있어야 했습니다. 사용자는 책을 만들다가 이미지가 필요해졌을 때 다른 앱으로 빠져나가지 않고, 같은 작업 흐름 안에서 그래픽을 만들 수 있어야 했습니다.
Penpot의 클로저
그래픽 에디터를 만들기로 마음먹고 나서 먼저 들여다본 것은 Penpot이었습니다. Penpot은 브라우저 기반 그래픽 에디터이고, Figma와 비슷한 방향의 구조를 가지고 있습니다. 저는 소스 코드를 받아서 ClojureScript 쪽을 파보기 시작했습니다. 코드를 그대로 가져오려는 목적은 아니었습니다. 이런 종류의 벡터 그래픽 에디터가 어떤 상태 구조와 렌더링 감각을 갖고 있는지 보고 싶었습니다.
벡터 그래픽 에디터는 일반 문서 편집기와 감각이 꽤 다릅니다. 텍스트 에디터는 block, inline, selection, undo history가 중심이지만, 그래픽 에디터는 object tree, transform, bounding box, path data, fill, stroke, layer order, group, frame, canvas coordinate, hit test가 중심입니다. 사용자가 도형 하나를 선택하는 순간에도 좌표계, 변환 행렬, 화면 scale, selection box, resize handle, snapping, z-index가 같이 움직입니다.
ClojureScript 코드를 보면서 확인하고 싶었던 것도 이 지점이었습니다. 그래픽 에디터는 UI 컴포넌트를 예쁘게 만든다고 되는 도구가 아닙니다. object model이 먼저 있어야 합니다. 도형은 어떤 단위로 저장되는지, group은 자식 object를 어떻게 품는지, frame과 page는 어떻게 나뉘는지, selection은 어떤 id를 가리키는지, undo stack에는 어떤 patch가 쌓이는지 봐야 했습니다.
그걸 보고 나니 제 쪽 방향도 더 분명해졌습니다. 옵클의 그래픽 에디터는 독립적인 디자인 툴이면서 동시에 EPUB 에디터의 하위 시스템이어야 했습니다. pathVector 같은 벡터/이미지 에디터가 따로 존재하되, 만든 결과는 EPUB project asset으로 들어와야 합니다. editor document state, IndexedDB, export asset bundle, AI command pipeline과 연결될 수 있어야 했습니다.
새로운 그래픽 벡터 에디터
벡터 그래픽 에디터에서 먼저 잡아야 하는 것은 object model이었습니다. 사각형, 원, path, text, image, group, frame 같은 요소가 모두 같은 canvas 안에서 움직여야 합니다. 각 object는 id를 가져야 하고, position, size, rotation, opacity, fill, stroke, transform 정보를 가져야 합니다. 이 상태가 있어야 선택, 이동, 크기 조절, 정렬, 복제, 삭제, undo, redo가 자연스럽게 이어집니다.
문서 에디터에서 block을 다루던 감각을 그대로 가져올 수는 없었습니다. 문단은 위에서 아래로 흐르지만, 그래픽 object는 2D 평면 위에서 겹칩니다. 같은 좌표에 여러 object가 있을 수 있고, 사용자는 가장 위의 object를 선택할 수도 있고, group 안으로 들어가 nested object를 선택할 수도 있습니다. hit testing과 layer order가 editor UX의 중심이 됩니다.
또 하나 계속 신경 쓰인 것은 SVG와의 관계였습니다. EPUB 안에서 벡터 그래픽을 쓰려면 SVG는 매우 자연스러운 포맷입니다. 하지만 그래픽 에디터의 내부 상태를 SVG 문자열 하나로만 들고 가면 편집이 어려워집니다. 내부에서는 object tree로 관리하고, export나 preview 시점에 SVG로 serialize하는 쪽이 맞았습니다. 반대로 외부 SVG를 불러오면 그것을 object tree나 raster result로 변환할 수 있어야 합니다.
이 단계에서 이미 AI command pipeline과도 연결이 보였습니다. 사용자가 “이 도형을 파란색으로”, “선택한 이미지를 흐리게”, “이 아이콘을 SVG로 바꿔줘”라고 말하면 AI는 자연어를 그래픽 editor command로 바꿔야 합니다. 9편에서 selector가 HTML/CSS patch를 만들었다면, 그래픽 에디터에서는 vector command catalog가 같은 역할을 하게 됩니다. 자연어는 유연하게 받고, 실행은 editor command로 고정하는 방식입니다.
WebAssembly가 필요한 지점
그래픽 에디터를 제대로 만들려면 브라우저 JavaScript만으로는 부족한 부분이 바로 보였습니다. 단순한 도형 조작은 TypeScript로 충분합니다. 하지만 이미지 필터, 블러, 노이즈, 포맷 변환, SVG rasterizing, raster image to SVG tracing, 폰트 변환, 압축 같은 작업은 계산량이 크고 바이트 단위 처리가 많습니다. 이런 작업을 메인 스레드 JS로만 처리하면 에디터가 쉽게 무거워집니다.
그래서 WebAssembly가 필요했습니다. 브라우저 안에서 돌아가면서도 네이티브에 가까운 계산 루프를 가져갈 수 있고, 픽셀 버퍼와 바이너리 데이터를 다루기 좋습니다. 특히 이미지 처리 쪽은 RGBA buffer를 직접 받아서 처리하고 다시 Vec
로 반환하는 구조가 잘 맞습니다. TypeScript는 UI와 상태 orchestration을 맡고, Rust/WASM은 무거운 그래픽 연산을 맡는 구조로 나누는 게 자연스러웠습니다.
Rust를 선택한 이유도 분명했습니다. 메모리 안전성과 성능을 같이 가져갈 수 있고, wasm-bindgen으로 JS와 연결하기 좋습니다. image, resvg, vtracer, zstd, brotli 같은 생태계도 바로 쓸 수 있습니다. 그래픽 에디터의 core는 이미지와 벡터와 폰트와 압축을 다뤄야 하므로, 이 부분은 Rust로 공장을 세워두는 것이 장기적으로 맞았습니다.
Cargo 설정도 처음부터 WASM library 기준으로 잡았습니다. crate type은 `cdylib`로 두고, wasm-bindgen을 중심에 놓았습니다. release profile은 크기를 줄이는 방향으로 맞췄습니다. `opt-level = "z"`, thin LTO, panic abort, strip 설정을 넣고, wasm-pack release에서는 `-Oz`를 사용했습니다. 그래픽 core는 브라우저로 내려가야 하므로, 성능만큼이나 wasm binary size도 중요했습니다.
Rust core 공장
이 Rust core를 저는 일종의 공장처럼 보기 시작했습니다. 프론트에서 필요한 그래픽 작업을 함수 단위로 던지면, Rust가 처리해서 다시 바이트나 JSON을 돌려주는 구조입니다. UI가 아니라 processing factory입니다. 사용자는 그래픽 에디터에서 버튼을 누르거나 AI 명령을 내리지만, 실제 무거운 작업은 Rust 함수가 맡습니다.
가장 먼저 필요한 것은 이미지 변환이었습니다. JPEG, PNG, WebP, SVG를 서로 오갈 수 있어야 했습니다. 래스터 이미지는 magic bytes로 포맷을 감지하고 DynamicImage로 디코딩합니다. SVG는 resvg로 rasterize하고, 래스터에서 SVG로 갈 때는 vtracer를 사용해 vector tracing을 합니다. JPEG는 알파 채널이 없으므로 흰 배경으로 합성한 뒤 인코딩합니다. 실제 편집기에서는 이런 작은 처리 하나하나가 파일 안정성으로 이어집니다.
SVG 처리는 특히 중요했습니다. 그래픽 에디터에서 SVG는 편집 가능한 벡터 자산이면서, 동시에 EPUB 안에 들어갈 수 있는 출력 포맷입니다. SVG를 래스터로 바꿔 preview하거나, 래스터 이미지를 tracing해서 SVG로 바꾸는 경로가 필요했습니다. 처음부터 완성된 path editor를 만들기 전에, 포맷을 오가며 asset을 처리할 수 있는 core가 먼저 필요했습니다.
이미지 필터 쪽도 Rust로 내려보냈습니다. RGBA Uint8Array를 받아서 RgbaImage로 바꾸고, 다시 flat bytes로 돌려주는 구조입니다. 노이즈, 그라데이션 노이즈, 컬러 커브 같은 작업은 픽셀 루프가 많습니다. 브라우저에서 프레임이 끊기지 않게 하려면 이런 연산은 Rust/WASM 쪽에서 안정적으로 처리하는 편이 좋습니다. 나중에는 그래픽 에디터의 filter panel이나 AI 이미지 보정 명령과도 연결될 수 있습니다.
이미지 처리 파이프라인
이미지 pipeline을 만들 때는 단순히 “변환된다”보다 “깨지지 않는다”가 더 중요했습니다. RGBA buffer를 받을 때 width, height, length를 반드시 검증해야 합니다. width × height × 4 계산은 u32로 하면 overflow 가능성이 있으므로 u64로 먼저 계산하고, usize 범위를 확인합니다. 픽셀 버퍼가 예상보다 짧으면 바로 에러를 반환합니다. 브라우저에서 넘어오는 바이너리 데이터는 항상 다시 확인해야 합니다.
블러도 같은 방식으로 잡았습니다. 가우시안 블러는 2D kernel을 그대로 돌리면 비용이 큽니다. 그래서 separable convolution으로 수평 1D pass와 수직 1D pass를 나눴습니다. 복잡도는 훨씬 낮아지고, raw buffer 직접 접근으로 get_pixel/put_pixel 비용을 줄일 수 있습니다. edge는 clamp 처리해서 검정 테두리나 접힘 artifact가 생기지 않게 했습니다.
sigma에도 상한을 두었습니다. 블러 강도는 사용자가 조절할 수 있는 값이지만, 무한히 크게 들어오면 kernel size가 폭발하고 overflow나 DoS 문제가 생길 수 있습니다. 그래서 sigma max를 두고, NaN이나 Infinity, 0 이하 값은 방어적으로 처리합니다. 그래픽 에디터의 UI slider에서 정상 값만 보낸다고 해도 core에서는 다시 막아야 합니다. Rust core는 마지막 방어선입니다.
노이즈도 같은 맥락입니다. 필름 그레인 같은 질감은 이미지 작업에서 자주 필요합니다. xorshift32로 간단한 PRNG를 두고, seed가 0이면 입력 픽셀 기반으로 자동 seed를 만듭니다. 사용자가 명시적으로 seed를 주면 재현 가능한 패턴을 만들 수 있습니다. 이런 작은 옵션들이 나중에 그래픽 에디터 안에서 하나의 효과가 됩니다.
Asset factory
그래픽 에디터는 이미지 하나만 처리하는 도구가 아닙니다. 프로젝트 안에는 SVG, PNG, JPEG, WebP, 폰트, JSON 상태, preview asset이 함께 들어갑니다. 그래서 압축 포맷도 필요했습니다. 일반 zip처럼 파일별로 따로 압축하는 방식보다, 전체 파일을 하나의 raw archive로 묶고 zstd로 한 번에 압축하는 방식이 더 맞다고 봤습니다. 파일 간 중복 패턴까지 압축될 수 있기 때문입니다.
그래서 OPKZ archive format을 잡았습니다. magic bytes, version, file count, path length, path, data length, data를 가진 단순한 raw layout을 만들고, 그 전체를 zstd로 압축합니다. 경로 길이와 파일 수, data length, 남는 바이트까지 모두 검증합니다. 손상된 archive가 조용히 통과하면 나중에 project restore에서 더 큰 문제가 되기 때문에, parsing 단계에서 명확히 막아야 합니다.
폰트도 그래픽/EPUB 쪽에서는 중요한 자산입니다. EPUB은 폰트 파일 크기가 직접 용량에 영향을 주고, 웹 기반 editor에서는 폰트를 preview에도 써야 합니다. 그래서 TTF/OTF에서 WOFF2로 바꾸는 pure Rust 경로를 만들었습니다. brotli를 사용하고, no-transform WOFF2를 만들어 다시 TTF로 복원할 수 있게 했습니다. 복잡한 외부 WOFF2까지 전부 흡수하는 것이 아니라, 제가 만든 파일은 확실히 되돌릴 수 있게 하는 쪽으로 잡았습니다.
이런 asset factory가 있어야 그래픽 에디터가 EPUB 에디터와 붙을 수 있습니다. 사용자가 만든 이미지와 벡터와 폰트가 프로젝트 상태 안에 들어가고, 압축되고, 다시 열리고, export될 수 있어야 합니다. 그래픽 에디터가 독립 앱으로만 끝나면 옵클 EPUB workflow와 분리됩니다. Rust core는 그 사이에서 asset을 처리하는 엔진이 됩니다.
첫 번째 Rust core
10편에서의 핵심은 아직 완성된 그래픽 에디터가 아닙니다. 그래픽 에디터를 제대로 만들기 위한 Rust core를 세운 것입니다. Penpot 코드를 보며 벡터 에디터의 object model과 canvas architecture를 감각적으로 잡고, 실제 구현에서는 브라우저가 하기 부담스러운 작업을 Rust/WASM으로 분리했습니다. 이 시점부터 옵클 에디터는 EPUB 문서 편집기이면서 동시에 그래픽 제작 환경으로 확장될 준비를 하게 되었습니다.
TypeScript 쪽은 여전히 UI와 document state를 맡습니다. 선택, panel, command dispatch, IndexedDB 저장, EPUB export와 연결되는 흐름은 프론트가 잡습니다. Rust core는 포맷 변환, SVG rasterizing, vector tracing, 이미지 필터, blur, noise, archive compression, font conversion 같은 계산과 바이너리 처리를 맡습니다. 역할을 이렇게 나누면 그래픽 에디터의 기능이 늘어나도 구조가 무너지지 않습니다.
이 단계도 안정적으로 정리되었습니다. WASM library 설정, release size 최적화, 이미지 디코딩/인코딩, SVG rasterize와 tracing, RGBA buffer 검증, separable gaussian blur, noise filter, OPKZ archive, zstd compression, WOFF2 변환까지 그래픽 에디터의 첫 Rust 공장이 만들어졌습니다. 반복 테스트에서도 이미지 변환, 픽셀 버퍼 검증, 블러/노이즈 처리, 압축/복원, 폰트 변환 경로가 안정적으로 통과했습니다.
이제 다음 단계에서는 이 Rust 공장을 실제 그래픽 에디터의 도구들로 연결해야 했습니다. 도형을 그리고, 이미지를 넣고, 필터를 적용하고, SVG와 래스터를 오가고, AI command가 그 작업을 호출할 수 있어야 합니다. 10편은 그 출발점이었습니다. 그래픽 에디터를 직접 만들기로 결정했고, 그 결정을 받쳐줄 Rust/WASM 기반 core를 성공적으로 세운 단계였습니다.
이전글
목록으로
다음글
저작권 고시
Copyright Notice
본 웹사이트의 모든 디자인 결과물 및 영상에 대한 저작권은 Abstract Cloud에 있으며, 저작권법 및 관련 법령에 의해 보호받습니다. 웹, 영상, 본문, 표지, 내지 디자인을 포함한 모든 콘텐츠는 저작권자의 자산으로, 사전 동의 없이 무단 복제, 배포, 2차 저작물 제작, 온라인 공유 등을 금지합니다. 이를 위반할 시, 저작권법에 따라 민형사상 책임을 질 수 있습니다. 정당한 구매와 저작권 보호는 창작자의 권리를 지키며, 더 나은 작품으로 보답할 힘이 됩니다.
저작권자: Abstract Cloud | 대표자: 배창규(uragen)
© Abstract Cloud. All Rights Reserved.
HOME
FAQ
이용 약관
개인정보 이용방침
help@opkle.app
010-2747-3403
상호 :
추상적 형상 디자인(Abstract cloud) |
대표자 :
배창규
사업자등록번호 :
249-74-00533
통신판매업 신고번호 :
2025-의정부송산-0634
주소 :
경기도 의정부시 부용로 49, 108동 402호
웹의 모든 콘텐츠, 디자인, 소스 코드에 대한
저작권은 Opkle에게 있습니다.