Hacker News

How my e-reader lost its stripes

내 e-리더의 줄무늬가 사라진 이유 — AI 에이전트와 함께한 e-ink 펌웨어 디버깅

저렴한 e-ink 리더 Xteink X3의 슬립 화면에서 발견한 세로 줄무늬와 회색 표현 버그를, Codex의 GPT-6 Astra와 Claude Code의 Fable 5.1 두 AI 에이전트와 협업해 디스플레이 웨이브폼 수준까지 파고들어 해결한 과정을 담은 기록입니다. FFT 기반 측정, 테스트 패턴, raw 사진 비교 같은 실험 설계는 사람이 맡고, 낯선 코드베이스 탐색과 빌드·측정 스크립트는 에이전트가 담당한 협업 사례입니다.

본문

저자는 저렴한 초소형 e-ink 리더 Xteink X3를 호기심에 구입하고, 곧바로 오픈소스 CrossPoint 펌웨어를 설치했습니다. 슬립 화면용으로 디더링된 흑백 비트맵을 올려 만족하던 중, 이 기기가 '4단계 회색'을 지원한다는 사실을 알고 회색조 이미지를 시도한 것이 탐정극의 시작이었습니다. 진한 회색이 검정으로 렌더링되고, 뷰어 앱에는 이전 화면의 잔상이 남으며, 원본 비트맵에 없는 세로 줄무늬가 사진 위에 나타났습니다.

e-ink의 회색 표현 원리와 첫 번째 버그

e-ink 패널은 전압 펄스로 흑백 안료 입자를 움직이며, 전원이 꺼져도 입자가 제자리에 머뭅니다. CrossPoint는 회색조를 표현할 때 먼저 회색 픽셀까지 검정으로 칠하는 흑백 베이스를 그린 뒤, 짧은 전압 펄스 웨이브폼으로 선택된 픽셀을 흰색 방향으로 '부분적으로(nudge)' 밀어 진한 회색과 옅은 회색을 만들어냅니다. 저자는 e-ink, ESP32, CrossPoint 모두 초보였기에 Codex의 GPT-6 Astra와 함께 조사를 시작했고, Astra는 뷰어 앱이 빠른 흑백 업데이트만 수행하고 회색 nudge를 아예 건너뛰는 버그를 찾아 수정했습니다. 잔상 문제는 이렇게 해결됐지만, 줄무늬는 남았습니다.

줄무늬를 측정 가능하게 만들기

Astra는 Floyd–Steinberg 디더링을 의심하는 등 헤맸고, 줄무늬라며 지목한 것은 실제로는 미세한 디더 텍스처였습니다. 저자는 Claude Code의 Fable 5.1로 갈아타 다른 시각을 얻었습니다. 문제는 사진에 노이즈가 겹겹이 쌓인다는 점이었습니다. 이미지 자체의 디더 패턴, 줄무늬 원인, 패널의 물리 특성에 더해, 259ppi 화면을 찍은 휴대폰 매크로 사진의 센서 노이즈·렌즈 왜곡·노출 편차·손떨림까지 얹혔습니다. Fable은 각 열의 밝기를 200행 높이 슬라이딩 윈도우로 평균내 디더 텍스처를 상쇄하고, 1차원 밝기 프로파일에 FFT를 적용해 줄무늬 간격과 강도를 측정했습니다.

결정적 실험과 두 번째 버그

단순 이미지 실험으로 '회색 픽셀이 존재하고 이웃 픽셀의 명도가 다를 때만' 줄무늬가 생긴다는 조건을 좁혔고, 소스 이미지 블록을 사진에 매칭해 줄무늬가 옅은 회색 픽셀에 실린다는 사실을 확인했습니다. 회색을 만드는 nudge 코드는 CrossPoint가 쓰는 하드웨어 라이브러리 freeink-sdk에 있었습니다. 이후 생성한 테스트 패턴(4단계 평탄 패치, 회색 혼합, 체커보드, 양방향 선)이 자의적인 '자' 역할을 하며, 줄무늬 주기가 추정 7픽셀이 아니라 8픽셀임이 밝혀졌습니다. 휴대폰 후처리가 줄무늬 진폭을 약 70% 과장한다는 것도 확인해 이후엔 raw DNG만 사용했습니다. 한편 테스트 패턴은 '4단계 중 실제로는 3단계만 표시된다'는 별개 버그도 드러냈습니다. 원인은 CrossPoint가 nudge용 픽셀당 2비트로 선택하는 4개 웨이브폼 테이블 중, 진한 회색용으로 아무 동작도 안 하는 테이블을 고르고 있던 것이었고, Fable은 이를 수정해 freeink-sdk에 PR을 올렸습니다. 덤으로 안티앨리어싱 텍스트의 진한 회색 픽셀이 검정으로 나오던 문제도 고쳐져 전체 글자 렌더링이 개선됐습니다.

XTH4 웨이브폼과 해결

nudge를 7프레임에서 10프레임으로 늘리거나 펄스와 휴지를 교차시켜도 줄무늬는 그대로여서 프레임 수 가설은 폐기됐습니다. 남은 카드는 제조사의 4회색 이미지 모드 XTH4로, 리프레시에 약 1초가 걸려 페이지 넘김엔 부적합하지만 이미지 표시엔 쓸 만했습니다. 다만 이 긴 웨이브폼은 4개 명도 모두에 별도 지시가 필요해 메모리가 문제였습니다. ESP32-C3의 RAM 약 380KB 중 최대 연속 여유 블록이 53KB뿐인데, 528×792 화면의 1비트 복사본만 52KB였습니다. Fable은 뷰어가 파일을 렌더링 패스마다 디코드하는 점을 이용해, 처음부터 픽셀당 2비트로 4개 명도를 식별하는 데이터를 직접 만들어 드라이버에 넘기는 방식을 제안했습니다. 결과 줄무늬는 완전히 사라졌고, 열 단위 밝기 변동은 흑백 범위의 약 4%에서 1%로 줄었으며 주파수 스펙트럼의 8픽셀 피크도 소실했습니다. 옅은 회색이 흰색에서 더 멀어지는 부수효과도 얻었습니다.

원인에 대한 추측과 협업의 의미

저자는 원인을 단정하지 않고 가설로 제시합니다. 패널 유리에 새겨진 게이트 드라이버의 인터리브된 클럭이 매 n번째 행을 조금씩 다른 시간 동안 여는데, 흑백으로 강하게 구동되는 픽셀은 타이밍 오차에 둔감하지만 3프레임짜리 nudge로 중간까지 밀리는 회색 픽셀은 그 시간 창만큼의 전하만 받습니다. 회색 디더처럼 전압 스윙과 타이밍 민감성이 겹칠 때만 매 8번째 행이 밝거나 어두워진다는 설명입니다. 드라이버 수정은 freeink-sdk#95가 됐고 두 PR 모두 몇 시간 만에 머지됐으며, CrossPoint 쪽 PR은 비슷한 변경을 낸 다른 기여자에게 선수를 빼앗겼습니다. 저자는 첫 e-ink 기기를 개봉한 지 몇 시간 만에 디스플레이 웨이브폼 수정을 테스트한 시간대가 '조금 우스울 정도'라고 회상하며, 에이전트가 낯선 코드·빌드 도구·측정 스크립트를 다루는 동안 자신은 FFT와 테스트 패턴 같은 실험 설계와 방향 수정에 집중할 수 있었다고 정리합니다.

Hacker News 반응

  • @mikestew — 글에 언급된 Xteink X3 리더에 관한 이전 HN 토론 링크를 공유했습니다.
  • @ikidd — 이 기기로 바로 '인쇄'해 파일을 업로드할 수 있게 해주는 Linux 프린트 드라이버를 누군가 만들었다고 전합니다.
  • @edoceo — 해당 프로젝트의 HN 링크를 덧붙여 확인을 도왔습니다.
  • @PerilousD — Xteink X3와 CrossPoint를 처음 알았다며, Kobo 노트 기기와 e-리더가 각각 너무 크고 애매해서 이 제품을 시도해보겠다고 했습니다. 다만 '실제 e-리더로서 기본 상태에서 어떻게 동작하는지' 설명이 없어 선택이 어려웠다고 지적했습니다.
  • @zem — 본 리뷰들은 대체로 긍정적이지만 '기본 상태'에 오픈소스 펌웨어 재설치가 포함된다는 점에 주의하라며, 재이미징 가능한 기기인지 확인하라고 조언했습니다.

원문: serpentine.com / 번역·요약: Trawling