This commit is contained in:
leafiy
2025-11-11 10:10:05 +08:00
parent 7ff4b9f331
commit 069c3b2092
2 changed files with 82 additions and 5 deletions
+4 -3
View File
@@ -12,9 +12,10 @@ matches what you would get when replacing the layer inside Photoshop.
bounding box, transform box). Use it to confirm the layer names you want to
target.
- **PSD Mockup Embedder** – Uploads PSD/PSB files directly from the node,
replaces the selected smart objects with your image, resizes the result to any
width (default 800 px), and emits the composited tensor plus an auto-saved
PNG.
reuses the PSD’s original clipping stack (shadows, highlights, rounded
corners, etc.), projects your image into the smart objects, resizes the result
to any width (default 800 px), and emits the composited tensor plus an
auto-saved PNG.
- Works with PSD/PSB files, multiple smart objects, and either ComfyUI `IMAGE`
tensors or file paths.
+78 -2
View File
@@ -233,6 +233,70 @@ def _polygon_mask(quad: Sequence[float], canvas_size: Tuple[int, int]) -> Image.
return mask
def _collect_clipping_stack(layer: Layer) -> List[Layer]:
stack = [layer]
parent = layer.parent
if parent is None:
return stack
siblings = list(parent)
try:
idx = siblings.index(layer)
except ValueError:
return stack
for sib in siblings[idx + 1 :]:
if not getattr(sib, "clipping", False):
break
stack.append(sib)
return stack
def _composite_layers(psd: PSDImage, layer_ids: Sequence[int]) -> Image.Image:
include = set(layer_ids)
def filter_fn(layer: Layer) -> bool:
if hasattr(layer, "layer_id") and layer.layer_id in include:
return True
return False
return psd.composite(layer_filter=filter_fn)
def _compute_shading_components(stack_image: Image.Image, base_image: Image.Image):
stack_rgba = stack_image.convert("RGBA")
base_rgba = base_image.convert("RGBA")
stack_arr = np.array(stack_rgba).astype(np.float32) / 255.0
base_arr = np.array(base_rgba).astype(np.float32) / 255.0
base_rgb = base_arr[..., :3]
stack_rgb = stack_arr[..., :3]
base_alpha = base_arr[..., 3:4]
multiplier = np.ones_like(base_rgb, dtype=np.float32)
mask = base_alpha > 1e-3
if np.any(mask):
denom = np.clip(base_rgb, 1e-3, None)
ratio = stack_rgb / denom
ratio = np.clip(ratio, 0.0, 4.0)
multiplier[mask.repeat(3, axis=2)] = ratio[mask.repeat(3, axis=2)]
base_alpha_rgb = np.repeat(base_alpha, 3, axis=2)
multiplier = multiplier * base_alpha_rgb + (1.0 - base_alpha_rgb)
alpha = stack_arr[..., 3:4]
alpha_img = Image.fromarray((np.clip(alpha, 0.0, 1.0) * 255).astype(np.uint8).squeeze(-1), mode="L")
return multiplier, alpha_img
def _apply_shading_map(image: Image.Image, multiplier: np.ndarray) -> Image.Image:
if multiplier is None:
return image
rgba = image.convert("RGBA")
arr = np.array(rgba).astype(np.float32) / 255.0
h, w, _ = arr.shape
if multiplier.shape[:2] != (h, w):
return image
arr[..., :3] = np.clip(arr[..., :3] * multiplier, 0.0, 1.0)
return Image.fromarray((arr * 255).astype(np.uint8), mode="RGBA")
def _output_directory() -> str:
if folder_paths and hasattr(folder_paths, "get_output_directory"):
return folder_paths.get_output_directory()
@@ -347,9 +411,21 @@ class PSDMockupEmbedder:
if quad is None:
left, top, right, bottom = layer.bbox
quad = (left, top, right, top, right, bottom, left, bottom)
stack_layers = _collect_clipping_stack(layer)
stack_ids = [l.layer_id for l in stack_layers if hasattr(l, "layer_id")]
stack_image = _composite_layers(psd, stack_ids) if stack_ids else None
base_image = _composite_layers(psd, [layer.layer_id])
shading_multiplier = None
alpha_mask = None
if stack_image is not None:
shading_multiplier, alpha_mask = _compute_shading_components(stack_image, base_image)
warped = _warp_image_to_canvas(source_image, quad, canvas_size)
mask = _polygon_mask(quad, canvas_size)
base_rgba = Image.composite(warped, base_rgba, mask)
warped = _apply_shading_map(warped, shading_multiplier)
fallback_mask = _polygon_mask(quad, canvas_size)
composite_mask = alpha_mask if alpha_mask and alpha_mask.getbbox() else fallback_mask
base_rgba = Image.composite(warped, base_rgba, composite_mask)
metadata.append(
{
"layer_id": layer.layer_id,