commit HalfTone node
This commit is contained in:
@@ -147,6 +147,7 @@ When this error has occurred, please check the network environment.
|
||||
|
||||
<font size="4">**If the dependency package error after updating, please double clicking ```repair_dependency.bat``` (for Official ComfyUI Protable) or ```repair_dependency_aki.bat``` (for ComfyUI-aki-v1.x) in the plugin folder to reinstall the dependency packages. </font><br />
|
||||
|
||||
* Commit [HalfTone](#HalfTone) node, use for halftone processing of images.
|
||||
* Add QuarkNetdisk model download link.
|
||||
* Support numpy 2.x dependency package.
|
||||
* Commit [PurgeVRAM V2](#PurgeVRAMV2) node.
|
||||
@@ -2222,6 +2223,21 @@ Node option:
|
||||
* line_density: The black line density.
|
||||
* opacity: The opacity of watercolor effects.
|
||||
|
||||
### <a id="table1">HalfTone</a>
|
||||
Convert the image to a halfton.
|
||||

|
||||
|
||||
Node Options:
|
||||

|
||||
* image: The input image.
|
||||
* mask: Optional mask input.
|
||||
* dot_size: The size of the dots.
|
||||
* angle: The angle of arrangement of dots.
|
||||
* shape: The shape of the dots. There are three options: circle、diamond、square。
|
||||
* dot_color: The color of dots.
|
||||
* background_color: The color of the background.
|
||||
* anti_alias: Anti aliasing intensity. the higher value will make the edges of the points smoother and increase processing time.
|
||||
|
||||
### <a id="table1">SoftLight</a>
|
||||
|
||||
Soft light effect, the bright highlights on the screen appear blurry.
|
||||
|
||||
@@ -128,6 +128,7 @@ os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
|
||||
## 更新说明
|
||||
<font size="4">**如果本插件更新后出现依赖包错误,请双击运行插件目录下的```install_requirements.bat```(官方便携包),或 ```install_requirements_aki.bat```(秋叶整合包) 重新安装依赖包。
|
||||
|
||||
* 添加 [HalfTone](#HalfTone) 节点,用于对图像进行半色调处理。
|
||||
* 增加模型下载夸克网盘链接。
|
||||
* 支持numpy 2.x依赖包。
|
||||
* 添加 [PurgeVRAM V2](#PurgeVRAMV2) 节点,不强制执行,增加any输出,便于在流程中灵活释放显存。
|
||||
@@ -1975,6 +1976,21 @@ mask反转
|
||||
* line_density: 线条密度。
|
||||
* opacity: 水彩效果的不透明度。
|
||||
|
||||
### <a id="table1">HalfTone</a>
|
||||
将图片转为半色调效果。
|
||||

|
||||
|
||||
节点选项说明:
|
||||

|
||||
* image: 图片输入。
|
||||
* mask: 可选遮罩输入。
|
||||
* dot_size: 点的大小。
|
||||
* angle: 点的排列角度。
|
||||
* shape: 点的形状。有3个选项:circle、diamond、square。
|
||||
* dot_color: 点的颜色。
|
||||
* background_color: 背景颜色。
|
||||
* anti_alias: 反锯齿强度。更高的值将使点的边缘更平滑,同时增加处理时间。
|
||||
|
||||
|
||||
### <a id="table1">SoftLight</a>
|
||||
柔光效果。
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 603 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
+186
@@ -0,0 +1,186 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw
|
||||
import math
|
||||
import random
|
||||
from .imagefunc import log, tensor2pil, pil2tensor, mask2image
|
||||
|
||||
|
||||
def create_dot_mask(size:int, shape:str='circle') -> np.ndarray:
|
||||
"""创建不同形状的点阵掩码
|
||||
|
||||
Args:
|
||||
size (int): 掩码大小
|
||||
shape (str): 形状类型 ('circle', 'diamond', 'square')
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: 掩码数组
|
||||
"""
|
||||
mask = np.zeros((size, size))
|
||||
center = size / 2
|
||||
|
||||
for x in range(size):
|
||||
for y in range(size):
|
||||
if shape == 'circle':
|
||||
distance = math.sqrt((x - center + 0.5) ** 2 + (y - center + 0.5) ** 2)
|
||||
radius = center if size > 4 else center * 1.1
|
||||
mask[y, x] = 1 if distance <= radius else 0
|
||||
elif shape == 'diamond':
|
||||
distance = abs(x - center + 0.5) + abs(y - center + 0.5)
|
||||
radius = center if size > 4 else center * 1.1
|
||||
mask[y, x] = 1 if distance <= radius else 0
|
||||
elif shape == 'square':
|
||||
mask[y, x] = 1
|
||||
|
||||
return mask
|
||||
|
||||
|
||||
def halftone(image: Image, dot_size:int = 10, shape: str = 'circle', angle: float = 45) -> Image:
|
||||
|
||||
if image.mode != 'L':
|
||||
image = image.convert('L')
|
||||
|
||||
width, height = image.size
|
||||
output = Image.new('L', (width, height), 0)
|
||||
draw = ImageDraw.Draw(output)
|
||||
|
||||
angle_rad = math.radians(angle)
|
||||
cos_angle = math.cos(angle_rad)
|
||||
sin_angle = math.sin(angle_rad)
|
||||
|
||||
img_array = np.array(image)
|
||||
|
||||
random_offset = dot_size * 0.05 # 添加 5% 的随机偏移,避免出现规则条纹
|
||||
|
||||
diagonal = math.sqrt(width ** 2 + height ** 2)
|
||||
margin = int(diagonal)
|
||||
|
||||
x_start = -margin // 2
|
||||
x_end = width + margin // 2
|
||||
y_start = -margin // 2
|
||||
y_end = height + margin // 2
|
||||
|
||||
step = dot_size
|
||||
rotated_step_x = math.sqrt(2) * step * cos_angle
|
||||
rotated_step_y = math.sqrt(2) * step * sin_angle
|
||||
|
||||
y = y_start
|
||||
while y < y_end:
|
||||
x = x_start
|
||||
while x < x_end:
|
||||
offset_x = random.uniform(-random_offset, random_offset)
|
||||
offset_y = random.uniform(-random_offset, random_offset)
|
||||
|
||||
grid_x = (x + offset_x) * cos_angle + (y + offset_y) * sin_angle
|
||||
grid_y = -(x + offset_x) * sin_angle + (y + offset_y) * cos_angle
|
||||
|
||||
if 0 <= grid_x < width and 0 <= grid_y < height:
|
||||
sample_x = int(grid_x)
|
||||
sample_y = int(grid_y)
|
||||
|
||||
region_x = min(sample_x, width - dot_size)
|
||||
region_y = min(sample_y, height - dot_size)
|
||||
region = img_array[region_y:region_y + dot_size, region_x:region_x + dot_size]
|
||||
|
||||
if region.size > 0:
|
||||
gaussian_kernel = np.exp(-np.linspace(-2, 2, dot_size) ** 2 / 2)
|
||||
gaussian_kernel = gaussian_kernel[:, np.newaxis] * gaussian_kernel[np.newaxis, :]
|
||||
gaussian_kernel = gaussian_kernel / gaussian_kernel.sum()
|
||||
|
||||
if region.shape[0] == gaussian_kernel.shape[0] and region.shape[1] == gaussian_kernel.shape[1]:
|
||||
mean_value = np.sum(region * gaussian_kernel)
|
||||
else:
|
||||
mean_value = np.mean(region)
|
||||
|
||||
dot_radius = math.sqrt(1 - mean_value / 255) * dot_size / 2
|
||||
|
||||
if dot_radius > 0:
|
||||
mask_size = int(dot_radius * 2)
|
||||
if mask_size > 0:
|
||||
dot_mask = create_dot_mask(mask_size, shape)
|
||||
|
||||
for dy in range(mask_size):
|
||||
for dx in range(mask_size):
|
||||
if dot_mask[dy, dx] > 0:
|
||||
px = int(grid_x - mask_size // 2 + dx)
|
||||
py = int(grid_y - mask_size // 2 + dy)
|
||||
if 0 <= px < width and 0 <= py < height:
|
||||
output.putpixel((px, py), 255)
|
||||
|
||||
x += step
|
||||
y += step
|
||||
|
||||
return output
|
||||
|
||||
|
||||
class LS_Halftone:
|
||||
|
||||
def __init__(self):
|
||||
self.NODE_NAME = 'Halftone'
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
shape_list = ['circle', 'diamond', 'square']
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", ), #
|
||||
"dot_size": ("INT", {"default": 10, "min": 4, "max": 100, "step": 1}), # 点大小
|
||||
"angle": ("FLOAT", {"default": 45, "min": -90, "max": 90, "step": 0.1}), # 角度
|
||||
"shape": (shape_list,),
|
||||
"dot_color":("STRING",{"default": "#FFFFFF"}),
|
||||
"background_color": ("STRING", {"default": "#000000"}),
|
||||
"anti_aliasing": ("INT", {"default": 1, "min": 0, "max": 4, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"mask": ("MASK",), #
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = 'halftone'
|
||||
CATEGORY = '😺dzNodes/LayerFilter'
|
||||
|
||||
def halftone(self, image, dot_size, angle, shape, dot_color, background_color, anti_aliasing, mask=None,
|
||||
):
|
||||
|
||||
l_masks = []
|
||||
ret_images = []
|
||||
upscale = anti_aliasing + 1
|
||||
if mask is not None:
|
||||
if mask.dim() == 2:
|
||||
mask = torch.unsqueeze(mask, 0)
|
||||
for m in mask:
|
||||
l_masks.append(tensor2pil(torch.unsqueeze(m, 0)).convert('L'))
|
||||
else:
|
||||
l_masks.append(Image.new('L', image[0].size, color='white'))
|
||||
|
||||
for idx,img in enumerate(image):
|
||||
orig_image = tensor2pil(img.unsqueeze(0)).convert('RGB')
|
||||
orig_mask = l_masks[idx] if len(l_masks) > idx else l_masks[-1]
|
||||
if orig_mask.size != orig_image.size:
|
||||
orig_mask = orig_mask.resize(orig_image.size, Image.LANCZOS)
|
||||
|
||||
upscaled_image = orig_image.resize((orig_image.width * upscale, orig_image.height * upscale), Image.LANCZOS)
|
||||
|
||||
halftone_image = halftone(upscaled_image, dot_size * upscale, shape=shape, angle=angle)
|
||||
halftone_image = halftone_image.resize(orig_image.size, Image.LANCZOS)
|
||||
color_image = Image.new('RGB', halftone_image.size, color=dot_color)
|
||||
background_image = Image.new('RGB', halftone_image.size, color=background_color)
|
||||
background_image.paste(color_image, mask=halftone_image)
|
||||
ret_image = Image.new('RGB', halftone_image.size, color=background_color)
|
||||
ret_image.paste(background_image, mask=orig_mask)
|
||||
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
|
||||
|
||||
log(f"{self.NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0),)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerFilter: Halftone": LS_Halftone
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerFilter: Halftone": "LayerFilter: Halftone"
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui_layerstyle"
|
||||
description = "A set of nodes for ComfyUI it generate image like Adobe Photoshop's Layer Style. the Drop Shadow is first completed node, and follow-up work is in progress."
|
||||
version = "2.0.17"
|
||||
version = "2.0.18"
|
||||
license = {text = "MIT License"}
|
||||
dependencies = ["numpy", "pillow", "torch", "matplotlib", "Scipy", "scikit_image", "scikit_learn", "opencv-contrib-python", "pymatting", "timm", "colour-science", "transformers", "blend_modes", "huggingface_hub", "loguru"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user