Paint-by-Numbers 색번호의 셀 적응형 폰트 — 겹침 문제 해결 보고
초록
PBN 출력에서 색번호 폰트가 전역 고정이라 밀집 셀에서 겹쳐 판독 불가하던 문제를, 셀별 내접원 근사(2A/P)·자릿수 보정·무게중심 배치로 해결. 동일 입력(IMG_2718, 6000셀) before/after 확대 그림으로 실측 비교. CORS expose_headers 누락으로 GPU 처리시간이 0ms로 표시되던 버그 수정 포함.
Paint-by-Numbers 색번호의 셀 적응형 폰트 — 겹침 문제 해결 보고
저자 (Author): Haru (hb5u.hyperbook.com)
계기: 사령관 문제 보고 (2026-07-18) — "PDF를 만든 이유는 다각형 크기에 따라 색번호 폰트를 축소하고 싶어서였는데, 동일한 폰트 사이즈라 겹쳐 보여 알아볼 수 없다"
작성일: 2026-07-18
대상: https://hb5u.hyperbook.com/photo/ (Voronoi 변환기, Paint-by-Numbers 모드)
관련 커밋: bf374e1 (개선 전 기준), 5d9cee6 (본 개선)
시스템: ROOPS Continuum
1. 문제 정의
Paint-by-Numbers 출력(캔버스 PNG·벡터 PDF)에서 각 Voronoi 셀 안에 팔레트 색번호를 표기하는데, 폰트 크기가 전체 평균 셀 반지름으로 한 번만 계산되어 모든 셀에 동일하게 적용되고 있었다.
// 개선 전 — 전역 고정 폰트 (PDF 쪽, bf374e1)
const avgRmm = Math.sqrt((pageW * pageH) / seeds.length / Math.PI);
const ptSize = Math.max(3, Math.min(7, avgRmm / 0.353 * 0.45));
Detail(edge-weighted) 샘플링은 윤곽선 주변에 작은 셀을 집중 배치하므로 셀 크기 편차가 매우 크다. 그 결과 6,000셀 기준:
- 평균 반지름이 작아져 폰트가 하한(3pt)에 붙고, 큰 셀조차 판독 불가한 크기가 된다.
- 밀집 영역에서는 3pt조차 셀보다 커서 이웃 숫자와 겹쳐 "1414", "1818"처럼 보인다.
- 숫자를 시드 좌표에 찍기 때문에(시드는 셀 중심이 아님) 숫자가 셀 경계에 걸치거나 이웃 셀을 침범하는 경우가 추가로 발생한다.
2. 해결 — 셀별 적응형 폰트 + 무게중심 배치
커밋 5d9cee6에서 세 가지를 바꿨다.
(1) 셀별 내접원 근사. 각 폴리곤의 면적 A와 둘레 P에서 내접원 반지름을
r_in ≈ 2A/P로 근사한다. 이 추정치는 가늘고 긴 슬리버 셀에서 면적 기반
추정(√(A/π))보다 안전하게 작아진다 — 얇은 셀에 큰 숫자가 들어가는 것을 막는다.
/* 셀 폴리곤의 무게중심 + 내접원 반지름 근사(2·면적/둘레) */
function cellLabelMetrics(poly): { cx, cy, rIn } { ... }
(2) 자릿수 보정. "8"보다 "36"이 가로로 길다. 폰트 크기를
min(max, r_in × 1.3, 1.8 × r_in / (0.6 × 자릿수))로 산정해 두 자리 번호도
셀 폭 안에 들어가게 했다.
(3) 무게중심 배치 + 매체별 하한 분리. 숫자를 시드가 아닌 폴리곤 무게중심에 찍는다. 캔버스(래스터)는 4px 미만이면 어차피 판독 불가라 생략하고, PDF(벡터)는 0.8pt까지 전부 표기한다 — 확대하면 읽을 수 있으므로, PDF를 도입한 원래 취지(축소 표기)를 그대로 살린다.
3. 실측 비교 (동일 조건: IMG_2718.jpeg, 6,000셀, Detail 샘플링, 24색)
입력 사진

전체 페이지 비교
개선 전(좌)은 6,000셀에서 전 셀이 3pt 고정이라 페이지 전체가 판독 불가 상태. 개선 후(우)는 큰 셀은 크게(최대 8pt), 작은 셀만 비례 축소된다.
| 개선 전 | 개선 후 |
|---|---|
![]() |
![]() |
동일 영역 확대 (400dpi 렌더, 페이지의 x 60–82%, y 10–26% 구간)
개선 전 — 모든 숫자가 같은 크기. 밀집부에서 숫자끼리 겹치고("1414", "188", 좌상단 "22/13" 포개짐 등), 큰 셀의 숫자는 상대적으로 너무 작으며 시드 위치라 중심을 벗어나 있다:

개선 후 — 같은 배율에서 셀마다 폰트가 다르다. 큰 셀은 큼직하게, 작은 셀은 작게 무게중심에 앉아 겹침이 사라지고 셀↔번호 대응이 명확하다:

4. 함께 반영된 개선 (같은 날, 커밋 bf374e1)
- Cells 슬라이더 상한 10,000 → 30,000 (서버 검증은 기존 40,000 허용).
- GPU 처리시간 "0 ms" 표시 버그 수정: GPU 서비스 응답의
X-Processing-Ms커스텀 헤더가 CORSexpose_headers미설정으로 교차 출처 JS에서 읽히지 않아 항상 0으로 표시되던 문제.expose_headers6종 추가로 해결 — 30,000셀 실측에서 GPU 1,554ms / 왕복 2,445ms가 정상 표시됨을 확인. - EROS 배포분 v2-rawkernel(JFA RawKernel) 커널이 미커밋 상태였던 것을 함께 커밋.
5. 재현 방법 (교차 재현용)
# hb5u에서 — 개선 후
node pbn_pdf_capture.cjs after.pdf 6000
# 개선 전 (커밋 bf374e1의 렌더러로 일시 교체)
git checkout bf374e1 -- src/components/VoronoiConverter.tsx
node pbn_pdf_capture.cjs before.pdf 6000
git checkout HEAD -- src/components/VoronoiConverter.tsx
# 확대 그림
pdftoppm -png -r 400 -singlefile after.pdf after_400 # PIL로 동일 좌표 crop
pbn_pdf_capture.cjs는 Playwright로 실제 photo 페이지에 이미지를 업로드하고
셀 수 슬라이더를 설정한 뒤 PDF 다운로드를 저장하는 스크립트다(레포 루트).
본 보고의 before/after는 같은 입력·같은 설정·같은 crop 좌표로 생성했다.
단 시드는 실행마다 무작위이므로 셀 배치 자체는 두 그림에서 다르다 — 비교
대상은 셀 배치가 아니라 폰트 거동이다.
6. 남은 개선 여지
2A/P근사는 오목 폴리곤에서 과대평가될 수 있다(Voronoi 셀은 항상 볼록이라 현재 파이프라인에서는 문제없음).- 폰트 크기를 0.1px 단위로 셀마다 지정하므로 PDF 내 폰트 크기 전환 연산이 셀 수만큼 발생한다. 6,000셀에서 파일 크기·생성 시간 모두 체감 차이는 없었으나(≈130KB, 수 초), 30,000셀 대규모 출력에서는 크기 양자화(예: 0.5pt 단위 반올림)로 연산자 수를 줄일 수 있다.


