화면은 켰는데 글자가 안 된다

1편 에서 좌표 변환으로 픽셀을 제자리에 보내는 데까지 성공했다. 이제 글자를 찍을 차례였다. 그런데 한글을 출력하니 획이 잘리고 깨졌다.

이 전광판이 띄워야 하는 문구는 전부 한글이다. "안전제일", "더위 조심", "안전미흡 식별시 관리자에게 보고" 같은 것들. 글자가 안 되면 프로젝트 자체가 성립하지 않는다.

왜 깨지는가

LED 매트릭스에 한글을 출력했으나 획이 뭉개지고 글자가 잘려 나온 화면
아두이노 기본 폰트로 한글을 출력한 결과. 글자가 잘리고 획이 깨진다.

아두이노 계열의 비트맵 폰트 라이브러리는 대부분 영문 기준으로 만들어져 있다. 알파벳은 획이 적어서 5×7이나 6×8 같은 작은 격자에도 들어간다.

한글이 어려운 이유한글은 초성·중성·종성이 한 글자 안에 겹쳐 들어간다. 예를 들어 '삶'은 ㅅ, ㅏ, ㄹ, ㅁ 네 요소가 하나의 네모 안에 배치된다. 같은 크기 격자에서 영문보다 훨씬 높은 획 밀도를 요구한다는 뜻이다. 저해상도에서는 획과 획 사이의 빈 픽셀이 없어져 뭉개진다.

패널 높이가 32px이고 그중 글자에 쓸 수 있는 영역은 24px 남짓이었다. 이 크기에서 기존 폰트로는 답이 없었다.

폰트를 찾는 대신 에디터를 만들었다

선택지는 두 가지였다. 한글을 지원하는 비트맵 폰트를 찾아 이식하거나, 필요한 글자만 직접 찍거나.

후자를 택했다. 이 전광판이 띄우는 문구는 정해져 있다. 시계 숫자, 날씨 단어, 안전 수칙 6단계. 전체 한글 음절 11,172자가 필요한 게 아니라 쓰는 글자만 있으면 된다.

그래서 브라우저에서 도는 HTML5 Canvas 픽셀 에디터를 만들었다. 격자 위에 마우스로 픽셀을 찍고, 결과를 C++ 배열로 뽑아내는 도구다.

왜 브라우저였나설치가 필요 없고, 캔버스 픽셀 조작이 바로 되고, 만든 결과를 그 자리에서 확대해 눈으로 확인할 수 있다. 전광판 도안은 찍어 보고 아니면 고치는 작업의 반복이라, 저장하고 컴파일하고 업로드하는 왕복이 없는 환경이 필요했다.

4배로 그려서 줄인다

픽셀을 하나씩 손으로 찍는 것만으로는 부족했다. 글자 모양의 기준이 필요했다. 그래서 실제 폰트를 캔버스에 렌더링한 뒤 그걸 격자로 내리는 방식을 썼는데, 여기서 바로 줄이면 획이 끊어졌다.

기법내용
4× 슈퍼샘플링96×96 오프스크린 캔버스에 먼저 렌더링한 뒤 24×24로 줄인다
Dual-threshold 획 보정단일 임계값으로 이진화하면 얇은 획이 통째로 사라진다. 두 단계 임계값으로 획 끊김을 막는다
404px 표준 크기 고정24×24 픽셀 가이드 기준으로 글자 크기를 404px에 고정해 도안 간 굵기를 통일

슈퍼샘플링은 원래 그래픽스에서 계단 현상을 줄일 때 쓰는 기법이다. 큰 해상도로 그린 뒤 축소하면 픽셀 하나에 여러 샘플의 평균이 담긴다. 여기서는 그 평균값이 "이 픽셀에 획이 얼마나 걸쳐 있는가"의 척도가 된다. 그 값을 두 단계로 나눠 판정하니 가는 획이 살아났다.

찍은 다음이 더 중요했다

도안을 53개 만들었다. 한글 단어와 아이콘이다. 이걸 손으로 C++ 배열에 옮겨 적는 건 현실적이지 않다. 하나 고칠 때마다 다시 옮겨야 하기 때문이다.

update_all_pixel_data.py 스크립트를 만들어 53개 전 도안의 16비트 RGB565 C++ 배열과 JSON을 자동 생성하도록 했다. 에디터에서 고치고 스크립트 한 번 돌리면 펌웨어에 반영된다.

왜 RGB565인가HUB75 패널과 Adafruit_GFX 는 색을 16비트로 다룬다. R 5비트, G 6비트, B 5비트다. 사람 눈이 녹색에 가장 민감해서 G에 한 비트를 더 준다. 24비트 트루컬러 대비 메모리가 3분의 2로 줄고, 마이크로컨트롤러의 좁은 플래시에서는 이 차이가 크다.

이 결정이 남긴 것

"라이브러리를 찾는다"에서 "필요한 것만 직접 만든다"로 방향을 튼 판단이었다. 결과적으로 세 가지를 얻었다.

반대로 대가도 있다. 이 에디터 자체가 유지보수 대상이 됐다. 새 문구를 추가하려면 도안을 찍어야 하고, 그건 폰트를 쓰는 프로젝트에서는 없는 작업이다. 문구 종류가 수십 개 수준이니까 성립하는 선택이었다.

2편 정리, 그리고 다음

여기까지가 화면에 원하는 한글을 띄우기까지의 이야기다. 폰트 문제를 도구 제작으로 우회했고, 그 도구가 만든 데이터가 자동으로 펌웨어에 들어가는 파이프라인까지 만들었다.

다음 편은 화면이 살아 움직이기 시작하면서 생긴 문제들이다. 기상청 API를 부르면 화면이 최대 15초씩 멈췄고, 부팅 초반에는 배경에 푸르스름한 잔상이 남았다. 각각 FreeRTOS 코어 분리와 가로 스위핑 연출로 해결했다.

그리고 4편에서 그 잔상 해결책이 사실 원인을 못 찾은 우회책이었다는 걸 알게 된다.