XBHH Image Title(图片标题)
Burn the model name into the pixels so you stop guessing later
- image
- image
You just swept a LoRA at 0.4 / 0.6 / 0.8 / 1.0 across three checkpoints. Twelve images land in your output folder, named ComfyUI_0037_.png through ComfyUI_0048_.png, and somewhere around image nine you cannot remember which one was the 0.8. Every comparison post on the internet has this disease - half of them leave you reverse-engineering which model is on the left. XBHH Image Title is the blunt fix: it draws the label onto the picture.
What it actually does
This is a small, honest node. It takes your IMAGE, pastes a semi-transparent black banner across the top strip, and centers white text in it. That's the whole feature. There's an optional title input; if nothing is connected, or the string arrives empty, the image passes through untouched, so a broken text link won't blow up your run.
It comes from Comfyui-xbhh-main, a Chinese-authored pack whose real business is LoRA management - a database, a recipe library, a Civitai downloader, a Live2D mascot. This is one of the loose utility nodes bolted onto that, and yes, you install the whole thing to get it.
How it works, mechanically
The source is image_title.py, and it's plain Pillow: the IMAGE tensor (batch, height, width, channels, floats 0–1) is scaled to 0–255, converted to a PIL image, drawn on, and stacked back. It loops over every frame in the batch, so a 30-image batch gets 30 banners.
Two details you'll feel in practice. First, the font path list is hardcoded - Microsoft YaHei, YaHei Bold, SimHei, Arial on Windows, then /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf for the Linux folks. If none of those resolve, it falls back to Pillow's built-in bitmap font, which ignores your font_size entirely and renders tiny text jammed in the corner. Second, the text isn't just clipped - the node measures it and truncates character by character with an ellipsis until it fits the image width, then centers it.
The inputs that matter
image is required and takes the IMAGE tensor, obviously. font_size is an INT from 8 to 512, default 40 - for a 1024-wide image, 40 is readable without stomping the picture; go bigger for thumbnails, smaller for wide panoramas.
title is the one to understand before you wire it up: it's a STRING input with forceInput set, so there is no text box to type into. You have to connect a string source - a primitive/text node, or the pack's own Lora stack Plus, which exposes a triggers string output that's exactly the kind of thing you want on a test image. Newlines get flattened to spaces.
One output: image, the same type that went in. Feed it to a Preview/Save Image, or keep chaining it into whatever comes next in your graph - nothing downstream cares that a banner got painted on.
Install
ComfyUI Manager, search xbhh, install, restart. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/xbhh123456/Comfyui-xbhh-main.git
cd Comfyui-xbhh-main
pip install -r requirements.txt
That requirements.txt is one line: openpyxl, and it's for the pack's XLSX viewer, not for this node. Pillow, numpy and torch ship with ComfyUI already. No models to download.
After the restart, hit Ctrl + F5. The pack ships a lot of frontend JS (the LoRA manager sidebar, the recipe panel, the mascot) and browser cache will happily serve you the old half of it. On first launch it also creates ComfyUI/user/xbhh/ with a SQLite DB and settings files - that's the LoRA manager setting up shop, not a problem, and it survives plugin updates.
Where people get burned
CJK titles render as empty boxes unless you're on Windows with a Chinese font installed. On Linux, DejaVuSans has no Chinese glyphs, so 触发词 comes out as tofu. The font list is hardcoded with no input to override it, so your options are: install a CJK font and add its path to FONT_CANDIDATES in image_title.py, or keep titles ASCII.
It's a destructive edit. The banner permanently owns the top 40–80 pixels. Don't let this be the only thing upstream of your Save Image if you care about the master copy - branch it: one save for the archive, one pass through this node for the version you actually post.
It's an 8-bit round-trip. Everything goes through uint8, so if anything downstream depended on float precision, this is a lossy detour mid-chain.
Last one, mild: the drawing is a per-frame CPU Python loop. Fine for stills. Paint titles across a 200-frame video batch and you'll feel it.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| font_size | INT | 408–512 | — |
| titleopt | STRING | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |