Save Image (Watermark) π§
Save Image (Watermark) π§
- images
- watermark_image
- watermark_image_mask
- image
- filename
- content_hash
The name undersells this one. "Save Image (Watermark)" from the aicuai/comfyui-save-image-watermark pack isn't just a fancier SaveImage - it's a three-in-one finishing node. It stamps a visible logo, burns in a text credit, and can bury a hidden message in the least significant bits of the PNG, then writes provenance metadata on top. It's a drop-in replacement for ComfyUI's built-in save node, so it slots in as the last stop on the chain: [KSampler] β [VAEDecode] β [Save Image (Watermark) π§].
Why reach for it? If you're posting gens publicly - social feeds, a gallery, a Discord server - you want a logo and a credit on the image without a second manual pass in Photoshop. And if you want to keep a lightweight provenance story for your own work (or just be a nerd about it), the invisible LSB channel plus a SHA-256 hash covers it. It's by AICU Japan, the folks behind the ComfyUI Master Guide, Apache 2.0, and it pulls in no dependencies beyond the Pillow/NumPy/PyTorch that already ship with ComfyUI.
How it works
Fixed processing order: the logo blends in first (bottom layer) using result = base * (1 - mask * opacity) + logo * (mask * opacity), the text watermark composites on top, the invisible watermark embeds last, then the file saves. Three outputs come out the right side: image (the watermarked tensor, forced back to RGB), filename, and content_hash - a SHA-256 hex of the saved PNG you could drop into a manifest.
The inputs that matter
images(IMAGE, required) - your VAEDecode output. Only the required pair isfilename_prefix(defaultaicuty) andfile_format(PNG/JPEG/WEBP).watermark_imageandwatermark_image_mask- both come from a single LoadImage node. The mask is the trap: wire only the IMAGE output and your logo's transparent areas composite as black. Get both wires.watermark_image_scale(default 0.15),watermark_image_opacity,watermark_image_position(bottom_left default;tilerepeats the logo across the whole frame).watermark_text(default "Β© AICU"),watermark_text_size(8β128px),watermark_text_color, opacity, position.dynamic_textappends straight ontowatermark_text- handy for injecting a seed or batch ID from another node.invisible_watermarkplusinvisible_watermark_enabled- the LSB steganography.save_to-output_folder,browser_download, orboth(default). Metadata flags (embed_workflow,embed_metadata,metadata_json) andjpeg_quality/webp_qualityround it out.
The invisible watermark is classic LSB: your message becomes UTF-8 bits written into the least significant bit of each R, G, B channel, scanning from the top-left pixel, with the alpha channel left alone. Capacity is roughly (width Γ height Γ 3) / 8 bytes, and a Β±1 shift per channel is invisible to the eye. But anything lossy - JPEG save, resize, crop, rotate, color tweak - scrambles it completely. The pack's own Photoshop test showed the top-left LSB block surviving edits made elsewhere on the canvas, so PNG re-saves are fine but where you edit matters. Don't trust it as security: no encryption, a fixed embed position, and anyone with the same algorithm can read it. It's an experiment and a toy, not DRM - the README is admirably upfront about that.
Install
ComfyUI Manager β Install Custom Nodes β search watermark or aicu, install, restart. Or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/aicuai/comfyui-save-image-watermark.git
No model files to download, no pip step - the requirements (Pillow, NumPy, PyTorch) are all already present. Restart and it lives under AICU β Save.
Gotchas
- Forgot
watermark_image_mask? Black boxes where your logo should be transparent. - Gallery demands JPEG? The invisible watermark is gone for good. If you embed one,
file_formatstays PNG. - The
imageoutput is RGB - don't go looking for alpha downstream. - The node computes both the pre- and post-watermark hashes internally, but only returns the watermarked
content_hash. If you want the before/after comparison, keep the original hash yourself.
Inputs (23)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | β | |
| filename_prefix | STRING | aicuty | β |
| file_format | COMBO | 3 options: PNG, JPEG, WEBP | |
| save_toopt | COMBO | both | 3 options: output_folder, browser_download, both |
| watermark_imageopt | IMAGE | β | |
| watermark_image_maskopt | MASK | β | |
| watermark_image_positionopt | COMBO | bottom_left | 6 options: bottom_right, bottom_left, top_right, top_left, center, tile |
| watermark_image_scaleopt | FLOAT | 0.150.01β1 | β |
| watermark_image_opacityopt | FLOAT | 1.000β1 | β |
| watermark_textopt | STRING | Β© AICU | β |
| watermark_text_enabledopt | BOOLEAN | true | β |
| watermark_text_positionopt | COMBO | bottom_right | 6 options: bottom_right, bottom_left, top_right, top_left, center, tile |
| watermark_text_opacityopt | FLOAT | 0.900β1 | β |
| watermark_text_sizeopt | INT | 248β128 | β |
| watermark_text_coloropt | STRING | #FFFFFF | β |
| dynamic_textopt | STRING | β | |
| invisible_watermarkopt | STRING | β | |
| invisible_watermark_enabledopt | BOOLEAN | false | β |
| embed_workflowopt | BOOLEAN | true | β |
| embed_metadataopt | BOOLEAN | true | β |
| metadata_jsonopt | STRING | {} | β |
| jpeg_qualityopt | INT | 951β100 | β |
| webp_qualityopt | INT | 901β100 | β |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | β |
| filename | STRING | β |
| content_hash | STRING | β |