算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
illustration-ink-sketch【设计与创意】Draws hand-inked cartoon critters and living objects as SVG cards (480x360), each acting out a coding or design idea. Identified by thick black brush-pen outlines that swell and taper and break into separate strokes, loose marker-and-watercolour fills that slip past the line, diagonal hatching instead of solid shadow, huge googly eyes with two specul…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Sketchy, hand-inked cartoons: a googly-eyed critter or object (a snail, a pair of worms, a potted cactus) whose body carries the coding metaphor, drawn with a wobbly brush pen over loose marker colour. It suits playful product moments: loading, errors, greetings, conflicts.
Boundary: for clean, even ~2 px ink outlines on people with flat fills, use illustration-outlined-cartoon. For people drawn with a black brush marker and one mint second colour, use illustration-two-colour-brush. For soft, unoutlined cosy animals with grain, use illustration-grainy-gouache.
Extracted from a set of 42 original design-and-coding cards in 14 styles; the three cards in examples/ are this style's shipped cards.
Every edge is a filled black brush polygon that swells from about 1 to 4.4 px and tapers to points, laid in a few separate strokes over a colour fill that slips 2 to 3.6 px past it, with dark areas hatched rather than filled.
Measured from the examples. Each colour family is base / dark / light; the dark goes into mottle and marker streaks, the light into one lit streak.
| Role | Hex | Where it goes |
|---|---|---|
| Card background | #ffffff (none drawn) | Don't draw a background rect. |
| Ink | #1d1a18 | Every outline, hatch line, hair, fleck, pupil, letter. Never pure black. |
| Red | #f0563d / #b8311f / #ff8a6a, deep #9a2414 | Shells, pots, one worm |
| Yellow | #ffc83d / #ef9a24 / #fff1b0 | Snail body, spark bursts, flower centres; #fff1b0 also bubble shadows |
| Green | #5cb946 / #2f8a38 / #a6dc62 | Grass patches, cactus |
| Blue | #66cdea / #2a8fbf, drop #bfe8f7 | Slime, mugs, progress fill, sweat drops |
| Teal | #2fb5a7 / #167a71 / #8fe3d6, deep #0f5c55 | A second character beside a red one |
| Wood | #eaa85c / #b8722f / #f8d6a0, deep #8a5320 | Desks, tables |
| Blush | #ff7f86 (op 0.5 to 0.75), ticks #d9434f or #c8303f | Cheek discs with three ticks; tongue |
| Eye shade | #c9d6ea | The crescent inside each eye white |
| Paper | #fffaf0, fold #d9d2c4 | Stickers, scraps, labels |
| Singles | mouth #3b1f1a, petal #ff8fa0, soil #5b3b2c, coffee #6b4330, soft shadow #7f97ab (op 0.22) | One-off details |
| White | #ffffff | Eye whites, gloss swipes, speech bubbles, highlights |
All ink is filled polygons from the brush (stroke() in the kit), never SVG strokes: the examples contain zero stroke attributes.
| Mark | Numbers at 480 wide |
|---|---|
| Outline | OUT(centre): w 4.6, max 4.4, min 1.0, press 0.4 (wavelength 26), nib 0.3, thicker on the down-right shadow side (k 0.3 to 0.36). Tapers t0/t1 3 to 30 px. 3 to 6 separate strokes per shape, one gap on the lit top-left, one overshoot of 2 to 14 px |
| Secondary line (rims, bubble tails, mouths) | w 2.4 to 3.2, nib 0.45, press 0.4, pf 8 |
| Detail (ribs, creases, wood grain) | w 1.1 to 1.8, ink at op 0.55 to 0.85 |
| Hatching | w 1.0 to 1.15, spacing 2 to 3.2, angle -48 to -58 deg (mirror to -130 on a right-lit shape), ends jittered inside the edge |
| Hairs | 2 to 5 per outline stroke, w 0.9 to 1.5, length 2.5 to 7, pointing outward on the shadow side |
| Flecks | 9 to 11 per card, size 0.8 to 1.7, in the empty margins |
| Fill | base colour, slipped 2 to 3.6 px off the outline (fillShape off), control points jittered 1.2 to 1.6 |
| Mottle | dark colour at op 0.36 to 0.5 through the mottle filter, over every fill |
| Marker streaks | dark colour, w 4 to 16, op 0.3 to 0.55, 1 to 3 along the shadow side, clipped to the shape |
| Gloss | white, w 2.4 to 6, op 0.85 to 0.95, one or two swipes on the lit upper-left of round forms |
Two filters per card, both prefixed: rough (fractal noise 0.45, displacement 0.7) wraps the whole card so every edge trembles; mottle (noise 0.035 x 0.06, 3 octaves, alpha 2.6/-1.25) masks the watercolour patches. svgOut writes both.
No people. The hero is a critter or a living object (snail, worms, cactus; equally a mug, a bug, a robot vacuum) whose body IS the metaphor: the shell is a loading spinner, the worms' thin tails are git branches with commit dots, the pot wears a </> sticker.
tube()), or a closed blob through 12 to 24 control points. Heads swell at the end of a tube (body 40, head 49). The subject spans about 60 to 70% of the card width.#c9d6ea shade crescent at the lower right, a rim that swells and overshoots its start, a pupil of radius 0.5 x the eye offset 4 to 10 px toward what the critter looks at, one big and one small white specular. Lids in the body colour (h 0.34 to 0.38, tilted 17 deg) make them grumpy.hand() in the kit). Sized like the cactus: fingers 12 to 14.5 long at scale 1.25. Put the thumb where the handedness table in references/craft.md says.shadeTube + limbOutline({ under: bodyLoop }), ending in a petal-pink oval 5.4 x 3.2 with a short ink underline. Draw the far pair before the body, the near pair after; angle the back leg behind the hip and the front leg ahead of the shoulder so a run reads.grassPatch, tuft); or a wooden desk top seen slightly from above with grain lines and one hatched leg.</> code sticker, a speech bubble with hand-lettered text, a torn paper scrap with code chevrons, a mug with steam curls, a progress bar (white track, hatched blue fill, hand-lettered %), commit dots, a spark burst star, a flower.const G = w => [B0[0] + (w[0] - B0[0]) / S, B0[1] + (w[1] - B0[1]) / S].<g transform="translate(cx gy) scale(1.12) translate(-cx -gy)"> about its ground point, with setWScale(1/1.12) so line widths stay true, then setWScale(1) after.svgOut(file, {shift: [0, -8]}) nudges the whole card to balance the margins.scripts/ink-kit.mjs is the seeded brush library the three examples were generated with: run with it in place of the original, the Merge Conflict and Hello World generators reproduce every mark of the shipped cards exactly (the only diff is a stray text node the old file writer left in <defs>). Every mark takes a string key with its own random stream, so editing or adding one mark never reshuffles the rest; change a key to re-roll a single mark.
// card.mjs, next to a copy of ink-kit.mjs
import * as K from './ink-kit.mjs';
const { PAL, OUT } = K;
K.setPrefix('xx-'); // every id: filters and clip paths
K.grassPatch('grass', { x0: 60, x1: 420, top: 276, bot: 320 });
K.hatch('cast', K.arc(240, 286, 70, 6, 0, 360, 10), { ang: -58, sp: 3, w: 1.0, inset: 1.2, over: 0.4, op: 0.9 });
const ctrl = []; // a round body: control points
for (let a = 0; a < 360; a += 30) ctrl.push([240 + 70 * Math.cos(a * K.deg), 220 + 62 * Math.sin(a * K.deg)]);
const loop = K.fillShape('body', ctrl, PAL.red.c, { off: [-3, 2], j: 1.4 }); // slipped fill
K.mottle(loop, PAL.red.d, 0.46);
K.clipOpen(loop); // shading stays inside
K.stroke('streak', K.arc(240, 220, 60, 54, -10, 120, 6), { w: 13, t0: 20, t1: 25, wob: 1.5 }, PAL.red.d, 0.45);
K.stroke('gloss', K.arc(240, 220, 52, 48, 198, 236, 4), { w: 6, t0: 8, t1: 12 }, '#ffffff', 0.95);
K.clipClose();
const dl = K.resample(K.catmull(ctrl, true, 10), 1); // outline in two strokes: gap top-left, overshoot
const o1 = K.stroke('o1', K.loopSlice(dl, 0.62, 1.18), { ...OUT([240, 220]), dense: true, t0: 12, t1: 10 });
K.stroke('o2', K.ext(K.loopSlice(dl, 0.2, 0.58), 3), { ...OUT([240, 220]), dense: true, t0: 8, t1: 6 });
K.hairs('hairs', o1.C, 4, { c: [240, 220] });
K.eye('eyeL', [216, 172], 21, [223, 176], 10.5);
K.eye('eyeR', [262, 168], 23, [269, 173], 11.5);
K.blush('blL', 206, 222); K.blush('blR', 280, 218);
[[96, 120, 1.5], [400, 96, 1.2], [420, 230, 1.1]].forEach((p, i) => K.fleck('fk' + i, ...p));
K.svgOut(new URL('./card.svg', import.meta.url).pathname, { label: 'Title, Ink sketch style' }); // next to card.mjs
Parts in the kit: stroke (the brush), OUT(c) (outline preset), hatch, fillShape, mottle, clipOpen/clipClose, hairs, fleck, dry, eye (with lid), tube plus across/acrossLine, shadeTube (fill, mottle, streak, gloss and rib for a limb or body), limbOutline (inks only the parts of a limb outside the body), hand, grassPatch, blade/tuft, blush, sweatDrop, codeSticker, paperScrap (torn note; returns T(x, y) for what you write on it), fillRing (a wheel or donut face-on: even-odd fill plus mottle), arc, loopSlice, ext, runsOutside, setWScale, svgOut.
A symbol drawn as one brush stroke, e.g. the infinity sign (a lemniscate, 26 px wide):
const inf = []; for (let t = 0; t <= 2 * Math.PI + 0.35; t += 0.07) { const s = Math.sin(t), c = Math.cos(t), d = 1 + s * s; inf.push([x + 13 * c / d, y + 13 * s * c / d]); }
K.stroke('inf', inf, { w: 3, t0: 3, t1: 5, nib: 0.45, press: 0.3, pf: 8 });
node scripts/ink-kit.mjs --demo $W/demo.svg writes a demo card and prints wrote <path> 203.5KB 316 elements: a red googly-eyed blob waving a 3-finger hand on a grass patch, with hatching, a code sticker, a sweat drop and flecks. It lints clean with --prefix ikd-.
stroke attributes read as vector. Every line is a brush polygon with taper and pressure.keep.setWScale(1/s) makes every line 12% too thick.setPrefix with a unique prefix per card; lint with --prefix.slow-build.svg (Slow Build): a sweating snail on a progress bar at 27%, its shell a spiral loading spinner with a hatched comet sweep; shows eyestalks, a code sticker, the grass patch, hand-lettered %, the soft contact shadow.merge-conflict.svg (Merge Conflict): a red and a teal worm butt heads where their branch lines meet; shows tube bodies with segment rings, grumpy lids, gritted teeth and a pout, commit dots, a spark burst, an anger mark, a flung <<<< scrap.hello-world.svg (Hello World): a potted cactus on a desk waves and says "hello, world" beside a coffee mug; shows a 3-finger waving hand with correct thumb, an arm resting over the pot rim, ribs with spine ticks, wood grain, steam curls, hand lettering in a speech bubble.Run from this skill folder. render.mjs needs playwright-core (or playwright) installed here, in the current folder or globally, plus a Chromium.
W=/tmp/ink-card; mkdir -p $W; cp scripts/ink-kit.mjs $W/, then write $W/card.mjs from the Techniques skeleton with a unique prefix.node $W/card.mjs && node scripts/render.mjs $W/card.svg $W/card.png (card.mjs writes card.svg next to itself).node scripts/render.mjs --sheet $W/sheet.png examples/*.svg $W/card.svg. Ask "same hand, same set?"node scripts/render.mjs $W/card.svg $W/eyes.png --zoom x,y,w,h --scale 8.references/craft.md and the Verify list; fix by editing the generator (never the SVG); repeat. Expect three or four rounds.node scripts/lint.mjs $W/card.svg --prefix xx- --palette style.json. Fix every ERROR; each WARN colour must be a deliberate one-off.stroke= attribute anywhere; every line is a filled brush polygon in #1d1a18.rough filter; ids all carry the prefix.