This turns a picture into the movements a Brushograph makes: where to dip the brush, where to put it down, and the path to drag it along. What comes out is a G-code file you stream to the machine.
How a run works
- A machine config (
.conf) describes the machine. Every option below it is generated from that file. - You upload one thresholded image per tray, painted yellow, magenta, cyan, then black — or one colour photograph, which is split into CMYK and thresholded into those plates.
- Each image is turned into brush strokes directly. Every ink pixel is labelled with its distance to the nearest bare paper, and an outline inset by d is then simply the contour of “at least d from the edge”. A concentric fill is the same thing at half a stroke, one and a half, two and a half … so one distance transform yields the whole fill, and the rings are drawn in parallel because none depends on another.
- copicograf turns those strokes into brush choreography: dipping in the paint tray, shedding the drip it comes up with, washing in water, and re-inking every few hundred millimetres.
Notes
- A tray's image can be already thresholded or a photo. A photo becomes a woodcut first, and the woodcut is strokes: long ones, about a brush wide, running closer together where the picture is darker and petering out into paper where it is lightest. They follow the picture's own directions, curving around a form the way a carved line does, rather than lying over it as straight stripes or a lattice. Nothing is filled in solid — a brush covers a solid by going round and round inside it, which is a hundred short strokes where a shadow was meant to be. A photo is also turned a quarter turn when its long side would then lie along the canvas's, which on a wide bed paints a portrait picture half as large again; a picture that is already black and white is painted the way up it arrived. Preview it before running.
- A colour photograph in Artwork is converted to CMYK with the same paper profile the rest of the repo uses, then each channel is cut at an ink cutoff into a two-tone plate. Those plates are traced exactly as already-thresholded uploads would be. The four colour cards are put away while a photograph is loaded, so the plates come from it alone; remove the photograph to upload them by hand. Tints below the cutoff stay paper, so the brush is not asked to paint a wash it cannot lay down. The profile beds dark tones on cyan, magenta and yellow the way a press does, under the black that follows; those three are knocked out wherever black paints, which halves the painting of a photograph and changes nothing you can see. A switch beside the cutoff paints the bed anyway, for trays out of register.
- If a person, animal or prominent object is detected in a tray's photo or in the colour photograph, an Isolate option appears. It cuts the subject out with a small segmentation network (U²-Net, 4.4 MB, downloaded once on first start and run through OpenCV), leaving the background as bare paper. Without the model it falls back to GrabCut, which is worse but keeps the feature working.
- The opening sequence — mix, wash, load the brush — is not part of a job. A job starts painting with whatever is already on the brush.
- Dip Depth sets how far the brush goes into a cup. It was fixed at Z −4, which is deeper than a shallow dish needs. It is always offered, even by a config that does not mention it, as are the backlash settings, which are ticked unless the config says otherwise.
- Cup Dip Lanes is how many places across a rectangular cup the pickups are spread over. The brush goes straight down at one of them and swipes up the stairs from there, and the next pickup takes another, so the paint is mixed by where the brush lands rather than by being stirred once it is down there — a brush dragged sideways through settled colour splays, and wipes off against the paint what the pass before it picked up. The lanes sit over the middle 70% of the cup and are visited out of order, so one pickup and the next work opposite ends of it. 1 puts every pickup down the middle. They are kept inside the machine's travel, which matters on a holder whose widest cup hangs over an endstop.
- The preview shows the path without backlash compensation. Those corrective moves are for the machine's slack, and drawing them would bury the artwork in strokes that are not in it.
- Simplify redraws a colour photograph before the separation the way a cartoon is drawn: eight flat colours, dark lines for the eyes, brows, nose and mouth, and fine lines where the light and shade meet, all measured against the largest face, or the frame when there is none. A cheek that drifts across the ink cutoff is painted as a coastline of islands, each one a brush-down; a flat colour is one field with a clean edge. A plain face paints in a fraction of the brush-downs; a head of curls, drawn line by line, costs about what the photograph did. Whether skin comes out as paper or ink is still the cutoff's decision.
- Caricature, offered when the colour photograph has a face in it, does what the portraitists in Montmartre do: each face's most unusual trait, measured off 68 landmarks against what is typical, is made grotesquely more so — a longer nose, heavier brows, eyes further apart. It is done before Simplify when both are ticked, so the lines are drawn on the face as it is to be painted.
- Insta Face Filter, offered when a face is found, evens the light across the face and smooths the skin before the cut. A woodcut has two tones, so a face lit from one side lands mostly on the black side and fills in as a single solid shape. The lighting is estimated over the skin alone — at about an eighth of the width of the face, which is the setting that decides how much shadow comes off — and divided back out, which keeps dark features dark in proportion; eyes, brows, nostrils and lips are put back at full strength afterwards. Faces are found with a small neural detector, so a head turned or tilted down is still recognised, with OpenCV's bundled cascades as the fallback. Only skin is touched — hair, glasses, a collar and the background keep the tone they had.
- A shape too thin to hold an outline gets its centreline painted instead, so every ribbon, hairline and rib is drawn rather than dropped.
- An infill line distance of 0 paints outlines only. A nominal 1 mm stroke width stands in for the brush, since the config then states none.
- Files are named after the picture and the trays used —
vali_letten_c1_infill.gcode— numbering the trays in the order the config declares them, with the water tray as 0. - The ink/paper split is found per image rather than fixed, so a print on cream paper is not read as one solid block of ink. It is taken on the darkest colour channel, so a light ink such as yellow still counts as ink.
- Set
infill_line_distanceto roughly the width your brush lays down. It is the gap between fill lines; a pen-plotter value such as 0.5 mm paints the same area ten times over. On one test image, going from 0.5 mm to 5 mm cut brush lifts from 598 to 103, tray dips from 318 to 74, and painted distance from 23.8 m to 4.5 m. - Strokes are chained back together, and ends within 1.5 line widths are
bridged into one serpentine, so the brush stays down for as long as possible. Reaching
further gains nothing: the brush must re-ink every
paint_per_runanyway. - copicograf detects brush-up and brush-down from two exact Z lines that only
cura-sliceremits. Strokes are written out in that form, socopicograf.pyitself is untouched. - Configs naming a generator other than
copicografrender their options here, but cannot be built — that generator is not part of this repository. M204,M203andM400come from the config'smovesblocks and are Marlin-only. They are kept for Controller Type: Marlin and stripped for GRBL and FluidNC, which halt on an unknown M-code. A config with no controller section is treated as GRBL.- Every file opens with
G90/G21, the normal feedrate and a lift to the config's safe Z, before anything moves sideways — where the last job left the brush is not known. - The brush is loaded before the first stroke of every colour. Each tray ends by washing
the brush and parking it in the water — which is what a colour change wants — and re-inking
otherwise only happens after
paint_per_runhas been painted, so each colour used to start by painting with water. The trip ends at the point that colour's painting starts from, so it leaves no mark of its own. - A colour change is: wash the brush three times in the water, lift clear, dip the next colour, shed the drip, and go to the first stroke. The brush is left standing in the water at the end of the job, so it does not dry with paint in it, but not between colours — it is already over the water and the next thing it does is go for paint.
copicografemitsG28 X Yonce near the top. Marlin homes on it; GRBL and FluidNC reject the bare axis words outright, so it is replaced with a comment. It is not rewritten to$H, which needs limit switches — home or zero the machine yourself before streaming.
Themes
Seven, in the footer, remembered by your browser: Default follows your system, Dark mode stays dark, Coconut mode is husk and palm, Pinkograph wears what the machine's own FluidNC dashboard wears, colours and all, with a sign that flickers like a neon tube, UwU is that sign with all the lights up, and 𝕭𝖗𝖚𝖘𝖈𝖍𝖔𝖑𝖔𝖌𝖎𝖘𝖈𝖍𝖊𝖗 𝕶𝖔𝖓𝖌𝖗𝖊𝖘𝖘 delivers a stark black-and-white print aesthetic set entirely in UniFraktur, and is the one theme that speaks German: every word of the interface is translated while it is on, and put back when it goes off. h4xx0r is a green phosphor terminal behind dirty glass, scanlines, static and a signal that keeps dropping out. Neither neon sign moves, and the terminal's glass holds still, if you have asked your system to reduce motion. The machine plan and the G-code preview follow the theme too. The tray colours never do: a coloured mark always means paint in a cup, whatever the interface is wearing. Black paint is the exception, and only because on a dark paper it would be the paper: each theme says what its darkest ink looks like, so black still reads as paint.