A picture is five numbers
In the first post we built a flow-field painter in the browser with nothing to install. Those sketches were deliberately forgetful: reload the page and the piece you liked is gone.
This is the same painter with a memory, and the memory is public. Below is the wall, with a handful of pieces already on it. Click any of them and its settings load into the painter above it. Make one you like, keep it, and it joins them — along with everything the next reader keeps.
A saved piece is not a PNG
Here is the idea that carries this post. A generative sketch is random, and that seems like a problem for saving: you can't store "what the random numbers happened to be." Except you can, with two lines:
jsp.randomSeed(params.seed);
p.noiseSeed(params.seed);
Seed the random number generator and the noise generator, and the sketch becomes deterministic. The same seed produces the same first particle positions, the same wind, the same drift, the same picture, every time. So a kept piece in this app is not an image file. It is five numbers: a palette name, a grain, a speed, a symmetry, and a seed. Give those five numbers to a fresh copy of the painter and it redraws the artwork from scratch, as many times as you like.
Two honest footnotes on "the same picture". The first is about what the numbers do not cover: they fix the recipe, not the moment. The canvas you are looking at when you press keep has been running for some number of frames, and may have your dragged strokes in it, and neither of those is one of the five numbers — a stroke is unbounded input that no seed can replay. So a card on the wall is the recipe cooked fresh for a fixed time, not a photograph of your screen. Keeping the exact pixels is a different job, and a one-line one — there is a section about it further down.
The second footnote is that the numbers fix the simulation, not the screen.
Draw it on a canvas of a different size, or at a different pixel density,
and the particles start in proportionally different places and the lines
land on different pixels, the way the same negative prints differently at
different sizes. And the sketch is pinned to one release of
p5, because a library update can change what noise() returns. The recipe
is exact; the print depends on the paper. The third post in this
series says why that pin
matters more for art than for most software.
Get posts like this in your inbox
One email field. Real updates. No algorithm required.
That changes what "save" and "share" mean. A PNG is a few hundred kilobytes that can only ever be that picture. Five numbers are a few dozen bytes, and they are the recipe — load somebody else's piece and you are holding their parameters, not their output. Nudge the speed and you have made a variation, not a copy. Every card on the wall below is a real p5 instance rendering the piece from its numbers, which is why the wall is cheap enough to hold hundreds of them and why clicking one hands you a live painter instead of a thumbnail.
The memory is one hook
Storing five numbers needs somewhere to put them. That is one line:
jsconst { database, useLiveQuery } = useFireproof("flowPieces");
Keeping a piece is one more:
jsawait database.put({ type: "piece", ...params, authorHandle, created: Date.now() });
And reading the wall back is the third:
jsconst { docs } = useLiveQuery("created", { descending: true, limit: 12 });
That is the entire data layer of this app. useLiveQuery is live in the
sense that matters — when anyone keeps a piece, every open copy of the app
re-renders with it, including the one embedded in this page. There is no
fetch, no refresh button, no loading state to write.
It is also local-first, which shows up as an absence: pressing keep does not wait for a server. The piece is in your copy of the database the instant you press the button, and it syncs outward when it can. Do this on a train and the wall fills in when you surface.
Two honest lines about being signed out. Your keeps still work — they stay on this device, yours alone. Sign in and they join the shared wall, stamped with your handle, which is what the next section is about.
The wall
Each card on the wall is a real p5 sketch, not a picture of one. It runs the
same simulation the big canvas runs — it just runs it all at once, inside
setup(), and then stops:
jsif (still) {
for (let i = 0; i < 260; i++) advance();
p.noLoop();
}
advance() there is the identical function the live painter calls sixty
times a second. A still render is a loop you ran to completion. One sketch
factory, one still flag, two modes — which is why a card can never drift
from the thing it is a card for, and why clicking one can hand you a
running painter instead of a thumbnail:
jsonClick={() => setParams({ palette, noiseScale, speed, symmetry, seed })}
The cards render at pixelDensity(1) because a thumbnail has no use for
retina pixels, and twelve of them at full density is a lot of fill rate for
something the size of a postcard.
The bouncer is ten lines
Who gets to post to a shared wall? Every vibe ships a server-side access rule, a small function that sees every write before it lands. This app's entire policy:
jsexport default function (doc, oldDoc, user, ctx) {
if (!user) throw { forbidden: "sign in to keep a piece" };
if (doc.type === "piece") {
if (doc.authorHandle !== user.userHandle) {
throw { forbidden: "sign your piece with your own handle" };
}
if (oldDoc && oldDoc.authorHandle !== user.userHandle) {
throw { forbidden: "not your piece" };
}
return { channels: ["pieces"], grant: { public: ["pieces"] } };
}
throw { forbidden: "unknown document type" };
}
Read it as house rules: pieces go on the public shelf, so anyone can see the wall, even signed out. Every piece must be signed with its author's own handle, both when it is kept and on every later change, so nobody can post under your name or hand you a piece you did not make; and after that only its author can touch it. The rule runs on the server, so it holds no matter what a client sends. It is the whole difference between a gallery and a scratch pad, and it is a dozen lines you can read.
The same app is a private sketchbook without touching that rule. Deploy it private and the piece-level policy above is unchanged — it still says every piece is signed and only its author can edit it — while who is in the room at all becomes a separate question, answered by who you invited. Nobody sees the wall but you, or you and the two people you shared it with.
A class gallery — anyone may look, only your students may post — is a
different job, and worth being precise about because it is the one people
get wrong. The grant above controls who can read; swapping public for
a role would hide the wall from visitors without stopping anyone from
posting to it. Restricting writing is a line in the function:
jsctx.requireAccess("class"); // must be in the class
return { channels: ["pieces"], grant: { public: ["pieces"] } }; // anyone may read
Then the owner adds a student by approving a small grant document, the same way a publication approves an author. So two of the three postures — public wall and private sketchbook — really are the same app with a different setting, and the third costs you one line and a roster.
When you do want a PNG
Sometimes you want the pixels. Printing, posting, a desktop background — fine, and it is one line:
jsp.saveCanvas("piece", "png");
Hang that on a download button and the reader gets a file. It is worth knowing what you traded, though: the PNG is finished. It is the print, at one size, forever. The five numbers are the negative — anyone holding them can print it bigger, in another palette, six-fold instead of three, or nudge the grain until it is theirs. The PNG is for the wall in your house. The numbers are for the wall in this post.
Remix it
The gallery is public, so remixing it hands you all three pieces at once: the painter, the database, and the access rule. The interesting move is to throw away the flow field and keep everything else. Whatever your sketch is — a typographic poster generator, a plotter path, a tile pattern — if you can describe one of its outputs as a handful of numbers, this wall works for it unchanged. Name your parameters, seed your randomness, and the gallery comes free.
The third post in this series takes the lid off: how p5 gets into the page at all, what a frame of this actually costs, and the same field pushed onto the GPU.
Your gallery is a remix away
Take the wall and make it yours — public show or private sketchbook.
Start building →