Upload comic/imaging.py with huggingface_hub
Browse files- comic/imaging.py +72 -0
comic/imaging.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Assemble a FLUX image prompt for one panel, and a deterministic seed.
|
| 2 |
+
|
| 3 |
+
Character consistency is the hard part of a multi-panel comic: FLUX renders every
|
| 4 |
+
panel independently, so the only lever we have to keep a character looking the same
|
| 5 |
+
across 20 images is to feed its FIXED visual description (from the bible) verbatim
|
| 6 |
+
into every panel it appears in. So a panel prompt is, in order:
|
| 7 |
+
|
| 8 |
+
<art style>, comic book panel, <scene>, <appearance of each character present>,
|
| 9 |
+
<palette>, <text-free contract>
|
| 10 |
+
|
| 11 |
+
The art style + palette are constant across the whole comic (style consistency);
|
| 12 |
+
the per-character appearance is constant per character (character consistency);
|
| 13 |
+
only the scene varies panel to panel (story variety).
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import hashlib
|
| 19 |
+
|
| 20 |
+
from .schema import Panel, ComicBible
|
| 21 |
+
|
| 22 |
+
# FLUX bakes garbled letters if asked for signage; the distilled model ignores
|
| 23 |
+
# negative prompts, so the "no text" contract lives in the positive prompt. The UI
|
| 24 |
+
# renders captions separately, so we never want text inside the image.
|
| 25 |
+
TEXT_FREE = (
|
| 26 |
+
"no text, no speech bubbles, no captions, no lettering or words anywhere in the image"
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
# A sane default if the writer omitted a style/palette.
|
| 30 |
+
DEFAULT_STYLE = (
|
| 31 |
+
"modern western comic book art, bold confident black ink linework, dynamic "
|
| 32 |
+
"cel shading, clean dramatic composition"
|
| 33 |
+
)
|
| 34 |
+
DEFAULT_PALETTE = "rich saturated comic-book color palette"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _appearance_clause(panel: Panel, bible: ComicBible) -> str:
|
| 38 |
+
"""The verbatim fixed appearance text for each named character in this panel.
|
| 39 |
+
|
| 40 |
+
This is the consistency anchor — same words every time a character appears.
|
| 41 |
+
"""
|
| 42 |
+
clauses = []
|
| 43 |
+
for name in panel.characters:
|
| 44 |
+
ch = bible.character(name)
|
| 45 |
+
if ch is not None and ch.appearance:
|
| 46 |
+
clauses.append(f"{ch.name}: {ch.appearance}")
|
| 47 |
+
return "; ".join(clauses)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def build_image_prompt(panel: Panel, bible: ComicBible) -> str:
|
| 51 |
+
"""Full FLUX prompt for one panel (idempotent; also stored on panel.image_prompt)."""
|
| 52 |
+
style = bible.art_style or DEFAULT_STYLE
|
| 53 |
+
palette = bible.palette or DEFAULT_PALETTE
|
| 54 |
+
scene = (panel.scene or "").strip().rstrip(".")
|
| 55 |
+
chars = _appearance_clause(panel, bible)
|
| 56 |
+
|
| 57 |
+
parts = [style, "comic book panel", scene]
|
| 58 |
+
if chars:
|
| 59 |
+
parts.append(chars)
|
| 60 |
+
parts += [palette, TEXT_FREE]
|
| 61 |
+
return ", ".join(p for p in parts if p and p.strip())
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def panel_seed(bible: ComicBible, panel: Panel) -> int:
|
| 65 |
+
"""Deterministic per-panel seed (stable across reruns, distinct per panel).
|
| 66 |
+
|
| 67 |
+
Keyed on the title + panel index so the same comic re-renders identically and
|
| 68 |
+
no two panels collide on a seed.
|
| 69 |
+
"""
|
| 70 |
+
key = f"{bible.title}|{panel.index}".encode("utf-8")
|
| 71 |
+
digest = hashlib.sha256(key).digest()
|
| 72 |
+
return int.from_bytes(digest[:4], "big") & 0x7FFFFFFF
|