/image-gen — Codex image_gen via ChatGPT OAuth
Spawn a fresh codex exec session in an empty sandbox dir, ask it to call image_gen, then extract the generated PNG out of the codex session rollout jsonl and write it to your destination path. Inspired by madrobotnet/hermes-codex-imagegen-skill — Hermes 식 instruction-based skill 패턴.
크로마 키 분기 게이트 — 소재색 먼저, 키는 그 다음 (BLOCKING)
투명 배경(크로마 키) 생성은 프롬프트를 쓰기 전에 소재의 주요 색부터 확인하고 키를 고른다. 소재색과 같은 계열의 키는 하드 키 삭제·경계 트림이 소재를 갉아먹는다:
소재 주요 색 크로마 키 핑크·보라·자주·마젠타 계열 (꽃, 씨앗봉투, 핑크/보라 의상·크리스탈) 그린 #00FF00녹색·청록 식물/초목/슬라임/그린 의상 마젠타 #FF00FF핑크와 녹색 둘 다 포함 더 크고 중요한 소재에서 먼 키를 고르고, 변환 후 두 색 모두 보존됐는지 확인 필수. sprite-gen 런이면 --chroma-key auto(거리 스코어)로 검증
- 프롬프트의 배경색 지시가 위 표와 일치한다 (무지성 마젠타 디폴트 금지)
- 변환 후 소재 주요 색이 살아있다 — 흰 꽃/탈색 = 키가 소재와 충돌한 것, 로컬 보정 말고 키를 바꿔 재생성
아래 본문의 마젠타 예시들은 "소재에 핑크/보라가 없는" 케이스의 프롬프트/fuzz 참고이고, 키 선택의 SSoT 는 이 표다.
image_gen 출력 모델 — 이 스킬의 핵심 전제 (codex v0.144.1, verified 2026-07-10)
codex
image_gen은 이미지를 세션 rollout jsonl(~/.codex/sessions/.../rollout-*-<SID>.jsonl)에 인라인 base64 로 싣는다. 레코드 이름은 codex 버전마다 다르다 —0.140.0= response_itemimage_generation_call.result,0.144.1= event_msgimage_generation_end.result(+status,saved_path).extract_imagegen.py가 두 타입을 모두 읽는다.0.144.1은saved_path로 디스크 저장을 되살렸지만 그 경로는 쓰지 않는다 — 인라인 디코드만 진실이다(경로 환각 표면 재개방 방지). 따라서:
- 추출·검증은
scripts/extract_imagegen.py로 그 base64 를 결정론적으로 디코드해$DEST에 쓴다.~/.codex/generated_images/는 쓰지 않는다 — v0.140.0 에선 아예 안 생겼고(항상 0 hit), v0.144.1 에선 다시 생기지만(<sid>/call_*.png) 그 경로를 믿는 순간 codex(LLM)에게 경로를 되묻는 환각 표면이 살아난다. 인라인 디코드만 진실.--ephemeral금지 — 세션 jsonl 이 디스크에 남아야 추출 가능 (추출 후 직접 청소).- 증상→원인: codex 가 "image_gen 결과 PNG의 파일시스템 절대 경로를 확인할 수 없습니다" 로 끝나면 = 추출을 안 한 것. image_gen·인증은 정상이다.
- 무관한 노이즈:
codex execstderr 의rmcp::transport::worker ... Auth(AuthorizationRequired)는 Cloudflare MCP 서버 인증 실패 로그지 image_gen 과 무관 — 무시. (없애려면 codexconfig.toml에서 해당 MCP 인증/비활성화.)동작 전환 내역(codex ≤0.125 디스크 저장 → 0.140.0 인라인)은
CHANGELOG.md.
Why spawn even when you are Codex
이 스킬은 cross-engine 이다 — Claude 에서 호출하면 codex 가 없는 image_gen 도구를 빌리는 거고, Codex 안에서 호출해도 fresh codex exec 를 또 띄우는 게 권장이다. 이유: 메인 세션 안에서 직접 image_gen 을 부르면 OpenAI 의 prompt cache 가 컨텍스트와 묶여서, 같은 프롬프트 결의 후속 호출이 이전 이미지 결과로 끌려가는 현상이 있다 (알렉스 검증, 2026-05-11) — "이미지 변경이 잘 안 됨". 빈 sandbox 로 매번 새 세션(fresh codex exec)을 띄우면 캐시 키가 깨져서 매 호출이 깨끗한 이미지로 분리된다 (캐시 분리는 fresh 세션에서 오는 것이지 --ephemeral 플래그가 아니다 — 추출을 위해 --ephemeral 은 오히려 빼야 한다). 즉:
- Claude → image_gen: codex CLI 가 image_gen tool 보유자라 codex exec 소환은 필수.
- Codex → image_gen: 도구는 같은 세션에서도 부를 수 있지만, 캐시 분리를 위해 fresh codex exec 소환을 권장.
Preconditions
which codex && codex --version
codex login status # must show "Logged in using ChatGPT" or equivalent
codex features list | grep image_generation # must be `stable true`
If any fail, surface the issue to the user and stop.
1.0 transparent PNG contract
image_gen native transparent/alpha output is not reliable on this setup. For game assets, stickers, sprites, UI cutouts, and any transparent image request, do not ask for alpha as the final truth. Use this deterministic contract:
- Prompt image generation for an exact solid chroma background:
SOLID FLAT MAGENTA #FF00FF background, no vignette, no gradient, no texture. - Convert the raw PNG with this skill's helper:
python3 $ALEX_EXTENSIONS_DIR/image-gen/scripts/chroma_key_transparent.py \
--input "$RAW_PNG" \
--out "$DEST_PNG" \
--key magenta \
--white-check /tmp/check-white.png
- Verify the helper prints
mode=RGBAandstale_transparent_rgb_pixels=0. - Inspect
/tmp/check-white.pngwhen the asset will sit on a bright background. Pink halo means the result is not done.
Use green only when the subject itself contains important magenta/pink colors:
python3 $ALEX_EXTENSIONS_DIR/image-gen/scripts/chroma_key_transparent.py \
--input "$RAW_PNG" \
--out "$DEST_PNG" \
--key green \
--white-check /tmp/check-white.png
sprite-gen uses the same chroma-key discipline for sheets, but its helper is sprite-demo specific. General transparent image generation owns the reusable helper here.
Core workflow — 생성(codex) → 추출(wrapper) → 후처리(local) (v0.140.0 검증)
codex 는 이미지를 디스크에 안 떨구고 세션 jsonl 에 인라인 base64 로만 준다(위 동작변경 참조). 그래서 캐노니컬 흐름은 3단계다: ① codex exec 로 image_gen 호출(생성만) → ② 래퍼가 세션 jsonl 에서 base64 추출 → ③ (투명 필요 시) 로컬 chroma 후처리. codex(LLM)에게 경로/저장/셸을 시키지 않으므로 hallucination 표면이 0 이다.
검증된 사실 (2026-06-18): 아래 흐름 그대로 사과·레몬·딸기 PNG 생성 → 추출 → 정상 1254×1254 PNG. SID 파싱→rollout→extract_imagegen.py 결정론 동작.
TS=$(date +%s)
TMP=$(mktemp -d /tmp/codex-image-XXXXXX)
RAW="$TMP/raw.png" # codex 가 생성한 원본 (chroma 배경 포함)
DEST=/project/path/asset-name.png # 최종 경로
# ① 생성만 (저장/셸/코드 금지). --ephemeral 없음 → 세션 jsonl 이 디스크에 남아야 추출 가능.
codex exec \
--sandbox workspace-write \
--skip-git-repo-check \
--color never \
--add-dir ~/.codex/generated_images \
-C "$TMP" \
- <<'PROMPT' 2>&1 | tee "$TMP/stdout.log"
image_gen 도구를 정확히 1번 호출해서 다음 프롬프트의 이미지 1장만 생성해줘.
파일 저장·셸 명령·코드 작성·경로 보고 전부 금지. 생성만 하고 끝.
프롬프트:
<USER_PROMPT — 투명 필요하면 'SOLID FLAT MAGENTA #FF00FF background, no shadow, no gradient' 명시>
PROMPT
# ② 세션 id → rollout jsonl → 인라인 base64 추출 (codex 응답 텍스트 신뢰 X, 파일만 신뢰)
SID=$(grep -oE 'session id: [0-9a-f-]+' "$TMP/stdout.log" | awk '{print $3}' | tail -1)
python3 "$ALEX_EXTENSIONS_DIR/image-gen/scripts/extract_imagegen.py" "$SID" "$RAW"
# → "OK <RAW> <bytes>" 출력 + PNG magic 검증됨. 실패 시 non-zero 로 죽음(거짓 성공 없음).
# ③-A 불투명(배경 포함) 그대로 쓰면: 바로 최종 경로로
cp "$RAW" "$DEST"
# ③-B 투명 필요하면: 로컬 chroma 후처리 (아래 transparent 섹션 규율 그대로)
python3 "$ALEX_EXTENSIONS_DIR/image-gen/scripts/chroma_key_transparent.py" \
--input "$RAW" --out "$DEST" --key magenta --white-check /tmp/check-white.png
# stdout 에 mode=RGBA, stale_transparent_rgb_pixels=0 확인 + Read 로 /tmp/check-white.png 핑크 halo 시각 확인 (필수)
# ④ 정리 — RAW 검증 끝났으면 세션 jsonl(인라인 이미지 ~1.5MB 포함)도 청소해 ephemeral 청결성 복원
SESS=$(find ~/.codex/sessions -name "rollout-*${SID}*.jsonl" 2>/dev/null | head -1)
[ -n "$SESS" ] && rm -f "$SESS"
rm -rf "$TMP"
왜 이 구조인가: (1) base64 추출은 extract_imagegen.py 가 결정론적으로 하므로 codex 의 환각 경로/거짓 성공이 끼어들 자리가 없다. (2) 후처리(chroma)는 로컬에서 우리가 실행 → codex sandbox 안 magick 의존/실패 표면 제거. (3) codex 는 "생성만" 시켜서 코드 폭주·자기 SKILL.md 정독 같은 사고가 안 난다.
언제 reference(-i)·batch 등 변형을 쓰나: 아래 ## Style-transfer / ## Batch 섹션. 추출·검증 단계(②④)는 모든 변형에서 동일하다.
Manual workflow (디버깅·실험용 분리 단계)
1. Snapshot timestamp + temp workspace
TS=$(date +%s)
TMP=$(mktemp -d /tmp/codex-image-XXXXXX)
2. Run codex exec (sandbox + writable gen dir, NO --ephemeral)
The decisive flag combo:
--sandbox workspace-write— image_gen needs write access (없으면 read-only sandbox 라 image_gen tool 자체가 등록 안 됨).--add-dir ~/.codex/generated_images—workspace-write디폴트 writable 셋에 안 들어가서 빠뜨리면 silent 실패. 검증된 사실 (옛 버전 핵심 단서, 지금도 그대로 넣는다).--skip-git-repo-check— 빈 sandbox 라 git repo 없음.--ephemeral는 절대 넣지 않는다 — 세션 jsonl 이 디스크에 남아야 거기서 인라인 base64 를 추출할 수 있다. (옛 버전엔 넣었지만 v0.140.0 추출 방식과 충돌. 추출 끝나면 ④에서 세션파일을 직접 지워 청결성 복원.)-C "$TMP"— codex 작업 디렉토리. 빈 dir → 코드 작업 폭주 안 함.-i <ref-image>(반복 가능) — style/character reference. codex 가 attach 해서 모델에 보여줌.
기본 호출 (생성만):
codex exec \
--sandbox workspace-write \
--skip-git-repo-check \
--color never \
--add-dir ~/.codex/generated_images \
-C "$TMP" \
- <<'PROMPT' 2>&1 | tee "$TMP/stdout.log"
image_gen 도구를 정확히 1번 호출해서 다음 프롬프트의 이미지 1장만 생성해줘.
파일 저장·셸 명령·코드 작성·경로 보고 전부 금지. 생성만 하고 끝.
프롬프트:
<USER_PROMPT_HERE>
PROMPT
reference 이미지가 있으면 -i /abs/path/ref1.png -i /abs/path/ref2.png 를 옵션 위치에 추가.
3. Extract session id from stdout, then decode the inline PNG
codex 의 stdout 에 한 줄로 들어옴: session id: <uuid>. 그 SID 의 rollout jsonl 에서 인라인 base64 를 디코드한다.
SID=$(grep -oE 'session id: [0-9a-f-]+' "$TMP/stdout.log" | awk '{print $3}' | tail -1)
python3 "$ALEX_EXTENSIONS_DIR/image-gen/scripts/extract_imagegen.py" "$SID" "$RAW"
# extract_imagegen.py: SID(또는 rollout 경로) → image_generation_call.result base64 디코드 → PNG magic 검증 → $RAW 기록.
# image_gen 결과가 없으면 non-zero 로 죽는다 (거짓 성공 없음, No Silent Fallback).
# 한 세션에 여러 장이면 --all 로 $RAW-0.png, $RAW-1.png ... 또는 --index=N 지정.
codex 의 응답 텍스트에 적힌 경로는 LLM 환각 가능. 절대 신뢰 X. extract_imagegen.py 가 디코드해 실제로 쓴 파일만 신뢰. (옛 find ig_*.png in generated_images 방식은 v0.140.0 에서 항상 0 hit 라 폐기.)
4. Use the asset, then clean the session
cp "$RAW" /project/path/asset-name.png # 불투명 그대로 쓸 때 (투명 필요하면 chroma 후처리 — transparent 섹션)
# 세션 jsonl 에 인라인 이미지(~1.5MB)가 들어있으니 추출 끝나면 청소
SESS=$(find ~/.codex/sessions -name "rollout-*${SID}*.jsonl" 2>/dev/null | head -1)
[ -n "$SESS" ] && rm -f "$SESS"
rm -rf "$TMP"
Style-transfer pattern (캐릭터/화풍 일관성)
기존 자산과 화풍 맞출 때 reference 이미지 첨부:
codex exec ... \
-i /project/assets/style-reference.png \
-i /project/assets/character-reference.png \
...
- <<'PROMPT'
첨부한 두 장의 painterly 화풍/캐릭터를 정확히 일치시켜서 image_gen 으로 X 1장 생성.
- 동일 라이팅, 동일 디테일 밀도, 동일 캔버스 비율
- 캐릭터 디자인은 두번째 ref 와 동일
PROMPT
같은 캐릭터의 다른 포즈 (idle / attack / win 시리즈) 가 필요하면:
- idle 한 장 먼저 생성
- 그 idle 을 ref 로 attack/win 생성 → 캐릭터 일관성 보장
codex exec호출은 위 ## Core workflow 의 플래그(--sandbox workspace-write/--add-dir/-C/ no--ephemeral)를 그대로 쓰고, 생성 후 ②④(SID 파싱 →extract_imagegen.py추출 → 세션 청소)는 동일하게 적용한다. ref(idle)로 쓸 PNG 도 추출로 디스크에 먼저 떨궈야 다음 호출에서-i로 첨부할 수 있다.
Pitfalls
- silent 실패 =
--sandbox workspace-write누락. 디폴트read-onlysandbox 면 codex 가 image_gen 등록 안 함 → tool call 자체가 안 일어나고codex빈 응답만 나옴. 검증 단서:sandbox: read-only가 stdout 헤더에 찍히면 무조건 워크스페이스-라이트로 다시. (검증:--sandbox workspace-write명시 후 같은 prompt 로 호출 즉시 image_gen 호출됨.) - silent 실패 =
--add-dir누락.workspace-write만으로는 generated_images writable 아님. 이 시스템에서 검증된 핵심 단서. - image_gen 결과의 진실은 세션 rollout jsonl 의 인라인 base64 다. 레코드 이름은 codex 버전마다 다르다 — v0.140.0
image_generation_call.result, v0.144.1image_generation_end.result.extract_imagegen.py로 디코드해야 파일이 생긴다. (v0.140.0 에서 "image_gen 안 됨"의 진짜 원인이 이거였음 — 인증·생성은 정상. v0.144.1 에서 레코드 rename 으로 같은 증상이 재발했다.) → 그래서--ephemeral금지 (세션파일 있어야 추출). v0.144.1 은<sid>/call_*.png로 디스크 저장을 되살렸지만 그 경로는 신뢰하지 않는다. - codex 응답의 거짓 경로. 응답에
/tmp/<sandbox>/generated_image.png같은 경로가 적혀도 신뢰 X — 실제 파일은 없다.extract_imagegen.py가 디코드해 쓴$RAW만 신뢰. rmcp ... Auth(AuthorizationRequired)stderr 노이즈 무시. Cloudflare MCP 서버(cloudflare-bindings/builds/observability) 인증 실패 로그지 image_gen 과 무관. "인증 안 됨" 처럼 보이지만 image_gen 은 정상 동작. 없애려면 codexconfig.toml에서 해당 MCP authenticate/비활성화.- bwrap loopback / sandbox introspection 에러 무시. codex 가 sandbox 내부 검사하다
RTM_NEWADDR같은 에러 출력해도 실패 아님. 추출된 파일 존재·PNG magic 만 봄. - 모델은
gpt-5.5기본. reasoning 시간 30~90초.--model <name>으로 가벼운 모델 쓸 수 있지만 image_gen tool 등록 보장 X — 검증 필요. - 프로젝트 디렉토리에서
-C없이 호출 절대 금지. codex 가 프로젝트 컨텍스트 보고 코드 작업으로 폭주함 (실제 사고 사례 있음). - Read 툴은 알파 채널 PNG 를 배경색처럼 렌더한다. magenta chroma key 후 Read 로 보면 마젠타 배경처럼 보여서 "투명 안 됐다" 착시. 진짜 검증은
magick identify -format '%[channels]'→srgba 4.0이면 알파 있음.
Transparent background 는 native 지원 안 됨 (codex CLI 0.124 / 0.125 둘 다 검증)
검증 사실 (재확인 0.125.0-alpha.3, 2026-04-26): prompt 에 "TRANSPARENT (alpha channel)", "RGBA", "no background fill", "no checkerboard" 모두 명시 + "거짓 보고 금지" 까지 박아도 결과는 동일하게 RGB 3-채널 + 흰-회색 (corner 220~252) 배경 PNG. PIL mode='RGB', has_alpha=False. magick srgb 3.0.
이건 모델 한계이며 prompt engineering 으로 해결 불가. 항상 chroma 배경 + 후처리 우회 사용.
Read 툴이 흰 배경 PNG 를 체커보드처럼 표시해서 transparent 로 착시 발생 — 항상 python3 -c "from PIL import Image; print(Image.open(p).mode)" 또는 magick identify -format '%[channels]' 으로 진짜 알파 채널 여부 확인.
→ NPC/캐릭터/sprite 처럼 외곽이 transparent 여야 하면 solid chroma background + scripts/chroma_key_transparent.py 후처리 필수. floodfill 은 chroma 배경을 명시하지 못한 이미지에만 쓰는 폴백이다.
Fuzz 가이드 — 캐릭터 의상 색에 따라 다름
| fuzz | 결과 |
|---|---|
| 18% (원래 시작값) | 가장자리 회색 잔여 거의 없음. 단 흰 ruff/cravat/셔츠 의상이면 connected pixel chain 으로 의상까지 점프해서 윤곽선만 남는다. |
| 8% (안전 기본값) | 외곽 회색 (220~235) 만 빠지고 의상 흰 (240+) 보존. 첫 시도 권장값. |
| 5% 이하 | 의상 보존 더 안전, 외곽 회색 안티앨리어싱 약간 잔여 가능. |
검증: 처리 후 magick identify -format '%[channels]' 로 srgba 4.0 확인 + Read 툴로 시각 확인 (의상 일부가 빠지지 않았는지).
Chroma key (그린/마젠타 키) — floodfill 보다 우선 권장
캐릭터/NPC 처럼 머리카락 highlights, 투명 천, 흰 의상 등이 있으면 floodfill 은 위험. 대신 prompt 에 솔리드 chroma 배경 명시 → magick -transparent 한 줄이 머리카락 사이 highlights 까지 보존하면서 깔끔.
키 선택의 SSoT 는 최상단 분기 게이트다. 아래는 선택이 끝난 뒤의 프롬프트/fuzz 참고:
| chroma | prompt 예시 | 안전한 fuzz | 언제 |
|---|---|---|---|
#FF00FF 마젠타 |
"solid flat MAGENTA #FF00FF background, hair highlights warm amber/gold ONLY, never near-magenta" | 12% | 소재에 핑크/보라/자주/마젠타가 없을 때 (사람/NPC, 녹색 식물). |
#00FF00 그린 |
"chroma key green #00FF00 background" | 18% | 핑크/보라/자주 소재, 검/금속/가죽 sprite. 그린 톤 소재는 피해야 함. |
# 마젠타 케이스 (인물/NPC/게임 cutout 표준)
python3 $ALEX_EXTENSIONS_DIR/image-gen/scripts/chroma_key_transparent.py \
--input "$SRC" --out "$DEST" --key magenta --white-check /tmp/check-white.png
# 그린 케이스 (마젠타/핑크가 중요한 subject)
python3 $ALEX_EXTENSIONS_DIR/image-gen/scripts/chroma_key_transparent.py \
--input "$SRC" --out "$DEST" --key green --white-check /tmp/check-white.png
검증: helper stdout 의 mode=RGBA, stale_transparent_rgb_pixels=0, alpha_zero_pct 를 확인한다. magick identify -format '%[channels] %wx%h\n' "$DEST" 가 가능하면 srgba 4.0 ... 도 확인한다. (Read 툴은 마젠타 배경처럼 보여줘도 신뢰 X.)
왜 floodfill 보다 우선인가: floodfill 은 외곽에서 시작해서 connected pixel chain 으로 번지므로 머리카락 사이 갭 / 흰 의상까지 침투할 수 있음 (실사고 2건 — CHANGELOG.md 부록). chroma key 는 픽셀 색 매치만 보고 위치 무관 → 캐릭터 안쪽으로 안 번짐.
⚠ magick -transparent 의 가장 큰 함정 — alpha 만 깎고 RGB 는 마젠타 그대로 남김 (verified 2026-04-26)
magick "$SRC" -fuzz 35% -transparent magenta "$DEST" 후, transparent area (alpha=0) 픽셀의 RGB 채널은 (255, 0, 255) 마젠타가 그대로 보존된다. ImageMagick 은 알파만 0 으로 만들고 RGB 는 안 건드림.
PIL 검사:
{'alpha=255 magenta': 0, 'alpha 1..254 magenta': 0} # 본체에 마젠타 0
transparent area still has magenta RGB (sampled 1/16): 28995 # 그러나 transparent 영역엔 마젠타 RGB 살아있음
대부분 viewer/브라우저는 alpha=0 이면 RGB 무시 → 문제 안 보임. 하지만 다음 환경에선 마젠타가 누설:
- 일부 브라우저/캔버스의 alpha-premultiplication 합성 모드
- 이미지 다운스케일링 시 인접 alpha 픽셀과 RGB 가 보간 → 가장자리 마젠타 halo
- macOS 스크린샷 도구 / 일부 picker / Read 툴
- CSS
filter: drop-shadow/mask-image
→ alpha=0 픽셀의 RGB 도 반드시 (0,0,0) 으로 청소해야 함. 실사고에서 asset 당 46만~48만 픽셀이 stale 마젠타 RGB 로 살아있었다 (CHANGELOG.md 부록).
for y in range(H):
for x in range(W):
r, g, b, a = px[x, y]
if a == 0 and (r or g or b):
px[x, y] = (0, 0, 0, 0) # transparent area RGB 청소
또는 ImageMagick 한 줄:
magick "$SRC" -channel RGBA -alpha set \
\( +clone -channel A -threshold 1 \) -compose CopyOpacity -composite \
-background black -alpha background \
"$DEST"
이 청소를 안 하면 chroma key + decontamination 다 해도 사용자 화면에 마젠타가 계속 비친다. 절대 빠뜨리지 마.
Decontamination pass — chroma key 후에도 fringe 가 보일 때 필수 후처리
검증된 사실: magick -fuzz 35% -transparent magenta 조차도 머리카락/모자 외곽의 anti-aliased 픽셀에 마젠타가 섞인 RGBA 를 남긴다. 검은 배경 위 합성에선 안 보이고, 흰 배경 위 합성에서만 핑크 halo 로 떠 보인다 — 그래서 밝은 배경에 올라갈 자산의 검증은 반드시 흰 배경 합성으로 한다.
원인 (3-단계):
- chroma key (
-transparent magenta) 는 fuzz 안에 든 색만 알파=0. 외곽 anti-alias 픽셀 (R, B 둘 다 G 보다 살짝 높은 핑크-tint) 은 fuzz 못 통과 → alpha 살짝만 깎인 채 RGB 마젠타-tint 살아남음. - PIL "opaque magenta 0 픽셀" 검사 통과해도, 반투명 핑크 fringe 가 살아있다.
- 어두운 배경에 합성하면 fringe 가 어둠에 묻혀 안 보임. 밝은 배경에 합성해야 핑크 halo 가 드러남. 검증은 반드시 흰 배경 합성으로.
해결: chroma key 후 PIL 로 두 단계 처리 — 의상/본체 (alpha=255) 는 절대 안 건드리고, alpha < 240 인 외곽 anti-alias 픽셀만 핑크끼면 RGB 를 grey 화 + alpha 1/3 로.
python3 - <<'PY'
from PIL import Image
p = "/path/to/asset.png"
im = Image.open(p).convert('RGBA')
px = im.load()
W, H = im.size
killed = 0
for y in range(H):
for x in range(W):
r, g, b, a = px[x, y]
# alpha=255 본체와 alpha=0 배경은 절대 안 건드림
if a == 0 or a >= 240:
continue
# 핑크/마젠타-tint anti-alias: R 과 B 둘 다 G 보다 4 이상 높음
# 와인-레드 의상 (R↑ G↓ B↓) 은 B<G 라 자동 제외
if (r - g) > 4 and (b - g) > 4:
# 완전 평탄화: R=G=B=G (grey), alpha 1/3 로 깎기
px[x, y] = (g, g, g, a // 3)
killed += 1
im.save(p)
print("fringe killed:", killed)
PY
검증 절차 (이걸 안 하면 사고 또 남):
# 흰 배경에 합성해서 fringe 가 진짜 사라졌는지 *눈으로* 확인
python3 - <<'PY'
from PIL import Image
im = Image.open("/path/to/asset.png").convert('RGBA')
bg = Image.new('RGBA', im.size, (255, 255, 255, 255))
bg.alpha_composite(im)
bg.convert('RGB').save("/tmp/check-white.png")
PY
# 그 다음 Read 툴로 /tmp/check-white.png 열어서 머리카락/모자 외곽 핑크 halo 검사
검증된 결과: asset 당 약 1000~1400 fringe 픽셀 중성화, 흰 배경 합성 후 잔여 핑크 0 픽셀.
그린 키 (#00FF00) 의 경우: G 가 높고 R/B 낮은 fringe → 조건을 (g - r) > 4 and (g - b) > 4 로 바꿔서 같은 logic 적용 (RGB 평탄화는 r 또는 b 기준).
캐시 함정: 후처리 다 끝나도 사용자 브라우저가 옛날 PNG 캐싱 중이면 fringe 가 그대로 보인다. md5 변경을 보여주고 DevTools → Network → "Disable cache" 또는 시크릿 창 안내. 단, 캐시 탓하기 전에 반드시 흰 배경 합성으로 자체 검증 먼저.
Floodfill 폴백 (chroma 배경 명시 못 했을 때만)
Batch / Continuation modes (검증 2026-04-28)
기본은 위 single-shot codex exec 패턴. 다음 두 시나리오에서는 별도 모드를 쓴다. 캐노니컬 참조는 아래 인라인 bash/pseudo 패턴이다.
옛
scripts/spike-*.pyPhase 0.x 탐색 파일들은 codex ≤0.125 의 출력 모델(~/.codex/generated_images/<sid>/ig_*.png)과 개인 로컬 fixture 경로에 묶여 있어 제거했다. 아래 인라인 패턴만 현재 캐노니컬 참조다.
A. Batch parallel — 게임 에셋 다량 / 카드뉴스 시리즈
같은 캐릭터/스타일을 다른 pose 또는 다른 표정으로 N 장 동시 생성. 매 호출이 독립이고 character lock 은 base ref 첨부 + 동일 prompt 토대로 유지.
- 캐노니컬 패턴: 아래 인라인 bash. N개
codex exec를 독립 sandbox 로 동시에 띄우되, 추출은 각 프로세스의 session id 별로 수행한다. - ChatGPT image_gen 동시성 한계 ≈ 4 (실측 2026-04-28). 5번째부터 quota 대기로 ~150s 추가.
- 시간 단축: 5장 sequential ~375s vs 4-동시 parallel ~230s = ~38%. 50장 단위 batch 면 sequential ~62분 vs parallel ~16분 (약 4배).
- 패턴 (각 프로세스가 자기 stdout 을 파일로 받아 → 끝나면 SID 추출):
# parallel 4 (concurrency cap = 4) — --ephemeral 없음(세션 jsonl 필요) for i in "${!poses[@]}"; do tmp_per=$(mktemp -d /tmp/codex-image-XXXXXX) codex exec --sandbox workspace-write --skip-git-repo-check --color never \ -C "$tmp_per" --add-dir ~/.codex/generated_images -i "$base" \ - <<<"image_gen 으로 1장만 생성, 저장/셸 금지: ${poses[$i]}" >"$tmp_per/out.log" 2>&1 & done wait # 각 tmp_per/out.log 에서 SID 파싱 → extract_imagegen.py "$SID" "out-$i.png" (추출 단계는 단발과 동일) - character lock: base ref
-i $base첨부 매번 + prompt 에 동일 style 명시 매번. thread context 없음. - 추출은 프로세스별로 SID 따로 (③ 단계와 동일). 한 프로세스가 N 장 만들면
extract_imagegen.py --all사용.
B. Continuation thread — 반복 수정 / 한 캐릭터 시리즈
알렉스가 이미지 한 장 보고 "더 어둡게", "각도 달리", "표정 바꿔" 식 자연어 후속 수정. 같은 thread 안에서 turn 1 = full character spec + base ref attach, turn 2~N = short pose nudge 만. thread context 가 character/style 자동 보존.
- 캐노니컬 패턴: 아래 Python pseudo.
app-server --listen stdio://daemon 과 JSON-RPCthread/start/turn/start로 thread 를 유지하되, 실제 추출은~/.codex/sessions/.../rollout-<thread_id>.jsonl에서extract_imagegen.py로 수행한다. - 검증된 동작 (Phase 0.5): turn 2/3 에서 base ref + style respec 안 박았는데도 character lock 유지 ✓
- Token 절감 ~85% (full prompt 매번 → short nudge 만)
- thread session disk 저장:
~/.codex/sessions/YYYY/MM/DD/rollout-<thread_id>.jsonl.thread/resume으로 다음 세션 이어가기 가능. - core 패턴 (Python pseudo):
daemon = subprocess.Popen(['codex','app-server','--listen','stdio://'], ...) send("initialize", {...}) thread_id = send("thread/start", {"approvalPolicy":"never","sandboxPolicy":{"mode":"workspace-write"},"cwd":"/tmp"}) # turn 1 — full spec + base ref send("turn/start", {"threadId":thread_id, "input":[{"type":"localImage","path":base},{"type":"text","text":FULL_PROMPT}]}) # ... wait for turn idle ... # turn 2~N — short nudge only, no base ref, no style respec send("turn/start", {"threadId":thread_id, "input":[{"type":"text","text":"Now generate same character with <pose nudge>"}]})
C. Fork batch — 미동작 (검증 결과)
Symphony SPEC 의 thread/fork 패턴 + 동시 image_gen 시도했지만 현재 codex CLI 0.125.0-alpha.3 에서 미동작 (Phase 0.6, 0.7 — 0/N PNG). image_gen tool 이 fork thread 에 inherit 안 되거나 daemon sequential 처리 한계. 알파 버전 이슈 가능성. Codex CLI 정식 release 후 재검증 가치 있음. 현재는 batch 가 필요하면 A (parallel exec) 사용.
모드 선택 가이드
| 시나리오 | 모드 |
|---|---|
| 단발 1장 | default single codex exec (이 문서 위) |
| N장 다량 batch (게임 에셋, 카드뉴스) | A. Batch parallel |
| 1장 + 후속 자연어 수정 (반복 수정) | B. Continuation thread |
| N장 batch + character lock 둘 다 | A 사용 (lock 은 base ref 로 유지). C (fork) 미동작 |
When NOT to use
- Hermes / 다른 agent 의 native 이미지 백엔드가 있으면 그쪽 우선.
- 이미 cmux 에 떠있는 codex 팀메이트(다람이/쭈니)가 idle 이면 위임 가능 — 근데 그 팀메이트가 사용자 작업 흐름의 일부면 끼어들지 말 것.
- 기존 이미지 편집/리터칭 — image_gen 은 생성 전용.
- SVG/캔버스로 그릴 수 있는 단순 도형.
Verification checklist
-
codex login status→Logged in using ChatGPT -
--add-dir ~/.codex/generated_images포함 -
--ephemeral안 넣음 (세션 jsonl 이 남아야 추출 가능) -
-C $TMP빈 sandbox dir - session id stdout 에 출력됨
-
extract_imagegen.py "$SID" "$RAW"가OK <RAW> <bytes>출력 (PNG magic 통과 = 실제 파일 생성됨) - 추출 후 세션 jsonl 청소 (
rm -f rollout-*$SID*.jsonl) - (선택)
Read로 PNG 미리보기 → 프롬프트와 매칭 확인 - transparent output 은
scripts/chroma_key_transparent.py로 만든다 - helper stdout 에
mode=RGBA와stale_transparent_rgb_pixels=0 - 밝은 배경 사용 자산은
/tmp/check-white.png로 halo 확인