LicykStyle
Grain, sensor dust and bad glass — added on purpose
- image
- image
What you're actually reaching for
LicykStyle is a finishing pass with two effects stacked in one node: a speckled noise overlay, then radial chromatic aberration - the colour fringing and stretched channels you get from cheap glass. It runs no model. It's numpy and Pillow on the CPU, deterministic for a given seed, and it wants one thing: an IMAGE in, an IMAGE out.
Why bother? Because your render is too clean. Grain is the cheapest single move for making output read as captured rather than generated, and chromatic aberration is one of the lens artifacts diffusion models basically never produce - real glass gives you distortion, vignetting and fringing at the edges, and the community's de-AI recipes name the same stack: grain, motion blur, camera shake, red-channel aberration, vignette.
LicykStyle does two of those in one node. Just don't crank the aberration: a little says "shot on a phone", a lot says "broken VHS".
It's a port of KohakuBlueleaf's a1111-sd-webui-haku-img extension - the LyCORIS author, so not a first-weekend node - and the chromatic half is Kromo v0.3, Yoonsik Park's lens simulator.
What happens inside
The noise pass. It builds an RGBA layer the same size as your image. A per-pixel random draw decides who gets a speck - noise_strength is that probability, not an intensity. The winners get a colour centred on noise_r/noise_g/noise_b, each channel jittered by up to offset_percentage% of 255, then composited over your image at opacity (0–255 alpha). At the defaults, 40% of your pixels get a half-opaque speck with colour jitter. That's loud, on purpose.
The chromatic pass. Kromo splits the image into R/G/B, converts to polar, and blurs each channel radially by a different amount (red least, blue most), then enlarges green and blue and re-centres the channels. chromatic_blur decides whether that radial blur happens: leave it off for crisp channel separation, tick it and the fringes soften into something more like real glass.
Two things the UI won't tell you. Kromo's polar transform needs odd dimensions, so the node crops a pixel off any even width or height before running - and since blue is the enlarged channel, the merged output is slightly bigger than the input, a percent or two depending on strength. And the node adds 0.12 to your strength internally: 0 means off, but 0.01 behaves like 0.13.
The inputs you'll actually set
image- what you're finishing. Usually straight off aVAE Decode.noise_strength- per-pixel probability of getting a speck. For grain that doesn't scream, go 0.02–0.08; the 0.4 default is a stylised effect.noise_r/noise_g/noise_b- the centre colour of the specks, so 255/255/255 is white sensor dust. Pull green and blue down a little (255/235/215) for something warmer and more film-like.offset_percentage- how far each speck strays from that centre colour. Big values give confetti; 0–5 gives near-monochrome grain.opacity- the alpha of the specks. 128 is about 50%; 30–60 is discreet.chromatic_strength- how hard the channels pull apart. 0.05–0.15 is the usable range; 0 turns the pass off.seed- set it, lock it, and note the node hascontrol_after_generatewired up. In a batch each frame getsseed + index, so grain never repeats.
Output is a single image. Wire it to SaveImage or a preview as the last node before saving - and if the pass grew your canvas, either resize after it or set chromatic_strength to 0 and use the node as pure grain.
Install
ComfyUI Manager → Custom Nodes Manager → search ComfyUI-HakuImg → Install → restart. Or by hand:
cd ComfyUI/custom_nodes
git clone --recurse-submodules https://github.com/licyk/ComfyUI-HakuImg
The --recurse-submodules flag is vestigial - the repo ships no submodules - but harmless. No model files to download. The dependencies (opencv-python, scipy, matplotlib, blendmodes, pixeloe, plus the usual torch/Pillow/numpy) are checked by a prestartup_script.py on every ComfyUI boot, and anything missing gets pip-installed into your ComfyUI environment - logged as "Check HakuImg requirements". Standard custom-node behaviour, but it is a startup hook, so if the pack misbehaves, that's the line to find in your console.
Where people get burned
The node is written against ComfyUI's newer backend authoring API (from comfy_api.latest import IO), so it needs a reasonably current ComfyUI - on an old install the nodes simply won't appear, and the fix is updating ComfyUI, not reinstalling the pack.
The other two traps are the defaults and the resolution. No tooltips, widget names that differ from the labels on screen (noise_r shows up as "Noise Color (R)"), and a noise_strength that's a pixel count rather than a blend amount adds up to a first run that looks broken. Start at strength 0.05, opacity 40, chromatic 0.1. Then check your dimensions: the odd-dimension crop plus the blue-channel expansion mean this node does not hand back the pixels you gave it. The chromatic pass is also the slow half by a wide margin - a loop-heavy Python remap of every pixel, on the CPU - so a 4K image sits there noticeably longer than the noise does.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| seed | INT | 00–18446744073709550000 | — |
| noise_strength | FLOAT | 0.400–1 | — |
| noise_r | INT | 2550–255 | — |
| noise_g | INT | 2550–255 | — |
| noise_b | INT | 2550–255 | — |
| offset_percentage | INT | 200–100 | — |
| opacity | INT | 1280–255 | — |
| chromatic_strength | FLOAT | 0.300–1 | — |
| chromatic_blur | BOOLEAN | false | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |