Repository navigation
Design colour mixing
Status: being built, 2026-10-09. Q232 holds the four decisions; this is the design they produce, and the investigation that led to them, written for the next session rather than for this one.
The owner's complaint, 2026-10-09: the simulated media "look and perform like
ass" next to Clip Studio Paint, and they would "give up the simulation
aspects if we can visually create more realistic paint." Measured against the
suite's own contact sheets (scripts/visuals.sh media) the complaint held: the
simulated watercolour is a pale tube with a seam, oil and gouache are flat blobs
with no bristle or chisel in them.
The cause is not the physics but where the simulation sits. MediumSimulator
reads only the stamped mark's alpha, seeds one uniform stroke colour, runs the
lattice and overwrites the scratch — so every per-dab colour the engine computed
(JitterColor: hue, saturation, value, a second colour) is thrown away, and a
bristle or chisel tip survives only as coverage, which the opacity map
1 − e^(−3·mass) then saturates: a 60 % bristle scratch reads α 0.83 against
0.95 for the solid. With a medium on, granulation, the stamped wet edge and the
paper-texture pass are skipped as well. And at the time of writing the shipped
Watercolor's EdgePull was 0.06 (B35), so the lattice's one unique effect was
nearly off — B431 found why it had to be, fixed the term, and put it back to
0.45.
Clip Studio Paint has no fluid simulation. Its wet media are textured
material tips, a paper texture multiplied in per plot, a whole-stroke
watercolor edge (what ApplyWetEdge already is), and — the thing Lightbox
lacked entirely — colour mixing in the Ink settings: each dab picks up the
colour already on the layer and mixes it with the brush's paint, with an
amount of paint, a colour stretch for how long the pickup persists, and a
perceptual mixing mode. Its oil brushes imitate impasto with the same mixing;
there is no height field. All of it is per dab, forward, single pass.
So the pass is: give the paint path per-dab colour mixing; keep tips, texture, jitter and the stamped wet edge on under a medium; and make the lattice take the colours it is given rather than discarding them. The lattice stays, as an opt-in that composes with the dabs instead of replacing them.
BrushSettings.Mixing, a nullable ColourMixing block. Null is off, and null
writes nothing — the camera's rule, and the optional-settings skill's two
traps avoided: the block is nullable (not inert at a default), and there is no
convenience getter beside it to reintroduce the key.
| Field | Meaning | CSP's name |
|---|---|---|
Amount 0..1 |
how much of a dab is the brush's own paint; the rest is what it picked up | Amount of paint |
Length 0..1 |
how much of the carried colour survives each dab — the smudge option's word for the same idea | Color stretch |
Reach 0..1 |
how far around the dab the ground is sampled, as a fraction of its radius | — |
Pressure does not drive the amount yet, deliberately. A pressure curve is
stored under a BrushDynamic key, and a key an older build does not know makes
that build's brush store unreadable — which it then saves over, empty. The
owner runs older builds daily, so the one mixing control an artist would reach
for by pressing waits for Q193's version field (the sensitivity guardian's
finding). Invariant 4: the block is on the stroke, so a preference change never
alters existing art.
Per dab, in StampDabRange, before the dab is drawn:
-
Sample the ground under the dab: the scratch so far (the stroke's own
earlier dabs) composited over the layer beneath, a five-point average at
radius × Reach— the same sampler the dulling smudge uses. -
Carry. The first dab's carried colour is the ground. Each later dab
mixes the ground into what it carries by
Length: at 0 the sample is replaced every dab and colour barely travels, at 1 it is dragged the length of the stroke. -
Deposit the brush's colour (after its own jitter) mixed toward the
carried colour by
(1 − Amount) × carriedAlpha. Nothing under the brush means nothing to mix with, so on a blank canvas a mixing brush is exactly the same brush without mixing — a test pins that identity. - Mix in Kubelka–Munk (
Pigment.Mixon K and S, mass tone back out), so yellow into blue goes green. Alpha is lerped; only the colour goes through the pigment model.
The deposit then takes the ordinary dab path — tip, roundness, flow, the
footprint ceiling — so everything a paint brush has stays. A mixing brush is
excluded from DrawsAsOneSilhouette and SubdividesForFidelity for the same
reason colour jitter is: its dabs are not interchangeable.
The live path stamps settled dabs once and re-stamps a tail every event
after restoring a backup, so dab k always sees dabs 0..k−1 in the scratch —
the same ground the commit sees. The carried colour is checkpointed at the
settled boundary, as SmudgeCarry is, and the tail continues from a copy.
The layer beneath is the frame's cached bitmap, as the medium pass already
borrows it.
Symmetry copies reuse the identity pass's mixed colours rather than
sampling at their own position. The copy is the mark, moved — Hash01 seeds
every other dynamic from authored coordinates for the same reason — and a copy
sampling its own ground would make two mirrored halves of one stroke disagree.
One known gap, filed as B426: the live path stamps the copies
into the same scratch the drawn mark samples, while the commit gives each copy
its own, so a mixing stroke crossing its axis picks up its reflection live and
not on commit.
Where a mixing stroke is refused, the way a smudge is: per-tile
rasterisation (CanTile), a region repaint on undo (RepaintRegion) and a
layer merge all replay strokes over pixels a full render would not have shown
them, so each takes the whole-frame route for a mixing stroke.
Bounded (invariant 6): one five-point sample of two bitmaps and one mix per dab, nothing proportional to the canvas.
MediumSimulator now seeds each cell's pigment colour from the downsampled
scratch — the colour the dabs actually laid there, unpremultiplied — rather
than from one stroke colour. WriteBack already recovers colour from the
deposit's mass, so nothing else changes. Existing media documents whose brushes
carried colour jitter re-render with that jitter restored. Two honest
qualifications from review: no render fingerprint in the suite covers a medium
stroke, so nothing was re-recorded and nothing would catch a further change;
and a plain single-colour stroke's rim cells — alpha of a few levels, above
the seed threshold — read back a colour quantised by premultiplied storage, so
those bytes move by a few levels too. Invisible at that alpha, and said here
rather than claimed away.
Mixing reads the canvas back per dab, so BrushCostOf puts a mixing brush in
the Expressive tier with the smudge and the media, and the picker badge
says why. The four media presets already sat there.
Not a height field, not pickup of a patch (that is the smearing smudge, and it is what a palette-knife brush would be built on later), not a change to the fluid lattice's physics. The lattice's edge pull, bleed and glazing remain the things only it can do; whether the shipped presets should lean on them again is a later decision, made by looking at the contact sheets once the dabs carry real colour.
ColourMixingTests (Raster): yellow over blue goes green not grey; a blank
canvas leaves a mixing brush identical to itself without mixing; length
carries a picked-up colour further; deterministic; not a silhouette.
ColourMixingRecordTests (Core): absent from the file until used; round-trips;
clones deep. LiveMatchesCommittedTests.AMixingBrushLooksTheSameLiveAndCommitted
(App). MediumSeedColourTests (Raster): the lattice keeps per-dab colour.
- Getting started
- Documents and projects
- Tools and strokes
- Brushes
- Colour
- Layers, selections and guides
- The timeline
- Onion skin, references and the camera
- Symbols
- Saving and recovery
- Exporting to a game engine
- AI assistance
- Keyboard, performance and what is planned
- Bones and rigging
- Effects — fire, smoke and water
- A simplified 3D space to draw in: planes, parallax first, orbit second
- Brush performance beyond 4k: what the GPU can and cannot buy us
- The add-on boundary
- Making the AI trustworthy
- What an AI request actually costs
- Bones and spline deformers for 2D — design
- Brush tips — a generated library, not a live calculation
- Cloud and SaaS: what the architecture already fits, and what it does not
- Colour mixing — paint that picks up what it is laid on
- DESIGN: Effects
- DESIGN: Fluid effects — fire, smoke and water as drawn line and fill
- Fluid and viscous media — notes for the next brush-engine pass
- Compositing on the GPU
- Decomposing the two big files: which pieces come out, and with which tool
- One memory limit for pictures
- .NET 10: what is safe, what is unverified, and what it narrows
- One picture cache: the paused canvas on tiles
- The paint path: one mark, made once, under the pen
- Pen dynamics: tilt and speed
- A pencil is graphite on the paper's tooth
- Performance: the ratchet and the map
- Playback's first loop
- Pressure curves — a shape, not an exponent
- Importing Procreate brushes
- A project as a game's asset set
- One hierarchy: folders scope everything
- Puppet pins — design and plan
- The raster checkpoint: making a painting cheap to reopen
- Scoped resources — palettes, references and everything else shared
- Sensitive topics, and the three-track flow
- Design: pose-assisted frame-by-frame animation (rigid bones + AI gap filling)
- Stroke groups: why a merge should never turn strokes into pixels
- The project window: what you do between drawings
- Reading the subject before drawing on it
- The ceiling of a swept soft mark (B349)
- Q171 — a symbol owns a layer stack
- Pillar 3 — reusable assets, as live symbols
- What a template is
- Text, and where fonts sit
- The tiled compositor, and getting it off the CPU
- Design tokens
- Transform tool design (M14)
- Why a brush stamps twice, and when it does not
- Migrating to the designed UI
- Undo stores only what changed
- Vector tooling: making the lines you already drew editable
- The smudge/blur family, and aligning brush settings with Photoshop
Generated from the repository. Edit the files under docs/, not these pages.