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, in the order given by
color_order— 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: shadows go solid black, midtones are carried by hatching that thickens as the tone darkens, highlights stay as paper. The hatching follows 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. Hatch spacing follows the brush width, so every mark is one the machine can actually paint. 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. A picture on a colour's card replaces that plate. Tints below the cutoff stay paper, so the brush is not asked to paint a wash it cannot lay down.
- If a person, animal or prominent object is detected in the photo, 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.
- 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.
- 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
Six, 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. Neither neon sign moves 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.