Layer Stack Composite (NH)
Drop layers onto a canvas with exact coordinates
- background_image
- layer_1
- layer_2
- layer_3
- layer_4
- layer_5
- layer_6
- layer_7
- layer_8
- layer_9
- layer_10
- layer_11
- layer_12
- layer_13
- layer_14
- layer_15
- layer_16
- layer_17
- layer_18
- layer_19
- layer_20
- layer_21
- layer_22
- layer_23
- layer_24
- layer_25
- layer_26
- layer_27
- layer_28
- layer_29
- layer_30
- layer_31
- layer_32
- layer_33
- layer_34
- layer_35
- layer_36
- layer_37
- layer_38
- layer_39
- layer_40
- layer_41
- layer_42
- layer_43
- layer_44
- layer_45
- layer_46
- layer_47
- layer_48
- layer_49
- layer_50
- layer_51
- layer_52
- layer_53
- layer_54
- layer_55
- layer_56
- layer_57
- layer_58
- layer_59
- layer_60
- layer_61
- layer_62
- layer_63
- layer_64
- image
- used_count
- layer_info
Sometimes you don't want a smart layout - you want each image exactly where you tell it, on top of a specific background. That's Layer Stack Composite (NH) (class NH_LayerStackComposite). It pastes up to 64 layers onto a background image at explicit pixel coordinates, the way you'd drag elements onto a design canvas. Think product shots on a branded background, cutouts assembled into a scene, or any "stack these things" composite where a grid just won't do.
How it works
You give it a background_image (any image), a layer_count, and a multiline positions box where each line is x,y for one layer. Up to 64 layer_1 … layer_64 IMAGE inputs - wire in as many as you set layer_count to. Each layer is pasted at its coordinates, clipped at the canvas edge, and later layers paint over earlier ones. Straightforward compositing, no resize magic: layers paste at their native resolution, so if a layer is bigger than the background it just gets clipped.
The outputs are image (the composite), used_count, and a JSON layer_info string. One implementation detail worth knowing: this node forces itself to rerun every time (its IS_CHANGED returns NaN), which means widget-driven positions always apply fresh and you never get a stale composite from ComfyUI's cache. That's deliberate - with 64 layers, a cached stale result would be maddening.
The inputs that matter
- positions - one
x,yper layer, in order, one per line. Layer 1 gets the first line, layer 2 the second, and so on. Coordinates are pixels from the top-left of the background. - layer_count - how many layers to actually paste. Set it to the number of wired layers; unused inputs are ignored.
- background_image - the canvas everything lands on. If you want a solid color instead, feed a solid-color image in.
Where you'd actually use it
This is the VTON/product-workflow glue of the NH pack - pair it with the pack's garment masks or cutouts, or with Layer Layout Composite (NH) when you want slot math instead of raw coordinates. Because positions are plain text, you can also drive them from a text node or a Math Eval (NH) upstream if you want computed placement. It's not an output node, so chain it into a save node or the large-preview nodes when you're done.
Installing
ComfyUI Manager → search NH-Nodes → install → restart. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/jetthuangai/NH-Nodes.git
cd NH-Nodes
pip install -r requirements.txt
Restart and refresh the browser tab if the node looks cached. Pillow/numpy is all this one touches from the requirements.
Gotchas
Layers don't get resized, which trips people up when their cutouts are huge and the background is small - check dimensions before you wire things in. And positions is positional: miss a line and everything below it shifts. Keep the count of lines in sync with layer_count or you'll spend an afternoon wondering why layer 12 is somewhere surprising. Also remember it always re-runs, so if you drop this into a huge graph it will invalidate the cache downstream - fine for a compositor, annoying if you accidentally leave one mid-pipeline.
Inputs (67)
| Name | Type | Default | Description |
|---|---|---|---|
| background_image | IMAGE | — | |
| layer_count | INT | 10–64 | — |
| positions | STRING | 0,0 | — |
| layer_1opt | IMAGE | — | |
| layer_2opt | IMAGE | — | |
| layer_3opt | IMAGE | — | |
| layer_4opt | IMAGE | — | |
| layer_5opt | IMAGE | — | |
| layer_6opt | IMAGE | — | |
| layer_7opt | IMAGE | — | |
| layer_8opt | IMAGE | — | |
| layer_9opt | IMAGE | — | |
| layer_10opt | IMAGE | — | |
| layer_11opt | IMAGE | — | |
| layer_12opt | IMAGE | — | |
| layer_13opt | IMAGE | — | |
| layer_14opt | IMAGE | — | |
| layer_15opt | IMAGE | — | |
| layer_16opt | IMAGE | — | |
| layer_17opt | IMAGE | — | |
| layer_18opt | IMAGE | — | |
| layer_19opt | IMAGE | — | |
| layer_20opt | IMAGE | — | |
| layer_21opt | IMAGE | — | |
| layer_22opt | IMAGE | — | |
| layer_23opt | IMAGE | — | |
| layer_24opt | IMAGE | — | |
| layer_25opt | IMAGE | — | |
| layer_26opt | IMAGE | — | |
| layer_27opt | IMAGE | — | |
| layer_28opt | IMAGE | — | |
| layer_29opt | IMAGE | — | |
| layer_30opt | IMAGE | — | |
| layer_31opt | IMAGE | — | |
| layer_32opt | IMAGE | — | |
| layer_33opt | IMAGE | — | |
| layer_34opt | IMAGE | — | |
| layer_35opt | IMAGE | — | |
| layer_36opt | IMAGE | — | |
| layer_37opt | IMAGE | — | |
| layer_38opt | IMAGE | — | |
| layer_39opt | IMAGE | — | |
| layer_40opt | IMAGE | — | |
| layer_41opt | IMAGE | — | |
| layer_42opt | IMAGE | — | |
| layer_43opt | IMAGE | — | |
| layer_44opt | IMAGE | — | |
| layer_45opt | IMAGE | — | |
| layer_46opt | IMAGE | — | |
| layer_47opt | IMAGE | — | |
| layer_48opt | IMAGE | — | |
| layer_49opt | IMAGE | — | |
| layer_50opt | IMAGE | — | |
| layer_51opt | IMAGE | — | |
| layer_52opt | IMAGE | — | |
| layer_53opt | IMAGE | — | |
| layer_54opt | IMAGE | — | |
| layer_55opt | IMAGE | — | |
| layer_56opt | IMAGE | — | |
| layer_57opt | IMAGE | — | |
| layer_58opt | IMAGE | — | |
| layer_59opt | IMAGE | — | |
| layer_60opt | IMAGE | — | |
| layer_61opt | IMAGE | — | |
| layer_62opt | IMAGE | — | |
| layer_63opt | IMAGE | — | |
| layer_64opt | IMAGE | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| used_count | INT | — |
| layer_info | STRING | — |