161 lines
5.9 KiB
Python
161 lines
5.9 KiB
Python
"""
|
|
ComfyUI Pixel Sort Node
|
|
Integrates ASDFPixelSort Processing script with ComfyUI workflows
|
|
"""
|
|
|
|
import subprocess
|
|
import os
|
|
import tempfile
|
|
import torch
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
class PixelSortNode:
|
|
"""
|
|
A ComfyUI node that applies pixel sorting effects using the ASDFPixelSort Processing script.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"mode": (["white", "black", "bright", "dark"], {"default": "white"}),
|
|
"white_threshold": ("INT", {"default": -12345678, "min": -16777216, "max": 0, "step": 1000}),
|
|
"black_threshold": ("INT", {"default": -3456789, "min": -16777216, "max": 0, "step": 1000}),
|
|
"bright_threshold": ("INT", {"default": 127, "min": 0, "max": 255, "step": 1}),
|
|
"dark_threshold": ("INT", {"default": 223, "min": 0, "max": 255, "step": 1}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "apply_pixel_sort"
|
|
CATEGORY = "image/effects"
|
|
|
|
def __init__(self):
|
|
# Path to Processing installation
|
|
self.processing_path = "/root/Processing/processing-4.3/processing-java"
|
|
self.sketch_path = "/root/Processing/ASDFPixelSort"
|
|
|
|
def apply_pixel_sort(self, image, mode, white_threshold, black_threshold, bright_threshold, dark_threshold):
|
|
"""
|
|
Apply pixel sorting to the input image using the Processing script.
|
|
"""
|
|
|
|
# Convert mode string to number
|
|
mode_map = {"white": 0, "black": 1, "bright": 2, "dark": 3}
|
|
mode_num = mode_map[mode]
|
|
|
|
# Create temporary files
|
|
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as input_file:
|
|
input_path = input_file.name
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as output_file:
|
|
output_path = output_file.name
|
|
|
|
try:
|
|
# Convert tensor to PIL Image and save
|
|
img_tensor = image[0] # Remove batch dimension
|
|
img_array = (img_tensor.cpu().numpy() * 255).astype(np.uint8)
|
|
img_pil = Image.fromarray(img_array)
|
|
img_pil.save(input_path)
|
|
|
|
# Prepare command arguments
|
|
cmd = [
|
|
self.processing_path,
|
|
f"--sketch={self.sketch_path}",
|
|
"--run",
|
|
input_path,
|
|
output_path,
|
|
str(mode_num),
|
|
str(white_threshold),
|
|
str(black_threshold),
|
|
str(bright_threshold),
|
|
str(dark_threshold)
|
|
]
|
|
|
|
# Set up environment for headless operation
|
|
env = os.environ.copy()
|
|
env["DISPLAY"] = ":99"
|
|
|
|
# Run the Processing script
|
|
print(f"Running pixel sort with mode: {mode}")
|
|
result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=120)
|
|
|
|
if result.returncode != 0:
|
|
print(f"Processing script error: {result.stderr}")
|
|
raise RuntimeError(f"Pixel sort failed: {result.stderr}")
|
|
|
|
# Load the processed image
|
|
if not os.path.exists(output_path):
|
|
raise RuntimeError("Output image was not created")
|
|
|
|
img_sorted = Image.open(output_path).convert("RGB")
|
|
img_array = np.array(img_sorted).astype(np.float32) / 255.0
|
|
img_tensor = torch.from_numpy(img_array).unsqueeze(0) # Add batch dimension
|
|
|
|
print(f"Pixel sort completed successfully")
|
|
return (img_tensor,)
|
|
|
|
except subprocess.TimeoutExpired:
|
|
raise RuntimeError("Pixel sort operation timed out")
|
|
except Exception as e:
|
|
raise RuntimeError(f"Pixel sort failed: {str(e)}")
|
|
finally:
|
|
# Clean up temporary files
|
|
try:
|
|
if os.path.exists(input_path):
|
|
os.unlink(input_path)
|
|
if os.path.exists(output_path):
|
|
os.unlink(output_path)
|
|
except:
|
|
pass
|
|
|
|
class PixelSortAdvancedNode:
|
|
"""
|
|
Advanced pixel sort node with more control options.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"mode": (["white", "black", "bright", "dark"], {"default": "white"}),
|
|
},
|
|
"optional": {
|
|
"white_threshold": ("INT", {"default": -12345678, "min": -16777216, "max": 0, "step": 1000}),
|
|
"black_threshold": ("INT", {"default": -3456789, "min": -16777216, "max": 0, "step": 1000}),
|
|
"bright_threshold": ("INT", {"default": 127, "min": 0, "max": 255, "step": 1}),
|
|
"dark_threshold": ("INT", {"default": 223, "min": 0, "max": 255, "step": 1}),
|
|
"custom_sketch_path": ("STRING", {"default": ""}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "apply_pixel_sort_advanced"
|
|
CATEGORY = "image/effects"
|
|
|
|
def apply_pixel_sort_advanced(self, image, mode, white_threshold=-12345678, black_threshold=-3456789,
|
|
bright_threshold=127, dark_threshold=223, custom_sketch_path=""):
|
|
"""
|
|
Advanced pixel sorting with optional custom sketch path.
|
|
"""
|
|
node = PixelSortNode()
|
|
if custom_sketch_path:
|
|
node.sketch_path = custom_sketch_path
|
|
|
|
return node.apply_pixel_sort(image, mode, white_threshold, black_threshold,
|
|
bright_threshold, dark_threshold)
|
|
|
|
# Node registration
|
|
NODE_CLASS_MAPPINGS = {
|
|
"PixelSortNode": PixelSortNode,
|
|
"PixelSortAdvancedNode": PixelSortAdvancedNode
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"PixelSortNode": "Pixel Sort (ASDF)",
|
|
"PixelSortAdvancedNode": "Pixel Sort Advanced (ASDF)"
|
|
}
|