What this simulator is, how it works, and how to use it.
What is this?
Rosemaling is a physically based painting simulator. Ordinary
painting apps store display colors (RGB) and blend them with arithmetic; that is
why mixing blue and yellow in them gives grey. Rosemaling instead stores
which real pigments, and how much of each, sit at every point of the
canvas, and computes what that physical film of paint looks like — so
blue + yellow makes green, thin paint glazes, thick paint hides, and a painting
can be re-lit or examined like a real object.
Everything runs on your device, in the browser. After one visit it works fully
offline and can be installed to a home screen; no image or document ever leaves
your machine.
How it works
Every texel of the canvas holds pigment amounts — quantities of
up to eight real paints plus a clear-medium channel — not a color. Appearance is
computed from that state each frame:
Measured pigments. Each paint in the libraries (Golden Matte
Fluid and Heavy Body acrylics, and 40 classical oil pigments) carries measured
absorption and scattering spectra — K(λ) and S(λ) — from published
spectroscopy, sampled on a 31-band grid across the visible spectrum (extended
into UV and near-IR for the technical views).
Spectral Kubelka–Munk optics. Mixtures combine by
concentration-weighted K and S; the renderer solves the finite-thickness
Kubelka–Munk equations for the reflectance of the wet film over whatever lies
beneath it — the ground, or dried paint layers. Thin, medium-rich films fall
into a Beer–Lambert glaze regime; thick films hide. Subtractive mixing,
hiding power, and glazing are consequences of the physics, not painted-on
features.
Two engines, one truth. A WebGPU compute engine runs the
product; a pure-TypeScript CPU engine is the reference implementation the GPU
kernels are validated against, texel for texel.
Brush transport. Strokes exchange paint in both directions —
the brush deposits and picks up — so brushes get dirty, wet paint
smears into wet paint, and the palette surface mixes like a real palette. The
brush's reservoir is spatial: an unevenly loaded brush paints unevenly.
Relief and gloss. Paint has thickness; a height field shades
impasto under a movable directional light, canvas weave catches dry-brush
strokes, and surface gloss follows the medium content.
Light × sensor rendering. The final color is the film's
reflectance integrated against an illuminant and a set of sensor
sensitivities. Swapping those matrices gives daylight, tungsten, UV-365
examination (with real fluorescence, including optical-brightener papers), IR
reflectography, and a measured digital-camera rendering — one renderer, many
instruments.
Using the Paint page
Getting paint onto the brush
Pick a palette (classical oils or a Golden acrylic line) — or press
Edit palette… to assign any paint from any library to the eight wells.
Click a well to arm a squirt, then tap the palette (or
canvas) to drop a glob of paint.
Stroke through the glob to load the brush — like a real
palette. Strokes on the palette surface mix paint exactly as on the canvas.
Painting
Size sets the brush radius (down to 1 px); Size ←
pressure makes pressure drive the radius. With a pen, pressure and
tilt are live; with a mouse, set pressure with the slider or scroll wheel.
Round and flat brushes; the flat's blade can
follow the stroke, hold a fixed angle, or follow pen tilt.
The brush picks up what it touches — drag through wet paint
and the next stroke carries it. Wipe partially unloads the brush;
Rinse empties it.
Glazing:Dip in medium charges the brush with clear
medium; heavily diluted paint goes down as a transparent glaze. Glaze over a
dried layer to deepen color the way real glazing does.
Sketching (pencil & eraser)
The Tool switch adds a Pencil — real
graphite, simulated with measured optics: light pressure catches only the
ground's tooth (grainy strokes), pressure works it into the valleys, and
layering saturates at the depth the pressure reaches — the grain survives
light hatching no matter how many passes. Grades 6H–8B
set how much graphite the lead gives up and how dark it can build.
Paper grounds change how the pencil bites: hot-press is
smooth and fills easily, sketch paper shows an even grain, rough
watercolour paper resists — its deep valleys stay white under anything
but heavy pressure. Canvas grounds keep the woven tooth.
The Eraser lifts graphite and leaves paint alone —
vinyl removes almost everything, kneaded lifts gently
for pulling highlights out of shading.
Paint works over a sketch physically: a wash shows the drawing through,
opaque paint hides it — and IR reflectography (in the
spectral views) reveals the buried sketch as a true underdrawing.
Heavy layering burnishes the graphite — aligned
platelets act as a rough semi-metal mirror, so worked passages flash
under near-normal light and go matte at raking angles. Sweep the
light-elevation slider across a dark drawing to see the sheen move.
Layers and drying
Dry layer commits the wet paint to the dried stack — it
stops smearing and becomes a substrate you can glaze over.
Dried layers can be undried (made editable again), deleted,
or reordered; nothing is ever flattened to pixels.
The ground select swaps the substrate — white gesso, black,
cream paper, umber-toned, an optically brightened paper (which glows under
UV), or the measured drawing papers: hot-press, sketch, rough watercolour,
and a toned hemp paper, each pairing a real reflectance spectrum (into the
near-IR) with its own tooth.
Looking at the painting
Light azimuth/elevation move the raking light; impasto and
canvas texture respond. Toggle Relief and Texture off for a
flat view.
Spectral view re-renders the same paint under D65 daylight,
tungsten, D50, UV-365 examination, IR reflectography, or a real camera's
sensitivities. A provenance note warns when a view depends on assumed (rather
than measured) data.
Thickness mode shows the film-height map.
Documents
Undo/redo: buttons or Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z.
Save/Load writes a .rosem file — the full
pigment state of every layer, reopenable and re-lightable, not a flat image.
Export PNG renders the current view.
Canvas size adapts to your hardware by default; larger
sizes cost real GPU memory.
The other pages
The Mixing lab is the physics
workbench: pick two or three real paints, sweep the mixing ratio, and compare
spectral Kubelka–Munk mixing against Mixbox and naive RGB interpolation, with
the underlying spectra and a virtual drawdown card. The
Feel bench is a development tool that
replays a deterministic stroke set under different feel parameters so brush
tuning is comparative rather than vibes.
Games turns the same physics into
daily puzzles: Hue (slide the tiles of a gradient mixed from
four real paints back into place — on a square, or on a diamond laid out like a
QAPF diagram, where no tile mixes more than three), Color Match (mix a swatch
from a few pigments, blind, with a new light — daylight, tungsten, UV, infrared
— unlocking on every guess, and a Metamer mode that demands a match
under all of them), Light Match (the same game for screen
light: dial the swatch in by RGB, HSL or HSV) and Setout (pick a
small palette that can mix every target, with the hidden palette's price as
par). Every colour in the paint games is a genuine Kubelka–Munk mixture of
measured pigments; only Light Match, by design, is an RGB blend.
Accuracy & provenance
Rosemaling prefers measured data and says so when it doesn't have it. Each
pigment's spectra are labeled by provenance (measured / derived / assumed, per
spectral range); technical views display a reliability warning when the
materials on screen lean on assumed data outside their measured range. The
simulation is a model, not a metrological instrument: Kubelka–Munk is an
approximation, some scattering coefficients are derived under stated
assumptions, and feel parameters are tuned by hand. Where spectral data could
not be found, plausibility wins over false precision — and the UI tells you.
Golden acrylic paint spectra — two-constant K/S for Golden
Matte Fluid (Okumura 2005, RIT MS thesis) and Golden Heavy Body (Berns 2016,
CIC24), courtesy of Golden Artist Colors and
R. S. Berns / RIT Munsell Color Science Laboratory
(Mellon "Art Spectral Imaging" project). Measured reflectance from the Berns
2022 Golden acrylics release.
Classical oil pigments — IFAC-CNR
Fiber Optics Reflectance Spectra of Pictorial Materials database
(Aldrovandi et al.), 270–1700 nm; digitized from the published plots.
Modern watercolours — IFAC-CNR MoWCReS database. Both cited
with thanks to IFAC-CNR, Florence.
Pigments Checker reference spectra — Cultural Heritage
Science Open Source (CHSOS, A. Cosentino).
Colorimetry — CIE 1931 color-matching functions and standard
illuminants from the official CIE datatables (CIE 018:2019 /
ISO/CIE 11664).
Camera sensitivities — Jiang, Liu, Gu & Süsstrunk,
"What is the Space of Spectral Sensitivity Functions for Digital Color
Cameras?" (WACV 2013), via Zenodo — CC BY-NC-SA 4.0.
Fluorescence data — PhotochemCAD and the Oregon Medical
Laser Center (OMLC) spectra collections; FPbase (CC BY-SA 4.0);
optical-brightener curves digitized from AAT Bioquest; UV-fluorescence
literature by Pelagotti et al. 2005, Pronti et al. 2017, and Caccia et
al. 2023.
Graphite pencil & drawing papers — per-grade pencil
reflectance and bare-paper spectra from the HYPERDOC
dataset (López-Baldomero et al. 2025, Sci Data 12:1248,
CC BY 4.0); watercolour-paper spectrum from IFAC-CNR MoWCReS;
graphite optical constants via refractiveindex.info; paper-roughness
anchors from Chryssou 2019 and Kyrychok et al. 2019 (CC BY 4.0).
Paint prices in Setout (its par) are approximate: the Golden
series numbers are hand-copied from public price lists and may be off by one,
and the historical oil pigments carry an invented rarity tier (earths cheap,
lapis and cobalts dear) that is a game device, not a quotation.
"Golden" and other paint names are trademarks of their respective owners and
are used here to identify the measured materials; no affiliation or endorsement
is implied. Simulated renderings are approximations of the named paints, not
color-accurate specifications. If you are a rights holder of any dataset above
and want a correction, please open an issue on the repository.