diff --git a/README.md b/README.md index b81ee86..7153c31 100644 --- a/README.md +++ b/README.md @@ -63,14 +63,17 @@ Civitai / Hugging Face and put it in `models/loras/`. ### Krea 2 Reference Encode (`TextEncodeKrea2Reference`) -Prompt + up to 4 reference images. Refer to them as `Picture 1..4` (or -`image1..4` — auto-normalized). Images connected but not mentioned get a role -clause appended automatically. +Prompt + up to 4 reference images. **Roles live in the prompt**: write +`character1..4` for identity references and `style1..4` for style-only +references (their patches are shuffled so the subject can't leak; `characterN` +wins if both appear). Both expand to the exact clauses the LoRA trains on — +`"The character1 driving a car in the style2"` is a complete prompt. Plain +`image1..4` / `Picture 1..4` also work; connected images never mentioned get a +character clause appended automatically. | Input | Description | |---|---| | `imageN_detail` | vision tokens per ref: low/normal/high/max = 256/512/1024/1280px. High for characters, low for style/pose hints | -| `imageN_role` | `content` (identity) or `style` (32px grid patch-shuffle: destroys the subject, keeps the style) | | `imageN_mask` | crop to mask bbox + white-out background before encoding | | `bg_removal` | wire `LoadBackgroundRemovalModel`: auto-cleans content refs | | `layout_split` | first fraction of steps conditioned on text only (0.25 kills collage layouts zero-shot; unnecessary with the LoRA) | diff --git a/__init__.py b/__init__.py index b391ac4..cda618e 100644 --- a/__init__.py +++ b/__init__.py @@ -21,10 +21,6 @@ DETAIL = {"low": 256, "normal": 512, "high": 1024, "max": 1280} DETAIL_TIP = ("Longest side this reference is scaled to before the vision encoder. Higher = more " "vision tokens = stronger, more literal reference. Use high for the character/subject " "and low/normal for pose or style hints to avoid side-by-side collages.") -ROLE_TIP = ("content: the image is used literally (character, subject, pose). " - "style: its patches are shuffled before the vision encoder, destroying the subject's " - "shape so only palette/linework/texture transfer — prevents the style image's subject " - "from appearing in the output.") SPLIT_TIP = ("Fraction of the denoise steps that see TEXT-ONLY conditioning before the references " "kick in. The first steps fix the layout, so 0.25 strongly prevents side-by-side " "collages of the references. 0 disables.") @@ -89,8 +85,8 @@ EDIT_TEMPLATE = ( _IMG_WORD = re.compile(r"\b(?:image|img|imagen|picture)[ _]?([1-4])\b", re.IGNORECASE) # Role-typed shorthand: "character1" / "style2" expand to the exact clauses the -# reference LoRA trains on, and "styleN" additionally forces that slot's role to -# style (patch shuffle) — the prompt wins over the widget. +# reference LoRA trains on. Roles are prompt-driven: styleN shuffles that slot's +# patches (style-only transfer); characterN keeps it literal and wins over styleN. _CHAR_WORD = re.compile(r"\b(?:character|char|personaje)[ _]?([1-4])\b", re.IGNORECASE) _STYLE_WORD = re.compile(r"\b(?:style|estilo)[ _]?([1-4])\b", re.IGNORECASE) @@ -98,11 +94,16 @@ _STYLE_WORD = re.compile(r"\b(?:style|estilo)[ _]?([1-4])\b", re.IGNORECASE) def expand_role_tokens(prompt): """'The character1 in a car in the style2' -> 'The character from Picture 1 in a car in the exact art style of Picture 2'. - Returns (text, slots_forced_to_style).""" + Returns (text, roles): roles are prompt-driven — styleN marks the slot as + style (patch shuffle); characterN marks it content and wins if both appear + for the same slot (identity needs the unshuffled image).""" style_slots = {int(m.group(1)) - 1 for m in _STYLE_WORD.finditer(prompt)} + char_slots = {int(m.group(1)) - 1 for m in _CHAR_WORD.finditer(prompt)} text = _CHAR_WORD.sub(lambda m: "character from Picture " + m.group(1), prompt) text = _STYLE_WORD.sub(lambda m: "exact art style of Picture " + m.group(1), text) - return text, style_slots + roles = tuple("style" if i in style_slots and i not in char_slots else "content" + for i in range(4)) + return text, roles # Auto-added when the prompt doesn't mention a connected image. Mirrors the @@ -142,26 +143,22 @@ class TextEncodeKrea2Reference: "prompt": ("STRING", { "multiline": True, "dynamicPrompts": True, - "tooltip": "Shorthand: character1..4 / style1..4 expand to the trained clauses (styleN also sets that slot to style role). Or refer to refs as image1..4 / Picture 1..4, " + "tooltip": "Roles come from the prompt: character1..4 = identity reference, style1..4 = style-only (patches shuffled). Both expand to the clauses the LoRA trains on. Plain image1..4 / Picture 1..4 also work, " "e.g. 'Redraw the character from image1 in the pose of image2'.", }), }, "optional": { "image1": ("IMAGE",), "image1_detail": (list(DETAIL), {"default": "high", "tooltip": DETAIL_TIP}), - "image1_role": (["content", "style"], {"default": "content", "tooltip": ROLE_TIP}), "image1_mask": ("MASK", {"tooltip": MASK_TIP}), "image2": ("IMAGE",), "image2_detail": (list(DETAIL), {"default": "high", "tooltip": DETAIL_TIP}), - "image2_role": (["content", "style"], {"default": "content", "tooltip": ROLE_TIP}), "image2_mask": ("MASK", {"tooltip": MASK_TIP}), "image3": ("IMAGE",), "image3_detail": (list(DETAIL), {"default": "high", "tooltip": DETAIL_TIP}), - "image3_role": (["content", "style"], {"default": "content", "tooltip": ROLE_TIP}), "image3_mask": ("MASK", {"tooltip": MASK_TIP}), "image4": ("IMAGE",), "image4_detail": (list(DETAIL), {"default": "high", "tooltip": DETAIL_TIP}), - "image4_role": (["content", "style"], {"default": "content", "tooltip": ROLE_TIP}), "image4_mask": ("MASK", {"tooltip": MASK_TIP}), "bg_removal": ("BACKGROUND_REMOVAL", {"tooltip": BG_TIP}), "layout_split": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 0.9, "step": 0.05, "tooltip": SPLIT_TIP}), @@ -177,15 +174,12 @@ class TextEncodeKrea2Reference: def encode(self, clip, prompt, image1=None, image2=None, image3=None, image4=None, image1_detail="high", image2_detail="high", image3_detail="high", image4_detail="high", - image1_role="content", image2_role="content", image3_role="content", image4_role="content", image1_mask=None, image2_mask=None, image3_mask=None, image4_mask=None, bg_removal=None, layout_split=0.0): slots = (image1, image2, image3, image4) details = (image1_detail, image2_detail, image3_detail, image4_detail) masks = (image1_mask, image2_mask, image3_mask, image4_mask) - prompt, style_slots = expand_role_tokens(prompt) - roles = tuple("style" if i in style_slots else r - for i, r in enumerate((image1_role, image2_role, image3_role, image4_role))) + prompt, roles = expand_role_tokens(prompt) full_prompt, connected = build_image_prompt(prompt, slots, roles) images_vl = []