Invisible Watermark Decode
Pull the hidden message back out of that image
- images
- STRING
Somewhere in an ARG there's an image that looks like a perfectly normal photo, and your job is to find out it isn't. IMWatermarkDecode is the tool for that second act: it takes an image and pulls the invisible watermark back out of it. It's the decode half of the pair with IMWatermarkEncode, both powered by the invisible-watermark library, and it returns the hidden text as a plain STRING.
What it needs
images(IMAGE) - the image(s) to decode. Works across the whole batch and concatenates the results.algorithm- must match the encoder:dwtDct(fast but unreliable),dwtDctSvd(the author's recommendation, robust), orrivaGan(slow, experimental, 32-bit). The decoder also has to load the RivaGAN model if you pick it, so that path is extra slow.types- must match the encoder's payload type:bytes,b16,bits,uuid, oripv4. Getting this wrong is the classic failure.length- this is the gotcha input. Invisible watermarks have no fixed message length for the byte-based types, so the decoder needs to know how many bits to read. The tooltip spells out the math:byteswants8 × message lengthbits,b16wants4 ×the hex length,bitswants the raw bit count. Default 96, and if you're off, the decode returns garbage or throws. You generally have to know the length from the encode side - which means, yes, this node assumes you know what you're looking for.encoding_format- how the recovered bytes decode to text;utf-8default,Otherfor exotic codecs.
The output
STRING- the decoded message. The node is defensive here: if byte decoding fails (wrong encoding, or the bits aren't valid UTF-8), it falls back to printing the raw bytes representation instead of crashing - a small mercy that tells you "there's something here, but the encoding's off."
Installing it
Part of the ComfyUI ARG Toolkit:
cd ComfyUI/custom_nodes
git clone https://github.com/AzelusLightvale/ComfyUI-ARG-Toolkit
Restart ComfyUI, or install via ComfyUI Manager ("ComfyUI ARG Toolkit"). The invisible-watermark dependency pulls opencv-python and pywavelets, so expect a heavier-than-usual install on a lean ComfyUI setup.
Where people get burned
- Not knowing the length. Decoding without the right
lengthis the #1 source of "it returned gibberish." If the encode side isn't yours, you may have to brute-force lengths - try 96 first, then scale by your guess at the message size. - Mismatched algorithm or type. DWT+DCT can't read what RivaGAN wrote, and
bytespayloads don't decode asb16. Match both to the encode side. - Degraded watermarks. Heavy editing, aggressive compression, or a small crop can destroy the bits.
dwtDctSvdsurvives more of this thandwtDct, which the author notes is "unreliable" for good reason. - The library is experimental. The upstream project says it's not production-grade and runs on CPU only. Fine for a puzzle, worth remembering before you trust it for anything serious.
For the "this picture is a delivery mechanism" reveal, IMWatermarkDecode is the payoff node: feed it the image, and the hidden coordinates, UUID, or message comes out the other end. Just bring the length - this node doesn't do surprises.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| length | INT | 961–2147483647 | — |
| algorithm | COMBO | In order of appearance: - dwtDct: Discrete wavelet transform + Discrete cosine transform. Works, but unreliable. - dwtDctSvd (Recommended): Same as above, but includes singular value decomposition. - rivaGan: Uses a 32-bit RivaGAN implementation. Slow, experimental, and limited to 32 bits worth of information. | |
| types | COMBO | In order of appearance (and encoding length needed): - bytes (8 * total byte length): Transforms message from string to bytes. - b16 (4 * total b16 length): Text -> Byte representation -> Hexadecimal -> Byte representation. - bits (equivalent to array length): 8 bits = 1 byte. Good for transmitting small binary messages. - UUID (128 bits, fixed): Takes a UUID string and embeds that. Do not use to send messages. - IPv4 (32 bits, fixed): Takes an IPv4 address and embeds that. | |
| encoding_format | COMBO | The encoding format available for text, may yield different results when converting. | |
| other_encoding_formatopt | STRING | If, for some reason, your chosen encoding format is not available in the dropdown, select "Other" in encoding_format and type in your encoding format here. Supports all format written in Python's `encoding` module. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| STRING | STRING | — |