810 lines
32 KiB
Python
810 lines
32 KiB
Python
import torch
|
|
import numpy as np
|
|
from PIL import Image
|
|
import io
|
|
import base64
|
|
from server import PromptServer
|
|
import threading
|
|
from aiohttp import web
|
|
import json
|
|
import torch.nn.functional as F
|
|
image_cache = {}
|
|
event_dict = {}
|
|
|
|
@PromptServer.instance.routes.post("/lg_relight/update_image")
|
|
async def update_image(request):
|
|
try:
|
|
data = await request.json()
|
|
node_id = data.get("node_id")
|
|
image_data = data.get("image")
|
|
|
|
if node_id and image_data:
|
|
image_cache[node_id] = image_data
|
|
if node_id in event_dict:
|
|
event_dict[node_id].set()
|
|
return web.Response(text=json.dumps({"status": "success"}))
|
|
else:
|
|
return web.Response(status=400, text=json.dumps({"error": "Invalid data"}))
|
|
except Exception as e:
|
|
return web.Response(status=500, text=json.dumps({"error": str(e)}))
|
|
|
|
@PromptServer.instance.routes.post("/lg_relight/window_closed")
|
|
async def window_closed(request):
|
|
try:
|
|
data = await request.json()
|
|
node_id = data.get("node_id")
|
|
original_image = data.get("original_image")
|
|
|
|
if node_id:
|
|
if original_image:
|
|
image_cache[node_id] = original_image
|
|
if node_id in event_dict:
|
|
event_dict[node_id].set()
|
|
return web.Response(text=json.dumps({"status": "success"}))
|
|
except Exception as e:
|
|
return web.Response(status=500, text=json.dumps({"error": str(e)}))
|
|
|
|
class LG_Relight_V2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"normals": ("IMAGE",),
|
|
"mask": ("MASK",),
|
|
},
|
|
"hidden": {
|
|
"unique_id": "UNIQUE_ID",
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "relight"
|
|
CATEGORY = "🎈LAOGOU"
|
|
OUTPUT_NODE = True
|
|
|
|
def encode_image_to_base64(self, image, is_mask=False):
|
|
image = (image * 255).clip(0, 255).astype(np.uint8)
|
|
|
|
if is_mask:
|
|
if len(image.shape) == 3:
|
|
image = image[0]
|
|
image = np.stack([image] * 3, axis=-1)
|
|
else:
|
|
if len(image.shape) == 4:
|
|
image = image[0]
|
|
|
|
image = Image.fromarray(image)
|
|
buffered = io.BytesIO()
|
|
image.save(buffered, format="PNG")
|
|
return base64.b64encode(buffered.getvalue()).decode()
|
|
|
|
def relight(self, image, normals, mask, unique_id):
|
|
try:
|
|
print(f"[DEBUG] 开始处理 relight 请求: node_id={unique_id}")
|
|
print(f"[DEBUG] 输入图像形状: image={image.shape}, normals={normals.shape}, mask={mask.shape}")
|
|
|
|
event = threading.Event()
|
|
event_dict[unique_id] = event
|
|
print(f"[DEBUG] 创建事件对象: event_id={id(event)}")
|
|
|
|
# 转换图像为base64
|
|
print("[DEBUG] 开始转换图像为base64...")
|
|
image_b64 = self.encode_image_to_base64(image.cpu().numpy()[0])
|
|
normals_b64 = self.encode_image_to_base64(normals.cpu().numpy()[0])
|
|
mask_b64 = self.encode_image_to_base64(mask.cpu().numpy(), is_mask=True)
|
|
print("[DEBUG] 图像转换完成")
|
|
|
|
# 准备发送数据
|
|
send_data = {
|
|
"node_id": unique_id,
|
|
"image": f"data:image/png;base64,{image_b64}",
|
|
"normals": f"data:image/png;base64,{normals_b64}",
|
|
"mask": f"data:image/png;base64,{mask_b64}"
|
|
}
|
|
print(f"[DEBUG] 准备发送数据到前端: node_id={unique_id}, data_size={len(str(send_data))}")
|
|
|
|
# 发送数据到前端
|
|
print("[DEBUG] 发送数据到前端...")
|
|
PromptServer.instance.send_sync("lg_relight_init", send_data)
|
|
print("[DEBUG] 数据发送完成,等待前端响应...")
|
|
|
|
# 等待前端响应
|
|
event.wait()
|
|
print(f"[DEBUG] 收到前端响应: node_id={unique_id}")
|
|
del event_dict[unique_id]
|
|
|
|
# 处理返回的图像
|
|
if unique_id in image_cache:
|
|
print(f"[DEBUG] 找到缓存的图像: node_id={unique_id}")
|
|
img_data = base64.b64decode(image_cache[unique_id].split(",")[1])
|
|
img = Image.open(io.BytesIO(img_data))
|
|
img_np = np.array(img).astype(np.float32) / 255.0
|
|
|
|
print(f"[DEBUG] 处理后的图像形状: {img_np.shape}")
|
|
|
|
if len(img_np.shape) == 2:
|
|
print("[DEBUG] 转换灰度图为RGB")
|
|
img_np = np.stack([img_np] * 3, axis=-1)
|
|
elif len(img_np.shape) == 3 and img_np.shape[-1] == 4:
|
|
print("[DEBUG] 移除alpha通道")
|
|
img_np = img_np[..., :3]
|
|
|
|
result = torch.from_numpy(img_np).unsqueeze(0)
|
|
print(f"[DEBUG] 最终输出图像形状: {result.shape}")
|
|
|
|
del image_cache[unique_id]
|
|
return (result,)
|
|
else:
|
|
print(f"[DEBUG] 未找到缓存的图像,返回原始图像: node_id={unique_id}")
|
|
return (image,)
|
|
|
|
except Exception as e:
|
|
print(f"[ERROR] relight处理发生错误: {str(e)}")
|
|
import traceback
|
|
print(traceback.format_exc())
|
|
return (image,)
|
|
finally:
|
|
print(f"[DEBUG] 清理资源: node_id={unique_id}")
|
|
if unique_id in event_dict:
|
|
del event_dict[unique_id]
|
|
if unique_id in image_cache:
|
|
del image_cache[unique_id]
|
|
|
|
|
|
class LG_Relight_Basic:
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"normals": ("IMAGE",),
|
|
"x": ("FLOAT", {
|
|
"display": "slider",
|
|
"default": 0.0,
|
|
"min": -1.0,
|
|
"max": 1.0,
|
|
"step": 0.001
|
|
}),
|
|
"y": ("FLOAT", {
|
|
"display": "slider",
|
|
"default": 0.0,
|
|
"min": -1.0,
|
|
"max": 1.0,
|
|
"step": 0.001
|
|
}),
|
|
"z": ("FLOAT", {
|
|
"display": "slider",
|
|
"default": 1.0,
|
|
"min": -1.0,
|
|
"max": 1.0,
|
|
"step": 0.001
|
|
}),
|
|
"brightness": ("FLOAT", {
|
|
"display": "slider",
|
|
"default": 1.0,
|
|
"min": 0.0,
|
|
"max": 3.0,
|
|
"step": 0.001
|
|
}),
|
|
"shadow_range": ("FLOAT", {
|
|
"display": "slider",
|
|
"default": 1.0,
|
|
"min": 0.0,
|
|
"max": 2.0,
|
|
"step": 0.001
|
|
}),
|
|
"shadow_strength": ("FLOAT", {
|
|
"display": "slider",
|
|
"default": 1.0,
|
|
"min": 0.0,
|
|
"max": 2.0,
|
|
"step": 0.001
|
|
}),
|
|
"highlight_range": ("FLOAT", {
|
|
"display": "slider",
|
|
"default": 1.0,
|
|
"min": 0.0,
|
|
"max": 2.0,
|
|
"step": 0.001
|
|
}),
|
|
"highlight_strength": ("FLOAT", {
|
|
"display": "slider",
|
|
"default": 1.0,
|
|
"min": 0.0,
|
|
"max": 2.0,
|
|
"step": 0.001
|
|
}),
|
|
"highlight_color": ("COLOR", {"default": "#FFFFFF"}),
|
|
"shadow_color": ("COLOR", {"default": "#000000"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
OUTPUT_NODE = True
|
|
FUNCTION = "relight"
|
|
CATEGORY = "🎈LAOGOU"
|
|
|
|
def relight(self, image, normals, x, y, z, brightness,
|
|
shadow_range, shadow_strength, highlight_range, highlight_strength,
|
|
highlight_color, shadow_color):
|
|
|
|
# 将十六进制颜色转换为RGB值
|
|
def hex_to_rgb(hex_color):
|
|
hex_color = hex_color.lstrip('#')
|
|
return [int(hex_color[i:i+2], 16)/255.0 for i in (0, 2, 4)]
|
|
|
|
highlight_color = hex_to_rgb(highlight_color)
|
|
shadow_color = hex_to_rgb(shadow_color)
|
|
|
|
norm = normals.detach().clone() * 2 - 1
|
|
norm = F.interpolate(norm.movedim(-1,1),
|
|
size=(image.shape[1], image.shape[2]),
|
|
mode='bilinear').movedim(1,-1)
|
|
|
|
light = torch.tensor([x, y, z], device=image.device)
|
|
light = F.normalize(light, dim=0)
|
|
|
|
diffuse = norm[:,:,:,0] * light[0] + norm[:,:,:,1] * light[1] + norm[:,:,:,2] * light[2]
|
|
diffuse = (diffuse + 1.0) * 0.5
|
|
|
|
shadow_offset = shadow_strength - 1.0
|
|
highlight_offset = highlight_strength - 1.0
|
|
|
|
shadow_threshold = 1.0 - shadow_range
|
|
highlight_threshold = 1.0 - highlight_range
|
|
|
|
shadow_mask = torch.clamp((diffuse - shadow_threshold) / max(shadow_range, 1e-6), 0, 1)
|
|
highlight_mask = torch.clamp((diffuse - highlight_threshold) / max(highlight_range, 1e-6), 0, 1)
|
|
|
|
light_intensity = torch.ones_like(diffuse)
|
|
|
|
if shadow_strength != 1.0:
|
|
light_intensity = light_intensity * (
|
|
shadow_mask +
|
|
(1.0 - shadow_mask) * (2.0 - shadow_strength)
|
|
)
|
|
|
|
if highlight_strength != 1.0:
|
|
light_intensity = light_intensity + highlight_mask * highlight_offset
|
|
|
|
color_effect = torch.ones_like(image[:,:,:,:3])
|
|
if highlight_color != [1.0, 1.0, 1.0] or shadow_color != [0.0, 0.0, 0.0]:
|
|
highlight_color = torch.tensor(highlight_color, device=image.device)
|
|
shadow_color = torch.tensor(shadow_color, device=image.device)
|
|
color_effect = (
|
|
shadow_mask.unsqueeze(-1) * highlight_color +
|
|
(1.0 - shadow_mask).unsqueeze(-1) * shadow_color
|
|
)
|
|
|
|
brightness_factor = brightness if brightness != 1.0 else 1.0
|
|
|
|
relit = image.detach().clone()
|
|
light_intensity = light_intensity.unsqueeze(-1).repeat(1,1,1,3)
|
|
|
|
relit[:,:,:,:3] = torch.clip(
|
|
relit[:,:,:,:3] * light_intensity * brightness_factor * color_effect,
|
|
0, 1
|
|
)
|
|
|
|
return (relit,)
|
|
|
|
|
|
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout,
|
|
QSlider, QLabel, QPushButton, QColorDialog)
|
|
from PyQt5.QtCore import Qt
|
|
from PyQt5.QtGui import QImage, QPixmap
|
|
|
|
class LightPreviewDialog(QDialog):
|
|
def __init__(self, initial_values=None, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle("LG_Relight")
|
|
self.setMinimumSize(512, 600)
|
|
|
|
self.current_values = initial_values.copy() if initial_values else {
|
|
'x': 0.0,
|
|
'y': 0.0,
|
|
'z': 1.0,
|
|
'brightness': 1.0,
|
|
'shadow_range': 1.0,
|
|
'shadow_strength': 1.0,
|
|
'highlight_range': 1.0,
|
|
'highlight_strength': 1.0,
|
|
'highlight_color': [1.0, 1.0, 1.0],
|
|
'shadow_color': [0.0, 0.0, 0.0]
|
|
}
|
|
layout = QVBoxLayout()
|
|
|
|
self.preview_label = ImageLabel()
|
|
self.preview_label.setMinimumSize(512, 512)
|
|
self.preview_label.setAlignment(Qt.AlignCenter)
|
|
self.preview_label.mouseMoveHandler = self.on_image_mouse_move
|
|
layout.addWidget(self.preview_label)
|
|
|
|
|
|
sliders_layout = QVBoxLayout()
|
|
|
|
SLIDER_WIDTH = 300
|
|
|
|
z_layout = QHBoxLayout()
|
|
z_layout.addWidget(QLabel("Z 轴:"))
|
|
self.z_slider = QSlider(Qt.Horizontal)
|
|
self.z_slider.setFixedWidth(SLIDER_WIDTH)
|
|
self.z_slider.setRange(-1000, 1000)
|
|
self.z_slider.setValue(1000)
|
|
|
|
z_layout.addWidget(self.z_slider)
|
|
self.z_value = QLabel("1.0")
|
|
z_layout.addWidget(self.z_value)
|
|
reset_z = QPushButton("重置")
|
|
|
|
z_layout.addWidget(reset_z)
|
|
sliders_layout.addLayout(z_layout)
|
|
|
|
|
|
shadow_range_layout = QHBoxLayout()
|
|
shadow_range_layout.addWidget(QLabel("阴影范围:"))
|
|
self.shadow_range_slider = QSlider(Qt.Horizontal)
|
|
self.shadow_range_slider.setFixedWidth(SLIDER_WIDTH)
|
|
self.shadow_range_slider.setRange(0, 200)
|
|
self.shadow_range_slider.setValue(100)
|
|
|
|
shadow_range_layout.addWidget(self.shadow_range_slider)
|
|
self.shadow_range_value = QLabel("1.0")
|
|
shadow_range_layout.addWidget(self.shadow_range_value)
|
|
reset_sr = QPushButton("重置")
|
|
|
|
shadow_range_layout.addWidget(reset_sr)
|
|
sliders_layout.addLayout(shadow_range_layout)
|
|
|
|
shadow_strength_layout = QHBoxLayout()
|
|
shadow_strength_layout.addWidget(QLabel("阴影强度:"))
|
|
self.shadow_strength_slider = QSlider(Qt.Horizontal)
|
|
self.shadow_strength_slider.setFixedWidth(SLIDER_WIDTH)
|
|
self.shadow_strength_slider.setRange(0, 200)
|
|
self.shadow_strength_slider.setValue(100)
|
|
|
|
shadow_strength_layout.addWidget(self.shadow_strength_slider)
|
|
self.shadow_strength_value = QLabel("1.0")
|
|
shadow_strength_layout.addWidget(self.shadow_strength_value)
|
|
reset_ss = QPushButton("重置")
|
|
|
|
shadow_strength_layout.addWidget(reset_ss)
|
|
sliders_layout.addLayout(shadow_strength_layout)
|
|
|
|
highlight_range_layout = QHBoxLayout()
|
|
highlight_range_layout.addWidget(QLabel("高光范围:"))
|
|
self.highlight_range_slider = QSlider(Qt.Horizontal)
|
|
self.highlight_range_slider.setFixedWidth(SLIDER_WIDTH)
|
|
self.highlight_range_slider.setRange(0, 200)
|
|
self.highlight_range_slider.setValue(100)
|
|
|
|
highlight_range_layout.addWidget(self.highlight_range_slider)
|
|
self.highlight_range_value = QLabel("1.0")
|
|
highlight_range_layout.addWidget(self.highlight_range_value)
|
|
reset_hr = QPushButton("重置")
|
|
|
|
highlight_range_layout.addWidget(reset_hr)
|
|
sliders_layout.addLayout(highlight_range_layout)
|
|
|
|
highlight_strength_layout = QHBoxLayout()
|
|
highlight_strength_layout.addWidget(QLabel("高光强度:"))
|
|
self.highlight_strength_slider = QSlider(Qt.Horizontal)
|
|
self.highlight_strength_slider.setFixedWidth(SLIDER_WIDTH)
|
|
self.highlight_strength_slider.setRange(0, 200)
|
|
self.highlight_strength_slider.setValue(100)
|
|
highlight_strength_layout.addWidget(self.highlight_strength_slider)
|
|
self.highlight_strength_value = QLabel("1.0")
|
|
highlight_strength_layout.addWidget(self.highlight_strength_value)
|
|
reset_hs = QPushButton("重置")
|
|
|
|
highlight_strength_layout.addWidget(reset_hs)
|
|
sliders_layout.addLayout(highlight_strength_layout)
|
|
|
|
brightness_layout = QHBoxLayout()
|
|
brightness_layout.addWidget(QLabel("亮度:"))
|
|
self.brightness_slider = QSlider(Qt.Horizontal)
|
|
self.brightness_slider.setFixedWidth(SLIDER_WIDTH)
|
|
self.brightness_slider.setRange(0, 300)
|
|
self.brightness_slider.setValue(100)
|
|
|
|
brightness_layout.addWidget(self.brightness_slider)
|
|
self.brightness_value = QLabel("1.0")
|
|
brightness_layout.addWidget(self.brightness_value)
|
|
reset_b = QPushButton("重置")
|
|
reset_b.clicked.connect(lambda: self.brightness_slider.setValue(100))
|
|
brightness_layout.addWidget(reset_b)
|
|
sliders_layout.addLayout(brightness_layout)
|
|
|
|
layout.addLayout(sliders_layout)
|
|
|
|
color_layout = QHBoxLayout()
|
|
|
|
|
|
highlight_color_layout = QHBoxLayout()
|
|
highlight_color_layout.addWidget(QLabel("高光颜色:"))
|
|
self.highlight_color_button = QPushButton()
|
|
self.highlight_color_button.setFixedSize(50, 25)
|
|
self.set_button_color(self.highlight_color_button, self.current_values['highlight_color'])
|
|
|
|
highlight_color_layout.addWidget(self.highlight_color_button)
|
|
|
|
highlight_reset_button = QPushButton("重置")
|
|
highlight_reset_button.setFixedSize(50, 25)
|
|
|
|
highlight_color_layout.addWidget(highlight_reset_button)
|
|
|
|
color_layout.addLayout(highlight_color_layout)
|
|
|
|
color_layout.addSpacing(20)
|
|
|
|
shadow_color_layout = QHBoxLayout()
|
|
shadow_color_layout.addWidget(QLabel("阴影颜色:"))
|
|
self.shadow_color_button = QPushButton()
|
|
self.shadow_color_button.setFixedSize(50, 25)
|
|
self.set_button_color(self.shadow_color_button, self.current_values['shadow_color'])
|
|
|
|
shadow_color_layout.addWidget(self.shadow_color_button)
|
|
|
|
shadow_reset_button = QPushButton("重置")
|
|
shadow_reset_button.setFixedSize(50, 25)
|
|
|
|
shadow_color_layout.addWidget(shadow_reset_button)
|
|
|
|
color_layout.addLayout(shadow_color_layout)
|
|
|
|
layout.addLayout(color_layout)
|
|
|
|
self.ok_button = QPushButton("确定")
|
|
|
|
layout.addWidget(self.ok_button)
|
|
|
|
self.z_slider.valueChanged.connect(lambda x: self.on_slider_changed())
|
|
self.shadow_range_slider.valueChanged.connect(lambda x: self.on_slider_changed())
|
|
self.shadow_strength_slider.valueChanged.connect(lambda x: self.on_slider_changed())
|
|
self.highlight_range_slider.valueChanged.connect(lambda x: self.on_slider_changed())
|
|
self.highlight_strength_slider.valueChanged.connect(lambda x: self.on_slider_changed())
|
|
self.brightness_slider.valueChanged.connect(lambda x: self.on_slider_changed())
|
|
|
|
reset_z.clicked.connect(lambda x: self.z_slider.setValue(1000))
|
|
reset_sr.clicked.connect(lambda x: self.shadow_range_slider.setValue(100))
|
|
reset_ss.clicked.connect(lambda x: self.shadow_strength_slider.setValue(100))
|
|
reset_hr.clicked.connect(lambda x: self.highlight_range_slider.setValue(100))
|
|
reset_hs.clicked.connect(lambda x: self.highlight_strength_slider.setValue(100))
|
|
reset_b.clicked.connect(lambda x: self.brightness_slider.setValue(100))
|
|
|
|
self.highlight_color_button.clicked.connect(lambda x: self.on_highlight_color_click())
|
|
self.shadow_color_button.clicked.connect(lambda x: self.on_shadow_color_click())
|
|
highlight_reset_button.clicked.connect(lambda x: self.reset_highlight_color())
|
|
shadow_reset_button.clicked.connect(lambda x: self.reset_shadow_color())
|
|
|
|
self.ok_button.clicked.connect(lambda x: self.accept())
|
|
|
|
self.setLayout(layout)
|
|
|
|
self.x_slider = QSlider(Qt.Horizontal)
|
|
self.y_slider = QSlider(Qt.Horizontal)
|
|
self.x_slider.setRange(-1000, 1000)
|
|
self.y_slider.setRange(-1000, 1000)
|
|
self.x_slider.hide()
|
|
self.y_slider.hide()
|
|
|
|
if initial_values:
|
|
|
|
self.z_slider.setValue(int(initial_values['z'] * 1000))
|
|
self.brightness_slider.setValue(int(initial_values['brightness'] * 100))
|
|
self.shadow_range_slider.setValue(int((2.0 - initial_values['shadow_range']) * 100))
|
|
self.shadow_strength_slider.setValue(int(initial_values['shadow_strength'] * 100))
|
|
self.highlight_range_slider.setValue(int(initial_values['highlight_range'] * 100))
|
|
self.highlight_strength_slider.setValue(int(initial_values['highlight_strength'] * 100))
|
|
|
|
self.x_slider.setValue(int(initial_values['x'] * 1000))
|
|
self.y_slider.setValue(int(initial_values['y'] * 1000))
|
|
|
|
self.z_value.setText(f"{initial_values['z']:.3f}")
|
|
self.brightness_value.setText(f"{initial_values['brightness']:.3f}")
|
|
self.shadow_range_value.setText(f"{2.0 - initial_values['shadow_range']:.3f}")
|
|
self.shadow_strength_value.setText(f"{initial_values['shadow_strength']:.3f}")
|
|
self.highlight_range_value.setText(f"{initial_values['highlight_range']:.3f}")
|
|
self.highlight_strength_value.setText(f"{initial_values['highlight_strength']:.3f}")
|
|
|
|
self.set_button_color(self.highlight_color_button, initial_values['highlight_color'])
|
|
self.set_button_color(self.shadow_color_button, initial_values['shadow_color'])
|
|
|
|
def reset_highlight_color(self):
|
|
self.current_values['highlight_color'] = [1.0, 1.0, 1.0]
|
|
self.set_button_color(self.highlight_color_button, self.current_values['highlight_color'])
|
|
self.on_value_changed()
|
|
|
|
def reset_shadow_color(self):
|
|
self.current_values['shadow_color'] = [0.0, 0.0, 0.0]
|
|
self.set_button_color(self.shadow_color_button, self.current_values['shadow_color'])
|
|
self.on_value_changed()
|
|
|
|
def set_button_color(self, button, color):
|
|
r, g, b = [int(c * 255) for c in color]
|
|
button.setStyleSheet(f"background-color: rgb({r},{g},{b})")
|
|
|
|
def on_highlight_color_click(self):
|
|
color = QColorDialog.getColor()
|
|
if color.isValid():
|
|
self.current_values['highlight_color'] = [c/255.0 for c in color.getRgb()[:3]]
|
|
self.set_button_color(self.highlight_color_button, self.current_values['highlight_color'])
|
|
self.on_value_changed()
|
|
|
|
def on_shadow_color_click(self):
|
|
color = QColorDialog.getColor()
|
|
if color.isValid():
|
|
self.current_values['shadow_color'] = [c/255.0 for c in color.getRgb()[:3]]
|
|
self.set_button_color(self.shadow_color_button, self.current_values['shadow_color'])
|
|
self.on_value_changed()
|
|
|
|
def on_value_changed(self):
|
|
if hasattr(self, 'update_preview'):
|
|
self.update_preview(
|
|
self.current_values['x'],
|
|
self.current_values['y'],
|
|
self.current_values['z'],
|
|
self.current_values['brightness'],
|
|
self.current_values['shadow_range'],
|
|
self.current_values['shadow_strength'],
|
|
self.current_values['highlight_range'],
|
|
self.current_values['highlight_strength'],
|
|
self.current_values['highlight_color'],
|
|
self.current_values['shadow_color']
|
|
)
|
|
def on_slider_changed(self):
|
|
|
|
x = self.x_slider.value() / 1000.0
|
|
y = self.y_slider.value() / 1000.0
|
|
z = self.z_slider.value() / 1000.0
|
|
brightness = self.brightness_slider.value() / 100.0
|
|
|
|
|
|
raw_shadow_range = self.shadow_range_slider.value() / 100.0
|
|
shadow_range = 2.0 - raw_shadow_range
|
|
|
|
shadow_strength = self.shadow_strength_slider.value() / 100.0
|
|
highlight_range = self.highlight_range_slider.value() / 100.0
|
|
highlight_strength = self.highlight_strength_slider.value() / 100.0
|
|
|
|
|
|
self.z_value.setText(f"{z:.3f}")
|
|
self.brightness_value.setText(f"{brightness:.3f}")
|
|
self.shadow_range_value.setText(f"{raw_shadow_range:.3f}")
|
|
self.shadow_strength_value.setText(f"{shadow_strength:.3f}")
|
|
self.highlight_range_value.setText(f"{highlight_range:.3f}")
|
|
self.highlight_strength_value.setText(f"{highlight_strength:.3f}")
|
|
|
|
# 更新存储的值
|
|
self.current_values.update({
|
|
'x': x,
|
|
'y': y,
|
|
'z': z,
|
|
'brightness': brightness,
|
|
'shadow_range': shadow_range,
|
|
'shadow_strength': shadow_strength,
|
|
'highlight_range': highlight_range,
|
|
'highlight_strength': highlight_strength
|
|
})
|
|
|
|
self.on_value_changed()
|
|
|
|
def set_preview_image(self, image_tensor):
|
|
if image_tensor is None:
|
|
return
|
|
|
|
image_np = (image_tensor[0].cpu().numpy() * 255).astype(np.uint8)
|
|
height, width = image_np.shape[:2]
|
|
|
|
bytes_per_line = 3 * width
|
|
q_img = QImage(image_np.data, width, height, bytes_per_line, QImage.Format_RGB888)
|
|
|
|
pixmap = QPixmap.fromImage(q_img)
|
|
scaled_pixmap = pixmap.scaled(self.preview_label.size(),
|
|
Qt.KeepAspectRatio,
|
|
Qt.SmoothTransformation)
|
|
|
|
self.preview_label.setPixmap(scaled_pixmap)
|
|
|
|
def on_image_mouse_move(self, x, y):
|
|
width = self.preview_label.width()
|
|
height = self.preview_label.height()
|
|
|
|
center_x = width / 2
|
|
center_y = height / 2
|
|
|
|
norm_x = -((x - center_x) / (width / 2))
|
|
norm_y = -(y - center_y) / (height / 2)
|
|
|
|
self.x_slider.setValue(int(norm_x * 1000))
|
|
self.y_slider.setValue(int(norm_y * 1000))
|
|
|
|
self.on_slider_changed()
|
|
|
|
class ImageLabel(QLabel):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setMouseTracking(False)
|
|
self.mouseMoveHandler = None
|
|
self.mousePressed = False
|
|
|
|
def mousePressEvent(self, event):
|
|
if event.button() == Qt.LeftButton:
|
|
self.mousePressed = True
|
|
if self.mouseMoveHandler:
|
|
self.mouseMoveHandler(event.x(), event.y())
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
if event.button() == Qt.LeftButton:
|
|
self.mousePressed = False
|
|
|
|
def mouseMoveEvent(self, event):
|
|
if self.mousePressed and self.mouseMoveHandler:
|
|
self.mouseMoveHandler(event.x(), event.y())
|
|
|
|
class LG_Relight:
|
|
|
|
default_values = {
|
|
'x': 0.0,
|
|
'y': 0.0,
|
|
'z': 1.0,
|
|
'brightness': 1.0,
|
|
'shadow_range': 1.0,
|
|
'shadow_strength': 1.0,
|
|
'highlight_range': 1.0,
|
|
'highlight_strength': 1.0,
|
|
'highlight_color': [1.0, 1.0, 1.0],
|
|
'shadow_color': [0.0, 0.0, 0.0]
|
|
}
|
|
def __init__(self):
|
|
self.last_values = self.default_values.copy()
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"normals": ("IMAGE",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
OUTPUT_NODE = True
|
|
FUNCTION = "relight_with_preview"
|
|
CATEGORY = "🎈LAOGOU"
|
|
|
|
def relight_calculation(self, image, normals, x, y, z, brightness,
|
|
shadow_range, shadow_strength,
|
|
highlight_range, highlight_strength,
|
|
highlight_color, shadow_color):
|
|
if image.shape[0] != normals.shape[0]:
|
|
raise Exception("Batch size for image and normals must match")
|
|
|
|
norm = normals.detach().clone() * 2 - 1
|
|
norm = F.interpolate(norm.movedim(-1,1),
|
|
size=(image.shape[1], image.shape[2]),
|
|
mode='bilinear').movedim(1,-1)
|
|
|
|
light = torch.tensor([x, y, z], device=image.device)
|
|
light = F.normalize(light, dim=0)
|
|
|
|
diffuse = norm[:,:,:,0] * light[0] + norm[:,:,:,1] * light[1] + norm[:,:,:,2] * light[2]
|
|
|
|
diffuse = (diffuse + 1.0) * 0.5
|
|
|
|
shadow_offset = shadow_strength - 1.0
|
|
highlight_offset = highlight_strength - 1.0
|
|
|
|
shadow_threshold = 1.0 - shadow_range
|
|
highlight_threshold = 1.0 - highlight_range
|
|
|
|
shadow_mask = torch.clamp((diffuse - shadow_threshold) / max(shadow_range, 1e-6), 0, 1)
|
|
highlight_mask = torch.clamp((diffuse - highlight_threshold) / max(highlight_range, 1e-6), 0, 1)
|
|
|
|
light_intensity = torch.ones_like(diffuse)
|
|
|
|
if shadow_strength != 1.0:
|
|
light_intensity = light_intensity * (
|
|
shadow_mask +
|
|
(1.0 - shadow_mask) * (2.0 - shadow_strength)
|
|
)
|
|
|
|
if highlight_strength != 1.0:
|
|
light_intensity = light_intensity + highlight_mask * highlight_offset
|
|
|
|
color_effect = torch.ones_like(image[:,:,:,:3])
|
|
if highlight_color != [1.0, 1.0, 1.0] or shadow_color != [0.0, 0.0, 0.0]:
|
|
highlight_color = torch.tensor(highlight_color, device=image.device)
|
|
shadow_color = torch.tensor(shadow_color, device=image.device)
|
|
color_effect = (
|
|
shadow_mask.unsqueeze(-1) * highlight_color +
|
|
(1.0 - shadow_mask).unsqueeze(-1) * shadow_color
|
|
)
|
|
|
|
brightness_factor = brightness if brightness != 1.0 else 1.0
|
|
|
|
relit = image.detach().clone()
|
|
light_intensity = light_intensity.unsqueeze(-1).repeat(1,1,1,3)
|
|
|
|
relit[:,:,:,:3] = torch.clip(
|
|
relit[:,:,:,:3] * light_intensity * brightness_factor * color_effect,
|
|
0, 1
|
|
)
|
|
|
|
return (relit,)
|
|
|
|
def relight_with_preview(self, image, normals):
|
|
from PyQt5.QtWidgets import QApplication
|
|
app = QApplication.instance()
|
|
if app is None:
|
|
app = QApplication([])
|
|
|
|
if not hasattr(self, 'last_values'):
|
|
self.last_values = self.default_values.copy()
|
|
|
|
dialog = LightPreviewDialog(initial_values=self.last_values)
|
|
|
|
def update_preview(x, y, z, brightness, shadow_range, shadow_strength,
|
|
highlight_range, highlight_strength,
|
|
highlight_color, shadow_color):
|
|
result = self.relight_calculation(
|
|
image, normals, x, y, z, brightness,
|
|
shadow_range, shadow_strength,
|
|
highlight_range, highlight_strength,
|
|
highlight_color, shadow_color
|
|
)
|
|
dialog.set_preview_image(result[0])
|
|
|
|
self.last_values = dialog.current_values.copy()
|
|
return result
|
|
|
|
dialog.update_preview = update_preview
|
|
|
|
initial_result = update_preview(
|
|
self.last_values['x'], self.last_values['y'], self.last_values['z'],
|
|
self.last_values['brightness'],
|
|
self.last_values['shadow_range'],
|
|
self.last_values['shadow_strength'],
|
|
self.last_values['highlight_range'],
|
|
self.last_values['highlight_strength'],
|
|
self.last_values['highlight_color'],
|
|
self.last_values['shadow_color']
|
|
)
|
|
|
|
if dialog.exec_():
|
|
|
|
self.last_values = dialog.current_values.copy()
|
|
return self.relight_calculation(
|
|
image, normals,
|
|
self.last_values['x'],
|
|
self.last_values['y'],
|
|
self.last_values['z'],
|
|
self.last_values['brightness'],
|
|
self.last_values['shadow_range'],
|
|
self.last_values['shadow_strength'],
|
|
self.last_values['highlight_range'],
|
|
self.last_values['highlight_strength'],
|
|
self.last_values['highlight_color'],
|
|
self.last_values['shadow_color']
|
|
)
|
|
|
|
return initial_result
|
|
|
|
|
|
|
|
WEB_DIRECTORY = "web"
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"LG_Relight": LG_Relight,
|
|
"LG_Relight_V2": LG_Relight_V2,
|
|
"LG_Relight_Basic": LG_Relight_Basic
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"LG_Relight": "LG Relight",
|
|
"LG_Relight_V2": "LG Relight V2",
|
|
"LG_Relight_Basic": "LG Relight Basic"
|
|
}
|