Image Extender(이미지 익스텐더)는 가지고 있는 이미지를 상·하·좌·우 어느 방향으로든 AI로 늘려 그리는 오픈소스 웹앱입니다. 1024×1024 정사각형 인물 사진을 16:9 와이드 시네마틱 장면으로 "더 많이" 그려 넣는 식이죠. 이렇게 이미지 바깥을 채워 그리는 작업을 아웃페인팅(outpainting)이라고 부릅니다.
그런데 단순 아웃페인팅 도구가 아닙니다. 같은 이미지 생성 엔진을 재활용해 2D 게임 아트 제작 스튜디오로 확장돼 있습니다. 하나의 앱 안에 다섯 개의 작업실이 들어 있습니다 — ① Extender(아웃페인팅), ② Parallax Studio(다층 스크롤 배경), ③ Tile Studio(13장짜리 오토타일 세트), ④ Sprite Studio(캐릭터 애니메이션 시트), ⑤ Props Studio(투명 장식 스프라이트).
이미지 생성은 구글의 Gemini 이미지 모델(별명 Nano Banana)을 OpenRouter라는 통합 API를 통해 호출합니다. 핵심은 — 모든 후처리(이음매 블렌딩, 색 보정, 투명 처리)가 서버가 아니라 사용자 브라우저의 HTML Canvas에서 일어나고, API 키도 브라우저에만 저장된다는 점입니다.
포토샵에서 캔버스를 넓히고 빈 공간을 그려 채우면, 원본과 새로 그린 부분 사이에 색이 살짝 튀는 이음매가 남기 마련입니다. "하늘이 갑자기 더 파래진" 느낌이죠.
Image Extender는 AI가 채운 부분을 포아송 블렌딩(Poisson blending)이라는 영상처리 기법으로 원본에 녹여, 경계를 수학적으로 보이지 않게 만듭니다. "AI가 그렸다"가 아니라 "원래 그런 사진이었다"처럼 보이게 하는 게 이 프로젝트의 정체성입니다.
AI 아웃페인팅 자체는 새롭지 않습니다. Photoshop의 Generative Expand, DALL·E, 각종 SD 웹UI가 다 합니다. 그런데 거의 모두가 공통으로 안고 있는 문제가 이음매(seam)입니다. AI가 채운 픽셀과 원본 픽셀 사이에서 미묘하게 밝기·색이 어긋나, 자세히 보면 "여기부터 AI가 그렸다"가 티 납니다. Image Extender는 바로 이 지점을 정면으로 공략해 입소문을 탔습니다.
많은 도구가 생성 영역을 원본 옆에 단순히 붙이고, 잘해야 경계에 살짝 페더(feather, 가장자리 흐리기)만 줍니다. 그러면 ① 경계의 색 점프, ② 여러 번 늘릴수록 누적되는 색 드리프트("패널 줄무늬"), ③ 타일·스프라이트에서 프레임마다 캐릭터가 달라지는 문제가 그대로 남습니다.
① 생성 영역을 포아송 블렌딩으로 원본에 녹여 경계를 지우고, ② 블렌딩 전에 색 드리프트를 미리 보정하며, ③ 같은 확장을 온도를 달리해 3번 시도하고 이음매 품질로 정렬해 가장 깔끔한 후보를 고르게 합니다(best-of-3). 게임 아트 모드에서는 마젠타 크로마키·결정론적 후처리·"아트 디렉터" 2단계 파이프라인으로 일관성까지 잡습니다.
| 비교 항목 | Image Extender | 일반 아웃페인팅 도구 |
|---|---|---|
| 이음매 처리 | 포아송 블렌딩(경계 소멸) | 페더링 정도 |
| 색 드리프트 | 사전보정 + 하모나이즈 | 방치되기 쉬움 |
| 후보 선택 | best-of-3 + 이음매 점수 정렬 | 1장 또는 수동 |
| 실행 위치 | 브라우저 Canvas(클라이언트) | 서버/클라우드 GPU |
| API 키 | BYOK · 브라우저에만 저장 | 서비스가 보관 |
| 범위 | 아웃페인팅 + 게임 아트 5종 | 아웃페인팅 위주 |
대부분 도구가 "퍼즐 조각을 옆에 끼워 넣고 테두리를 살짝 문지른다"면, Image Extender는 "두 조각의 경계를 아예 녹여 한 장의 종이로 다시 굳힌다"에 가깝습니다. 차이를 만드는 건 화려한 AI가 아니라 경계를 다루는 고전 영상처리 수학입니다.
의외로 의존성이 매우 가볍습니다. package.json의 런타임 의존성은 단 4개(next·react·react-dom·jszip)뿐입니다. OpenCV도, sharp도, 어떤 이미지 라이브러리도 없습니다 — 무거운 영상처리를 전부 브라우저 Canvas로 손수 구현했다는 뜻입니다. 이게 이 레포의 가장 큰 학습 포인트입니다.
| 기술 / 버전 | 역할 |
|---|---|
| Next.js 14 | App Router 기반. 화면(페이지)과 API 라우트를 한 프로젝트에 담는 풀스택 프레임워크 |
| React 18 | UI 컴포넌트와 상태 관리. 5개 스튜디오 화면이 모두 React 컴포넌트 |
| TypeScript 5 | 타입 안전성. 이미지 좌표·매니페스트 같은 데이터 구조를 타입으로 고정 |
| Tailwind CSS 3 | 다크 "스튜디오" 테마 UI 스타일링 |
| 구성 | 역할 |
|---|---|
| HTML Canvas | 모든 픽셀 조작의 무대. getImageData/putImageData로 픽셀 배열을 직접 다룬다 |
| imageProcessor.ts | 캔버스 확장, 포아송 블렌딩, 크로마키, 타일러빌리티, 이음매 점수, 스프라이트 정렬/스케일 |
| poseRig.ts / rigCore.ts | 스프라이트용 "포즈 가이드"(ControlNet 스타일 마네킹)를 코드로 그려 AI에 참조로 투입 |
| rigs/ (5종) | 이족·사족·뱀/물고기·새·블롭 등 몸 구조별 포즈 리그 |
| 구성 | 역할 |
|---|---|
| OpenRouter | 여러 AI 모델을 하나의 API로 묶어주는 중개 서비스. 키 하나로 Gemini·GPT 등 호출 |
| Gemini 이미지 모델 | Nano Banana 계열(3 Flash/Pro Image, 2.5 Flash) — 실제 그림을 그리는 모델 |
| GPT-5.4 Image 2 | openai/gpt-5.4-image-2 — 고화질·처리 느림(약 4분/콜)·maxAttempts:1. Settings에서 전환 가능 |
| gemini-2.0-flash | 추론·비전 QA용. 장면 브리프 작성, 소품 "아트 디렉터", 타일 검수에 사용 |
| Next.js API Routes | app/api/*가 OpenRouter로 요청을 대신 보내는 프록시. 키를 로그·저장하지 않음 |
| 기술 | 역할 |
|---|---|
| JSZip | 브라우저에서 결과물(PNG 여러 장 + JSON 매니페스트)을 ZIP으로 묶어 다운로드 |
| JSON 매니페스트 | 패럴랙스 깊이 순서·스크롤 속도, 스프라이트 FPS·프레임 좌표 등을 기록해 게임엔진이 바로 읽게 함 |
| localStorage | OpenRouter API 키를 브라우저에만 저장(BYOK). 서버 디스크에 절대 쓰지 않음 |
먼저 숲을 봅니다. 브라우저(클라이언트)가 거의 모든 일을 하고, 서버(API 라우트)는 OpenRouter로 요청을 대신 보내는 얇은 프록시일 뿐입니다.
핵심은 "AI는 그림만 그리고, 품질은 클라이언트가 만든다"입니다. 이음매 제거·색 보정·투명 처리 같은 "잘 보이게 하는" 작업이 전부 브라우저 Canvas에서 일어나므로, 서버 비용이 거의 없고 사용자 키도 안전합니다.
가장 대표적인 동작 하나 — "사진을 오른쪽으로 늘린다" — 를 입력부터 출력까지 따라가 보겠습니다. 이 한 줄기만 이해하면 나머지 스튜디오도 같은 뼈대의 변주입니다.
가로 확장은 온도를 다르게 준 3번의 시도를 병렬로 돌리고, 각 결과의 이음매 잔차를 재서 "가장 깔끔한 블렌딩" 순으로 보여줍니다. 사용자는 ← →로 후보를 넘기며 마음에 드는 그림을 고를 수 있습니다. 세로 확장은 결정론적인 청크 방식이라 보통 1번이면 충분합니다.
두 이미지를 합칠 때 픽셀 값을 직접 섞지 않고, 픽셀 간 변화량(gradient, 기울기)을 원본에서 가져와 경계의 색만 새 영역에 맞춰 풉니다(Pérez 외 2003, "Poisson Image Editing"). 그래서 "무엇이 그려졌나"는 AI 그림을 따르되 "색·밝기 흐름"은 원본과 이어져, 경계가 사라집니다. 라이브러리 없이 imageProcessor.ts에서 Gauss-Seidel 반복(기본 250회)으로 직접 풉니다.
같은 확장을 온도(temperature)를 달리해 여러 번 생성하고, 각 결과의 이음매 잔차(seam residual)를 코드로 측정해 좋은 순으로 정렬합니다. AI 생성은 본질적으로 도박이라, "한 방"이 아니라 "여러 후보 + 객관적 채점 + 선택"으로 품질을 끌어올립니다. 사람은 최종 취향만 고르면 됩니다.
투명 PNG를 직접 생성하지 못하는 모델의 한계를, "형광 마젠타로 배경을 칠하라 → 클라이언트가 마젠타를 알파로 변환"하는 방식으로 우회합니다. 패럴랙스의 Far/Mid/Near 레이어, 소품, 스프라이트가 모두 이 방식으로 깨끗한 투명 합성을 얻습니다. 모델의 약점을 후처리로 메우는 전형적인 엔지니어링입니다.
소품 스튜디오는 ① 추론 모델이 아트 디렉터가 되어 "다음에 어떤 새 소품을 만들지" 결정하고, ② 이미지 모델이 그걸 그립니다. 타일 스튜디오는 반대로 그린 뒤 비전 모델이 검수해 고칠 점을 돌려보내 다시 칠합니다(keep-best로 항상 개선만). 발상과 렌더링을 나누면 "같은 등불·항아리만 반복되는" 단조로움을 깰 수 있습니다.
스프라이트는 "한 칸에 캐릭터가 둘 그려짐(twin)", "프레임마다 크기가 들쭉날쭉", "발 높이가 안 맞음" 같은 측정 가능한 결함을 픽셀 분석·스케일 정규화·베이스라인 정렬로 잡습니다(QA 모델 대기 없이 빠름). 반면 타일의 "팔레트가 어울리나" 같은 주관적 판단은 비전 비평 모델에 맡깁니다. 객관/주관을 분리한 설계입니다.
Next.js App Router 구조라 app/ 하나에 화면·API·로직이 모여 있습니다. 역할별로 api(서버 프록시) / components(화면) / lib(상수·도메인 데이터) / utils(픽셀 알고리즘)로 깔끔히 나뉩니다.
| 위치 | 한 줄 역할 |
|---|---|
| app/api/* | 유일한 "서버 코드". 키를 헤더로 받아 OpenRouter에 중계만 한다 |
| app/utils/imageProcessor.ts | 이 레포의 심장. 모든 픽셀 수학(블렌딩·크로마키·정렬)이 여기 있다 |
| app/utils/rigs/ | 스프라이트 애니메이션을 위한 "마네킹"을 코드로 그리는 곳 |
| app/lib/*.ts | 모델 목록·아트 스타일·각 스튜디오의 상수와 프롬프트 규칙 |
| app/page.tsx | 5개 스튜디오의 상태와 생성 파이프라인·QA 루프를 묶는 앱 셸 |
"포아송 블렌딩"이라는 고전 영상처리 기법을 라이브러리 없이 brute-force로 구현한 실제 코드를 볼 수 있습니다. 이미지를 픽셀 값이 아니라 "기울기(변화량)"의 세계에서 다루면 무엇이 가능해지는지, Gauss-Seidel 반복이 어떻게 경계 색을 풀어내는지를 코드로 익힐 수 있습니다.
GROW_PX(마스크 확장 폭, 기본 8)와 iterations(반복 횟수, 기본 250)를 바꿔가며 이음매가 어떻게 달라지는지 눈으로 비교해 보세요. 값을 너무 낮추면 경계가 보이고, 너무 높이면 느려집니다 — "품질 vs 속도" 트레이드오프를 직접 만질 수 있습니다.
getImageData/putImageData로 픽셀 배열을 직접 다루는 모든 패턴이 한 파일(imageProcessor.ts)에 모여 있습니다. 크로마키(특정 색 → 투명), 청크 단위 처리, 타일러빌리티(반복 이음매 치유), 이음매 잔차 측정 같은 실전 캔버스 기법을 한 번에 배울 수 있습니다.
마젠타 크로마키 함수를 찾아, 키 색을 초록(#00FF00)으로 바꿔 "그린 스크린" 버전을 만들어 보세요. 색 허용 오차(tolerance)를 조절하면서 가장자리가 어떻게 깨지거나 깔끔해지는지 관찰합니다.
클라이언트가 가진 키를 서버가 받아 저장·로그 없이 중계만 하는 프록시 패턴(app/api/extend/route.ts)은 모든 "내 키로 AI 부르는 앱"의 기본기입니다. CORS·키 노출·요금 폭탄을 어떻게 다루는지 실제 코드로 확인할 수 있습니다.
best-of-N + 자동 채점, 2-call(아트 디렉터→화가), 결정론적 후처리 vs 비전 비평 — 모두 "AI 출력은 불확실하다"는 전제 위에서 품질을 끌어올리는 설계입니다. 단순히 모델을 부르는 것과 "신뢰할 수 있는 결과를 뽑는 시스템"을 만드는 것의 차이를 배웁니다.
실무에서 AI를 제품에 넣을 때 가장 어려운 건 모델 호출이 아니라 "들쭉날쭉한 출력을 어떻게 일정 품질로 만드나"입니다. 이 레포는 그 답(여러 후보·자동 채점·검수 루프·결정론적 보정)을 작은 코드로 압축해 보여줍니다.
스프라이트가 프레임마다 흐트러지지 않도록, 몸 구조별 마네킹(포즈 리그)을 SVG/Canvas로 직접 그려 AI에 구조 참조로 넣습니다(rigs/). 무거운 ControlNet 모델 없이도 "구조 가이드 → 일관된 생성"의 핵심 아이디어를 얻을 수 있습니다.
| 항목 | 요구사항 |
|---|---|
| 런타임 | Node.js (Next.js 14 권장 18+) · npm |
| AI 키 | OpenRouter API 키(BYOK). 첫 실행 시 브라우저가 한 번 묻고 localStorage 저장 |
| 비용 | 확장 1회당 약 $0.03(Nano Banana 2 Flash Image 기준) |
| 브라우저 | Canvas를 쓰는 최신 브라우저면 충분 — GPU 서버 불필요 |
| 라이선스 | MIT (상업적 사용·수정 자유) |
# 1) 클론 후 의존성 설치
git clone https://github.com/boona13/image-extender.git
cd image-extender
npm install
# 2) 개발 서버 실행 → http://localhost:3000
npm run dev # 첫 로드 시 OpenRouter 키 입력(브라우저에만 저장)
# 브라우저에 키를 넣기 싫거나 데모를 호스팅할 때만
cp .env.example .env.local
# .env.local 에 OPENROUTER_API_KEY=... 추가
# → 클라이언트 키가 없는 요청에 한해 서버가 이 키를 사용
특별한 빌드 도구나 GPU 설정이 없습니다. npm install → npm run dev 두 줄이면 로컬에서 돌아갑니다. 무거운 모델 다운로드도 없습니다 — 그림은 전부 OpenRouter 너머의 클라우드 모델이 그리고, 내 컴퓨터는 후처리만 합니다.
아무 정사각형 사진이나 올려 좌·우로 확장하고, best-of-3 후보 3개를 ← →로 넘겨 비교해 보세요. 어떤 후보의 이음매가 가장 안 보이는지, AI 채움 내용이 어떻게 달라지는지 눈으로 익히는 게 첫걸음입니다.
app/lib/app.ts의 EXTENSION_PERCENT(한 번에 늘리는 비율, 기본 38), app/lib/models.ts의 maxAttempts(후보 수)를 바꿔보세요. 값에 따라 결과 품질과 생성 시간이 어떻게 변하는지 기록하면 "튜닝"의 감이 잡힙니다.
imageProcessor.ts의 블렌딩 함수를 찾아, ① 마스크를 키우는 부분, ② Gauss-Seidel 반복 루프, ③ 이음매 잔차 측정 부분에 한국어 주석을 직접 달아보세요. "기울기를 푼다"가 코드로 어떻게 표현되는지 이해하는 게 목표입니다.
app/lib/artStyles.ts에 새 스타일(예: "수묵화", "8비트 도트")을 한 항목 추가하고 Extender에서 적용해 보세요. 프롬프트 한 줄이 결과를 어떻게 바꾸는지, 프롬프트 엔지니어링을 작은 단위로 실험할 수 있습니다.
이 레포를 참고서로 삼아, 캔버스 확장 → 모델 호출 → 단순 페더 합성까지만 하는 최소 아웃페인터를 새 프로젝트로 만들어 보세요. 그다음 페더를 포아송 블렌딩으로 교체하며 before/after를 비교하면, 이 레포의 핵심 가치를 몸으로 이해하게 됩니다.
| 주차 | 주제 | 학습 내용 / 실습 |
|---|---|---|
| 1주차 | Canvas 픽셀 조작 | getImageData/putImageData 기초 → 그레이스케일·반전·크로마키를 직접 구현. imageProcessor.ts와 비교 |
| 2주차 | 그래디언트-도메인 합성 | Pérez 2003 "Poisson Image Editing" 개념 → Gauss-Seidel로 작은 영역 블렌딩 직접 풀어보기 |
| 3주차 | AI 이미지 API 연동 | OpenRouter로 텍스트→이미지 호출, Next.js API Route 프록시 + BYOK 패턴 구현 |
| 4주차 | 신뢰성 있는 AI 파이프라인 | best-of-N + 자동 채점, 2-call(기획→생성), 검수 루프 설계 → 미니 아웃페인터에 적용 |
AI 모델 호출부터 보지 말고 Canvas 픽셀 조작 → 블렌딩 수학 → 그다음 AI 연동 순서를 권합니다. 이 레포의 진짜 가치는 "AI를 부르는 법"이 아니라 "AI 결과를 사람 눈에 완성도 있게 만드는 후처리"에 있기 때문입니다.