Skip to main content

plotline

Turn images into drawings for pen plotters.

CI License: MIT Python 3.11+ Status: early

Open it in your browser  ·  Command line  ·  Design brief

plotline converts raster images into vector line art, such as stippling, single continuous lines, dashes and sketched strokes. It comes with a live preview that shows the algorithm at work, so you can see what a setting does before you commit ink to paper.

A still life of three balls and a cube
The image
The still life drawn with the grid style
grid: dots on a regular grid
The still life drawn with the hatch style
hatch: crossing parallel lines
The still life drawn with the waves style
waves: rippling lines, one stroke each
The still life drawn with the spiral style
spiral: one rippling spiral
The still life drawn with the ink style
ink: dark areas as filled shapes, at threshold 0.3
The still life drawn with the scribble style
scribble: loops along one line
The still life drawn with the mosaic style
mosaic: patches of parallel lines
The still life drawn with the truchet style
truchet: nested arcs on tiles in random turns

plotline drew every picture above with a 0.3 mm pen, from images it renders in code.

Features

  • Twenty-one styles, from dots on a grid to one continuous spiral. The table below describes each one, and the style guide shows each on the same picture with its settings and pen tips.
  • Live preview. The browser GUI shows the drawing being built. It runs the engine inside the page as WebAssembly, so your image is never uploaded.
  • Suggestions. The engine measures a new image and suggests changes: line art, levels, uneven paper, invert, noise, page orientation, framing, which crops the image to its subject when that fills only part of it, and colour, for which it picks two to four ink pens that draw the image's colours best. You apply each one with a click. The tone stage's Levels can also be Auto, which sets black and white from the image.
  • Focus points. Click the preview, or have an assistant set tone.focus, to mark where the picture matters: every style keeps its detail near those points and smooths it further away, so a drawing's detail sits where you want it.
  • Plot lint. After every run the engine checks the drawing for trouble on paper: marks drawn over each other, specks, lines closer than the pen, a drawing too light for the image's darks, heavy pen lifting or travel, and marks off the page or the machine. Where a setting fixes one, it says which.
  • Budgets. Give a plot time, a path count, metres of pen-down line or a dot count, and plotline finds the value of the style's density setting, such as its line spacing, that meets it in up to eight runs.
  • Tone match. Every run scores how closely the drawing's tones follow the image, seen from a metre or two, as a tone error and an SSIM. Optimize tries the style's density setting from a quarter to four times its value and suggests the one that brings them closest, with its plot time. It skips values that would plot over twice as long (or an hour) or that the plot lint warns about, and stays within a budget if you give one; nothing changes until you apply it. The tone stage's Response can also be Auto, which measures how much paper the style covers at each grey on a small test wedge and corrects the image for it.
  • Short pen travel. Every run orders its paths to keep the pen's travel short, and the line styles lift the pen far less often than grid.
  • Plotter-ready output. An SVG in millimetres holds one layer per pen that draws, each labelled with the name of its pen. A run can also write the paths as JSON, and a PNG preview that draws each mark at its real pen width. For a pen-fitted 3D printer, the GUI, the command line and the MCP server write one G-code file per pen. For pen plotters that read HPGL, the same front ends write the whole drawing as one HPGL file, with a pen selected for each layer.
  • Several pens. Pen use shares a drawing between your pens: light to dark in list order, or each pen by its colour, so cyan, magenta, yellow and black pens can draw a colour image. By default the first pen draws alone.
  • Real pens. A bank of common fineliners, technical pens, gel pens, ballpoints and markers gives each pen its maker's line width, and a list of common inks gives it a colour.
  • Looks. Most styles offer a few starting points chosen by measuring test images: Balanced is the defaults, Faithful follows the image's tones most closely, Quick plots in about half an hour and Fine draws the densest marks that still plot cleanly. A look changes only the few settings that set it.
  • Presets. A small JSON file holds the style, page, pens, settings and machine of a run, so you can keep a setup and share it. An exported SVG or JSON drawing carries the preset of the run that made it, and loads as a preset too.
  • One engine. The engine is written in Rust and compiled twice: to a native extension for the Python package, and to WebAssembly for the browser. The Python package and the command line are thin layers over it.
Style What it draws
grid A dot on a regular grid wherever the image is dark enough.
hatch Straight parallel lines over the dark areas, in three levels that cross over darker tones.
waves Parallel lines that ripple more where the image is darker, one pen stroke per line.
spiral One spiral from the centre out that ripples the same way.
truchet Quarter-circle arcs on square tiles in random turns, joining into winding curves, with more nested arcs where the image is darker.
ink Line art, lettering and inked drawings. It outlines every ink shape and fills it with rings, and draws strokes thinner than the pen once down the middle, so the drawing keeps its own shapes and weights.
stipple Points that spread evenly while staying dense in dark areas (weighted Voronoi relaxation), each drawn as a dot sized by the ink under it.
line The same points joined into one unbroken line that does not cross itself.
dashes A short stroke at each point, along the image's edges.
tree The points joined by the shortest tree.
mesh A triangle mesh over the points.
cells The outlines of the points' Voronoi cells.
scribble Loops traced along the one line through the points, small and close where the image is darker, wide and loose where it is lighter.
sketch Straight strokes chained from dark to dark, like a quick pencil sketch.
halftone Dots on a regular grid, each sized by the ink, like a printed halftone.
dither Dots placed by error diffusion.
circles Circles packed without overlap, larger and closer where the image is darker.
contours Level lines of the image, like a topographic map.
flow Evenly spaced lines that follow the image's edges, closer together where it is darker.
hilbert One space-filling curve over the whole page that folds tighter where the image is darker.
mosaic The image cut into patches that follow its shapes, each filled with parallel lines at its own angle, closer together where it is darker.

stipple, line, dashes, tree, mesh, cells and scribble share one placement of points, so switching between them reuses the points. A style switch keeps the settings you changed wherever the new style has the same stage, Pen use included, and gives the rest the new style's defaults; Reset to defaults puts them all back.

In the browser

Open jonathanrjoyner.github.io/plotline. There is nothing to install.

  1. Drop or paste a PNG, JPEG or WebP image, or click Sample image.
  2. Pick a style and a look, change its settings and click Run to watch it draw. Try all styles, under the style menu, draws every style small on your image so you can choose by eye, and Undo in the toolbar (Ctrl+Z) steps back through changes to the style, settings, page and pens.
  3. Set up your pens in the Pens dialog, and choose how they share the drawing under Pen use. The page remembers your setup between visits.
  4. Click Export SVG in the toolbar, ready for a pen plotter's own software.

For a new image the Suggestions panel, at the top of the right column, lists what would suit it, each with an Apply button. Nothing changes until you click. Under the stats, the plot lint lists what it found in the last drawing, with an Apply button where a setting fixes it. Optimize, beside Fit to target, suggests the density that makes the drawing's tones follow the image best, with an Apply button too.

To check a pen and a page on paper, Test chart in the export panel replaces the drawing with a page of labelled patches: lines at growing spacings, dots of growing sizes, and a solid fill drawn over one to eight times. Plot it and see where lines merge, how big dots come out and how many passes the paper takes, and time the plot against the estimate. It needs no image.

For G-code, enter your machine in the Settings dialog: the drawable area, pen heights, feeds, and start and end G-code. The export panel then writes one G-code file per pen. The preset carries the machine, so save a preset to keep it. A short tour of the page opens on your first visit, and Tour in the toolbar opens it again.

Command line

There is no release yet, so work from a clone of this repository. Building the Python extension needs a Rust toolchain: install one with rustup. With uv installed as well, run this in the clone, where uv sync builds the extension and installs the package:

uv sync
uv run plotline --help

Then draw an image of your own:

uv run plotline run grid your-photo.jpg --out drawing.svg --preview preview.png

This writes drawing.svg and preview.png, and prints a one-line summary with the path count and an estimated plot time, then the tone error and SSIM. The plot lint's findings follow on stderr, each with the --set options that fix it where a setting does.

To Use
Set the page and margin, in mm --width 210 --height 297 --margin 15
Set the paper colour the preview shows, by ink name or #rrggbb colour --paper '#f5f0e1'
Choose the pen, by ink name or #rrggbb colour --pen 'Fine:0.3:blue'
Share the drawing between pens given with --pen more than once; by default only the first draws --set split.mode=light_to_dark or by_colour
Change a setting --set points.spacing_mm=3
Meet a budget of plot time, paths, pen-down metres or dots by changing the style's density setting --fit-minutes 60, --fit-paths 2000, --fit-metres 50 or --fit-dots 20000
Also find the density whose drawing follows the image's tones best, and print the --set that applies it --optimize, within a budget when given with a --fit-… option
Correct the tone map for the style's own response --set tone.response=auto
List a style's settings plotline describe grid
Start from one of the style's looks, which plotline describe lists --look faithful, quick or fine
Save the run's setup as a preset --save-preset my-setup.json
Start a later run from a preset, or from an exported SVG or JSON drawing --preset my-setup.json or --preset drawing.svg
See an image's statistics and suggested changes as JSON plotline analyse your-photo.jpg
Fail when the plot lint finds a warning or an error --strict
Draw for a machine: its drawable area becomes the page --machine my-printer.json, a machine or a preset that has one
Write one G-code file per pen for the machine --gcode plots/
Write the drawing as one HPGL file, with a pen selected for each layer --hpgl drawing.hpgl; --hpgl-units 40 sets the plotter units per mm, 40 by default and at most 1000
Write a test chart for a pen, to plot and check spacings, dot sizes, passes and the plot time plotline testchart --out chart.svg --pen 'Fine:0.3:blue', with --preview chart.png for a picture
Open the live preview locally plotline gui

A machine file holds the same object as a preset's machine: name, area (x, y, width and height in the machine's mm), z_down_mm, z_up_mm, z_safe_mm, draw_feed, travel_feed and z_feed in mm per minute, settle_ms, start_gcode and end_gcode. A pen lifted by a servo or a solenoid instead of Z takes "pen_lift": "gcode" with pen_down_gcode and pen_up_gcode, the lines written in place of each Z move. max_segment_mm cuts every move into pieces no longer than it, for firmware that would draw a long straight move as a curve. The easiest way to make one is to enter the machine in the GUI's Settings dialog and save a preset, which --machine also reads.

plotline gui serves the browser GUI from your machine. Building it needs Node, wasm-pack and the wasm32-unknown-unknown Rust target: run npm run wasm, npm ci and npm run build in web/.

With an AI assistant

plotline mcp serves plotline to an AI assistant over the Model Context Protocol, on stdio. From a clone, uv sync installs the MCP extra; once there is a release, pip install "plotline[mcp]" will. Give an MCP client the server as {"command": "plotline", "args": ["mcp"]}, where command is the plotline on the client's PATH or the full path to it (for a clone, the one in its .venv). The assistant can load an image, ask analyse_image what would suit it, run styles, starting from one of their looks if it likes, and read the tone match and the plot lint's findings in each result, fit one to a budget, ask optimize for the density that matches the image's tones best, draw a testchart for the pen, look at previews, also zoomed in on a part of the page, and export the result, as G-code too once set_machine has described the plotter.

Roadmap

plotline is early software. The goals, architecture, decisions and everything still planned are in the design brief.

License

MIT

Metadata

Release files for plotline 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for plotline 0.2.0
File Size Uploaded
plotline-0.2.0.tar.gz 1.3 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for plotline 0.2.0
File
plotline-0.2.0-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details
plotline-0.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64 Details
plotline-0.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.11 abi3 Linux glibc 2.17+ ARM64 Details
plotline-0.2.0-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl CPython 3.11 abi3 macOS 10.12+ x86-64, macOS 11.0+ ARM64, macOS 10.12+ universal2 (ARM64, x86-64) Details

Total release size: 13.0 MB

Release files / plotline-0.2.0.tar.gz

Download URL plotline-0.2.0.tar.gz
Size 1.3 MB
Tags Source
SHA-256 checksum
How to use checksums
123fcc9c44bbeb56dd970eca00396b81cdcacb99633af7b954d40c37247140f8
BLAKE2b-256 checksum
How to use checksums
c18d9674a82fcf1337683dd827cb49b38993199a3703b5f4d0f1513967b593ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / plotline-0.2.0-cp311-abi3-win_amd64.whl

Download URL plotline-0.2.0-cp311-abi3-win_amd64.whl
Size 2.6 MB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
d82dc7b49df66951ddd3a4fb1200734459d7a70c3a04d1f2dff4a648bb8e51d3
BLAKE2b-256 checksum
How to use checksums
a5d6885599e837d5e73a40938816fe0d862966895fad7375ebbdd625b0514e9c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / plotline-0.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL plotline-0.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 2.6 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
c61ac9d8948ce1a02bd4026cd4c1610fc4ef6e870e264288265bf5d91149c766
BLAKE2b-256 checksum
How to use checksums
23dac90e228b14b29a0b157eba74e07f91aa09a1f35d672fb0ecc21f811ba038
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / plotline-0.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL plotline-0.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 2.5 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
231af47061fbe846a8b6c5b10fd80a222d6ab8954ab5f73a6a0a872eeaae2e69
BLAKE2b-256 checksum
How to use checksums
aa258c677caf0906edc74925326e98b9164c7e664b0d6334a66a05764a003d24
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / plotline-0.2.0-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl

Download URL plotline-0.2.0-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Size 4.0 MB
Tags CPython 3.11 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
cc1a4e8653bb7d3c584f0fe84544df2d5a8862727ba23b8842c03f0ed85f9b95
BLAKE2b-256 checksum
How to use checksums
35242e22e787baae4ec0883193cef64f364cea7a3b699b72c3900c55d0804fd3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

5 release files

0.1.0

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page