diff --git a/README-CN.md b/README-CN.md index bb8e5e5..699fd24 100644 --- a/README-CN.md +++ b/README-CN.md @@ -3,6 +3,7 @@ # 人像处理等相关的 ComfyUI 节点 目前包含以下节点: +- 视频自动水印; - 图像自动水印; - 图片人脸对齐(正脸); - 人脸检测裁剪, 可选是否对齐, 可调裁剪区域大小, 角度; @@ -28,6 +29,8 @@ ## 📣 更新 +[2025-04-24]⚒️: 新增自动视频水印. + [2025-04-14]⚒️: 新增自动图像水印. [2025-04-11]⚒️: 发布版本 v1.0.0. diff --git a/README.md b/README.md index cc7212c..eaf613e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ # ComfyUI Nodes for Portrait Processing Currently includes the following nodes: +- Automatic video watermarking; - Automatic image watermarking; - Image face alignment (frontal); - Face detection and cropping, with optional alignment, adjustable crop area size, and angle; @@ -27,6 +28,8 @@ Examples: ## 📣 Updates +[2025-04-24]⚒️: Add automatic video watermarking. + [2025-04-14]⚒️: Add automatic image watermarking. [2025-04-11] ⚒️: Released version v1.0.0. diff --git a/node.zip b/node.zip new file mode 100644 index 0000000..26f64c4 Binary files /dev/null and b/node.zip differ diff --git a/ptnodes.py b/ptnodes.py index 76b15ce..4cb3884 100644 --- a/ptnodes.py +++ b/ptnodes.py @@ -99,6 +99,8 @@ class Watermarker(object): space=75, chars_per_line=8, font_height_crop=1.2, + offset_x=0, + offset_y=0, ): """_summary_ @@ -135,6 +137,8 @@ class Watermarker(object): self.size = size self.space = space self.chars_per_line = chars_per_line + self.offset_x = offset_x + self.offset_y = offset_y self.image = self._add_mark_striped() @staticmethod @@ -168,8 +172,10 @@ class Watermarker(object): watermark_image = Watermarker.crop_image_edge(watermark_image) Watermarker.set_image_opacity(watermark_image, self.opacity) - c = int(math.sqrt(origin_image.size[0] ** 2 + origin_image.size[1] ** 2)) + # 确保水印覆盖整个图像,增加水印掩码的尺寸 + c = int(math.sqrt(origin_image.size[0] ** 2 + origin_image.size[1] ** 2) * 1.5) watermark_mask = Image.new(mode="RGBA", size=(c, c)) + y, idx = 0, 0 while y < c: x = -int((watermark_image.size[0] + self.space) * 0.5 * idx) @@ -180,11 +186,19 @@ class Watermarker(object): y += watermark_image.size[1] + self.space watermark_mask = watermark_mask.rotate(self.angle) + # 计算安全的偏移范围 + max_offset = c // 4 # 限制最大偏移量为水印掩码尺寸的1/4 + safe_offset_x = max(min(self.offset_x, max_offset), -max_offset) + safe_offset_y = max(min(self.offset_y, max_offset), -max_offset) + + paste_x = int((origin_image.size[0] - c) / 2) + safe_offset_x + paste_y = int((origin_image.size[1] - c) / 2) + safe_offset_y origin_image.paste( watermark_mask, - (int((origin_image.size[0] - c) / 2), int((origin_image.size[1] - c) / 2)), + (paste_x, paste_y), mask=watermark_mask.split()[3], ) + return origin_image @@ -193,7 +207,7 @@ class ImageWatermark: def INPUT_TYPES(s): return { "required": { - "image": ("IMAGE",), + "images": ("IMAGE",), "text": ("STRING", {"default": "@明文视界"}), "font_file": ("STRING", {"default": ""}), "angle": ("INT", {"default": 30, "min": 0, "max": 360, "step": 1}), @@ -203,19 +217,21 @@ class ImageWatermark: "opacity": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.1}), "size": ("INT", {"default": 50, "min": 1, "max": 100, "step": 1}), "space": ("INT", {"default": 75, "min": 1, "max": 150, "step": 1}), + "movement_type": (["None", "up_down", "left_right", "angel_change"], {"default": "None"}), + "movement_amount": ("FLOAT", {"default": 1, "min": 0.2, "max": 5, "step": 0.2}), # "chars_per_line": ("INT", {"default": 8, "min": 1, "max": 10, "step": 1}), # "font_height_crop": ("FLOAT", {"default": 1.2, "min": 0.1, "max": 5.0, "step": 0.1}) }, } RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("image",) + RETURN_NAMES = ("images",) FUNCTION = "watermarkgen" CATEGORY = "🎤MW/MW-PortraitTools" def watermarkgen( self, - image, + images, text: str, font_file: str, angle=30, @@ -225,25 +241,77 @@ class ImageWatermark: opacity=0.15, size=50, space=75, + movement_type="None", + movement_amount=1, chars_per_line=8, font_height_crop=1.2, ): if font_file.strip() == "": font_file = os.path.join(current_dir, "ChironGoRoundTC-600SB.ttf") - watermarker = Watermarker( - input_image=tensor2pil(image), - text=text, - font_file=font_file, - angle=angle, - color=f"#{red:02x}{green:02x}{blue:02x}", - opacity=opacity, - size=size, - space=space, - chars_per_line=chars_per_line, - font_height_crop=font_height_crop, - ) + + # 处理批量图像 + batch_size = images.shape[0] + result_tensors = [] + + for i in range(batch_size): + # 获取当前图像 + current_image = images[i:i+1] + pil_image = tensor2pil(current_image) + + # 获取图像尺寸 + img_width, img_height = pil_image.size + + # 计算安全的最大偏移量(不超过图像尺寸的1/4) + max_offset_x = img_width // 8 + max_offset_y = img_height // 8 + + # 根据移动类型计算当前图像的水印参数 + current_angle = angle + current_offset_x = 0 + current_offset_y = 0 + + if movement_type == "angel_change": + # 角度在原始角度的基础上变化 + current_angle = (angle + i * movement_amount) % 360 + elif movement_type == "up_down": + # 上下移动水印位置,使用正弦函数实现平滑循环 + cycle_position = (i * movement_amount) % (max_offset_y * 4) + current_offset_y = int(math.sin(cycle_position * math.pi / (max_offset_y * 2)) * max_offset_y) + elif movement_type == "left_right": + # 左右移动水印位置,使用正弦函数实现平滑循环 + cycle_position = (i * movement_amount) % (max_offset_x * 4) + current_offset_x = int(math.sin(cycle_position * math.pi / (max_offset_x * 2)) * max_offset_x) + # elif movement_type == "circular": + # # 圆形轨迹移动 + # cycle = 2 * math.pi * (i * movement_amount % 100) / 100 + # current_offset_x = int(math.cos(cycle) * max_offset_x) + # current_offset_y = int(math.sin(cycle) * max_offset_y) + else: + # 不移动水印位置 + current_offset_x = 0 + current_offset_y = 0 - return (pil2tensor(watermarker.image),) + watermarker = Watermarker( + input_image=pil_image, + text=text, + font_file=font_file, + angle=current_angle, + color=f"#{red:02x}{green:02x}{blue:02x}", + opacity=opacity, + size=size, + space=space, + chars_per_line=chars_per_line, + font_height_crop=font_height_crop, + offset_x=current_offset_x, + offset_y=current_offset_y + ) + + # 转换回tensor并添加到结果列表 + result_tensor = pil2tensor(watermarker.image) + result_tensors.append(result_tensor) + + # 合并所有结果为一个批量tensor + return (torch.cat(result_tensors, dim=0),) class AlignFace: @classmethod diff --git a/pyproject.toml b/pyproject.toml index 2ebef8a..aa5ac35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "portraittools-mw" description = "Portrait Tools: Facial detection cropping, alignment, ID photo, etc." -version = "1.0.3" +version = "1.0.4" license = {file = "LICENSE"} dependencies = []