Easel

A headless painting engine for AI agents: brushes, paint load, wet blending, canvas texture.

LLM Mart 0 views 4 listing impressions
Transport
Not stated
Package
—
Registry id
io.github.Gemberkoekje/easel

No install snippet on purpose. A working MCP config is a command, its arguments and an environment block — the last two are where API keys live, so this catalogue never stores them and cannot publish them. Follow the link above for the authors' own instructions.

Can an LLM paint with real brush strokes, rather than draw with pixels? Yes — this is the API for it.

Easel is a headless painting engine for AI agents: brushes, paint load, wet blending and canvas texture, driven from Python, from a shell, or over MCP. A brush carries a finite load of paint and runs out along a stroke. Paint lands wet and mixes with what is already there, in a pigment model where blue and yellow make green rather than grey. The canvas has tooth, and a brush low on paint catches only the high points — so dry brush is not a special effect, it is what happens when you run out of paint on rough canvas. Between strokes you look at your own work, which is the whole point: the engine is built for an agent that can see what it just did.

It is not a drawing library, not a rasteriser, and not an image generator — nothing here turns a prompt into a picture. You choose and make every mark. There are no layers, and no undo that costs nothing: you work in passes, and when something is wrong you paint over it.

Read the guide before you paint. The engine is only the brush; PAINTER.md is the method, and six measured runs say the method is the half that matters. It ships inside the package, so there is nothing to go and find: easel guide prints its first page — the whole workflow, about a thousand words — and easel guide --full prints the rest.

from easel import Session, blob, cell

s = Session(1024, 768, texture="linen", ground="toned_grey", seed=7)
s.palette["shadow"] = s.palette.mix("ultramarine", "burnt_umber", 0.4)

s.block_in(blob(cell("D5")), brush="bristle", color="shadow", density=0.8)
s.look(values=True)                       # check the value structure
s.stroke([(0.2, 0.6), (0.6, 0.55), (0.9, 0.62)], "bristle", "yellow_ochre")
s.export("painting.png")

Paintings, made this way

Inside a car wash seen from the driver's seat: a magenta foam arch overhead, a bloom
of white light down the tunnel, a red stop light, and a foam-covered side brush
swinging in from the right, past a steering wheel and rear-view mirror

Inside a car wash, from the driver's seat — 206 strokes of a 300 budget, 1152×720 linen, no reference photograph. The nineteen pass scripts beside it reproduce that PNG byte for byte.

A laundromat at night seen from the sidewalk opposite: a wide lit shopfront window set
in a dark building, a row of washing machines with round doors inside it, one person
sitting alone at the right-hand end, and the window's light broken into streaks down a
wet road in the foreground

A laundromat at night, from across the street — 286 strokes of a 300 budget, 1024×768 linen, no reference photograph. Fourteen pass scripts beside it reproduce that PNG byte for byte.

A grey heron seen from high above in a flooded parking lot at dawn, the water filling
nearly the whole frame: a near-black band of trees across the top with a warm yellow
reflection under it, the bird standing large and high in the picture with a white head
and a long ochre bill and its own pale reflection directly below it, and painted stall
lines running away across the water in the
foreground

From the project's README.