ComfyUI Node

🐧 TextEdit

Stop making the model render text — slap it on with Pillow instead

By PenguinTeo·Created about a year ago·Updated about a year ago· 6
🐧 TextEdit
  • image
  • IMAGE
text
x_pct50
y_pct50
h_anchor
v_anchor
offset_x0
offset_y0
font_size120
font_fileBond Story.ttf
text_color#ffffff
text_opacity1.00
use_gradientfalse
start_color#ff0000
end_color#0000ff
angle0
stroke_width8
stroke_color#000000
stroke_opacity1.00
shadow_x0
shadow_y0
shadow_color#000000
shadow_opacity1.00

Diffusion models are still bad at text. Flux and Qwen-Image got a lot better at it, but ask a model to spell out a paragraph of small legible text and you'll still get a garbled mess - and a chunk of your VRAM doing it. The boring, reliable answer is to not have the model render text at all: generate a clean image and overlay the words afterwards. That's exactly what PenguinTextOnImage (shown in the node menu as "🐧 TextEdit") does. It's a single text-overlay node that draws strings onto an image with Pillow - gradients, outlines, shadows, the works - and hands you back an ordinary ComfyUI IMAGE. No model, no API key, no VRAM cost.

What it actually is

Despite the "AI text editor" vibes of the name, there is no AI here. The entire node is ~200 lines of Pillow drawing code in a one-file pack (project.py) plus a font folder. It converts your IMAGE tensor to an RGBA PIL image, draws the text on a transparent layer, and pastes it back - pure post-processing. That means it's deterministic, instant (mostly), and works on anything that flows through the graph: a KSampler output, an upscale, a batch of frames.

Core ComfyUI has no text overlay built in, so people usually end up in WAS Node Suite, which has one buried in a pack of a few hundred nodes. If you want just the text node and nothing else, this pack is the minimalist alternative - though "minimalist" also means "you're on your own" when it breaks, so read the gotchas below.

The inputs that matter

There are 22 inputs, but you'll actually touch maybe six of them:

  • text - multiline string. The whole point. Leave it blank and the node returns your image untouched.
  • image - the image you're stamping on.
  • x_pct / y_pct (0–100, default 50/50) - percentage position across the image, like 50/50 = dead center.
  • h_anchor / v_anchor - left/center/right and top/center/bottom. Think of it as "which part of the text box sits at the percentage point." Center-center at 50/50 puts the middle of the text in the middle of the image.
  • offset_x / offset_y - fine pixel nudges after anchoring. This is the pair you'll fiddle with most to get a label sitting exactly on a border.
  • font_file - a dropdown of fonts in the pack's font/ folder. It ships with one (Bond Story.ttf). Drop your own .ttf/.ttc/.otf in that folder and restart ComfyUI and it appears in the list.

The rest is styling: font_size, hex colors (text_color, stroke_color, shadow_color, and gradient start_color/end_color), and opacity floats. Flip use_gradient on and the fill sweeps from one color to the other along angle.

Output is a single IMAGE - wire it straight into a Save Image node, or chain more post-processing after it.

Install

The pack has no requirements.txt and no model downloads - it only needs numpy, torch, and Pillow, which every ComfyUI install already has. Either:

  • ComfyUI Manager → search Comfyui-TextEditor-Penguin → Install, then restart, or
  • cd ComfyUI/custom_nodes
    git clone https://github.com/PenguinTeo/Comfyui-TextEditor-Penguin
    
    and restart ComfyUI.

Where people get burned

This pack has essentially no community footprint (it's tiny and new), so treat these as "things the code itself tells you to watch out for":

  • Your font isn't in the dropdown. The list is built at load time from files inside the pack's font/ folder, not your system fonts. Add the font there and restart.
  • Text lands in the wrong place or doesn't render. The author recommends PIL ≥ 10.0 because the node relies on font.getbbox() for metrics. On older Pillow versions that can return junk, and the text ends up offset or invisible. If text placement looks wrong, update Pillow first.
  • The gradient is slow. It's a per-pixel Python loop, not a C rasterizer. A huge font at a big size can take a second or two. Fine for a watermark, annoying if you're doing this across hundreds of frames.
  • Thick strokes are drawn N² times (the outline is literally the text redrawn at every pixel offset around the glyph - 289 draws at the default stroke_width of 8). Keep strokes modest.
  • Batches only get the first image. The node reads image[0] and ignores the rest of the batch dimension. If you're stamping frames of a video, expect to loop it per-frame.

The trade-off is the whole deal: one tiny pack, zero dependencies, and pixel-perfect text you control completely - in exchange for a node that's quiet, unmaintained-looking, and all on you if something odd happens. For watermarking and labeling, that's usually a fair trade.

Category📝 Text/Label

Inputs (23)

NameTypeDefaultDescription
textSTRING
imageIMAGE
x_pctINT500–100
y_pctINT500–100
h_anchorCOMBO3 options: left, center, right
v_anchorCOMBO3 options: top, center, bottom
offset_xINT0-4096–4096
offset_yINT0-4096–4096
font_sizeINT1201–512
font_fileCOMBOBond Story.ttf1 options: Bond Story.ttf
text_colorSTRING#ffffff
text_opacityFLOAT1.000–1
use_gradientBOOLEANfalse
start_colorSTRING#ff0000
end_colorSTRING#0000ff
angleINT0-180–180
stroke_widthINT80–128
stroke_colorSTRING#000000
stroke_opacityFLOAT1.000–1
shadow_xINT0-100–100
shadow_yINT0-100–100
shadow_colorSTRING#000000
shadow_opacityFLOAT1.000–1

Outputs (1)

NameTypeDescription
IMAGEIMAGE