Colour Me Impressed: Building an AI Colouring Book Factory in an Afternoon

Share

My nine-year-old has been asking for colouring books. Not any particular colouring book, mind you - a steady stream of them, on whatever theme is occupying her brain that week. Dinosaurs one week, space the next. And every time, the options are the same: buy another one, or ask ChatGPT to make one.

If you have ever asked a chatbot for a colouring book, you know how that goes. It generates one image with the entire book crammed onto a single page - eight tiny scenes in a grid, completely uncolourable by anyone holding a crayon. What I actually wanted was simple: proper A4 pages, one drawing per page, a cover with her name on it, and a PDF I could print and staple.

So I built it. A small native Mac app called CrayonPress (it started life as ColourMe, but that name belongs to half the internet), knocked together in an afternoon.

The plan

The idea was straightforward: a SwiftUI app where you type a theme, pick how many pages you want, choose a complexity level, and optionally add a child's name for the cover. The app talks to OpenRouter with your own API key, generates one line-art image per page, shows you a preview grid where you can regenerate any page you don't like, and exports the lot as a multi-page A4 PDF with a cover.

The clever bit is not the image generation - it is the page variety. Before generating anything, the app asks a cheap text model for N distinct scene ideas for your theme. So a dinosaur book isn't eight near-identical T-Rexes; it's a T-Rex waving, a baby triceratops hatching, a long-neck munching leaves, and so on. Each idea then becomes its own image generation call to Gemini 2.5 Flash Image, which turns out to be remarkably good at clean, bold-outline colouring pages. Cost: about 4 cents per page.

The gotchas

Two things bit me along the way, and both are worth writing down.

First, the OpenRouter docs say the image models endpoint returns supported_parameters as a list of parameter names. It doesn't. The live API returns a dictionary of parameter name to full spec, which meant my Codable model fell over the moment it met real data. The fix was a custom decoder that accepts either shape. Always test against the live API, not the docs.

GET /api/v1/images/models
// docs say:  "supported_parameters": ["aspect_ratio", "seed"]
// reality:   "supported_parameters": {"aspect_ratio": {"type": "enum", ...}}

Second, a classic SwiftUI lesson. The API key lives in the macOS Keychain, pasted once into a Settings screen. But the main window checked the Keychain exactly once at launch - so after saving your key, the app still showed the "add your API key" banner and an empty model picker until you restarted it. Keychain state isn't observable, so nothing told the UI to look again. The fix was a tiny shared @Observable object that both windows watch, refreshed whenever the key is saved. Obvious in hindsight, invisible until someone actually used the app.

The result

The finished app is about 1,200 lines of Swift, sandboxed, using the macOS 26 Liquid Glass design so it looks like it belongs on the system. Complexity ranges from Simple (one big shape, extra-thick lines, ages 2 to 4) up to Intricate, which produces genuinely lovely adult-colouring-book pages - fine-line owls covered in zentangle patterns, that sort of thing. I may have generated a few for myself.

The PDF composer draws proper A4 pages with Core Graphics: a bordered cover page with the book title - "Ellie's Dinosaur Colouring Book" - then one aspect-fitted drawing per page. Print, staple down the edge, hand over, receive brief but sincere gratitude from a nine-year-old.

Try it

The whole thing is open source under MIT: github.com/beaglemoo/crayonpress.

If you just want the app, there's a signed and notarized build on the releases page - unzip, drag to Applications, paste your OpenRouter key into Settings, and you're making colouring books. You'll need macOS 26 and an OpenRouter API key; since this post first went up the app has grown a quality picker, a book library, and smarter model defaults, and a twelve-page book now costs from a few cents.

Reflection

This is the kind of project that would have been a weekend of fiddling a few years ago and is now an afternoon: a real native app, signed, notarized, and on GitHub before dinner. The lesson that sticks with me is the same one as always - the docs lie, the happy path hides bugs, and nothing replaces actually clicking through your own app the way a real user would. My daughter doesn't care about any of that, though. She has a capybara book queued up and strong opinions about what comes after.