← Back to work
Tool · 2026

GfxKit

SfxKit’s visual sibling. A generated recipe goes in, a deterministic palette-snapped PNG comes out. No image editor, no sprite sheets to hand-pull, no Unity dependency. The same recipe always renders the same pixels.

The problem it solves

Image generators exist. Stable Diffusion, the rest of them. They’re wrong for this job. I don’t want a beautifully rendered throwaway I have no control over, and running one locally burns a pile of time per image. I also don’t want to scribble something in Paint and feed that through a tool. What I wanted is something my Agent can drive: I say “I need a placeholder key for a scriptable-object icon” and it handles it. It drafts an SVG the same way it would a diagram for a web site, sends it through the tool, and puts the image where it needs to be. The result doesn’t have to be beautiful. It doesn’t have to be perfect. It merely has to be recognizable and match the style of the icons around it. That’s the whole job. And it’s hard to prioritize when I’m focused on other things. Hence this tool.

It’s the same friction as needing a placeholder sound. It doesn’t matter, anything is better than nothing. You can grab a random sprite off the internet that you swear you’ll replace, or you stall the session opening a pixel editor to scribble an ugly icon you swear you’ll throw out next week. GfxKit renders consistent, labeled placeholder graphics from a generatable spec. Same input, same output, so regenerating from the same spec never shifts a single pixel. Try to do that with NanoBanana.

Approach

A small pipeline: an SVG gets rasterized by SkiaSharp at the target size (16², 32², 64²), then every pixel snaps to the nearest RGB in a configured palette. After that, it’s written out as a PNG. The quantizer is really the only part I wrote that does any work. It’s some simple math to handle the color snapping. The SVG raster and PNG encode are handled by stable dependencies.

Same layer split as SfxKit too. A core module holds the palette and quantization math, and a thin CLI on top does all the I/O. The core has no image library in it at all, which keeps it embeddable later (a Unity editor window, a Blazor page) without dragging a renderer along.

The palette setting is surprisingly powerful. Some built in palettes cover mono(2 colors), Game Boy, NES, CGA, and Resurrect 64 (A nice palette I’ve been using from Lospec), plus any GIMP .gpl palette you can throw at it. Recolor the same input spec with multiple palettes and you’d be surprised how different the results can be.

Once it all comes together, I can just ask my Agent for “a key,” “an open book,” “a satellite dish,” and it writes a small SVG. The SVG is linked in the recipe. The tool doesn’t care where the SVG came from, which keeps it honest as a renderer. If I wanted to author something or tweak an SVG in Adobe Illustrator, I could.

Have a look

The same handful of SVGs run through the pipeline. The leftmost column is each source SVG rasterized straight, no palette. Every column after it snaps that same source to a different palette, fewest colors to most. Nothing about the input changes across a row, only the palette it snaps to.

A recipe entry is small. An SVG (inline or a file path), a size, and a palette:

{ "name": "key", "svg_file": "svgs/key.svg", "size": 32, "palette": "snap:gameboy" }

Everything’s tiny by design (32×32 here), shown scaled up so the pixel grid stays crisp. The checkerboard is the transparent background to illustrate my point, and won’t show in a normal render.

originalsvg raster
mono2 colors
gameboy4 colors
nes8 colors
resurrect-6464 colors
key
Key, raw SVG raster Key snapped to mono Key snapped to Game Boy Key snapped to NES Key snapped to Resurrect 64
book
Book, raw SVG raster Book snapped to mono Book snapped to Game Boy Book snapped to NES Book snapped to Resurrect 64
potion
Potion, raw SVG raster Potion snapped to mono Potion snapped to Game Boy Potion snapped to NES Potion snapped to Resurrect 64
satellite-dish
Satellite dish, raw SVG raster Satellite dish snapped to mono Satellite dish snapped to Game Boy Satellite dish snapped to NES Satellite dish snapped to Resurrect 64

You can also configure the size. These are generated with the same SVG, rasterized at 32² and at 64², both snapped to Resurrect 64. The bigger canvas keeps more of the detail.

Tape measure rendered at 32 by 32
tape-measure32² · snap:resurrect-64
Tape measure rendered at 64 by 64
tape-measure64² · snap:resurrect-64

Status

Built. Used all the time for throwaway graphics. It also packages macOS .icns launcher icons when a recipe asks for an iconset. This is for little tools I launch with the GUI instead of the CLI.