Generating bilingual Open Graph images locally with Qwen-Image
All fourteen backgrounds are finished: thirteen cover earlier articles, and the fourteenth belongs to this one. Each passed the first visual inspection. This article documents the image workflow now used across the site.
One background, two published assets
The blog has an English page and a Japanese page for each article. A shared text-free background serves both pages. The build script reads each localized title from Markdown frontmatter and draws a separate social card for that locale.
| Asset | Path | Use |
|---|---|---|
| Source artwork | public/og/source/<translationKey>.png | Lossless input retained for regeneration |
| Display artwork | public/og/display/<translationKey>.webp | Post hero and listing thumbnail |
| Localized card | public/og/posts/<translationKey>-<locale>.jpg | Open Graph and Twitter metadata |
The source is 1216×640, matching the wide composition generated in ComfyUI. A quality-82 WebP copy keeps page weight down. Social cards come out at 1200×630 as JPEG files. The HTML title remains the heading on the article page. The hero uses the clean display artwork.
Generating the backgrounds on the 780M
The image workflow runs through the same ComfyUI-TheRock setup as the other local generation projects on this site. Qwen-Image-2512 supplies the backgrounds.
| Setting | Value |
|---|---|
| Diffusion model | qwen-image-2512-Q4_K_M.gguf |
| Text encoder | qwen_2.5_vl_7b_fp8_scaled.safetensors |
| VAE | qwen_image_vae.safetensors |
| Canvas | 1216×640 |
| Sampling | 20 steps, CFG 2.5, Euler, simple scheduler |
Each prompt describes the article through one physical scene. It also reserves the left third as quiet negative space and puts the subject toward the right. A shared style block asks for dark slate, restrained teal, one warm amber light, shallow depth of field, and fine film grain. The negative prompt covers writing, logos, signatures, and interface elements.
A scene-only prompt reduces accidental lettering in the background. The compositor receives the exact wording from frontmatter. A cold run after an overnight pause took 18 minutes 52 seconds from execution start to success. The next ten uninterrupted jobs averaged 15 minutes 45 seconds at the same settings. An H3 video process overlapped the beginning of this article’s job and ended at 09:58:49. Qwen’s sampler ran for 17 minutes 36 seconds, and the PNG was written at 10:16:30. I count the 17 minutes 41 seconds from the end of contention to the file write for this image. That puts the total processing time across all fourteen jobs at 3 hours 48 minutes 3 seconds.
Importing completed ComfyUI jobs
Each Save Image node uses a predictable prefix such as og_minus_zero. The importer finds the newest matching file and copies it under the post’s translationKey.
npm run og:posts -- --import-comfy "C:\path\to\ComfyUI\output"
Completed sources are rendered immediately. A source that is still pending logs a skip, and its post retains the site’s shared social image. Website work can continue during the queue.
Deterministic text after generation
The compositor uses @napi-rs/canvas. Segoe UI Bold handles English, and Meiryo Bold handles Japanese on the Windows host. A dark horizontal gradient sits between the title and the generated scene. The JPEG quality is fixed at 86.
English wrapping can split cleanly at spaces. Japanese needed more care. Intl.Segmenter supplies the first set of word boundaries, then the script groups katakana runs, punctuation, numeric units such as 21分, and Latin product names such as MiniMax Music 3.
An early balancing pass created breaks like 21 / 分 and 動 / かした. Greedy wrapping then left ループ on a line by itself. The current script accepts validated line hints for long titles. It checks that a hint still contains the complete frontmatter title and throws an error when the wording has changed. Shorter titles continue through automatic wrapping.
Connecting the files to Astro
Image-enabled posts carry localized alt text in frontmatter:
image:
alt: "An amber audio waveform suspended in glass above a compact computer"
The translationKey determines both file paths. Post listings use the display WebP as a lazy-loaded thumbnail with an empty alt attribute because the adjacent title already names the link. Article pages load the same WebP as a hero with the descriptive localized alt text. The localized JPEG feeds the social meta tags and the BlogPosting structured data.
The optional image field activates after a checked source and both localized cards are present. Articles without the field keep their existing text layout.
Going live
The image workflow went live with this article. Fourteen backgrounds now appear on their article pages and feed twenty-eight localized social cards. Every background passed visual review.