Home›Journal›This post

Canvas Watercolor With a Wet-Paint Grid

Separate water from suspended and deposited pigment, preserve a mass ledger, and author reproducible dry, wet-on-wet, and late-water swatches.

JP
JP Casabianca
AI Engineer and Product Designer · full-stack delivery · Bogotá

A Canvas watercolor effect becomes believable when water, moving pigment, deposited stain, and paper tooth remain separate fields instead of one blurred stroke. This tutorial builds a bounded wet-paint grid, checks its mass ledger, and renders seeded dry, wet-on-wet, and late-water swatches with an inspectable receipt.

A Canvas watercolor effect starts with separate fields

A convincing Canvas watercolor effect does not require a full fluid simulator. It does require refusing the easiest shortcut: blurring one translucent stroke and calling it wet paint. Water moves, suspended pigment rides that movement, deposited pigment stains the paper, and drying changes how readily later color can travel.

The practical model is a bounded grid with separate fields for paper height, water, suspended pigment, and deposited pigment. A brush writes water and pigment into nearby cells. Each fixed simulation step moves a conservative fraction to neighbors, transfers some suspended color into the paper, evaporates water, and renders the result back to Canvas.

This is procedural watercolor, an artistic approximation rather than physical fidelity. The classic computer-generated watercolor work demonstrates how layered models of paper, pigment, and fluid produce characteristic marks; our browser version deliberately keeps only the concepts that remain inspectable in a small local lab.

The result should be judged by controlled behaviors: a dry stroke stays narrow, wet-on-wet pigment blooms into connected water, and late water pushes a drying wash into a backrun rim. Those three swatches provide a better engineering target than “looks watercolor-ish,” and they connect naturally to diffusion curves for vector painting when you need authored edges rather than a cell simulation.

Wet-paint grid layersA cutaway separates paper height, water, suspended pigment, deposited pigment, and the rendered glaze.SEPARATE WHAT MOVES FROM WHAT STAINSRENDERED GLAZEdisplay heuristicSUSPENDED PIGMENTmoves with waterWATER FILMbounded flowDEPOSITED PIGMENTmostly fixedPAPER HEIGHT + ABSORBENCYseeded toothDeposited and suspended pigment remain separate in state and receipt.
Wet-paint grid layers. The diagram and visible semantic equivalent state the same conclusion.
Paper
Seeded height and absorbency fields shape flow and staining.
Water
A nonnegative scalar flows within a bounded neighbor budget.
Suspended pigment
Moves in proportion to water outflow.
Deposited pigment
Stains the paper and lifts only by a declared bounded fraction.
Rendered glaze
An artistic Canvas color mapping, excluded from the numeric simulation hash.

Reading rule: text, symbols, patterns, and structure carry every conclusion; color is supplementary.

Build paper before adding pigment

Paper is not a noise texture pasted on at the end. Give every cell a seeded height and absorbency value. Low-frequency variation bends broad flow; fine grain modulates deposition and breaks perfectly smooth borders. Use a small number of seeded octaves or filtered value noise, normalize once, and record the seed and algorithm version in the recipe.

Keep the height range modest. If one random cell becomes a cliff, the simulation produces starbursts instead of fibers. A simple slope term can bias water toward lower neighbors, while absorbency controls how quickly a cell transfers water and pigment into deposited layers. Clamp both so every update remains finite.

The grid is the work budget. A 192×108 field already has 20,736 cells and several arrays. Cap width, height, pigment channels, iterations, and brush radius before allocation. Render at higher display resolution by scaling Canvas, not by silently multiplying the simulation. This makes the Canvas watercolor effect predictable on phones and laptops.

Seeded paper also gives you a fair comparison. Dry, wet, and late-water swatches can share the same paper realization, so visible differences come from water timing rather than a different random texture. When you explore reaction-diffusion art on WebGPU, the same discipline applies: separate deterministic initial conditions from the update rule you are evaluating.

Track water, suspended pigment, and stain

Use one nonnegative water scalar per cell. For each paint color, keep suspended and deposited quantities separate. Suspended pigment can move with water. Deposited pigment stays mostly fixed, though a bounded fraction can lift when fresh water arrives. This split creates glazing and backruns without pretending every color is a physical mineral.

Each step begins with inputs frozen from the previous step. Compute water outflow to the four cardinal neighbors from local water differences and paper slope. Limit total outflow to a fraction of the source water, then move suspended pigment in the same proportions. Apply deposits and evaporation after flow. Double-buffering prevents traversal order from changing the painting.

Track a mass ledger: water before, water added, evaporated water, and water after; pigment before, pigment added, any declared spill, and pigment after. Floating-point rounding will introduce tiny error, so declare a tolerance scaled to total mass. Nonfinite or negative values are immediate failures, not visual quirks.

This accounting is inspired by the stability questions in research on real-time thin-film digital painting, but the grid here does not implement that paper’s equations. It borrows the engineering instinct—bounded updates and explicit film behavior—while keeping an intentionally smaller, teachable model.

Use a stable, fixed update loop

Animation time should decide how many fixed simulation steps to run, never change the equations directly. Choose a step size, accumulate elapsed time, execute at most a small number of catch-up steps per frame, and drop excess lag with a visible diagnostic. That keeps a backgrounded tab from attempting thousands of updates on return.

Within a step, cap every transfer coefficient to a range proven stable for this stencil. Compute candidate flows, scale them when their sum exceeds the source budget, then apply them to the next buffers. Evaporation removes at most the available water. Deposition removes at most the suspended pigment. Lifting removes at most the deposited amount.

The mass ledger should close after each stage, not only at the final frame. If flow loses pigment, inspect the flow stage. If drying creates water, inspect evaporation. Stage-level receipts turn a mysterious bad bloom into one violated invariant.

Do not use frame rate as a quality knob. Iterations are an artistic control with computational cost, while Canvas presentation can repaint at the display’s cadence. A paused recipe with the same seed, grid, inputs, and step count should produce the same numeric receipt. Rendering differences may still occur across browsers because Canvas color conversion and PNG encoding are outside the deterministic simulation core.

Stable update loop and mass ledgerA fixed-step pipeline double-buffers flow, deposits pigment, evaporates water, validates finite mass, and renders without feeding pixels back into simulation.FREEZE INPUTS · CAP TRANSFER · CHECK EACH STAGEBRUSHadd massFLOWdouble bufferDEPOSITsuspend → stainEVAPORATEwater onlyCHECKfiniteWATER AFTER = BEFORE + ADDED − EVAPORATED − DECLARED SPILLPIGMENT AFTER = BEFORE + ADDED − DECLARED SPILLtolerance scales with total mass · negative or nonfinite cell = failfixed step repeats within a per-frame catch-up cap; rendering never mutates state
Stable update loop and mass ledger. The diagram and visible semantic equivalent state the same conclusion.
  1. Add bounded brush water and pigment.
  2. Read previous buffers and scale neighbor outflow so it never exceeds source water.
  3. Move suspended pigment with water into next buffers.
  4. Deposit at most the suspended amount; optionally lift at most the deposited amount.
  5. Evaporate at most available water.
  6. Reject negative or nonfinite cells and compare the stage ledger within tolerance.
  7. Render from accepted state; never feed display pixels back into the update.

Reading rule: text, symbols, patterns, and structure carry every conclusion; color is supplementary.

Author dry, wet, and late-water swatches

The first swatch places pigment with a small amount of water on dry paper. It should preserve brush direction, show tooth in its interior, and develop only a modest dark edge. If it blooms as widely as the wet swatch, the model has too much baseline diffusion or too little absorption.

The second swatch pre-wets a region, then adds pigment. Suspended color follows the connected water field, so the mark widens and softens. It should stop at the wet boundary rather than diffuse forever across the paper. This is the wet-on-wet simulation’s most useful qualitative test.

The third starts as a wash, advances through partial drying, then receives a ring or drop of fresh water. New water moves toward the drier boundary and redistributes suspended pigment. Increased deposition near the moving front makes a backrun-like rim. Call it a stylized backrun, not a verified fluid phenomenon.

Show the three at synchronized step counts: 0, 24, 72, and 144, for example. That time strip prevents a finished-image comparison from hiding different run lengths. It also creates a natural bridge to anisotropic Kuwahara filtering on Canvas: a filter can stylize a raster, but it cannot recover the temporal cause recorded by the wet-paint state.

Render pigment without inventing spectral physics

For each cell, combine paper color with deposited and suspended pigment through a declared artistic mixing function. A simple option maps pigment load to optical-density-like attenuation per sRGB channel, then converts the result to display bytes. Name it a heuristic. Do not call it Kubelka–Munk unless you actually implement and validate that model.

Edge darkening can depend on deposited pigment, local water gradient, and a drying-front witness. Granulation can modulate deposition with paper height. Glazes accumulate deposited layers while keeping alpha bounded. Every coefficient belongs in the exported recipe so an appealing accident can be reproduced.

The HTML Canvas standard defines the bitmap surface and pixel APIs used for display and export. It does not guarantee identical color management or PNG bytes across every environment. Keep numeric simulation hashes independent from the rendered bitmap hash, and label each correctly.

If accurate material mixing is the actual goal, move beyond this tutorial and compare measured pigments, illuminants, and a spectral pipeline such as the concerns discussed in spectral color mixing. The Canvas watercolor effect here optimizes for readable code, bounded work, and expressive control—not print matching.

Dry, wet, and late-water time stripThree synchronized rows show a narrow dry stroke, a widening wet-on-wet bloom, and a late-water backrun rim at steps zero, 24, 72, and 144.SAME PAPER SEED · SAME STEP COUNTS · DIFFERENT WATER TIMINGSTEP 0STEP 24STEP 72STEP 144DRYWET-ON-WETLATE WATERWitnesses: wet area > dry area · dry center > wet center · late-water rim > control rim
Dry, wet, and late-water time strip. The diagram and visible semantic equivalent state the same conclusion.
Synchronized qualitative witnesses
SwatchWater timingExpected witness at equal stepsBoundary
DryPigment plus little water on dry paperNarrow area and stronger centerNot a real dry-brush fiber model
Wet-on-wetPre-wet field, then pigmentLarger connected occupied areaStops at the modeled wet boundary
Late waterFresh water after partial dryingStronger rim witness than controlStylized backrun, not physical validation

Reading rule: text, symbols, patterns, and structure carry every conclusion; color is supplementary.

Test invariants and artistic witnesses

Tests need both numbers and images. Numeric checks assert that every cell remains finite and nonnegative, total water equals previous water plus input minus evaporation within tolerance, and total pigment equals previous pigment plus input minus declared spill. Boundary cells must not leak unless the recipe explicitly enables an open edge.

Behavioral witnesses compare the three swatches. Wet-on-wet should occupy more cells than dry at the same step. Dry should retain a higher central concentration. Late water should create a stronger rim witness than the corresponding wash without the late-water event. These are algorithm-specific tests, not universal watercolor metrics.

Use exact seeds and normalized inputs. Test minimum and maximum grids, zero pigment, zero water, maximum brush radius, exact coefficient bounds, and the iteration cap. Reject NaN, Infinity, negative counts, unknown modes, excessive dimensions, and hostile recipe names before allocating arrays. Render user labels with text nodes, never HTML injection.

Mutant tests should fail when in-place traversal replaces double buffering, total outflow exceeds source water, evaporation goes negative, deposited pigment moves as freely as suspended pigment, or the renderer’s color bytes are included in the canonical numeric hash. Those failures target the claims that make the lab useful, not incidental source formatting.

Save a recipe and a bounded receipt

A useful export contains schema and algorithm versions, seed, grid dimensions, fixed-step count, paper parameters, brush events, coefficients, per-stage mass ledger, finite/nonnegative checks, swatch witnesses, limitations, and a canonical receipt hash. The recipe should recreate the numeric state; the optional PNG is a visual edition from one browser.

Keep the lab local and dependency-free. It should not upload images, inspect arbitrary files, store data, or fetch remote palettes. Limit recipe text, grid size, steps, and download size. Release typed arrays between runs by replacing references, and avoid starting a second simulation while one is active.

The limitation statement belongs beside the export button: this is a grid-based procedural watercolor, not a physically faithful paint model. Paper noise, pigment transport, deposition, and edge darkening are designed controls. Canvas color handling and raster encoding can vary. The receipt proves the declared arithmetic and inputs, not that a digital swatch matches a real pigment on real paper.

That boundary is also the creative opportunity. Once every field is inspectable, you can author water as composition, use deposition as rhythm, and let seeded paper contribute without surrendering reproducibility. The technique becomes both an artwork and a small piece of forward-engineered simulation.

Call the result JavaScript generative art, not a laboratory paint prediction. The Canvas pigment diffusion is a bounded authored rule, while the Canvas watercolor effect is the visual edition built from it. A Canvas watercolor effect becomes reproducible when the recipe travels with the image; a Canvas watercolor effect remains honest when its numerical ledger and display limitations travel too. Canvas watercolor effect controls can then be creative without becoming mysterious. The Canvas watercolor effect stays art-directed and inspectable.

Runnable local artifact — The lab is an artistic grid approximation, not physical watercolor, Kubelka–Munk mixing, measured pigment matching, or cross-browser PNG byte identity.

Plain text1 line
Seed paper, bound every transfer, double-buffer the fixed update, separate suspended from deposited pigment, and hash the numeric recipe rather than browser pixels.