ComfyUI Node

2apng

The APNG save node built for chat previews

By N0shiro·Created a day ago·Updated a day ago· 0
2apng
  • content
  • cover
  • image
filename_prefix2apng
compression_ratio0.5

2apng is the whole reason the ComfyUI-TOAPNG pack exists: a single output node that takes two images and saves them as one APNG where the first is a cover and the second is the image you actually care about. A PNG that shows one picture in the thumbnail and a different one when it animates.

Why would anyone want that? The author's stated use case is the Chinese messenger QQ, where inline previews show only the static first frame but the full file, once opened, plays as an animation. APNG is the right vehicle: a full-color, lossless cousin of GIF that most browsers and apps handle fine. So the pattern is - chat preview shows your cover, the real render appears when someone actually views the file. Be warned, the README (Chinese) says the QQ trick "isn't guaranteed risk-free," and you shouldn't assume WeChat, Telegram, or Discord behave identically. What survives moving platforms is the portable part: a save node that opens a file on a cover and finishes holding on your content. If you share AI output anywhere APNGs play, that's niche but real.

How it works

The node is a terminal save node like core SaveImage, reusing the same folder_paths machinery, so files land in ComfyUI/output with a .png extension (APNG is a PNG container - the animation lives in extra chunks). Three things go into that one file:

  1. The cover as the PNG default image - what every non-animated viewer and thumbnail renderer shows.
  2. Your content image as the animated frame.
  3. The sneaky part: a "hold frame" identical to the content except one pixel whose alpha is zeroed out.

That third frame is the clever bit. Some players ignore an APNG's finite play count and restart the animation after it ends - and on restart they reset to the default image, your cover. Without the hold frame the picture would visibly snap back to the cover on every replay. With it, the animation ends visually stuck on the content, because a player that tries to reset finds the last frame and the default are indistinguishable. One transparent pixel and the whole trick works.

If your cover and content differ in size, the cover gets scale-fit to the content canvas, centered, and letterboxed with white - never stretched. And like any self-respecting ComfyUI save node it embeds the prompt and workflow text chunks into the PNG, so the workflow-in-the-image convention survives: drag the file back onto the canvas and your graph rebuilds. Batches work, as long as both inputs have the same count or one of them is a single image.

The inputs that matter

Only four, and a beginner really touches three:

  • content (IMAGE, required) - the actual image. This is the animated frame and the "final frame" your file lands on.
  • cover (IMAGE, optional) - the default image shown by static previews. Leave it unconnected and the node falls back to the bundled default.png; wire it and your cover wins.
  • filename_prefix (STRING, default 2apng) - prefix for the saved file, same convention as SaveImage.
  • compression_ratio (FLOAT, 0.11.0, default 0.5) - output scale. 0.5 is half width and height (roughly a quarter of the pixels); 1.0 keeps full resolution. This exists to keep the shared file small, and it scales the content output too.

The single output, image, is the compressed content image - handy for previewing exactly what got saved without loading the file back from disk.

Installing it

In ComfyUI Manager, search for ComfyUI-TOAPNG and install, then restart. Or the manual route:

cd ComfyUI/custom_nodes
git clone https://github.com/N0shiro/ComfyUI-TOAPNG

Then restart ComfyUI - the node appears under the image category as 2apng. No model downloads, no extra Python dependencies: just Pillow, NumPy, and Torch, which ComfyUI already ships. About the lightest install you'll do.

Where people get burned

  • "My file is static!" If your viewer or app doesn't play APNG, you'll only ever see the cover. That's not a bug - it's the design. Test with a browser tab before you conclude it's broken.
  • FileNotFoundError: Bundled default cover is missing - default.png must sit in the same directory as nodes.py. If you hand-copied just the Python files, you broke it; clone or copy the whole folder.
  • ValueError: The content image is fully transparent. - the hold-frame trick needs at least one opaque pixel. Feed it a real image, not an empty alpha layer.
  • Wrong expectations in chat apps. The QQ behavior is the stated goal, not a universal guarantee. Apps that don't animate APNG just show a static cover - know which camp your destination is in before you ship it.

It's a tiny, single-purpose node with a slightly occult audience - but if you've ever wanted one image in the thumbnail and a different one when it plays, this is the cleanest way to get it out of ComfyUI.

Categoryimage

Inputs (4)

NameTypeDefaultDescription
contentIMAGESecond image, stored as the APNG content frame.
filename_prefixSTRING2apng
compression_ratioFLOAT0.50.1–1Output width-and-height scale. 0.5 is half width and height.
coveroptIMAGEOptional cover image. Uses bundled default.png when unconnected.

Outputs (1)

NameTypeDescription
imageIMAGE