EDITORCLASSBLOGLOGINimg default descriptionimg default descriptionimg default description표현한다는 것의무한한 가능성,새로운 형태로
담아내다.
img default descriptionimg default descriptionimg default descriptionimg default descriptionimg default descriptionimg default descriptionimg default description새로운 형태의 콘텐츠Opkle은 코드 없이 웹을 마음껏 만들고, 누구나 자기 화면을 그릴 수 있도록 에디터를 만들고, 그 결과물을 어디서나 즐길 수 있게 돕는 팀입니다.텍스트와 화면과의 조화를 통해, 웹을 짓는다는 것이 그저 단순한 코딩이 아닌, 상상을 펼치고 감각을 깨우는 과정이 될 수 있도록 좋은 도구를 만들어 냅니다.
옵클 에디터 개발기: Valkey 큐 서버의 기본 구조dev7옵클 에디터 개발기: Worker loop와 AI 결과 검증dev8옵클 에디터 개발기: 미리보기가 그대로 코드로dev9옵클 에디터 개발기: 그래픽 에디터도 결국 직접 만들기로 했다dev10옵클 에디터 개발기: Rust 공장을 그래픽 엔진으로 다듬다dev117891011...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가 쌓이는지 봐야 했습니다.그걸 보고 나니 제 쪽 방향도 더 분명해졌습니다. 그래픽 에디터는 독립적인 문서 모델을 가져야 하지만, 출판 문서와 만나는 경계에서는 결과의 의미를 잃지 않아야 했습니다. 내부 레이어를 그대로 본문에 흘려보내는 것도 맞지 않고, 완성된 픽셀만 남겨 원본 편집 가능성을 지우는 것도 맞지 않습니다. 편집 원본과 출판 자산을 분리하고 둘의 관계만 안정적으로 유지하는 것이 일반적인 저작 도구에서 필요한 경계였습니다.새로운 그래픽 벡터 에디터벡터 그래픽 에디터에서 먼저 잡아야 하는 것은 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로 변환할 수 있어야 합니다.상호작용 계층도 같은 원칙으로 정리했습니다. 버튼, 단축키, 메뉴와 자동화 도구가 각자 오브젝트를 직접 고치기 시작하면 undo와 검증 규칙이 금방 갈라집니다. 입력 방식과 관계없이 선택, 변환, 서식, 자산 교체를 명시적인 명령으로 정규화하고, 실제 상태 변경은 같은 실행 경계를 통과하게 해야 합니다. 입력은 여러 형태일 수 있지만 문서가 받아들이는 연산은 유한하고 검증 가능해야 한다는 command pattern의 전형적인 적용이었습니다.WebAssembly가 필요한 지점그래픽 에디터를 제대로 만들려면 모든 연산을 같은 실행 계층에 둘 수는 없었습니다. 선택과 패널 갱신처럼 DOM과 가까운 작업은 브라우저 언어가 잘 처리하지만, 수백만 픽셀을 순회하거나 압축된 바이너리를 해석하는 작업은 계산량과 메모리 접근 패턴이 다릅니다. 한 프레임 안에서 끝나야 하는 상호작용과 수십 밀리초 이상 걸릴 수 있는 계산을 같은 event loop에 올려두면 입력 지연과 화면 정지가 곧바로 나타납니다.그래서 WebAssembly는 단순히 빠른 언어를 쓰기 위한 선택이 아니라 계산 영역을 격리하는 수단이 되었습니다. 중요한 것은 언어의 이름보다 경계의 크기였습니다. 작은 함수마다 런타임 경계를 오가면 호출 비용과 메모리 복사가 실제 계산보다 커질 수 있습니다. 반대로 충분히 큰 픽셀 블록이나 하나의 완결된 파일 작업을 넘기면 고정된 경계 비용을 긴 계산 구간에 분산할 수 있습니다. Amdahl의 법칙에서 병렬화되지 않는 구간이 전체 가속의 상한을 정하듯, 브라우저와 계산 모듈 사이의 직렬화 구간도 체감 성능의 상한을 정합니다.Rust는 이 계층에서 메모리 소유권과 실패 경계를 분명하게 만드는 데 잘 맞았습니다. 픽셀과 파일은 결국 연속된 바이트이므로 길이, 정렬, 수명과 최대 크기를 먼저 확정해야 합니다. 안전한 타입 체계는 잘못된 참조와 수명 문제를 줄여주지만, 외부에서 들어온 바이트가 유효한 이미지인지까지 자동으로 보장하지는 않습니다. 따라서 언어의 안전성과 데이터 검증은 별개의 책임으로 두었습니다.브라우저로 전달되는 계산 모듈은 실행 속도만 빠르면 끝나지 않습니다. 내려받는 바이너리의 크기, 초기 컴파일 시간, 최초 호출 지연과 메모리 증가량이 모두 사용자 경험에 포함됩니다. 사용 빈도가 낮은 기능을 초기 부팅에 전부 포함하지 않고, 공통 계산 경로는 작게 유지하며, 큰 작업은 필요해진 시점에 준비하는 식으로 배치했습니다. 이 단계에서 성능은 단일 함수의 벤치마크가 아니라 전송, 초기화, 계산, 복사를 합친 전체 지연으로 판단하게 되었습니다.Rust core 공장이 Rust core를 저는 일종의 공장처럼 보기 시작했습니다. 프론트는 편집 의도와 문서 상태를 관리하고 계산 계층에는 완결된 작업 단위를 전달합니다. 계산 결과도 내부 객체를 그대로 노출하기보다 이미지, 벡터, 폰트처럼 경계가 분명한 데이터로 돌아옵니다. UI의 생명주기와 수치 연산의 생명주기를 분리하니 한쪽의 구조 변경이 다른 쪽 전체를 흔들지 않았습니다.가장 먼저 정리한 것은 포맷 변환의 의미였습니다. 래스터 이미지는 유한한 격자 위의 표본이고 벡터 이미지는 도형과 곡선을 기술한 명령의 집합입니다. 둘 사이의 변환은 단순한 확장자 변경이 아닙니다. 벡터를 래스터로 바꾸려면 연속 도형을 해상도와 표본 위치에 맞춰 적분해야 하고, 래스터를 벡터로 바꾸려면 이산 픽셀에서 경계와 곡률을 추정해야 합니다. 전자는 정보의 표본화이고 후자는 손실을 전제로 한 모델 추정입니다.알파 채널도 포맷 사이의 중요한 의미 차이였습니다. 투명도를 지원하지 않는 출력에 반투명 픽셀을 보낼 때는 어떤 배경과 합성할지 먼저 결정해야 합니다. 색 공간과 감마를 무시한 채 채널 값을 단순 평균하면 가장자리 밝기가 달라질 수 있으므로, 가능한 경우 선형광 공간에서 합성한 뒤 표시 공간으로 돌려보내는 편이 정확합니다. 파일이 열리는 것과 시각적으로 같은 결과가 나오는 것은 다른 검증 문제였습니다.필터 계층에서는 픽셀 배열을 함수의 정의역으로 보았습니다. 점별 색 변환은 각 픽셀을 독립적으로 계산할 수 있지만, 블러와 샤픈은 주변 이웃을 읽고, 기하 변환은 다른 위치의 표본을 다시 가져옵니다. 연산의 종류에 따라 메모리 순회 방향과 임시 버퍼 수, 경계 조건이 달라집니다. 이 차이를 먼저 분류한 것이 이후 도구를 늘릴 때도 일관된 성능 모델을 유지하는 기반이 되었습니다.이미지 처리 파이프라인이미지 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는 마지막 방어선입니다.노이즈도 같은 맥락입니다. 필름 그레인 같은 질감은 이미지 작업에서 자주 필요하지만, 균등분포의 난수를 채널에 그대로 더하는 것만으로는 자연스러운 결과가 나오지 않습니다. 밝기 축에 같은 진폭을 더할지, 색 채널을 서로 독립적으로 흔들지에 따라 질감이 달라지고, 공간적으로 상관된 noise는 흰색 잡음과 다른 주파수 분포를 가집니다. 재현 가능한 결과가 필요할 때는 확률 과정의 초기 상태를 고정하되, 구체적인 생성 방식은 효과의 통계적 성질과 계산 비용을 기준으로 선택해야 했습니다.Asset factory그래픽 에디터는 이미지 하나만 처리하는 도구가 아닙니다. 하나의 프로젝트에는 벡터와 래스터, 폰트, 문서 상태와 미리보기가 함께 존재합니다. 그래서 저장 형식은 단순한 압축 파일이 아니라 객체 그래프를 다시 세울 수 있는 컨테이너여야 했습니다. 압축은 반복되는 바이트를 줄이는 문제이고, 컨테이너는 파일의 이름과 종류, 버전과 관계를 보존하는 문제입니다. 두 책임을 분리해 생각해야 포맷을 바꾸더라도 문서 의미가 흔들리지 않습니다.바이너리 컨테이너에서 먼저 필요한 것은 자기 기술성과 경계 검증입니다. 헤더는 어떤 규칙으로 읽어야 하는지 알려주고, 버전은 앞으로의 해석 방법을 결정하며, 각 구간의 길이는 다음 데이터의 시작점을 정합니다. 여기서 길이를 신뢰하고 메모리를 할당하면 손상된 입력 하나가 과도한 메모리 사용으로 이어질 수 있습니다. 전체 크기, 항목 수, 개별 항목 크기와 누적 offset을 각각 검증하고, 읽기가 끝난 뒤 설명되지 않은 바이트가 남는지도 확인해야 합니다.압축률만으로 저장 방식을 평가하지도 않았습니다. 사전 크기와 탐색 가능성, 작은 변경 때 다시 써야 하는 범위, 손상된 블록을 발견하는 위치, 압축 해제에 필요한 최대 메모리까지 함께 봐야 합니다. 여러 자산의 중복이 큰 프로젝트와 이미 압축된 이미지가 대부분인 프로젝트는 같은 전략에서 전혀 다른 결과를 냅니다. 데이터의 엔트로피와 접근 패턴을 먼저 보고 컨테이너와 압축 계층을 고르는 것이 맞았습니다.폰트 역시 단순한 파일이 아니라 여러 표가 서로 참조하는 바이너리 데이터베이스에 가깝습니다. 글리프 윤곽, 문자 매핑, advance width, kerning과 이름 정보가 함께 움직입니다. 용량을 줄이려면 쓰지 않는 글리프만 덜어내는 것으로 끝나지 않고, 복합 글리프가 참조하는 구성 요소와 layout table의 관계도 닫혀 있어야 합니다. 변환 뒤에는 다시 열리는지만 볼 것이 아니라 같은 문자열의 glyph 선택과 metric이 유지되는지까지 확인해야 했습니다.이런 자산 공학이 있어야 그래픽 에디터가 실제 출판 도구가 됩니다. 화면에서 보이는 한 장의 이미지는 원본 문서, 사용한 폰트, 색 공간, 출력 규격과 복원 가능한 관계를 가집니다. 저장과 내보내기를 부가 기능이 아니라 편집 모델의 마지막 단계로 포함시키면서 그래픽 작업이 프로젝트의 수명 전체를 견딜 수 있게 되었습니다.첫 번째 Rust core10편에서의 핵심은 아직 완성된 그래픽 에디터가 아닙니다. 그래픽 에디터를 제대로 만들기 위한 계산 기반을 세운 것입니다. 기존 벡터 편집기의 object model과 canvas architecture를 살펴보고, 실제 구현에서는 상호작용과 수치 계산, 편집 원본과 출판 자산, 컨테이너와 압축의 책임을 분리했습니다. 이 시점부터 옵클 에디터는 EPUB 문서 편집기이면서 동시에 그래픽 제작 환경으로 확장될 준비를 하게 되었습니다.프론트는 UI와 문서 상태, 명령과 사용자 피드백을 맡고 계산 계층은 픽셀과 곡선, 파일과 폰트처럼 연속 메모리와 수치 안정성이 중요한 작업을 맡습니다. 어느 언어가 더 우월해서가 아니라 각 계층이 가진 시간 제약과 데이터 구조가 다르기 때문입니다. 역할을 이렇게 나누면 기능이 늘어나도 event loop의 반응성과 계산 결과의 재현성을 함께 유지할 수 있습니다.이 단계도 안정적으로 정리되었습니다. 실행 경계의 비용, 메모리 소유권, 래스터와 벡터의 표본화 관계, 알파 합성, 픽셀 버퍼 검증, 컨테이너 무결성과 폰트 테이블의 참조 관계까지 그래픽 시스템의 바닥을 공학적으로 확정했습니다. 반복 테스트에서도 다양한 크기의 이미지, 손상된 입력, 투명 경계, 장시간 계산, 저장과 복원 경로가 모두 안정적으로 통과했습니다.이제 다음 단계에서는 이 Rust 공장을 실제 그래픽 에디터의 도구들로 연결해야 했습니다. 도형을 그리고, 이미지를 넣고, 필터를 적용하고, SVG와 래스터를 오가고, AI command가 그 작업을 호출할 수 있어야 합니다. 10편은 그 출발점이었습니다. 그래픽 에디터를 직접 만들기로 결정했고, 그 결정을 받쳐줄 Rust/WASM 기반 core를 성공적으로 세운 단계였습니다.이전글목록으로다음글저작권 고시Copyright Notice본 웹사이트의 모든 디자인 결과물 및 영상에 대한 저작권은 Abstract Cloud에 있으며, 저작권법 및 관련 법령에 의해 보호받습니다. 웹, 영상, 본문, 표지, 내지 디자인을 포함한 모든 콘텐츠는 저작권자의 자산으로, 사전 동의 없이 무단 복제, 배포, 2차 저작물 제작, 온라인 공유 등을 금지합니다. 이를 위반할 시, 저작권법에 따라 민형사상 책임을 질 수 있습니다. 정당한 구매와 저작권 보호는 창작자의 권리를 지키며, 더 나은 작품으로 보답할 힘이 됩니다.저작권자: Abstract Cloud | 대표자: 배창규(uragen)
© Abstract Cloud. All Rights Reserved.
HOMEFAQ이용 약관개인정보 이용방침help@opkle.app
010-2747-3403
상호 :추상적 형상 디자인(Abstract cloud)  |  대표자 :배창규사업자등록번호 :249-74-00533통신판매업 신고번호 :2025-의정부송산-0634주소 :경기도 의정부시 부용로 49, 108동 402호웹의 모든 콘텐츠, 디자인, 소스 코드에 대한
저작권은 Opkle에게 있습니다.
img default description