CommunityImagegithub.com

Doodle

Draw any subject as measured pen-and-ink line art in code: outline first, then hatch, stipple and colour.

What is Doodle?

Doodle turns a subject, place or sequence into a hand-drawn line illustration rendered in plain HTML and JavaScript. Every drawing starts as a finished outline, then gains pen class, hatch, cross-hatch or stipple, with perspective or axonometric framing. It covers products, people, animals, plants, buildings, interiors, landscapes and small storyboards without a build step.

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/vishlbhardwaj/doodle/tree/HEAD

Installed? Explore more Image skills: steipete/songsee, affaan-m/frontend-design, mattpocock/improve-codebase-architecture · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

What does Doodle do?

A code-drawn illustration system. Subjects are authored as geometry, projected through a camera, and rendered by a seeded "hand" that makes a mark look drawn. Plain HTML + JS, no build, no dependencies: open a page in a browser.

Setup: where you work

The engine ships inside this skill at engine/ (next to this file). Never edit it there; an update of the skill overwrites it.

  1. If LOCAL.md exists next to this file, read it first: it names this machine's workspace and tools, and it overrides the default below.
  2. Otherwise the workspace is ./.doodle (hidden) in the current project. If it does not exist, create it: cp -R "<this skill's folder>/engine" ./.doodle.
  3. Every path below (src/…, bin/…) is relative to the workspace. Rendering needs Chrome or Chromium (CHROME=/path to override) and python3; nothing else.

One drawing = bin/new.sh <name>, which makes drawings/<name>.js (draw inside draw(S), full toolkit, auto-framed) and the page that renders it. Several things in one picture are one drawing; several separate pictures are several names.

The examples are code, not pages. The modules in src/ (things.js, people.js, trials.js, scapes.js …) hold worked subjects: read them and copy their construction into your drawing; never add to them or edit them. The routing table below names which module to start from.

Deliver IMAGES, not pages. When a drawing reads, run bin/render.sh <name>: it writes doodles/<name>/<name>.png (colour) and <name>-line.png (line only) in the project and opens that folder for the user. Tell them the folder path. Do not build, open or point the user at an HTML page, canvas or dial panel unless they ask for one.

This file is the whole contract, and it is short on purpose so it loads whole. The detail lives in references/ (moved there verbatim, nothing dropped). The ROUTING table below says which reference to read for the job. Reading it is part of the job, not optional: every rule in those files was paid for with a rejected drawing.

The procedure, every time

  1. Name the FORM and the CLASS (tables below). The form decides the page, framing and detail budget; the class decides the pen, nib, fill and tone.
  2. Route. Read the reference files the routing table names for your subject, and open the module it names as your starting point. Never start a subject from a blank file when a kit already exists for it.
  3. Measure before drawing. From a reference image: proportions and palette, probed, never recalled. Without one: write the subject's proportions down as numbers in the module header (the shoe needed eight numbers; three renders without them failed). references/hand-and-craft.md § Measurement.
  4. Build LINE FIRST. Judge ?outline=1 before colour. Every value is carried twice, as a fill and in line. Detail falls off from focus to mid-ground to paper. references/line-first.md.
  5. Render and LOOK, in line AND colour, at frame size. Contact sheet for composition, the large single frame for line quality. Both, every round.
  6. Fix in the KIT, not the subject, when the break is a helper's. Loop until every subject reads.
  7. bin/check.sh drawings/<name>.html (no argument = every drawing): it renders, no NaN, no console errors, no class drift.
  8. Deliver: bin/render.sh <name> — PNGs in doodles/<name>/, folder opened.
  9. Fold what you learned into this skill IN THE SAME TURN (§ Evolving).

Routing: what to read, where to start

SubjectStart fromRead
any doodle, first time in a sessionn/areferences/line-first.md, references/forms-and-classes.md
an everyday object (phone, keys, bottle, cloth, glasses …)src/things.js (nearest of 20; cam, S.prism, S.on, S.dash)things.md
a product, device, gadget, appliancesrc/retro.js (Retro._kit)products.md, geometry.md
a round object (cup, vase, fruit, bottle)src/stilllife.js stage (lathe, ball) or src/still.jsstill-life.md, geometry.md (path construction)
a still life / arrangement / from a photosrc/stilllife.jsstill-life.md
furnituresrc/furniture3.js, src/kitchen.jsproducts.md
an everyday object (laptop, scissors, glasses, toothbrush, hairdryer, t-shirt, soap, notebook, padlock, teddy, hanger, backpack, bottle…)src/things.js def(...)products.md, geometry.md
a PERSON, people, anyone in a scenesrc/people.js People.figure; never Trials._figure (dead)people.md
an animalsrc/trials.js (cat)figures-and-trials.md
glass, liquid, transparencysrc/trials.js (glass)figures-and-trials.md
a bicycle, a frame, anything of thin tubessrc/trials.js (bicycle), S.limbfigures-and-trials.md
a shoe, a garment, clothsrc/trials.js (sneaker, fruit cloth)figures-and-trials.md
plants, flowers, leavessrc/plants3.js, src/flowers.js; holed leaves: trials.js monsteraplants.md, figures-and-trials.md
a building, house, facadesrc/houses.js, src/arch.js, src/arch2.jsarchitecture.md, geometry.md
a landscape, outdoor viewsrc/scapes.js (the scene kit)landscapes.md
night, dark, weather, rainsrc/trials.js (night, rain) on the scene kitfigures-and-trials.md, landscapes.md
a street, a diorama, a cafésrc/cafe.js, src/scenes.jslandscapes.md, geometry.md
a room / interiorsrc/kitchen.js, src/deskscene.jsgeometry.md, landscapes.md (fidelity)
pen and ink, no coloursrc/pen.jsline-first.md (tone), forms-and-classes.md (ink)
a storyboard / sequencesrc/board.jsstoryboards.md
lettering, a signsrc/letter.jspages-and-files.md
a screen, UI, terminal (light text on dark)src/letter.js + tone-coloured linespages-and-files.md (letter.js entry)
make it move (Rive)the Rive exporter (not bundled; see the file)animation.md
watch it draw, underdrawing?draw=<key>, ?sketch=1hand-and-craft.md
clean / vector / not doodlyn/athe clean-illustration skill if installed
a SET, a stress testan existing set page as templatechecking.md
a doodle that must LINE UP with a render (sketch → final wipe)trace the silhouette, author the interiorregistration.md
which page / file does whatn/areferences/pages-and-files.md

All references/ paths are relative to this skill folder.

Forms

They saidFormExample to start fromDetail budget
"draw me a mug / a chair"subjectsrc/things.js, src/retro.jseverything, one thing at full fidelity
"make me N of them"setone bin/new.sh drawing per item, shared helpers in eachone toolkit, detail lives in the toolkit
"a house / a shopfront"elevationsrc/houses.js, src/arch.jsthree bays, 55% solid black fill
"a street / a view / a landscape"scenesrc/scapes.js, stated frame + clipthe three-rung depth ladder
"the whole block / a little world"dioramasrc/cafe.jsthe block's edges stay IN the picture
"a room"interiorsrc/kitchen.js, src/deskscene.jsforeground full, back wall characteristic
"storyboard this"boardsrc/board.jsset by SHOT SIZE, not the subject
"pen and ink / no colour"register inksrc/pen.jstone carried in line, no fill at all

A set is not a scene (things on one page share nothing but a sheet), and a board is not a set (equal fidelity loses the sequence). The tell: must the things share the SAME SPACE? Then it is a scene.

Classes (src/spec.js; full text in references/forms-and-classes.md)

classpennibfitfilltone
object0.30firm.84/.84flat, shaded by face normalshadow is a darker FILL, plus its line twin
ink0.26pen.90/.88NONE, the paper is the objectline only; a dot screen is forbidden
product0.30firm.84/.84one material = one colourcreases > ~35° only
elevation0.30firm.86/.8455% solid black paneshatch roofs, the shadow side
plant0.30firm.84/.88flat; pale shade .96, others .84scribble for mass foliage only
flower0.26firm.92/.90flatnone
scene0.30firm.96/.94flat; shadow a darker fillhatch only where nothing stands in front
diorama0.22firm.94/.92flatnone
panel0.26firm.94/.94flat, less than the subject aloneone value ground, one sky
furniture, interior0.16 / 0.15pen—LEGACY weights; new work takes object—

Declare the class, never retype the numbers: Spec.wrap(cls, label, build). bin/check.sh audits drift on every page. A class owns the hand; a page owns the paper. Add a class only when the fill or tone policy genuinely differs.

Hard rules (each one is a rejected drawing)

Line first and fidelity

  • Line only (?outline=1) must be a FINISHED drawing. Every page honours ?outline=1; check pages you copied (one hard-coded outline: false).
  • Every value is carried twice: dark material = hatch; a turn = ribs that bow with the surface; a cast shadow = hatch with no outline; a mark that NAMES the thing = an outlined mark, never a w: 0 fill. Audit every w: 0.
  • Focus gets full detail, the mid-ground contour plus 2–3 lines, the background is PAPER (its only tone the shadow the subject throws). Squint: if the wall is as busy as the subject, cut the wall.
  • Hatching is QUEUED as lines (S.hatch), never a k.T tone; later fills cut it. gap is the value: 2.4 light, 1.5 mid, 1.1 dark. Cross-hatch only the ONE darkest thing in the focus.
  • A shadow side is a CRESCENT bounded by curves that bow with the form, never a straight half-plane cut (that reads as an object split in two).

Geometry and path construction

  • A straight line you did not author is a path bug: a chord from concatenating two halves in the wrong direction, or an open shape stroked closed. Print the points.
  • A band round an opaque body is its NEAR half only; only an open mouth is a full ellipse.
  • Holes are CUT (keyhole), never painted in the background colour. Hole shows in line but not in colour = its winding.
  • Thin things (limbs, tubes, stems) are ONE strip through their joints; outline the sides, let the ends plug into what is drawn later.
  • Containers: inside → far rim → contents → front wall. Contents sink below the lip.
  • Measure proportions as numbers before drawing a known object. Stretching a wrong shape keeps it wrong (the shoe).
  • Size the furniture from the body that uses it, never the reverse.
  • A doodle that must register with an image: TRACE its silhouette from the image's alpha, author the interior, fix the frame to the image and crop the class fit off on export (registration.md).
  • People: People.figure only. Gesture before detail (weight on one leg, hips and shoulders tilt opposite); poses are ANGLES so proportion is locked; furniture and held things are derived from the returned joints. Back hair paints before the body.
  • A long straight dotted line across a soft shape = the crescent-wrap bug (fixed in S.soft); a wavy lathe = few knots + per-segment easing: use a dense profile with steps: 1.
  • { dash: true } does nothing: use S.dash. Frame objects to their content bbox, not by eye.
  • "Scrappy" = construction, not detail: judge ONE subject at full size; outlines must JOIN (no open ends at joints), tone is tight and regular or absent, pen weight fits the form's width.

Framing

  • The frame is DERIVED, never typed: on the picture-plane kits it is the FIRST region's bounds, so the first call is S.panel(<the frame>); with a camera, from the projection. Something not visible? Check it is inside the frame before redrawing it bigger. clip = true clips to the stated frame.
  • Every ground layer runs to the frame bottom (FOOT), or sky shows where a nearer layer dips. One ground rule reaches both edges of a sheet, not past them.

Scenes, night, weather

  • Depth is a number d (0 near, 1 far) and pen, hatch gap, colour and detail all read it. A mountain at the bench's weight is THE flat-landscape failure.
  • Night inverts value: the sky is the densest hatch, every light a keyholed gap; a glow is LIGHTER than the sky. A figure against light is a hatched silhouette.
  • Rain is drawn last, absent under an umbrella; a reflection is the subject flipped, squashed, in broken bands, no outline.

Pages and palettes

  • A page must declare every palette key its subjects use; an unknown key once rendered black silently.
  • Parallel headless Chrome runs need a separate --user-data-dir each, or shots come back blank.

Verify

cd <workspace>                                     # see Setup
bin/check.sh drawings/<name>.html                  # renders, no NaN, no console errors, no class drift
bin/shot.sh "drawings/<name>.html?outline=1" out.png [1120x700]   # a working look; drop ?outline=1 for colour
bin/render.sh <name>                               # the delivery: doodles/<name>/*.png, folder opened

Chrome/Chromium is found by bin/chrome.sh (override CHROME=/path); each shot gets its own --user-data-dir, so parallel runs are safe. Look at every PNG (open it, or read it if you can see images). A passing check.sh proves it rendered, not that it reads.

Evolving this skill

When you fix a gap, write it down where a FRESH session will find it (if this skill is installed read-only, keep these notes in the workspace and say so):

  1. The topic's reference file (references/<topic>.md): the rule, with its symptom, stated as an instruction. Create a reference file for a new topic and add it to the ROUTING table.
  2. This file's Hard rules, as one line, if it applies beyond one subject.
  3. New page, module or helper: the routing table here and references/pages-and-files.md.
  4. LEARNINGS.md: the dated history (symptom → cause → rule). It is a log, not the instructions: it is long, so grep it rather than read it whole. A rule that lives ONLY in LEARNINGS.md is lost to the next session; promote it. Older entries mention VDL (The author's private design canvas) and Rive projects that are not bundled; the rules in them still hold, skip those steps.

Keep this file short enough to load whole (target under ~300 lines). If it grows, move detail into a reference file and leave a one-line rule here.

Related Skills