Files
LAOGOU-666-Comfyui-LG_Relight/__init__.py
T
2025-02-17 14:37:21 +08:00

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"
}