comicx / comic /imaging.py
ASTRALK's picture
Upload comic/imaging.py with huggingface_hub
15dcc39 verified
Raw
History Blame Contribute Delete
2.83 kB
"""Assemble a FLUX image prompt for one panel, and a deterministic seed.
Character consistency is the hard part of a multi-panel comic: FLUX renders every
panel independently, so the only lever we have to keep a character looking the same
across 20 images is to feed its FIXED visual description (from the bible) verbatim
into every panel it appears in. So a panel prompt is, in order:
<art style>, comic book panel, <scene>, <appearance of each character present>,
<palette>, <text-free contract>
The art style + palette are constant across the whole comic (style consistency);
the per-character appearance is constant per character (character consistency);
only the scene varies panel to panel (story variety).
"""
from __future__ import annotations
import hashlib
from .schema import Panel, ComicBible
# FLUX bakes garbled letters if asked for signage; the distilled model ignores
# negative prompts, so the "no text" contract lives in the positive prompt. The UI
# renders captions separately, so we never want text inside the image.
TEXT_FREE = (
"no text, no speech bubbles, no captions, no lettering or words anywhere in the image"
)
# A sane default if the writer omitted a style/palette.
DEFAULT_STYLE = (
"modern western comic book art, bold confident black ink linework, dynamic "
"cel shading, clean dramatic composition"
)
DEFAULT_PALETTE = "rich saturated comic-book color palette"
def _appearance_clause(panel: Panel, bible: ComicBible) -> str:
"""The verbatim fixed appearance text for each named character in this panel.
This is the consistency anchor — same words every time a character appears.
"""
clauses = []
for name in panel.characters:
ch = bible.character(name)
if ch is not None and ch.appearance:
clauses.append(f"{ch.name}: {ch.appearance}")
return "; ".join(clauses)
def build_image_prompt(panel: Panel, bible: ComicBible) -> str:
"""Full FLUX prompt for one panel (idempotent; also stored on panel.image_prompt)."""
style = bible.art_style or DEFAULT_STYLE
palette = bible.palette or DEFAULT_PALETTE
scene = (panel.scene or "").strip().rstrip(".")
chars = _appearance_clause(panel, bible)
parts = [style, "comic book panel", scene]
if chars:
parts.append(chars)
parts += [palette, TEXT_FREE]
return ", ".join(p for p in parts if p and p.strip())
def panel_seed(bible: ComicBible, panel: Panel) -> int:
"""Deterministic per-panel seed (stable across reruns, distinct per panel).
Keyed on the title + panel index so the same comic re-renders identically and
no two panels collide on a seed.
"""
key = f"{bible.title}|{panel.index}".encode("utf-8")
digest = hashlib.sha256(key).digest()
return int.from_bytes(digest[:4], "big") & 0x7FFFFFFF