PhotoLab
The node that gives AI skin pores, peach fuzz, and a film look
- images
- reference_image
- face_mask
- images
- face_mask
If you've ever rendered a portrait and thought "this is a skincare ad, not a person," PhotoLab is aimed squarely at that. The most reliable tell that an image is AI-made has been too-smooth, poreless, plastic skin since 2023, and no checkpoint fully fixes it in one pass. PhotoLab is the post-processing answer: it's a single node that bakes in film grain, compression artifacts, color grading, vignette, and a whole suite of procedural skin effects - pores, freckles, blemishes, acne, subsurface scattering, peach fuzz, even a little T-zone oil shine - so a clean render finally looks lived-in and human. It's the kind of finishing pass the "amateur photography" LoRAs try to teach the model; here it's deterministic and you control every dial.
The name is a lie in the best way: this is not a neural anything. It's pure classic image processing - PIL, numpy, and a couple of blur routines running on CPU in milliseconds. It's not a model, it downloads nothing, and it won't touch your VRAM. That's also what makes it worth keeping in a workflow: you can rerun it a dozen times tweaking skin_seed and it costs you nothing but time.
How it works
Every input image goes through a fixed pipeline, roughly in this order: optional lighting match (first, in CIE LAB space), saturation and color grade, pixelation, the masked skin effects, then film grain, blur, vignette, and finally JPG compression. That compression pass isn't an accident - it saves the image to JPEG into an in-memory buffer and reopens it passes times, so quality at 60 with two passes gives you authentic blocky artifacts, not a filter approximation.
The skin effects are the interesting part. Each one is a different synthesis trick: pores are a high-frequency Difference-of-Gaussians texture layer blended in Overlay mode, peach fuzz is procedurally drawn micro-strokes, subsurface scattering is a low-opacity orange-pink blurred layer Screen-blended in. Before applying anything, the node samples the central 40% of the image in LAB space to estimate a Fitzpatrick skin-tone bucket (1–6) and scales the melanin-aware effects (freckles, blemishes) to match. That's genuinely thoughtful - the same freckle setting on a fair face and a dark face doesn't look identical.
The inputs that matter
You don't touch most of these on day one. The node ships with collapsible Global Photo Effects and Face Skin Effects preset buttons in its UI - Film Snapshot, Darkroom B&W, Golden Hour, plus face presets like Natural Skin and High-Detail Skin - and you can save your own to localStorage. Start there.
When you go manual, three things matter most:
mask_mode- "Face Only" applies skin effects inside a connectedface_mask, "Inverted" applies them outside (for body skin), and "Disabled" skips all skin effects so the node becomes a pure global-effects pass. If a face preset seems to do nothing, this is the first place to look.skin_seed- one seed drives every procedural pattern (pores, freckles, spots, fuzz). Same seed + same image = same face every time; bump it to reshuffle blemishes.quality/passes- your compression-artifact dials. Lowerquality, morepasses, more degradation.
The two inputs worth wiring from elsewhere: reference_image (only read when lighting_match_mode isn't Disabled - pick Histogram (L-channel) as the best all-rounder for matching another photo's light and shadow) and face_mask, typically from an Impact Pack or similar face-detect node. Outputs are images and a passthrough face_mask (full white if you never connected one), so you can reuse the same mask downstream without a second mask node.
Installing it
Here's the gotcha: the PhotoLab repo's own README is now just a stub that says moved to ComfyUI-ialhabbal, install the suite instead. The author folded PhotoLab into that larger suite, so don't chase the standalone repo - install the suite:
cd ComfyUI/custom_nodes
git clone https://github.com/ialhabbal/ComfyUI-ialhabbal
or search ComfyUI-ialhabbal in ComfyUI Manager. The old ialhabbal/PhotoLab path still installs and works if you really want it, but it's no longer where updates land.
Where people get burned
The one real trap is the lighting match doing nothing. It relies on scikit-image, which isn't in the pack's dependency list - if it's missing, the node prints a console warning and silently skips the match. If you set lighting_match_mode and see no change, pip install scikit-image and restart. Everything else is just sliders being at 0 - which, in this node, means "off," by design.
Inputs (31)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | The images to apply photo lab effects to. | |
| quality | INT | 700–100 | JPG compression quality. Lower = more artifacts (0-100) |
| passes | INT | 11–10 | Number of compression passes. More passes = more degradation |
| pixelate_strength | INT | 00–100 | Pixelation strength. Reduces image to a lower resolution grid and scales back up. Set to 0 to disable (0-100) |
| grain_strength | INT | 50–100 | Amount of film grain. Set to 0 to disable (0-100) |
| vignette_strength | INT | 00–100 | Vignette darkness around edges. Set to 0 to disable (0-100) |
| color_grade | COMBO | Faded | Color grading preset |
| color_grade_strength | INT | 500–100 | Strength of color grade effect. 0 = disabled, 100 = full effect (0-100) |
| saturation | INT | 700–200 | Color saturation (0 = grayscale, 100 = original, 200 = max boost) |
| blur_type | COMBO | None | Type of blur to apply |
| blur_strength | INT | 00–100 | Strength of blur effect. Set to 0 to disable (0-100) |
| lighting_match_mode | COMBO | Disabled | Algorithm used to match lighting from reference image. 'Histogram (L-channel)' - best all-rounder, matches luminance distribution only. 'Reinhard Transfer' - fast mean/std transfer, good for subtle corrections. 'Full LAB Histogram' - matches all LAB channels including color tone. |
| lighting_match_strength | FLOAT | 1.000–1 | Blend between original (0.0) and fully matched lighting (1.0) |
| mask_mode | COMBO | Face Only | Controls the face_mask and ALL skin effects below this widget. 'Face Only' — skin effects apply only inside the connected mask. 'Inverted' — skin effects apply only outside the mask (e.g. body skin). 'Disabled' — ALL skin effect widgets below are completely skipped; only global effects above (color grade, blur, grain, vignette, compression) run. |
| pores_strength | INT | 00–100 | Simulates visible skin pores via a high-frequency Difference-of-Gaussians texture layer blended in Overlay mode. Subtle: 10-25 | Natural: 30-50 | Heavy/macro: 60-80. Set to 0 to disable. |
| blemishes_strength | INT | 00–100 | Adds soft reddish/brownish skin blemishes (pigmentation spots) using Gaussian-falloff ellipses in melanin-aware colour space. Light: 10-25 | Moderate: 30-55 | Heavy: 60-100. Set to 0 to disable. |
| acne_strength | INT | 00–100 | Renders raised-looking acne spots with a reddish inflamed surround and a subtle specular highlight to suggest a 3-D bump. Few/mild: 10-25 | Moderate breakout: 30-55 | Severe: 60-100. Set to 0 to disable. |
| freckles_strength | INT | 00–100 | Scatters small melanin-tinted freckles concentrated on cheeks and nose bridge, with randomised size, opacity, and slight colour variation. Light dusting: 10-25 | Natural coverage: 30-55 | Dense: 60-100. Set to 0 to disable. |
| skin_texture_strength | INT | 00–100 | Enhances micro skin texture (fine lines, pore shadows, subtle relief) by boosting a high-frequency detail layer extracted via unsharp masking. Subtle: 10-20 | Natural film look: 25-45 | Hyper-detailed: 50-70. Set to 0 to disable. |
| sss_strength | INT | 00–100 | Subsurface scattering: adds a warm translucent glow beneath skin by Screen-blending a low-opacity orange-pink blurred layer, simulating how light travels through the dermis. Breaks the flat opaque 'plastic' AI look. Subtle: 5-15 | Natural: 16-35 | Strong: 36-60. Set to 0 to disable. |
| peach_fuzz_strength | INT | 00–100 | Renders procedural vellus (peach fuzz) hair as fine curved micro-strokes oriented downward with slight random angles, at very low opacity. Dramatically breaks the AI plastic-skin look by adding the fine hair that covers real faces. Barely visible: 5-15 | Natural: 16-40 | Dense: 41-70. Set to 0 to disable. |
| skin_redness_strength | INT | 00–100 | Adds natural facial redness zones — cheeks, nose tip, chin — from blood vessels near the skin surface, using soft Gaussian blobs in warm-red tint. Subtle flush: 5-20 | Rosy cheeks: 21-50 | Strong redness: 51-80. Set to 0 to disable. |
| sebum_shine_strength | INT | 00–100 | Simulates T-zone sebum/oil shine (forehead, nose bridge, chin) using small specular highlight ellipses in Screen blend. Adds the natural sheen of oily skin without overexposing the whole image. Matte skin: 0 | Light shine: 5-20 | Oily: 21-55 | Very oily: 56-80. Set to 0 to disable. |
| skin_seed | INT | 00–2147483647 | Master seed for all procedural skin effects (pores, freckles, blemishes, acne, peach fuzz, shine). Same seed always produces the same pattern on the same image. Change to get a different arrangement of spots, pores, and hairs. |
| pores_opacity | INT | 1000–100 | Opacity of the pores effect (0 = invisible, 100 = full). Controls visibility independently of density/strength. |
| blemishes_opacity | INT | 1000–100 | Opacity of blemishes (0 = invisible, 100 = full). Allows many faint marks vs few vivid ones. |
| acne_opacity | INT | 1000–100 | Opacity of acne spots (0 = invisible, 100 = full). |
| freckles_opacity | INT | 1000–100 | Opacity of freckles (0 = invisible, 100 = full). Allows dense faint freckles vs sparse vivid ones. |
| skin_texture_opacity | INT | 1000–100 | Opacity of the skin texture effect (0 = invisible, 100 = full). |
| reference_imageopt | IMAGE | Reference image whose lighting and shadows will be matched onto the input images. Only used when lighting_match_mode is not 'Disabled'. | |
| face_maskopt | MASK | Optional grayscale mask (0 = no effect, 1 = full effect) used to spatially restrict skin effects. Behaviour set by mask_mode: 'Face Only' keeps effects inside the mask; 'Inverted' keeps them outside; 'Disabled' skips all skin effects entirely regardless of this input. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |
| face_mask | MASK | — |