1
1
The code and the canvas are the same artwork - edited from either side.
Every component is a snippet of Paintlang code, shared by everyone. + Insert appends one to the painting you have open - then edit its numbers and colours like anything else. To contribute, pick one layer of your own painting below.
loading...
Records this painting painting itself, straight from your canvas, and downloads it as a video (WebM). Keep this tab visible while it records.
Paintings published by everyone. Open one and it arrives held - glance at the code, press Ctrl+Enter to run it, remix it, and publish your own version. Remixes credit the original.
loading...
loading...
Reports go privately to the Paintlang moderators. Your name is recorded with the report so it can be reviewed properly, and is never shown next to the painting or to its artist.
Messages about your published paintings. Nothing here is public.
Every report, with who sent it and whose painting it is. This queue is private to staff. Removing a painting deletes it and its thumbnail for good; the report stays as the record of why.
loading...
The studio is free forever - every brush, medium, tool, export and the community. Plans only raise the ceilings: more cloud paintings, more versions, more daily traces. Cancel anytime; nothing is ever deleted.
Take this painting into other tools as code, vectors or animation. Exports are one-way snapshots - the tab you are in stays the editable original.
Your private cloud gallery. Every save adds a version, so you can step any painting back to an earlier stage. Only you can see these.
loading...
Everything on the canvas is a function call. The code re-renders live as you type
(Ctrl+Enter forces a run). Painting with any tool writes code back
into the editor. The menu (top left) holds importing, examples, the Coded Gallery,
the global library, sharing, replay and your account. The
Tools box floats over the canvas - drag its header to move it, its corner to
resize it, the caret to collapse it. The sun and moon button in the header switches
between the dark and light studio. With Select: drag an object to move it, drag a corner handle to
resize it, press Delete to hide it - each rewrites the actual numbers in the
call that painted it, and selecting highlights that exact line in the editor.
Recolour rewrites an object's color: literal; Eyedropper picks
a colour off the canvas. Undo / Redo (Ctrl+Z /
Ctrl+Y) covers gestures, imports and rewrites.
Import image (or dropping a file on the canvas) traces any PNG, JPG or SVG into
editable code at your chosen detail level. Each import opens in its own tab, and its
underpainting, brush passes, and recognised objects (a person, a dog, a boat...) appear as named layers in the Layers pane - show, hide, reorder,
rename, rescale or delete them there. Tabs are mirrored above the code and the canvas,
and your open paintings are remembered by this browser.
The canvas is 960 × 600; constants W, H, PI are available.
| sky({from, to}) | Vertical sky gradient over the whole canvas. |
| sun(x, y, r, {color, glow}) | Sun disc with a radial glow. glow scales the halo. |
| moon(x, y, r, {color, glow}) | Moon with faint craters and halo. |
| stars(n, {max, color}) | n stars in the top max (0-1) of the sky; big ones sparkle. |
| clouds(n, {y, color, scale}) | Puffy clouds drifting around height y. Each cloud is selectable. |
| mountains({layers, base, height, color, fade, roughness}) | Layered ridgelines with atmospheric fade toward fade. |
| hills({...}) | Softer, rounder mountains. |
| water({from, color, deep, reflect}) | Water from y downward. reflect (0-1) mirrors everything above the waterline - captured after the whole scene is painted, so trees and birds added later still reflect. reflect: [0.35, 'inplace'] restores paint-order capture (only what was painted before the water reflects). |
| ground(y, {color}) | Solid ground plane from y down. |
| tree(x, y, size, {color, type}) | One tree at its base point; type 'pine' or 'round'. |
| trees(n, {from, to, base, size, color, type}) | n trees scattered between x = from...to. |
| grass(y, {color, height, density}) | Swaying grass blades along a baseline. |
| birds(n, {x, y, spread, size, color}) | A flock of distant birds around (x, y). |
| fog(opacity, {y, height, color}) | Soft horizontal mist band centred on y. |
| circle(x, y, r, {color, opacity, stroke, strokeWidth}) | Filled circle. |
| ring(x, y, r, {color, size}) | Outlined circle. |
| rect(x, y, w, h, {color, opacity, round}) | Rectangle, optional corner radius. |
| ellipse(x, y, rx, ry, {color, rotate}) | Ellipse, rotation in radians. |
| line(x1, y1, x2, y2, {color, size}) | Straight stroke. |
| polygon(points, {color, smooth, seal}) | Closed filled polygon from [[x,y], ...]. smooth: true draws it as a flowing curve through the points instead of straight edges; seal: true outlines it in its own colour to close hairline gaps between traced shapes; shade: ['#tone', angle°] adds the same lighting gradient as form(). |
| form({x, y, w, h, color, shade, pts}) | A traced painting mass with readable placement: x, y is its centre, w, h its size - edit those numbers (or click one for a slider) to move and stretch it. color is its dark tone; shade: ['#tone', angle°] grades it toward a lit tone in that direction, sampled from the imported photo. pts holds the packed outline (leave it be; delete it and the form becomes a plain rectangle). What the image tracer writes; positional form(x, y, w, h, {...}) also works. Select a form and press P to unpack its outline into an editable polygon() point list. |
| strokes(blob, {size, media, opacity}) | A packed bundle of painterly detail strokes - the trace service's coarse-to-fine paint passes. media renders the same strokes in any of the sixteen media listed below. Traced paintings declare const MEDIUM at the top - change that one word and the whole painting re-renders in a different medium. |
| triangle(x1,y1, x2,y2, x3,y3, {color}) | Filled triangle. |
| star(x, y, spikes, outer, inner, {color}) | Star polygon. |
| ngon(x, y, sides, r, {color, rotate}) | Regular polygon - diamond (4), pentagon (5), hexagon (6)... |
| arrow(x1, y1, x2, y2, {color, size, head}) | Arrow from tail to tip. |
| heart(x, y, size, {color}) | Heart centred at x, y. |
| crescent(x, y, r, {color}) | Crescent moon opening left. |
| stroke(points, {brush, color, size, opacity}) | A painted stroke - what the Paint tool writes. Points are [x, y] or [x, y, pressure] (0-1, from your pen or synthesized from mouse speed). brush picks from the library below; without it you get a clean plain line. Legacy nib: 'chisel' still works. |
| spray(points, {color, size, density, opacity}) | Airbrush dots along a path (also available as the Airbrush brush). |
| text(x, y, str, {size, color, font, align}) | Painted lettering. |
Every value below works as media in strokes() and as the
const MEDIUM word at the top of a traced painting. The strokes stay the
same; the physics of the paint changes.
| 'oil' | Blended paint with a body: colour lands exactly as sampled, edges feather into their neighbours, and large lit strokes catch one studio light. The traced default. |
| 'impasto' | Oil laid on thick - heavier body, brighter catch of light, a deposit of paint where each stroke lifts off. Relief stays quiet in shadow. |
| 'acrylic' | Crisp, clean body with a thin satin sheen in the lit passages. |
| 'gouache' | Opaque and matte - a flat velvet body with softly feathered edges, no shine at all. |
| 'watercolor' | Transparent glazes that fuse into washes; the lightest passages are reserved as untouched paper, and dried edges appear only where light shows them. |
| 'pastel' | Soft dust - a broad tender body with grain drifting off both edges. |
| 'chalk' | Dry broken pressure - each segment lands with its own weight and wobble. |
| 'charcoal' | Burnt willow: colour surrenders to warm grey, and the side of the stick smudges alongside the line. |
| 'graphite' | The pencil sketch: graphite on paper - tone is hatched, never filled. Lights stay paper, darks gain a cross-hatch, and the sheet dries to warm grey. What the Sketch trace tier speaks natively. |
| 'pencil' | Coloured pencil over paper - two fine jittered passes that never quite align. What the Balanced trace tier speaks natively. |
| 'pointillism' | Seurat's arithmetic: every stroke dissolves into touched dots of slightly wandering hue. |
| 'marker' | Alcohol marker - flat translucent bands that darken where they overlap. |
| 'spray' | Airbrush - a drifting cloud of fine droplets around each path. |
| 'ink' | Sumi wash on reserved paper: colour becomes ink density, the lights stay paper, only the true darks carry the drawing. |
| 'neon' | The painting becomes a night scene: the underpainting dims and a measured share of strokes become additive glowing tubes. |
| 'flat' | Plain flat strokes - the raw traced data, no medium physics. |
All brushes are pressure- and direction-aware stamp engines - press harder (or move slower with a mouse) for wider, denser marks. Keys 1-9, 0 switch brushes.
| round / ink | Clean hard strokes; ink tapers strongly at both ends like a nib pen. |
| pencil / charcoal | Grainy, textured marks built from speckle stamps - charcoal is broader and rougher with side-scatter. |
| calligraphy | Fixed 45° flat nib - width depends on stroke direction. |
| marker | Flat translucent nib that follows the stroke direction and multiplies with what's beneath - overlapping passes darken like a real marker. |
| watercolour | Low-flow soft washes with a darker wet edge and slight granulation - build colour up in passes. |
| oil | Directional bristle streaks that pick up and mix with the paint already on the canvas as the stroke travels. |
| airbrush / glow | Soft gradient spray; glow is additive - it brightens, for neon and light effects. |
| smudge | The blending finger: applies no colour of its own - it picks up whatever paint is under the path and drags it along. Tune its pull strength in the settings. |
| eraser | Paints paper colour over anything. |
The gear settings panel tunes any brush - spacing, flow, pressure response, taper,
scatter, bristles, blend mode - with a live sample stroke. Tuned values are written into
each stroke's code as tune: { ... }, so a customised stroke re-renders
identically anywhere, and re-tuning a brush later never changes strokes already painted.
| Tabs | Each painting is a tab, mirrored above the code and the canvas, with its own undo history. Tabs are remembered by this browser; signed in, they are your account's workspace and follow you to any device. |
| Account | Sign in with GitHub, Google, or a key account (no email - you hold a secret key). Signing in loads your studio from the cloud; work painted before signing in is carried into your account. Signing out saves everything up and leaves this device's studio completely clean - nothing remains for the next person. |
| Cloud saves and versions | Save this painting (menu, Account) stores a new version every time, with an optional label. My gallery lists your paintings; History reopens any earlier version as a new tab. Free: 25 paintings, 20 versions each. Hobby: 90 and 45. Artist: 200 and 100. |
| Import and trace | Any PNG, JPG or SVG becomes editable code at four detail levels: Sketch is a graphite pencil drawing, Balanced a coloured pencil sketch, Fine a full painting, Ultra the finest (up to 900 masses and 96,000 strokes). Both painterly tiers are metered daily and generously: Ultra 10 free / 30 Hobby / 70 Artist, Fine 50 free / 150 Hobby / 400 Artist. Guests get a smaller daily taste - signing in is free and raises it. |
| Global library | Shared components - official and community. Insert appends the snippet to your painting as a copy. Contribute one layer of your own work; you can remove your contributions later. |
| Share links | Small paintings travel entirely inside the link. Large ones are stored server side behind a short link. Either way, a shared painting opens held and unrendered - the recipient glances at the code and presses Ctrl+Enter to run it. |
| Replay | Watch any painting paint itself, stroke by stroke. Download replay video (menu, Pro) records it as WebM at your chosen speed, in any of the sixteen media. |
| Export | One door for every format (the Export button, or menu, This painting, Export as...): PNG or JPG images at 2x resolution (no watermark, ever), raw Paintlang code, SVG vectors, a self-contained HTML5 canvas page with the full ctx command stream, a p5.js sketch, a WebGL page with an editable GLSL shader over your painting, or a Lottie animation of it painting itself. For video use Download replay video (WebM). |
| Profiles and follows | Every author name in the community is a profile: their published paintings, joined date and followers. Follow artists to get a Following tab on the community wall. Profiles show only what is already public - private galleries and workspaces never appear. |
| background(color | {from, to, angle}) | Solid or angled gradient ground layer. |
| grain(amount) | Paper grain over everything painted so far. |
| vignette(amount) | Darkened edges, like an old photograph. |
| hsl(h, s, l, a?) | Colour from hue/saturation/lightness. |
| mix(c1, c2, t) | Blend two colours, t = 0...1. |
| seed(n) | Fixes the randomness so the painting re-renders identically. |
| rand(a?, b?) | Seeded random: rand() → 0-1, rand(a,b) → a-b. |
| noise(x) | Smooth 1-D noise, 0-1 - for organic variation. |
| layer(name, {x, y, scale, hidden}) | Start a named layer: everything painted after it belongs to that layer until the next layer(). The Layers pane beside the canvas shows the active tab's layers - show/hide, reorder, rename, rescale and delete all rewrite this call. Traced imports create an underpainting layer plus one layer per brush pass. |
| repeat(n, i => { ... }) | Run a block n times - loops of shapes, patterns, orbits. |
| resize(i, factor) | Scale the i-th painted object around its centre. Written automatically when handle-resizing a loop-made object. |
| nudge(i, dx, dy) | Move the i-th painted object by an offset. You rarely write this: dragging with Select edits coordinates directly, and only falls back to nudge() when the object was born inside a loop or flock (one call, many objects), where there is no single literal to rewrite. |
| hide(i) | Hide the i-th painted object (Select + Delete writes this). |
Object indices follow paint order, top of the code downward - the
status bar shows an object's index when you select it. If you insert new calls above
existing nudge/hide lines, their indices shift with the paint order.