Eses Image Effect Curves
Draggable curves inside ComfyUI — the color-grading node that remembers every drag
- image
- mask
- adjusted_image
- adjusted_mask
- image
- mask
ComfyUI will happily render you a gorgeous image and then make you fix its look the hard way. Want to crush the blacks, lift the shadows, or push a cold film grade onto everything? Out of the box you're stitching together color-math nodes or hoping the VAE magically does the grading for you. That's the gap Eses Image Effect Curves fills: a proper Photoshop-style curves editor that lives right on the node, in your graph, and saves every drag into the workflow file.
If you've used curves in Lightroom or Photoshop, you already know the model: a diagonal line from black to white, grab it and drag, pull the endpoints inward to crush, push them out to expand. This node puts that whole editor on the node surface with a live preview. Connect an image (and/or a mask), pick a channel, and drag points. Because the class declares IS_CHANGED as NaN, ComfyUI never skips a re-run, so the preview updates as you drag instead of after a manual queue. It's also non-destructive by construction - it hands you the original right next to the adjusted result.
The mechanism is simple and worth knowing. Each channel's curve becomes a 256-entry lookup table, and pixels run through them in a deliberate order: convert to HSV, apply the Luminosity curve to value and the Saturation curve to saturation, convert back to RGB, then apply the Red, Green, Blue and finally the master RGB curve. That's a real grading pipeline, not a single brightness slider. One honest note: the README bills it as a "GPU-accelerated PyTorch backend," but what actually executes is PIL/NumPy with tensors round-tripped through CPU. For a few images - which is the whole use case - it's effectively instant, so you won't notice.
Three inputs matter, and you'll barely touch two of them:
- channel - RGB / Red / Green / Blue / Luminosity / Saturation / Mask. This is just which curve the editor is showing, a UI selector rather than an effect toggle.
- preset -
Noneplus thecold,low contrast,metallic, andpainterlygrades that ship in the pack's/presetsfolder. Pick one as a starting point. - all_curves_json - the serialized state of every channel's curve. You don't edit this by hand; the widget writes it, and it rides along inside your saved workflow.
Then the optional image and mask inputs. Outputs: adjusted_image and adjusted_mask (the graded versions) plus image and mask passthroughs. Wire adjusted_image into your save or VAE-encode step; keep the passthrough when you want a clean before/after comparison or need to grade a reference image feeding an IPAdapter.
Where does it fit? End of the graph - after upscale and VAE decode, right before save. It's also quietly great on masks: pick the Mask channel, drag a hard S-curve, and you've feathered or hardened your mask without a single blur node. The metallic preset alone is worth stealing for product shots.
Install is painless - the only thing in requirements.txt is torch>=2.6.0, which you already have if ComfyUI runs.
- ComfyUI Manager → search ComfyUI-EsesImageEffectCurves → install → restart.
- Or:
cd ComfyUI/custom_nodes && git clone https://github.com/quasiblob/ComfyUI-EsesImageEffectCurves.gitand restart.
No model downloads, no extra heavy deps. Because the editor ships as a JS frontend widget, do a full ComfyUI restart after installing rather than just a browser refresh. The node appears under Eses Nodes/Image Adjustments.
Gotchas worth knowing: this is a genuinely tiny pack - no search impressions, no community threads to lean on - so if it misbehaves you're reading the short open source yourself. Curves persist with the workflow, and so does the node's own size and state, so a graded node comes back exactly as you left it. To roll your own preset, hit Copy Settings, drop the JSON into the pack's /presets folder, and reload the page. And the usual custom-node caveat applies: it executes arbitrary Python on import, so give the small repo a glance if you're the paranoid type.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| preset | COMBO | 5 options: None, cold, low contrast, metallic, painterly | |
| channel | COMBO | 7 options: RGB, Red, Green, Blue, Luminosity, Saturation, +1 | |
| all_curves_json | STRING | {"rgb": [[0.0, 0.0], [1.0, 1.0]], "r": [[0.0, 0.0], [1.0, 1.0]], "g": [[0.0, 0.0], [1.0, 1.0]], "b": [[0.0, 0.0], [1.0, 1.0]], "luma": [[0.0, 0.0], [1.0, 1.0]], "sat": [[0.0, 0.0], [1.0, 1.0]], "mask": [[0.0, 0.0], [1.0, 1.0]]} | — |
| imageopt | IMAGE | — | |
| maskopt | MASK | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| adjusted_image | IMAGE | — |
| adjusted_mask | MASK | — |
| image | IMAGE | — |
| mask | MASK | — |