Add files via upload

This commit is contained in:
mrm987
2025-12-28 12:00:52 +09:00
committed by GitHub
parent 29bd317bb9
commit 1ebe548ac7
2 changed files with 113 additions and 3 deletions
+6
View File
@@ -159,6 +159,9 @@ For each prompt:
| variety | BOOL | False | Enable variety (diverse compositions) |
| decrisper | BOOL | False | Dynamic thresholding (for high CFG) |
| free_only | BOOL | True | Force Opus free conditions |
| reference_image | IMAGE | None | Character reference image |
| char_ref_style_aware | BOOL | True | Copy style along with identity |
| char_ref_fidelity | FLOAT | 1.0 | How strictly to match the character |
| enable_upscale | BOOL | True | Enable local upscale |
| save_prefix | STRING | "NAI" | Output folder name |
@@ -337,6 +340,9 @@ output/[save_prefix]/03_sad_00001.png
| variety | BOOL | False | 다양성 모드 (구도 다양화) |
| decrisper | BOOL | False | Dynamic Thresholding (고CFG용) |
| free_only | BOOL | True | Opus 무료 조건 강제 |
| reference_image | IMAGE | None | 캐릭터 레퍼런스 이미지 |
| char_ref_style_aware | BOOL | True | 스타일도 함께 복사 |
| char_ref_fidelity | FLOAT | 1.0 | 캐릭터 일치도 (높을수록 정확) |
| enable_upscale | BOOL | True | 로컬 업스케일 활성화 |
| save_prefix | STRING | "NAI" | 저장 폴더명 |
+107 -3
View File
@@ -3,6 +3,7 @@ import sys
import subprocess
import importlib
import glob
import io
import torch
import numpy as np
from PIL import Image
@@ -530,7 +531,22 @@ class MultiPromptGenerator:
print(f"[MultiPrompt] Complete! Generated {len(all_images)} images.")
# 마지막 프리뷰는 이미 send_preview로 전송됨
# 마지막 이미지를 UI에 표시
if all_images and enable_preview:
last_image = all_images[-1]
import time
temp_dir = folder_paths.get_temp_directory()
timestamp = int(time.time() * 1000)
preview_filename = f"multiprompt_final_{timestamp}.png"
preview_path = os.path.join(temp_dir, preview_filename)
img = last_image[0].cpu().numpy()
img = (img * 255).astype(np.uint8)
pil_img = Image.fromarray(img)
pil_img.save(preview_path, compress_level=1)
return {"ui": {"images": [{"filename": preview_filename, "subfolder": "", "type": "temp"}]}, "result": (all_images,)}
return {"ui": {"images": []}, "result": (all_images,)}
def _get_next_counter(self, prefix):
@@ -578,6 +594,54 @@ def load_nai_token():
return os.environ.get("NAI_ACCESS_TOKEN", None)
# Character Reference 헬퍼 함수들
ACCEPTED_CR_SIZES = [(1024, 1536), (1536, 1024), (1472, 1472)]
def _choose_cr_canvas(w, h):
"""캐릭터 레퍼런스용 캔버스 크기 선택 (원본 비율에 가장 가까운 것)"""
aspect = w / h
best = None
best_diff = 9e9
for cw, ch in ACCEPTED_CR_SIZES:
diff = abs((cw / ch) - aspect)
if diff < best_diff:
best_diff = diff
best = (cw, ch)
return best
def pad_image_to_canvas(tensor_image, target_size):
"""이미지를 캔버스 크기에 맞게 letterbox 패딩"""
_, H, W, C = tensor_image.shape
tw, th = target_size
arr = (tensor_image[0].cpu().numpy() * 255).clip(0, 255).astype(np.uint8)
mode = "RGBA" if (C == 4) else "RGB"
pil = Image.fromarray(arr)
scale = min(tw / W, th / H)
new_w = max(1, int(W * scale))
new_h = max(1, int(H * scale))
pil_resized = pil.resize((new_w, new_h), Image.LANCZOS)
if mode == "RGBA":
canvas = Image.new("RGBA", (tw, th), (0, 0, 0, 0))
else:
canvas = Image.new("RGB", (tw, th), (0, 0, 0))
offset = ((tw - new_w) // 2, (th - new_h) // 2)
canvas.paste(pil_resized, offset)
out = np.array(canvas).astype(np.float32) / 255.0
return torch.from_numpy(out)[None,]
def image_to_base64(tensor_image):
"""텐서 이미지를 base64로 변환"""
import base64
arr = (tensor_image[0].cpu().numpy() * 255).clip(0, 255).astype(np.uint8)
pil = Image.fromarray(arr)
buffer = io.BytesIO()
pil.save(buffer, format="PNG")
return base64.b64encode(buffer.getvalue()).decode("utf-8")
class NAIMultiPromptGenerator:
"""
NovelAI API를 사용하여 다중 프롬프트 이미지 생성 + 업스케일 + LUT
@@ -612,6 +676,9 @@ class NAIMultiPromptGenerator:
"free_only": ("BOOLEAN", {"default": True}),
"cfg_rescale": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"uncond_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.5, "step": 0.01}),
"reference_image": ("IMAGE",),
"char_ref_style_aware": ("BOOLEAN", {"default": True, "tooltip": "Copy style along with identity"}),
"char_ref_fidelity": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "How strictly to match the character"}),
"lut_name": (get_lut_files(),),
"lut_strength": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 0.01}),
"enable_preview": ("BOOLEAN", {"default": True}),
@@ -629,7 +696,7 @@ class NAIMultiPromptGenerator:
CATEGORY = "image/generation"
OUTPUT_NODE = True
def call_nai_api(self, prompt, negative_prompt, width, height, seed, steps, cfg, sampler, scheduler, smea, nai_model, variety, decrisper, cfg_rescale, uncond_scale):
def call_nai_api(self, prompt, negative_prompt, width, height, seed, steps, cfg, sampler, scheduler, smea, nai_model, variety, decrisper, cfg_rescale, uncond_scale, reference_image=None, char_ref_style_aware=True, char_ref_fidelity=1.0):
"""NAI API 호출"""
import requests
from requests.adapters import HTTPAdapter
@@ -694,6 +761,24 @@ class NAIMultiPromptGenerator:
}
}
# Character Reference 처리
if reference_image is not None:
_, h_raw, w_raw, _ = reference_image.shape
canvas_w, canvas_h = _choose_cr_canvas(w_raw, h_raw)
padded = pad_image_to_canvas(reference_image, (canvas_w, canvas_h))
base_caption = "character&style" if char_ref_style_aware else "character"
params["director_reference_images"] = [image_to_base64(padded)]
params["director_reference_descriptions"] = [{
"use_coords": False,
"use_order": False,
"legacy_uc": False,
"caption": {"base_caption": base_caption, "char_captions": []}
}]
params["director_reference_strength_values"] = [1.0]
params["director_reference_secondary_strength_values"] = [1.0 - char_ref_fidelity]
params["director_reference_information_extracted"] = [1.0]
# k_euler_ancestral + non-native scheduler
if sampler == "k_euler_ancestral" and scheduler != "native":
params["deliberate_euler_ancestral_bug"] = False
@@ -793,6 +878,7 @@ class NAIMultiPromptGenerator:
width, height, seed, steps, cfg, sampler, scheduler, smea, nai_model, save_prefix,
skip_indices="", variety=False, decrisper=False, free_only=True,
cfg_rescale=0.0, uncond_scale=1.0,
reference_image=None, char_ref_style_aware=True, char_ref_fidelity=1.0,
lut_name="None", lut_strength=0.3, enable_preview=True,
unique_id=None, prompt=None, extra_pnginfo=None):
@@ -880,7 +966,8 @@ class NAIMultiPromptGenerator:
image = self.call_nai_api(
full_prompt, negative_prompt, width, height,
current_seed, steps, cfg, sampler, scheduler, smea,
nai_model, variety, decrisper, cfg_rescale, uncond_scale
nai_model, variety, decrisper, cfg_rescale, uncond_scale,
reference_image, char_ref_style_aware, char_ref_fidelity
)
# 프리뷰
@@ -898,6 +985,23 @@ class NAIMultiPromptGenerator:
all_images.append(image)
print(f"[NAI MultiPrompt] Complete! Generated {len(all_images)} images.")
# 마지막 이미지를 UI에 표시
if all_images and enable_preview:
last_image = all_images[-1]
import time
temp_dir = folder_paths.get_temp_directory()
timestamp = int(time.time() * 1000)
preview_filename = f"nai_multiprompt_final_{timestamp}.png"
preview_path = os.path.join(temp_dir, preview_filename)
img = last_image[0].cpu().numpy()
img = (img * 255).astype(np.uint8)
pil_img = Image.fromarray(img)
pil_img.save(preview_path, compress_level=1)
return {"ui": {"images": [{"filename": preview_filename, "subfolder": "", "type": "temp"}]}, "result": (all_images,)}
return {"ui": {"images": []}, "result": (all_images,)}
def _get_next_counter(self, prefix):