How do coloring apps save your progress?
The obvious answer is to save the picture. The right answer is to save one byte per region and regenerate the picture, which changes what saving costs and what it survives.
The obvious way to save a half-finished colouring picture is to save the picture. Almost nothing about that is a good idea.
The naive answer, and what is wrong with it
The naive design is to render the current state of the canvas to an image file and read it back when you return. It is easy to describe, easy to implement, and it fails in four separate ways.
It is large. The artwork here has a long edge of 1536 pixels, so a lossless snapshot of it is on the order of a megabyte, per picture, per save. The state it is recording could be measured in hundreds of bytes.
It is expensive to write, which forces an awkward question about when to write it. Encoding an image is real work, so doing it on every tap turns a trivial operation into a heavy one; batching it instead means deciding when to flush and deciding what happens if the app is killed between a fill and a flush. Either way you have invented a save problem that did not previously exist.
It throws away the structure. A flat image does not know which regions are filled, which colour is next, or how many regions remain. Every one of those questions is one the interface has to answer, so the structured state has to be stored anyway — at which point the image is redundant.
And it is ambiguous. Given only pixels, a region filled with a mid grey and an unfilled region drawn in a similar shade are the same thing. There is no reliable way to read filled state back out of a rendering, because the rendering was never designed to carry it.
What is actually stored: one byte per region
The state that genuinely changes as you colour is very small. For each region there is one fact: whether it has been filled. That fits in a byte, and the whole picture fits in an array of bytes with one entry per region plus one for the background. Numbrush stores exactly that, encoded as text in a small local table.
The picture itself is not stored at all, because it does not need to be. It is regenerated every frame. A shader reads the region-id map, rebuilds the region id for the pixel it is drawing, looks that id up in a small palette texture, reads a state flag out of that entry’s alpha byte, and draws either the palette colour, the unfilled shade, or the highlight treatment. The visible image is a function of two inputs — the read-only id map and the small state array — evaluated fresh on every frame.
So saving is: write a few hundred bytes. Loading is: read them back and rebuild the palette texture, which is a few kilobytes. The 1536-pixel id map is read-only for the life of the app. It is never modified, never re-encoded, never written. The expensive object is the one that never has to be saved.
What that buys
Saving stops being an event
When a save is a few hundred bytes there is no reason to ration it, so there is no Save button, no prompt about unsaved changes, and no question of when to write. It can simply happen as you go, and the app being killed mid-picture costs at most the last fill.
Resuming is immediate
Returning to a picture means loading the id map — which you would load anyway, since it is the picture — and reading a short array. There is no large image to decode and composite on top. The half-finished state appears with the picture rather than after it.
A picture can wait a month
Because what is stored is a description rather than a rendering, it does not go stale in the way a rendering would. It is a list of which regions you have done, and that remains true regardless of how long the app sits unopened.
It survives changes an image would not
The state is recorded against region ids, not against pixels. If a later release adjusts a picture’s palette, the regions you filled come back correctly in the new colours, because what was saved was that region 214 is filled and not that a particular pixel was a particular shade. A saved image would come back in the old colours and there would be nothing to be done about it.
The honest limits
Two of them. The state is on the device and no server holds a copy, so it does not follow you to a second device and cannot be restored by anyone if the device is gone. And it depends on region ids staying stable between releases, which is why the artwork pipeline ends with a verification pass that reads the finished files back off disk and rejects any that break the format rules.
The general principle is worth stating on its own, because it applies well beyond colouring. Store what changed, not what it looks like. The rendering can always be recomputed from the description; the description can never be recovered from the rendering.
Frequently asked questions
Do colouring apps save an image of your half-finished picture?
A naive one might, and it goes badly: the file is large, encoding it on every fill is expensive, and a flat image cannot say which regions are filled or which remain. Numbrush stores one byte per region instead and regenerates the picture from that plus the read-only id map every frame.
How big is a saved picture?
A few hundred bytes — one byte for each region, plus one for the background. That is small enough that saving needs no button and no prompt, because there is no reason to ration something that cheap.
Why is resuming a picture instant?
Because nothing large has to be read. The app loads the region-id map, which it would load anyway to show the picture at all, and a short array of bytes. There is no saved rendering to decode or composite.
What happens to my progress if the artwork is updated?
Progress is recorded against region ids rather than pixels, so a change to a picture’s colours brings your filled regions back correctly in the new colours. That is a property a saved image would not have.
Try it on a real canvas
Numbrush is colour by number for adults: mandalas, rose windows and geometric pattern work, twelve colours a picture, a magnifier for the tight spots, and a wrong tap that costs you nothing. Free, offline, no ads and no account.