Codex vs Claude

Why Claude Code Draws in SVG: Missing Tool, Not Missing Eyes

David Guzenburg/ / 10 min read

An agent that can write files can write the source of a picture — which turns out to be worth more than the picture.

toolingdesignbuild systemsgenerated code

The usual explanation is wrong, and the wrong explanation misleads you

You will read that Claude Code cannot produce raster images because the model underneath it "lacks a visual rendering engine." That is not the reason, and believing it will lead you to the wrong workarounds.

No language model has a rendering engine. Codex does not draw a PNG either; it calls a separate image model through a tool and writes the bytes that come back. The difference between the two agents is not perceptual machinery, it is the tool list. One ships an image-generation tool. The other does not.

That distinction matters because it tells you what the fix looks like. If the limitation were cognitive, you would be stuck. Because it is a missing tool, you have two routes: attach one, or use the tool the agent already has — the ability to write code.

Writing a picture instead of painting one

An agent that can write files can write the source of an image. SVG is XML. A chart is a plotting script. A diagram is a graph description. In each case the agent produces the artefact a designer would have produced upstream of the picture, and the picture is a build step.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
     fill="none" stroke="currentColor" stroke-width="1.75"
     stroke-linecap="round" stroke-linejoin="round"
     role="img" aria-label="Cart">
  <path d="M2.5 3h2.2l2.1 10.3a1.6 1.6 0 0 0 1.6 1.3h7.8a1.6 1.6 0 0 0 1.6-1.2L21 6H6"/>
  <circle cx="9.5" cy="19" r="1.4"/>
  <circle cx="17" cy="19" r="1.4"/>
</svg>

Read that file and you can tell what it is. Change the stroke weight and every icon in the set follows. Put it in a review and the diff is three paths. Ask an agent to make a matching icon and it has a specification to match, not a vibe to approximate.

The property that actually matters is reviewability

The argument for vector output here is not aesthetic. It is that the artefact participates in the workflow the rest of your repository already has. It diffs. It merges. It greps. git blame gives a useful answer. A linter can assert things about it.

A PNG does none of that, which is the substance of the repository problem that follows generated raster assets. If you have ever tried to work out which of four similar logo files is the current one, you have met the failure mode.

The test to apply

Ask what happens when the brand colour changes. If the answer is "search and replace currentColor is already handled", the asset is source. If the answer is "regenerate and hope", it is not.

Rasterise deterministically, in the build

You still need PNGs. Favicons, social preview cards, app icons and email assets all want pixels. The point is that they should be produced from source by a command, not summoned.

{
  "scripts": {
    "icons:png": "for f in assets/icons/*.svg; do resvg --width 512 \"$f\" \"dist/icons/$(basename $f .svg).png\"; done",
    "icons:opt": "oxipng -o4 --strip safe dist/icons/*.png",
    "icons": "npm run icons:png && npm run icons:opt"
  }
}

This is the inversion that makes the whole thing work. With generated raster assets, the image is source and there is no upstream. Here the vector is source, the raster is output, and dist/ can be deleted at any time without losing anything. That is the arrangement your build system was designed for.

Where vector genuinely loses

Not everything is an icon, and pretending otherwise produces bad advice. Vector output is the wrong answer for photography, for textured or painterly illustration, for marketing hero imagery, and for anything whose value is in surface detail rather than shape. An agent asked to draw a photorealistic product shot in SVG will produce something that costs more to look at than to ignore.

It is also the wrong answer when the deliverable is genuinely a mood — a comp meant to show a client a direction. Nobody wants a specification for that, they want an impression, and an image model produces impressions much faster than a person writing path data.

Attaching the missing tool

When you do need raster generation inside the agent's loop, the honest route is to give the agent the tool rather than to work around its absence. An MCP server exposing an image API turns generation into a normal tool call, subject to the same permissions and logging as everything else the agent does. That has its own trade-offs — a credential, an outbound network call, a third-party dependency in your loop — and they are the subject of reviewing third-party MCP servers.

What you should not do is the middle path: asking the agent to base64 a placeholder, or to write a data URI it invents, or to describe an image in prose and leave a TODO. Those produce artefacts that look like progress and are not.

The practical split

The division that has held up for me: anything that is a shape belongs in source, and anything that is a scene belongs to an image model. Icons, diagrams, charts, logos, illustrations built from flat fills, loading states, empty states, badges — source. Photographs, textures, mockup photography, hero imagery — a model, or a stock library, with the provenance written down either way.

The line is not about capability. It is about whether the next person to change the asset will want to edit it or replace it. Editing wants source. Replacing does not care.

Charts are the clearest case

If you want the argument in its purest form, ask for a chart. An agent that returns a PNG of a bar chart has given you a picture of your data. An agent that returns the plotting script has given you the chart, plus the ability to change the axis, fix the label, add last month, restyle it for the dark theme, and see in review that the aggregation is a mean rather than a median.

The PNG hides the aggregation. That is not a minor point: the most common serious defect in a chart is not visual, it is that the number being plotted is the wrong number, and a rendering makes that invisible while a script makes it a line you can read.

Diagrams: the same argument, one level up

Architecture diagrams drift because they live in a format nobody edits. Written as text — a graph description, a sequence definition — they sit beside the code, change in the same pull request as the thing they describe, and show up in a diff when someone adds a service.

This is the property that makes text-source diagrams worth the slightly worse layout you get from automatic placement. A beautiful diagram that is eighteen months stale is worse than a plain one that is correct, because people believe it.

Render in CI, commit the source

Keep the diagram source in the repository and produce the image during the build. If the render step fails, the build fails, which means a diagram that no longer parses cannot quietly rot — the usual way stale diagrams survive.

The blind spot: the agent cannot see what it wrote

Here is the real limitation of drawing in code, and it is not the one people cite. An agent writing SVG is writing coordinates. It does not perceive the result. Path data that is syntactically perfect can produce a shape that is subtly wrong — a curve that kinks, a join that overshoots, an icon that is optically off-centre even though it is mathematically centred.

The fix is a loop, not better prompting. Rasterise the SVG, hand the image back to the agent, and ask it to compare against the intent. That takes one command and turns an open-loop generation into a closed one.

#!/usr/bin/env bash
# Render every icon into a contact sheet so the set can be judged together.
set -euo pipefail
mkdir -p .tmp/icons

for f in assets/icons/*.svg; do
  resvg --width 96 "$f" ".tmp/icons/$(basename "$f" .svg).png"
done

montage .tmp/icons/*.png -tile 8x -geometry +8+8         -background '#ffffff' .tmp/contact-light.png
montage .tmp/icons/*.png -tile 8x -geometry +8+8         -background '#0b0d10' .tmp/contact-dark.png

echo "wrote .tmp/contact-light.png and .tmp/contact-dark.png"

The contact sheet is the useful artefact, not the individual render. Icons are judged relative to each other, so a set laid out in a grid exposes the one with the heavier stroke immediately, in a way that reviewing eight files in sequence never does. Producing both backgrounds catches the icon that assumed a light theme.

Hand-authored SVG is better than the export, and that is the point

There is a quiet advantage here that is easy to miss. An SVG exported from a drawing tool is machine output: absolute coordinates, generated group wrappers, editor namespaces, a <defs> block holding things nothing references. It renders correctly and it is not written for a reader. Nobody edits it; they open it in the tool again.

An SVG an agent writes from a description is authored. It uses round numbers because it reasoned in round numbers. Its paths are in the order a person would draw them. It has no editor cruft because there was no editor. The result is a file you can change with a text editor, which means it can be changed in a pull request, by someone who does not have the design tool installed, in the same commit as the component that uses it.

That is a genuine improvement over the traditional pipeline, not a consolation prize for lacking an image model. It is also fragile: one round-trip through a drawing tool and you have the export again. If the authored form matters to you, say so in the instruction file, and treat a commit that replaces clean paths with a 40KB export as a regression.

Round numbers are a review signal

Coordinates like M4 6h16 mean the geometry was reasoned about. Coordinates like M4.00021 6.00073h15.9998 mean it was nudged in a tool or produced by a transform that should have been applied. The precision tells you which you have.

The workflow I settled on

Shapes are authored as SVG in the repository, one file per icon, with a linter enforcing the structural rules. Charts are plotting scripts. Diagrams are text source rendered during the build, so a diagram that no longer parses breaks the build instead of quietly going stale. Rasters are produced from that source by a script into a directory that can be deleted at any time.

An image model enters only where the deliverable is a scene — a hero image, a photographic mockup, an illustration with texture — and when it does, the output is treated as source with a recorded prompt rather than as something the build can recreate. That inversion, between what is source and what is output, is the whole of the difference, and everything else in this article follows from getting it the right way round.

One caveat about scale

None of this holds for a set of four hundred icons. At that size you are not authoring, you are curating, and the right answer is an existing icon library with a licence you have read. An agent authoring four hundred SVGs produces four hundred slightly different interpretations of your stroke weight, and the contact sheet that would have caught it is now eight pages long.

The band where authoring wins is roughly ten to eighty icons: enough that a library feels like overkill or does not contain what you need, few enough that one person can look at the whole set in one screen and say whether it hangs together. Below ten, draw them yourself in less time than the conversation takes. Above eighty, buy.

Knowing which band you are in before you start is worth more than any technique in this article, and it is the question people skip because generating the first icon is so easy.

Takeaway

Claude Code writes SVG rather than PNG because it has no image-generation tool, not because the model cannot see — and that framing points at the right response. Keep shapes as source so they diff, merge and restyle; rasterise from that source with a deterministic build step; attach an MCP image server when you genuinely need generation in the loop; and reach for an image model only where the deliverable is a scene rather than a specification.

Keep reading
Codex vs Claude

Codex Generates Raster Assets: The Repository Problem That Follows

Codex writes real PNGs through its image_gen tool and $imagegen skill, into a cache directory rather than your tree. What that means for review, reproducibility, cost and the placeholders that ship by accident.

Codex vs Claude

Canvas or Static Asset: Deciding Where the Picture Gets Computed

An agent will offer a canvas script or a generated image and the choice is about cost distribution, not appearance. Opacity to accessibility and search, missing fallbacks, and the devicePixelRatio bug in every generated canvas.

Codex vs Claude

Mockup to Code: The Component Matches the Picture, Which Is the Problem

An image carries layout and proportion and omits breakpoints, states, content variance and semantics. Why generated UI is full of bracketed pixel values, and the token-extraction step that fixes it.

Codex vs Claude

Optimising SVG: The Byte Count Reports the Saving, Not the Damage

Default optimiser settings strip viewBox, mangle referenced ids, merge animated paths and round flush edges apart. A safe checked-in configuration, and verifying by rendered pixels rather than file size.

← Native Image Generation or an External Toolchain?  ·  Reading the Design File: Precise Values Are Not the Same as Tokens →

All codex vs claude articles  ·  Every article