214 lines
8.0 KiB
Markdown
214 lines
8.0 KiB
Markdown
# Aioli Nodes — ComfyUI Custom Node Suite
|
||
|
||
Three utility nodes for outpainting and inpainting in ComfyUI.
|
||
|
||
## Installation
|
||
|
||
### Via ComfyUI Registry (recommended)
|
||
|
||
Search **"Aioli Nodes"** directly in the ComfyUI Manager → Install Nodes.
|
||
|
||
### Via Git URL
|
||
|
||
```
|
||
https://github.com/aiolicollective/aioli-nodes
|
||
```
|
||
|
||
### Manual installation
|
||
|
||
1. Copy the `aioli-nodes` folder into `ComfyUI/custom_nodes/`
|
||
2. Restart ComfyUI
|
||
3. The nodes appear under the **Aioli Nodes** category
|
||
|
||
No extra dependencies — only `math` (Python stdlib), `torch` and `Pillow` (already bundled with ComfyUI).
|
||
|
||
---
|
||
|
||
## 🖼️ Ratio Outpaint Calc
|
||
|
||
Prepares an image for outpainting to a standard aspect ratio.
|
||
Automatically computes padding and generates the mask.
|
||
|
||
**Inputs**
|
||
| Parameter | Type | Description |
|
||
|-----------|------|-------------|
|
||
| image | IMAGE | Source image |
|
||
| ratio | dropdown | `none` · `1:1` · `4:5` · `5:4` · `3:4` · `4:3` · `16:9` · `9:16` |
|
||
|
||
**Outputs**
|
||
| Output | Type | Description |
|
||
|--------|------|-------------|
|
||
| image_padded | IMAGE | Image padded with neutral grey (0.5) |
|
||
| mask | MASK | Binary mask (0 = keep, 1 = generate) |
|
||
|
||
**Workflow**
|
||
```
|
||
Load Image → 🖼️ Ratio Outpaint Calc → VAE Encode (Inpaint) → KSampler
|
||
```
|
||
|
||
---
|
||
|
||
## 📐 BBox Multiple Fix
|
||
|
||
Plugs in right after **Mask Bounding Box** (ComfyUI Essentials).
|
||
Rounds the crop to the chosen multiple and optionally upscales to a Flux-friendly resolution.
|
||
|
||
The node ensures the inpainted region stitches back **pixel-perfectly** onto the base image — no border artefacts, no alignment drift.
|
||
|
||
**Example**
|
||
|
||

|
||
|
||
*The inpaint applied back onto the base image fits the original contours exactly — pixel-perfect edges, no alignment drift.*
|
||
|
||
**Inputs**
|
||
| Parameter | Type | Description |
|
||
|-----------|------|-------------|
|
||
| image | IMAGE | Full source image (before crop) |
|
||
| mask | MASK | Full source mask (before crop) |
|
||
| x | INT | `x` output from Mask Bounding Box |
|
||
| y | INT | `y` output from Mask Bounding Box |
|
||
| width | INT | `width` output from Mask Bounding Box |
|
||
| height | INT | `height` output from Mask Bounding Box |
|
||
| multiple | dropdown | `8 (VAE)` · `32 (SD1.5)` · `64 (SDXL/Flux)` |
|
||
| target | dropdown | `none` · `512` · `768` · `1024` · `1536` · `2048` |
|
||
|
||
**Outputs**
|
||
| Output | Type | Description |
|
||
|--------|------|-------------|
|
||
| image_cropped | IMAGE | Cropped image (scaled if target is set or if bbox > 2048px) |
|
||
| mask_cropped | MASK | Cropped mask (scaled if target is set or if bbox > 2048px) |
|
||
| x | INT | x position for ImageCompositeMasked |
|
||
| y | INT | y position for ImageCompositeMasked |
|
||
| orig_width | INT | Crop width BEFORE scale (use for resize-back after VAE Decode) |
|
||
| orig_height | INT | Crop height BEFORE scale (use for resize-back after VAE Decode) |
|
||
| width | INT | Final width (after scale) |
|
||
| height | INT | Final height (after scale) |
|
||
| target_size | INT | Target value as INT (0 if `none`) — connect directly to ImageResize+ |
|
||
|
||
**Scaling behaviour**
|
||
|
||
| Situation | Behaviour |
|
||
|-----------|----------|
|
||
| bbox ≤ 2048, target `none` | Round up to multiple only — no scale |
|
||
| bbox ≤ 2048, target set | Upscale crop to target — pixel-perfect ratio via GCD |
|
||
| bbox > 2048 (any target) | Downscale crop to fit 2048px — ratio preserved via GCD |
|
||
|
||
**Workflow without scale**
|
||
```
|
||
BBox Fix → VAE Encode → KSampler → VAE Decode → ImageCompositeMasked ← x, y
|
||
```
|
||
|
||
**Workflow with upscale / downscale**
|
||
```
|
||
BBox Fix → VAE Encode → KSampler → VAE Decode
|
||
↓ orig_width, orig_height ↓
|
||
↓ x, y → ImageResize+ ←────────┘
|
||
↑ target_size
|
||
ImageCompositeMasked ← x, y
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 Inpaint Color Fix
|
||
|
||
Plugs in right after **VAE Decode**, before `ImageResize+` / `ImageCompositeMasked`.
|
||
|
||
Corrects colorimetric drift introduced by the generation — selectively applies a LAB color match only on pixels that haven't significantly changed (similar zone), leaving the truly creative pixels untouched.
|
||
|
||
**Inputs**
|
||
| Parameter | Type | Description |
|
||
|-----------|------|-------------|
|
||
| original_crop | IMAGE | `image_cropped` from BBoxMultipleFix (before KSampler) |
|
||
| inpainted_crop | IMAGE | IMAGE from VAE Decode |
|
||
| delta_e_threshold | FLOAT | Similarity threshold (-1 = auto). Below = corrected, above = creative/intact |
|
||
| blend_strength | FLOAT | Color match strength on similar zones (0 = none, 1 = full) |
|
||
| feather_radius | INT | Gaussian blur radius on the correction mask (0 = disabled) |
|
||
| mask *(optional)* | MASK | Override mode: bypasses Delta-E, the mask drives correction directly |
|
||
|
||
**Outputs**
|
||
| Output | Type | Description |
|
||
|--------|------|-------------|
|
||
| image_corrected | IMAGE | Color-corrected crop — connect to ImageResize+ |
|
||
| correction_mask | MASK | Debug mask (white = corrected, black = creative/intact) |
|
||
|
||
**Modes**
|
||
|
||
| Mode | Behaviour |
|
||
|------|-----------|
|
||
| No mask connected | Delta-E auto-detects similar vs creative pixels |
|
||
| Mask connected | Delta-E is bypassed — the mask controls correction directly |
|
||
|
||
**Delta-E threshold guide**
|
||
|
||
| Value | Effect |
|
||
|-------|--------|
|
||
| `-1` (auto) | Recommended starting point |
|
||
| `5–10` | Strict — corrects almost everything except highly creative pixels |
|
||
| `15–20` | Balanced — fixes subtle drift, preserves real changes |
|
||
| `25–35` | Loose — only corrects near-identical pixels |
|
||
| `50+` | Near-global color match |
|
||
|
||
**Position in workflow**
|
||
```
|
||
BBoxMultipleFix
|
||
└── image_cropped ──────────────────────────────────┐
|
||
↓
|
||
└── image_cropped → KSampler → VAEDecode → 🎨 InpaintColorFix → ImageResize+ → ImageCompositeMasked
|
||
```
|
||
|
||
---
|
||
|
||
## 🔁 Example Workflow — Flux2Klein Inpaint
|
||
|
||
> **[⬇ Download workflow JSON](examples/WF_Inpaint_aioli-nodes.json)**
|
||
|
||
A complete, ready-to-use inpaint workflow for **Flux2Klein** (9B), packaged as a ComfyUI subgraph (`FLUX2KLEIN_INPAINT`).
|
||
|
||
**Required models**
|
||
| Role | File |
|
||
|------|------|
|
||
| UNet | `flux2/flux-2-klein-9b-fp8.safetensors` |
|
||
| VAE | `flux2/flux2-vae.safetensors` |
|
||
| Text encoder | `qwen_3_8b_fp8mixed.safetensors` |
|
||
|
||
**Required custom nodes**
|
||
- **Aioli Nodes** (this repo) — `BBoxMultipleFix`, `InpaintColorFix`
|
||
- **ComfyUI Essentials** — `MaskBoundingBox+`, `ImageResize+`
|
||
- **ComfyUI KJNodes** — `GrowMaskWithBlur`
|
||
- **rgthree-comfy** — `Image Comparer` (optional, for before/after preview)
|
||
|
||
**Subgraph inputs**
|
||
| Input | Description |
|
||
|-------|-------------|
|
||
| IMAGE | Source image (with painted mask) |
|
||
| MASK | Inpaint mask |
|
||
| Resize_Megapixels | Working resolution in megapixels (default: 4) |
|
||
| PROMPT | Inpaint prompt |
|
||
| Resize_Inpaint_Target | BBox target size (`none` · `512` · `768` · `1024` · `1536` · `2048`) |
|
||
|
||
**Internal pipeline**
|
||
```
|
||
LoadImageOutput (with mask painter)
|
||
└→ FLUX2KLEIN_INPAINT subgraph
|
||
├── ImageScaleToTotalPixels (resize source to working MP)
|
||
├── MaskBoundingBox+ (detect mask region)
|
||
├── BBoxMultipleFix (crop + scale for Flux, cap 2048px)
|
||
├── GrowMaskWithBlur (soften mask edges)
|
||
├── VAEEncode × 2 + SetLatentNoiseMask
|
||
├── CLIPTextEncode → ReferenceLatent → FluxGuidance
|
||
├── Flux2Scheduler + KSamplerSelect + RandomNoise
|
||
├── SamplerCustomAdvanced
|
||
├── VAEDecode
|
||
├── InpaintColorFix (selective LAB color match)
|
||
├── ImageResize+ (resize back to orig_width × orig_height)
|
||
└── ImageCompositeMasked (stitch result onto source)
|
||
└→ SaveImage + Image Comparer (before / after)
|
||
```
|
||
|
||
**Usage**
|
||
1. Download the JSON and drag it into ComfyUI
|
||
2. Point `LoadImageOutput` to your image and paint your mask
|
||
3. Set your prompt and target resolution in the subgraph inputs
|
||
4. Run — the result is composited pixel-perfectly back onto the source image
|