Files

153 lines
5.6 KiB
Python

import numpy as np
from PIL import Image, ImageFilter
import torch
class ShadowOrOutlineFromAlpha:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"mode": (["shadow", "outline"], {"default": "shadow"}),
"threshold": ("INT", {"default": 10, "min": 0, "max": 255, "step": 1}),
"outline_thickness": ("INT", {"default": 8, "min": 1, "max": 128, "step": 1}),
"outline_feather": ("INT", {"default": 2, "min": 0, "max": 64, "step": 1}),
"outline_r": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
"outline_g": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
"outline_b": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
"outline_opacity": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"shadow_blur": ("INT", {"default": 18, "min": 0, "max": 128, "step": 1}),
"shadow_expand": ("INT", {"default": 4, "min": 0, "max": 64, "step": 1}),
"shadow_offset_x": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
"shadow_offset_y": ("INT", {"default": 16, "min": -512, "max": 512, "step": 1}),
"shadow_opacity": ("FLOAT", {"default": 0.28, "min": 0.0, "max": 1.0, "step": 0.01}),
"shadow_r": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
"shadow_g": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
"shadow_b": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
}
}
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("effect_image", "effect_mask")
FUNCTION = "make_effect"
CATEGORY = "image/wps"
@staticmethod
def _tensor_to_rgba_pil(image_tensor):
arr = image_tensor[0].cpu().numpy()
arr = np.clip(arr, 0.0, 1.0)
if arr.shape[-1] == 4:
rgba = (arr * 255).astype(np.uint8)
return Image.fromarray(rgba, mode="RGBA")
rgb = (arr[:, :, :3] * 255).astype(np.uint8)
alpha = np.full((rgb.shape[0], rgb.shape[1], 1), 255, dtype=np.uint8)
rgba = np.concatenate([rgb, alpha], axis=2)
return Image.fromarray(rgba, mode="RGBA")
@staticmethod
def _pil_rgba_to_tensor(img):
arr = np.array(img).astype(np.float32) / 255.0
return torch.from_numpy(arr)[None,]
@staticmethod
def _pil_l_to_mask_tensor(img):
arr = np.array(img).astype(np.float32) / 255.0
return torch.from_numpy(arr)[None,]
@staticmethod
def _apply_opacity(mask_img, opacity):
alpha_np = np.array(mask_img).astype(np.float32)
alpha_np = np.clip(alpha_np * float(opacity), 0, 255).astype(np.uint8)
return Image.fromarray(alpha_np, mode="L")
@staticmethod
def _shift_mask(mask_img, offset_x, offset_y):
shifted = Image.new("L", mask_img.size, 0)
shifted.paste(mask_img, (int(offset_x), int(offset_y)))
return shifted
@staticmethod
def _threshold_alpha(alpha, threshold):
alpha_np = np.array(alpha).astype(np.uint8)
alpha_np[alpha_np < int(threshold)] = 0
return Image.fromarray(alpha_np, mode="L")
def _make_outline_mask(self, alpha, thickness, feather):
expanded = alpha.filter(ImageFilter.MaxFilter(size=thickness * 2 + 1))
orig_np = np.array(alpha).astype(np.int16)
exp_np = np.array(expanded).astype(np.int16)
outline_np = np.clip(exp_np - orig_np, 0, 255).astype(np.uint8)
outline = Image.fromarray(outline_np, mode="L")
if feather > 0:
outline = outline.filter(ImageFilter.GaussianBlur(radius=feather))
return outline
def _make_shadow_mask(self, alpha, expand, blur, offset_x, offset_y):
if expand > 0:
alpha = alpha.filter(ImageFilter.MaxFilter(size=expand * 2 + 1))
if blur > 0:
alpha = alpha.filter(ImageFilter.GaussianBlur(radius=blur))
if offset_x != 0 or offset_y != 0:
alpha = self._shift_mask(alpha, offset_x, offset_y)
return alpha
def make_effect(
self,
image,
mode,
threshold,
outline_thickness,
outline_feather,
outline_r,
outline_g,
outline_b,
outline_opacity,
shadow_blur,
shadow_expand,
shadow_offset_x,
shadow_offset_y,
shadow_opacity,
shadow_r,
shadow_g,
shadow_b,
):
rgba = self._tensor_to_rgba_pil(image)
alpha = rgba.getchannel("A")
alpha = self._threshold_alpha(alpha, threshold)
if mode == "outline":
effect_mask = self._make_outline_mask(alpha, outline_thickness, outline_feather)
effect_mask = self._apply_opacity(effect_mask, outline_opacity)
color = (int(outline_r), int(outline_g), int(outline_b), 0)
else:
effect_mask = self._make_shadow_mask(
alpha,
shadow_expand,
shadow_blur,
shadow_offset_x,
shadow_offset_y,
)
effect_mask = self._apply_opacity(effect_mask, shadow_opacity)
color = (int(shadow_r), int(shadow_g), int(shadow_b), 0)
effect_rgba = Image.new("RGBA", rgba.size, color)
effect_rgba.putalpha(effect_mask)
return (self._pil_rgba_to_tensor(effect_rgba), self._pil_l_to_mask_tensor(effect_mask))
NODE_CLASS_MAPPINGS = {
"ShadowOrOutlineFromAlpha": ShadowOrOutlineFromAlpha,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ShadowOrOutlineFromAlpha": "Shadow Or Outline From Alpha",
}