├── AIraster.py ├── README.md ├── __init__.py ├── example └── rasterworkflow.json ├── pyproject.toml └── requirements.txt /AIraster.py: -------------------------------------------------------------------------------- 1 | import vtracer 2 | import tempfile 3 | from wand.image import Image as WandImage 4 | from PIL import Image as PILImage 5 | import torch 6 | import numpy as np 7 | import os 8 | 9 | class AIraster: 10 | """ 11 | A example node 12 | 13 | Class methods 14 | ------------- 15 | INPUT_TYPES (dict): 16 | Tell the main program input parameters of nodes. 17 | IS_CHANGED: 18 | optional method to control when the node is re executed. 19 | 20 | Attributes 21 | ---------- 22 | RETURN_TYPES (`tuple`): 23 | The type of each element in the output tulple. 24 | RETURN_NAMES (`tuple`): 25 | Optional: The name of each output in the output tulple. 26 | FUNCTION (`str`): 27 | The name of the entry-point method. For example, if `FUNCTION = "execute"` then it will run Example().execute() 28 | OUTPUT_NODE ([`bool`]): 29 | If this node is an output node that outputs a result/image from the graph. The SaveImage node is an example. 30 | The backend iterates on these output nodes and tries to execute all their parents if their parent graph is properly connected. 31 | Assumed to be False if not present. 32 | CATEGORY (`str`): 33 | The category the node should appear in the UI. 34 | execute(s) -> tuple || None: 35 | The entry point method. The name of this method must be the same as the value of property `FUNCTION`. 36 | For example, if `FUNCTION = "execute"` then this method's name must be `execute`, if `FUNCTION = "foo"` then it must be `foo`. 37 | """ 38 | def __init__(self): 39 | pass 40 | 41 | @classmethod 42 | def INPUT_TYPES(s): 43 | """ 44 | Return a dictionary which contains config for all input fields. 45 | Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". 46 | Input types "INT", "STRING" or "FLOAT" are special values for fields on the node. 47 | The type can be a list for selection. 48 | 49 | Returns: `dict`: 50 | - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required` 51 | - Value input_fields (`dict`): Contains input fields config: 52 | * Key field_name (`string`): Name of a entry-point method's argument 53 | * Value field_config (`tuple`): 54 | + First value is a string indicate the type of field or a list for selection. 55 | + Secound value is a config for type "INT", "STRING" or "FLOAT". 56 | """ 57 | return { 58 | "required": { 59 | "image": ("IMAGE",), 60 | "colormode": (["color", "binary"], { 61 | "default": "color" 62 | }), 63 | "hierarchical": (["stacked", "cutout"], { 64 | "default": "stacked" 65 | }), 66 | "mode": (["spline", "polygon", "none"], { 67 | "default": "spline" 68 | }), 69 | "filter_speckle": ("INT", { 70 | "default": 4, 71 | "min": 1, 72 | "max": 128, 73 | "step": 1 74 | }), 75 | "color_precision": ("INT", { 76 | "default": 6, 77 | "min": 1, 78 | "max": 10, 79 | "step": 1 80 | }), 81 | "layer_difference": ("INT", { 82 | "default": 16, 83 | "min": 1, 84 | "max": 20, 85 | "step": 1 86 | }), 87 | "corner_threshold": ("INT", { 88 | "default": 60, 89 | "min": 1, 90 | "max": 100, 91 | "step": 1 92 | }), 93 | "length_threshold": ("FLOAT", { 94 | "default": 4.0, 95 | "min": 3.5, 96 | "max": 10.0, 97 | "step": 0.1 98 | }), 99 | "max_iterations": ("INT", { 100 | "default": 10, 101 | "min": 1, 102 | "max": 20, 103 | "step": 1 104 | }), 105 | "splice_threshold": ("INT", { 106 | "default": 45, 107 | "min": 1, 108 | "max": 100, 109 | "step": 1 110 | }), 111 | "path_precision": ("INT", { 112 | "default": 8, 113 | "min": 1, 114 | "max": 10, 115 | "step": 1 116 | }), 117 | "output_directory": ("STRING", { 118 | "default": "" 119 | }) 120 | } 121 | } 122 | 123 | RETURN_TYPES = ("IMAGE",) 124 | RETURN_NAMES = ("vector_image",) 125 | 126 | FUNCTION = "process_image" 127 | 128 | #OUTPUT_NODE = False 129 | 130 | CATEGORY = "Vectorization" 131 | 132 | 133 | 134 | def process_image(self, image, output_directory, colormode, hierarchical, mode, filter_speckle, color_precision, 135 | layer_difference, corner_threshold, length_threshold, max_iterations, splice_threshold, path_precision): 136 | try: 137 | print(f"输入图像形状:{image.shape}") 138 | 139 | # 确保输入 Tensor 是四维,具有 (B, C, H, W) 结构 140 | if len(image.shape) == 4: 141 | # 将图像从 (1, H, W, C) 转换为 (H, W, C) 142 | image = image.squeeze(0) 143 | 144 | print(f"处理后的图像形状:{image.shape}") 145 | 146 | # 将 Tensor 转换为 PIL 图像 147 | image_array = image.numpy() 148 | image_array = (image_array * 255).astype(np.uint8) 149 | pil_image = PILImage.fromarray(image_array) 150 | 151 | print(f"转换后的 PIL 图像:{pil_image}") 152 | 153 | # 将输入图像保存到临时文件 154 | with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_img_file: 155 | pil_image.save(temp_img_file, format="PNG") 156 | temp_img_path = temp_img_file.name 157 | 158 | print(f"临时 PNG 文件路径:{temp_img_path}") 159 | base_filename = os.path.splitext(os.path.basename(temp_img_path))[0] 160 | 161 | # 检查并创建输出目录 162 | if not output_directory: 163 | output_directory = tempfile.gettempdir() 164 | elif not os.path.exists(output_directory): 165 | os.makedirs(output_directory) 166 | 167 | # 生成 SVG 文件路径 168 | svg_filename = os.path.join(output_directory, f"{base_filename}.svg") 169 | 170 | print(f"SVG 文件路径:{svg_filename}") 171 | 172 | # 使用 vtracer 将图像转换为 SVG 173 | vtracer.convert_image_to_svg_py(temp_img_path, 174 | svg_filename, 175 | colormode=colormode, 176 | hierarchical=hierarchical, 177 | mode=mode, 178 | filter_speckle=filter_speckle, 179 | color_precision=color_precision, 180 | layer_difference=layer_difference, 181 | corner_threshold=corner_threshold, 182 | length_threshold=length_threshold, 183 | max_iterations=max_iterations, 184 | splice_threshold=splice_threshold, 185 | path_precision=path_precision) 186 | 187 | print("SVG 文件已成功生成") 188 | 189 | # 使用 wand 将 SVG 转换为 PNG 190 | with WandImage(filename=svg_filename) as wand_image: 191 | wand_image.format = 'png' 192 | output_png_filename = os.path.join(output_directory, f"{base_filename}.png") 193 | wand_image.save(filename=output_png_filename) 194 | 195 | # 读取转换后的 PNG 图像 196 | output_image = PILImage.open(output_png_filename).convert('RGB') 197 | print(f"转换后的图像信息: 模式={output_image.mode}, 尺寸={output_image.size}") 198 | 199 | # 将 PIL 图像转换为 torch tensor 200 | tensor_image = torch.from_numpy(np.array(output_image)).permute(2, 0, 1).float() / 255 201 | print(f"tensor 图像形状: {tensor_image.shape}, 最大值: {tensor_image.max()}, 最小值: {tensor_image.min()}") 202 | 203 | 204 | return (tensor_image,) 205 | 206 | except Exception as e: 207 | print(f"错误:{str(e)}") 208 | return (None,) 209 | 210 | NODE_CLASS_MAPPINGS = { 211 | "AIraster": AIraster 212 | } 213 | 214 | NODE_DISPLAY_NAME_MAPPINGS = { 215 | "AIraster": "AIraster" 216 | } 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image-vector-for-ComfyUI 2 | 一个打包了vtracer的comfyui节点,用于把像素转矢量 a wrap-up comfyui nodes for concerting pix to raster 3 | shot out to Vtracer! 4 | https://github.com/visioncortex/vtracer 5 | 6 | 7 | install guide安装指南: 8 | 9 | 1. cd comfyui/custom_nodes(导航至自定义节点文件夹) 10 | 2. git clone(把代码复制到本地) 11 | 3. pip install -r requirements.txt(安装依赖) 12 | (if you are using comfyui-portable, first cd to the folder called"python_embeded",then run "python.exe -m pip install"+"requirements" 如果你用的是comfyui便携版,先导航到python_embeded 这个文件夹,然后用python.exe -m pip install+你要安装的依赖) 13 | 4. install another requirement(https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-33-Q16-HDRI-x64-dll.exe), details on: https://docs.wand-py.org/en/latest/guide/install.html#install-imagemagick-on-windows 14 | 5. restart comfyui(重启comfyUI) 15 | 16 | user guide使用说明: 17 | 18 | ![image](https://github.com/archifancy/Image-vector-for-ComfyUI/assets/125116261/36c8bd0d-1621-4ba3-966d-2ae54c203bb8) 19 | SVG file 转换后效果: 20 | ![image](https://github.com/archifancy/Image-vector-for-ComfyUI/assets/125116261/cd49ccec-f76b-418a-ae3e-a41954d90877) 21 | 22 | 具体参数调整参考:https://github.com/visioncortex/vtracer 23 | just copy your dir to the output_dir,and click queue prompt, you will get your svg file in the folder 24 | 你进需要把文件保存路径拷贝到output_dir,点击生成就可以获得文件 25 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .AIraster import AIraster 2 | 3 | NODE_CLASS_MAPPINGS = { 4 | "AIraster": AIraster 5 | } 6 | 7 | NODE_DISPLAY_NAME_MAPPINGS = { 8 | "AIraster": "AIraster" 9 | } 10 | 11 | __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] 12 | -------------------------------------------------------------------------------- /example/rasterworkflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 32, 3 | "last_link_id": 53, 4 | "nodes": [ 5 | { 6 | "id": 30, 7 | "type": "AIraster", 8 | "pos": [ 9 | -264, 10 | -820 11 | ], 12 | "size": { 13 | "0": 315, 14 | "1": 322 15 | }, 16 | "flags": {}, 17 | "order": 1, 18 | "mode": 0, 19 | "inputs": [ 20 | { 21 | "name": "image", 22 | "type": "IMAGE", 23 | "link": 52 24 | } 25 | ], 26 | "outputs": [ 27 | { 28 | "name": "vector_image", 29 | "type": "IMAGE", 30 | "links": [ 31 | 53 32 | ], 33 | "shape": 3, 34 | "slot_index": 0 35 | } 36 | ], 37 | "properties": { 38 | "Node name for S&R": "AIraster" 39 | }, 40 | "widgets_values": [ 41 | "color", 42 | "stacked", 43 | "spline", 44 | 4, 45 | 6, 46 | 16, 47 | 60, 48 | 4, 49 | 10, 50 | 45, 51 | 8, 52 | "D:\\AI\\ComfyUI\\ComfyUI_windows_portable" 53 | ] 54 | }, 55 | { 56 | "id": 31, 57 | "type": "LoadImage", 58 | "pos": [ 59 | -719, 60 | -829 61 | ], 62 | "size": [ 63 | 315, 64 | 314.0000114440918 65 | ], 66 | "flags": {}, 67 | "order": 0, 68 | "mode": 0, 69 | "outputs": [ 70 | { 71 | "name": "IMAGE", 72 | "type": "IMAGE", 73 | "links": [ 74 | 52 75 | ], 76 | "shape": 3, 77 | "slot_index": 0 78 | }, 79 | { 80 | "name": "MASK", 81 | "type": "MASK", 82 | "links": null, 83 | "shape": 3 84 | } 85 | ], 86 | "properties": { 87 | "Node name for S&R": "LoadImage" 88 | }, 89 | "widgets_values": [ 90 | "AARG lOGO (1).png", 91 | "image" 92 | ] 93 | }, 94 | { 95 | "id": 32, 96 | "type": "SaveImage", 97 | "pos": [ 98 | 179, 99 | -818 100 | ], 101 | "size": { 102 | "0": 315, 103 | "1": 58 104 | }, 105 | "flags": {}, 106 | "order": 2, 107 | "mode": 0, 108 | "inputs": [ 109 | { 110 | "name": "images", 111 | "type": "IMAGE", 112 | "link": 53 113 | } 114 | ], 115 | "properties": {}, 116 | "widgets_values": [ 117 | "ComfyUI" 118 | ] 119 | } 120 | ], 121 | "links": [ 122 | [ 123 | 52, 124 | 31, 125 | 0, 126 | 30, 127 | 0, 128 | "IMAGE" 129 | ], 130 | [ 131 | 53, 132 | 30, 133 | 0, 134 | 32, 135 | 0, 136 | "IMAGE" 137 | ] 138 | ], 139 | "groups": [], 140 | "config": {}, 141 | "extra": { 142 | "ds": { 143 | "scale": 1.0152559799477212, 144 | "offset": [ 145 | 923.5094841179331, 146 | 1211.8058920105257 147 | ] 148 | } 149 | }, 150 | "version": 0.4 151 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "image-vector-for-comfyui" 3 | description = "" 4 | version = "1.0.0" 5 | license = "LICENSE" 6 | dependencies = ["vtracer", "Wand", "Pillow", "torch", "numpy"] 7 | 8 | [project.urls] 9 | Repository = "https://github.com/AARG-FAN/Image-Vector-for-ComfyUI" 10 | # Used by Comfy Registry https://comfyregistry.org 11 | 12 | [tool.comfy] 13 | PublisherId = "" 14 | DisplayName = "Image-Vector-for-ComfyUI" 15 | Icon = "" 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | vtracer 2 | Wand 3 | Pillow 4 | torch 5 | numpy 6 | --------------------------------------------------------------------------------