[KongchangAI]
· 1 min read· 861 words

OG Image Generator Renders Blank Cards: A Silent Failure Triggered by an Ampersand

OG Image Generator Renders Blank Cards: A Silent Failure Triggered by an Ampersand

Titles containing '&' caused PIL's drawing context to misparse them as HTML entities, silently generating blank OG image cards.

A social card generator showed a strange bug after launch: all cards with '&' in the title returned solid-color blank PNGs. The root cause was that PIL's drawing context inherited assumptions about HTML entity handling, parsing `Q&A` as `&` and silently producing an empty string. The fix was to call `html.escape()` before layout calculations and add a guard to refuse rendering when width collapses to zero. The article points out that image generation APIs fail differently from text APIs — a blank PNG is a legitimate response, and HTTP 200, correct Content-Type, and normal response body length are insufficient to judge success. Monitoring must verify the pixel content itself.

An Invisible Failure Caused by a Single Character

Someone built a social card generator: input a title, description, and optional header image, and it outputs a 1200x630 PNG image. Simple and straightforward. It ran smoothly for a week after launch, then a bug report came in — every card with an "&" in the title returned a solid-color background with no text at all.

The strangest part: no exceptions thrown, no error logs, HTTP status code always 200. From the API's perspective, everything was fine. But the generated PNG was blank.

OG image generator blank card issue

Root Cause: Missing Entity Escaping in the Text Measurement Path

The core issue was in the text measurement step. The developer used PIL (Python Imaging Library)'s default font for layout calculations — measuring string width to determine line-breaking strategy and font size.

ImageFont.load_default() itself doesn't support font fallback, but that wasn't the key issue. The real problem was that the title string was passed directly into the drawing context, which had inherited assumptions about HTML entity handling from an early iteration. Titles like Q&A Session were parsed as entity references (&), ultimately producing an empty string silently passed to the drawing call.

No exception, no log line, just a blank card.

HTML entity references are special sequences starting with & and ending with a semicolon, used in HTML/XML to represent reserved characters or characters that can't be directly typed, such as &amp; for & and &lt; for <. When text is incorrectly parsed by entity rules, the parser looks for &...; patterns and replaces them with the corresponding characters; if no valid entity name is found, some lenient parsers will simply swallow the fragment, causing the output content to be silently truncated. In this case, the &A in Q&A Session was treated as the start of an entity, ultimately collapsing to an empty string.

Font fallback refers to when the primary font lacks a glyph for a certain character, the rendering engine automatically switches to another font that contains that glyph. PIL's ImageFont.load_default() only provides a single bitmap font; when a string contains characters it doesn't support, it typically falls back to blank or placeholder characters without raising an error. This explains why even when the string itself is fine, the measured width may be abnormally small or zero.

The Fix: Boring but Correct

The fix was simple and correct: escape before measuring, and never assume input is already sanitized.

Specifically, call html.escape(title) before any layout calculations, then draw the escaped string. Additionally, a safeguard was added — if the measured text width collapses to zero, refuse to render. Better to fail loudly than to deliver a blank image.

This fix reveals a more universal principle: at the intersection of text processing and image rendering, the absence of input sanitization leads to the most insidious bugs.

html.escape() is a function provided by Python's standard html module. By default, it replaces &, <, and > with &amp;, &lt;, and &gt;, and can optionally escape quotes. Its semantics are to prepare data safely for output into an HTML context, not to "clean data." In this case, since the downstream drawing context incorrectly expected HTML entities, calling html.escape() first conveniently turned & into &amp;, thereby avoiding the behavior of being swallowed as an incomplete entity. This is a temporary measure to align expectations between mismatched abstraction layers — a more fundamental fix should be to remove the drawing context's implicit assumptions about HTML entities, but this is often costly in legacy code.

Key Lesson: The Silent Failure Mode of Image Generation APIs

This experience repeatedly confirmed an easily overlooked fact: image generation APIs fail in ways completely different from text APIs.

A blank PNG is a perfectly legitimate response. From the HTTP protocol's perspective, the server successfully processed the request and returned a 200 status code. The content type is correct, the file format is correct, and the response body length is normal. But there's nothing inside the image.

This means monitoring strategies must be upgraded: you can't just check HTTP status codes; you must verify the pixel content itself. For any image generation service, this is the first and most important line of defense against users seeing blank results.

The developer packaged and published this tool at x402.freeq.one/tools/og_image_generator.html. If you're also building similar social card generation functionality, this pitfall is worth avoiding in advance.

A blank PNG is a legitimate image at the byte level: it contains a correct PNG signature, IHDR chunk, and IDAT data, just with all pixels being a single color. Therefore, any check based on response size, Content-Type, or magic number cannot detect the problem. To detect this type of failure, the monitoring program needs to decode the response body into a pixel matrix, then analyze color distribution, the ratio of non-background pixels, or the average brightness of text regions. Common lightweight approaches include: calculating the image's color variance, detecting the number of edges, or performing perceptual hashing on the rendered result and comparing it against a baseline image. Once these metrics fall below a threshold, an alert is triggered, exposing the problem before users discover blank cards.

#OG image generator#blank card bug#HTML entity escaping#PIL text rendering#silent failure#image generation API monitoring
Share:

Related articles