Initial release: Vectorize Pro PNG-to-SVG node for ComfyUI

This commit is contained in:
Aaron Burton
2026-03-18 17:23:52 -07:00
commit 96c6d998dd
8 changed files with 448 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
__pycache__/
*.pyc
*.pyo
*.egg-info/
dist/
build/
.env
.venv/
*.log
.DS_Store
Thumbs.db
node.zip
+9
View File
@@ -0,0 +1,9 @@
Copyright 2026 Augment Studio - Vectprize Pro
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+75
View File
@@ -0,0 +1,75 @@
# Augment Vectorize Pro: PNG to SVG Vectorizer for ComfyUI
Convert raster images into clean, scalable vector graphics, right inside ComfyUI. Augment Vectorize Pro takes your PNGs (logos, icons, illustrations, brand marks) and returns production-ready SVG files with crisp edges, accurate colors, and none of the artifacts you get from auto-trace tools.
Built for designers, studios, and agencies that need vector output they can actually hand off to a client or send to print without cleanup.
## Why this exists
Most PNG-to-SVG converters produce noisy paths, stray anchor points, and muddy fills. Vectorize Pro is powered by the Augment Studio API and tuned specifically for **design assets**: flat artwork, logomarks, icons, and brand graphics. The result is a minimal, well-structured SVG you can open in Illustrator, Figma, or Affinity and use as-is.
## What you get
- **True vector output.** Paths, not embedded rasters. Scale to any size without quality loss.
- **Clean geometry.** Smooth curves, minimal nodes, no stray fragments.
- **Accurate color mapping.** Fills and strokes match the source, ready for brand guidelines.
- **Native ComfyUI SVG type.** Outputs the ComfyUI `SVG` type so you can preview, save, or pipe it downstream.
- **Chainable workflows.** Includes Trigger and Wait nodes for wiring multi-step pipelines (upscale first, then vectorize, etc.).
## Getting started
1. Grab an API key at [augmentstudio.app/pricing](https://augmentstudio.app/pricing)
2. Install from ComfyUI Manager (search **"augment Vectorize Pro"**)
3. Add the **Vectorize Pro** node to your workflow, paste in your key, and connect an image
The node submits your image, polls for completion, and returns the SVG automatically. No extra steps.
## Included nodes
| Node | Category | Description |
| ----------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------ |
| **Vectorize Pro** | Augment / Enhance | Converts a PNG image to a production-ready SVG via the Augment Studio API. |
| **Trigger** | Augment / Utils | Converts any input into a trigger signal for sequencing nodes. |
| **Wait (Int, Float, String, Bool, Image, Latent, ...)** | Augment / Utils | Holds a value until a trigger fires. Use these to enforce execution order in complex workflows. |
## Example workflow
```
Load Image > Vectorize Pro > Save SVG
```
Or chain it with [Augment Upscale Pro](https://github.com/augment-lib/upscale-pro) for maximum quality. Upscale your asset 2x first, then vectorize:
```
Load Image > Upscale Pro (2x) > Vectorize Pro > Save SVG
```
## Manual install
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/augment-lib/vectorize-pro.git augment-vectorize-pro
```
Restart ComfyUI and the nodes will register automatically.
## Requirements
- ComfyUI (latest recommended)
- Python 3.10+
- An Augment Studio API key ([get one here](https://augmentstudio.app/pricing))
Dependencies (`numpy`, `Pillow`, `requests`, `torch`) are installed automatically by ComfyUI Manager or listed in `pyproject.toml` for manual installs.
## Who it's for
- **Brand designers** cleaning up logo assets for style guides
- **Agencies** converting client-supplied rasters into print-ready vectors
- **Icon designers** producing SVG icon sets from pixel art or AI-generated images
- **ComfyUI power users** building end-to-end asset pipelines that output scalable graphics
## License
MIT. See [LICENCE.md](LICENCE.MD) for details.
+44
View File
@@ -0,0 +1,44 @@
"""
Augment - Vectorize Pro
"""
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
_failed = []
# ── Flow Control ──
try:
from .trigger import GateNode, make_wait_node, WAIT_TYPES
NODE_CLASS_MAPPINGS["GateNode"] = GateNode
NODE_DISPLAY_NAME_MAPPINGS["GateNode"] = "Trigger"
for _name, _type_str in WAIT_TYPES.items():
_cls_name = f"AugmentWait{_name}"
try:
NODE_CLASS_MAPPINGS[_cls_name] = make_wait_node(_name, _type_str)
NODE_DISPLAY_NAME_MAPPINGS[_cls_name] = f"{_name} (Improved)"
except Exception as e:
_failed.append((_cls_name, e))
print(f"[augment-vectorize] ⚠ {_cls_name} unavailable: {e}")
except Exception as e:
_failed.append(("Wait nodes", e))
print(f"[augment-vectorize] ⚠ Flow control nodes unavailable: {e}")
# ── PNG to SVG Pro (Paid) ──
try:
from .png_to_svg_pro import AugmentPNGToSVGPro
NODE_CLASS_MAPPINGS["AugmentPNGToSVGPro"] = AugmentPNGToSVGPro
NODE_DISPLAY_NAME_MAPPINGS["AugmentPNGToSVGPro"] = "Vectorize Pro"
except Exception as e:
_failed.append(("AugmentPNGToSVGPro", e))
print(f"[augment-vectorize] ⚠ AugmentPNGToSVGPro unavailable: {e}")
# ── Summary ──
WEB_DIRECTORY = "./web/js"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
if _failed:
print(f"[augment-vectorize] ✓ Registered {len(NODE_CLASS_MAPPINGS)} nodes ({len(_failed)} failed)")
for node_id, err in _failed:
print(f"[augment-vectorize] ✗ {node_id}: {err}")
else:
print(f"[augment-vectorize] ✓ Registered {len(NODE_CLASS_MAPPINGS)} nodes")
+123
View File
@@ -0,0 +1,123 @@
# "Design-focused node suite for ComfyUI."
# Copyright 2026 Augment Studio
# Paid Node, credit values may change.
# Augmentstudio.app
import io
import json
import requests
import time
import numpy as np
from PIL import Image
NODE_ID = "png_to_svg_pro"
API_URL = "https://augmentstudio.app/api"
class AugmentPNGToSVGPro:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"api_key": ("STRING", {"default": ""}),
"image": ("IMAGE",),
},
"optional": {
"trigger": ("TRIGGER",),
},
}
RETURN_TYPES = ("SVG", "AUGMENT_JSON", "TRIGGER")
RETURN_NAMES = ("svg", "json_result", "trigger")
FUNCTION = "execute"
CATEGORY = "Augment/Enhance"
OUTPUT_NODE = True
@classmethod
def IS_CHANGED(cls, **kwargs):
return time.time()
@classmethod
def VALIDATE_INPUTS(cls, **kwargs):
return True
def execute(self, api_key, image, trigger=None):
api_url = API_URL.rstrip("/")
auth = {"Authorization": f"Bearer {api_key}"}
img_np = (image[0].cpu().numpy() * 255).astype(np.uint8)
pil_img = Image.fromarray(img_np, "RGB")
buf = io.BytesIO()
pil_img.save(buf, format="PNG")
img_bytes = buf.getvalue()
print(f"[Augment API] PNG to SVG Pro: submitting {img_np.shape[1]}x{img_np.shape[0]} image ({len(img_bytes)} bytes)")
try:
r = requests.post(
f"{api_url}/process",
files={"image": ("image_input.png", img_bytes, "image/png")},
data={"node_id": NODE_ID},
headers=auth,
timeout=30,
)
except requests.exceptions.RequestException as e:
print(f"[Augment API] Submit failed: {e}")
raise
if r.status_code != 200:
raise RuntimeError(f"Submit error {r.status_code}: {r.text[:500]}")
request_id = r.json().get("request_id")
if not request_id:
raise RuntimeError(f"No request_id: {r.text[:500]}")
print(f"[Augment API] Job submitted: {request_id}")
max_wait = 60
elapsed = 0
job_status = "unknown"
while elapsed < max_wait:
time.sleep(1)
elapsed += 1
try:
status_r = requests.get(
f"{api_url}/job/{request_id}/status",
headers=auth, timeout=10,
)
job_status = status_r.json().get("status", "unknown")
except Exception as e:
print(f"[Augment API] Poll error: {e}")
continue
if job_status == "done":
break
elif job_status == "error":
raise RuntimeError(f"Job failed: {status_r.json().get('error')}")
if job_status != "done":
raise RuntimeError(f"Timed out after {max_wait}s")
svg_r = requests.get(
f"{api_url}/job/{request_id}/image",
headers=auth, timeout=60,
)
if svg_r.status_code != 200:
raise RuntimeError(f"SVG fetch error: {svg_r.status_code}")
from comfy_api.latest._util.image_types import SVG
svg_bytes = io.BytesIO(svg_r.content)
size = svg_bytes.getbuffer().nbytes
print(f"[Augment API] Done! Received SVG ({size} bytes)")
json_result = json.dumps({"node": "PNGToSVGPro", "size_bytes": size})
return {"ui": {"text": [svg_r.text[:500]]}, "result": (SVG([svg_bytes]), json_result, "done")}
NODE_CLASS_MAPPINGS = {
"AugmentPNGToSVGPro": AugmentPNGToSVGPro,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"AugmentPNGToSVGPro": "PNG to SVG Pro",
}
+21
View File
@@ -0,0 +1,21 @@
[project]
name = "augment-VectorizePro"
description = "✨ augment Vectorize — PNG to SVG vectorizer for ComfyUI. Convert raster images, logos, and icons into clean scalable vector graphics. A professional vectorization tool built directly into your ComfyUI workflow via the Augment Studio API with automatic job polling and SVG output. Ideal for logo design, brand asset pipelines, and any workflow that needs crisp vector conversion. Includes built-in flow control trigger and wait nodes for chaining multi-step pipelines. Get your API key at https://augmentstudio.app/pricing"
version = "1.0.0"
licence = { file = "LICENCE.md" }
dependencies = [
"numpy>=1.19.0",
"torch>=1.9.0",
"Pillow>=8.0.0",
"requests>=2.25.0"
]
[project.urls]
Repository = "https://github.com/augment-lib/vectorize-pro"
Website = "https://augmentstudio.app/"
[tool.comfy]
PublisherId = "augmentstudio"
DisplayName = "✨ augment Vectorize Pro - PNG to SVG Vectorizer for ComfyUI"
Icon = "https://avatars.githubusercontent.com/u/243209371?s=400&u=db6e0d310dcf8863dd5c735d3ebbdaa5c317530d&v=4"
+99
View File
@@ -0,0 +1,99 @@
# "Design-focused node suite for ComfyUI."
# Copyright 2026 Augment Studio
# Augmentstudio.app
import json
class GateNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("*",),
},
}
RETURN_TYPES = ("TRIGGER", "*", "STRING")
RETURN_NAMES = ("trigger", "value", "json")
OUTPUT_NODE = True
FUNCTION = "execute"
CATEGORY = "Augment/Utils"
@classmethod
def VALIDATE_INPUTS(cls, **kwargs):
return True
def execute(self, input):
json_result = json.dumps({"node": "Convert Any", "type": type(input).__name__, "value": str(input)})
return {"ui": {"text": [str(input)]}, "result": ("done", input, json_result)}
WAIT_DEFAULTS = {
"INT": {"default": 0},
"FLOAT": {"default": 0.0, "step": 0.01},
"STRING": {"default": ""},
"BOOLEAN": {"default": False},
}
def make_wait_node(type_name, type_str):
class WaitNode:
@classmethod
def INPUT_TYPES(cls):
has_widget = type_str in WAIT_DEFAULTS
inputs = {
"required": {},
"optional": {
"trigger": ("TRIGGER",),
},
}
if has_widget:
inputs["required"]["value"] = (type_str, WAIT_DEFAULTS[type_str])
inputs["optional"]["input"] = (type_str, {"forceInput": True})
else:
inputs["required"]["value"] = (type_str,)
return inputs
RETURN_TYPES = (type_str, "AUGMENT_JSON", "TRIGGER")
RETURN_NAMES = ("value", "json", "trigger")
OUTPUT_NODE = True
FUNCTION = "execute"
CATEGORY = "Augment/Utils"
@classmethod
def VALIDATE_INPUTS(cls, **kwargs):
return True
def execute(self, value=None, trigger=None, input=None):
v = input if input is not None else value
json_result = json.dumps({"node": type_str, "type": type_str.lower(), "value": str(v)})
return {"ui": {"text": [str(v)]}, "result": (v, json_result, "done")}
WaitNode.__name__ = f"Wait{type_name}Node"
WaitNode.__qualname__ = f"Wait{type_name}Node"
return WaitNode
WAIT_TYPES = {
"Int": "INT",
"Float": "FLOAT",
"String": "STRING",
"Bool": "BOOLEAN",
"Image": "IMAGE",
"Latent": "LATENT",
"Condition": "CONDITIONING",
"VAE": "VAE",
"Model": "MODEL",
"Clip": "CLIP",
"Mask": "MASK",
}
NODE_CLASS_MAPPINGS = {"GateNode": GateNode}
NODE_DISPLAY_NAME_MAPPINGS = {"GateNode": "Trigger"}
for name, type_str in WAIT_TYPES.items():
cls_name = f"Wait{name}Node"
node_cls = make_wait_node(name, type_str)
NODE_CLASS_MAPPINGS[cls_name] = node_cls
NODE_DISPLAY_NAME_MAPPINGS[cls_name] = f"{name} (Improved)"
+65
View File
@@ -0,0 +1,65 @@
/*"Augment Vectorize — AI PNG to SVG Vectorizer for ComfyUI."
# Copyright 2026 Augment Studio
Augmentstudio.app */
import { app } from "../../../scripts/app.js";
const TITLE_BG = "#443322";
const NODE_BG = "#665533";
const PAID_NODES = [
"AugmentPNGToSVGPro",
];
const BADGE_FONT = "bold 9px sans-serif";
const BADGE_RADIUS = 4;
const BADGE_PAD_X = 6;
app.registerExtension({
name: "augment-vectorize.NodeStyle",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (!PAID_NODES.includes(nodeData.name)) return;
const origCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
origCreated?.apply(this, arguments);
this.addWidget("button", "Get Tokens", null, () => {
window.open("https://augmentstudio.app/pricing", "_blank");
});
const jsonIdx = this.outputs?.findIndex(o => o.name === "json_result");
if (jsonIdx >= 0) {
this.outputs.splice(jsonIdx, 1);
}
};
const origDraw = nodeType.prototype.onDrawForeground;
nodeType.prototype.onDrawForeground = function (ctx) {
this.color = TITLE_BG;
this.bgcolor = NODE_BG;
origDraw?.apply(this, arguments);
ctx.save();
ctx.font = BADGE_FONT;
const creditsText = "🔸 1 export / run";
const creditsW = ctx.measureText(creditsText).width + BADGE_PAD_X * 2;
const creditsX = this.size[0] - creditsW - 6;
const y = -22;
ctx.beginPath();
ctx.roundRect(creditsX, y, creditsW, 14, BADGE_RADIUS);
ctx.fillStyle = "#8d6932";
ctx.fill();
ctx.fillStyle = "#ffffff";
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText(creditsText, creditsX + BADGE_PAD_X, y + 7);
ctx.restore();
};
},
});