Skip to main content

Hand-drawn weighted Voronoi stippling pipeline (Python 3.9.10 / Rhino CPython compatible)

Project description

Rhino Grasshopper-Compatible Stippling Processor

for parameters used here, see Example Parameters


This is a replication of the following article:

Weighted Voronoi Stippling, Adrian Secord. In: Proceedings of the 2nd International Symposium on Non-photorealistic Animation and Rendering. NPAR ’02. ACM, 2002, pp. 37– 43.

where the author introduced a techniques for generating stipple drawings from grayscale images using weighted centroidal Voronoi diagrams as in the traditional artistic technique of stippling that places small dots of ink onto paper such that their density give the impression of tone.

Authors and credits

  • Original replication — Nicolas P. Rougier (BSD license, 2017), replicating Adrian Secord, Weighted Voronoi Stippling, NPAR 2002.
  • Python 3.9.10 port, revised hand-drawn stippling pipeline, Lu et al. scientific-illustration extensions, and Grasshopper / Rhino integration — Max Benjamin Eschenbach.

Pre-requisites

The original replication was written and tested on OSX 10.12 (Sierra) using Python 3.6, Numpy 1.12, Scipy 0.18, Matplotlib 2.0 and tqdm 4.10.

This revision ports and extends that code for Python 3.9.10, matching the Rhino 8 / Grasshopper CPython runtime. The port replaces the removed scipy.misc.imread with a small Pillow-based loader; the compute core remains plain numpy / scipy.spatial. It has been verified with:

  • Python 3.9.10
  • Numpy 2.0
  • Scipy 1.13
  • Pillow 11.3
  • Matplotlib 3.9
  • tqdm 4.x

Create the environment with conda:

conda create -n stippler -c conda-forge python=3.9.10 numpy scipy matplotlib pillow tqdm

Original data is in the data directory and you can also obtain it from Adrian Secord homepage.

Installation

Install from PyPI:

pip install stippler

For local development from a clone:

pip install -e .

This installs the dependencies (numpy, scipy, Pillow, tqdm, matplotlib), the importable package stippler, and a stippler console command.

Usage (classic stippler)

The original replication CLI is preserved as the stippler.classic module (run it with python -m stippler.classic ...):

 usage: python -m stippler.classic
                    [--n_iter n] [--n_point n] [--save] [--force]
                    [--pointsize min,max] [--figsize w,h]
                    [--display] [--interactive] file

 Weighted Vororonoi Stippler

 positional arguments:
   file                  Density image filename

 optional arguments:
   -h, --help            show this help message and exit
   --n_iter n            Maximum number of iterations
   --n_point n           Number of points
   --pointsize (min,max) (min,max)
                         Point mix/max size for final display
   --figsize w,h         Figure size
   --force               Force recomputation
   --save                Save computed points
   --display             Display final result
   --interactive         Display intermediate results (slower)

Hand-drawn pipeline (stippler command)

The stippler console command (module stippler.pipeline) wraps the relaxation in a grayscale-image → stipple-output pipeline and adds three controls that make the result read as hand drawn rather than machine generated. The compute core depends only on numpy, scipy and Pillow (Rhino-friendly); matplotlib is used only for preview rendering.

  1. Early-stopped relaxation — fewer Lloyd iterations means the points never settle into the regular hexagonal lattice, so spacing stays organically uneven. This is the single biggest dial.
    • --n_iter n — hard cap on iterations (lower = looser).
    • --epsilon d — optional: stop when mean point movement drops below d.

To control contrast (denser blacks / cleaner whites), use --gamma g. It applies a power curve d ** g to the density that drives point placement, relaxation and dot size, so g > 1 (e.g. 2.02.5) concentrates the same n_point dots into the dark areas and thins the light ones; g < 1 flattens the tonal range; g = 1 is the original linear mapping. (--threshold is a blunter, hard cutoff that forces lighter greys to pure white.) 2. Varying dot size — a min/max radius spread plus per-dot random jitter breaks the constant-radius "machine stipple" giveaway.

  • --pointsize min max — radius range (density drives the base size).
  • --size_jitter f — per-dot multiplicative radius noise, e.g. 0.15.
  1. Imperfect placement & edges — Gaussian positional jitter plus wobbly, non-circular dot outlines.
    • --position_jitter s — Gaussian noise std on final positions.
    • --edge_noise f — dot-edge wobble as a fraction of radius, e.g. 0.08.
    • --edge_segments n — vertices per dot outline.

Output format follows the --out extension (.png, .pdf, .svg).

stippler data/boots.jpg --n_point 12000 --n_iter 8 \
         --pointsize 0.8 3.0 --size_jitter 0.2 --position_jitter 0.5 \
         --edge_noise 0.09 --seed 3 --out data/boots-stipple.png

The pipeline can also be imported as a library: stippler.stipple(...) returns a StippleResult carrying points, radii and per-dot polygons (handy for feeding geometry into Rhino later), which render_matplotlib, render_svg and save_points consume.

Scientific-illustration pipeline (Lu et al.)

The hand-drawn pipeline is further extended with optional controls adapted from Lu et al., Non-Photorealistic Volume Rendering Using Stippling Techniques (resources/vis_stipple.pdf). Each feature is independently toggled via IllustrationParams (library) or the CLI illustration argument group (stippler --help):

  • Boundary — boost stipple density on high-gradient edges.
  • Silhouette density — concentrate dots on view-facing silhouette regions (uses an optional normal map).
  • Interior — sparse stipples in flat, low-gradient areas.
  • Lighting — modulate density from inferred or supplied normals.
  • Depth attenuation — thin stipples in far regions (requires a depth map).
  • Gradient size — scale dot radius by local gradient magnitude.
  • Silhouette curves — extract and draw feature-line strokes over the dots.

Normal and depth maps should be aligned with the beauty-pass image. In Rhino, the Grasshopper component can capture these automatically when the options that need them are enabled.

Grasshopper / Rhino integration

Max Benjamin Eschenbach integrated the revised pipeline into Grasshopper for Rhino 8 CPython 3.9.10:

  • grasshopper_userobjects/STIPPLER_StippledViewCapture.ghuser — drop-in user object that captures the active viewport and writes a _stippled output (PNG, SVG, or PDF), including hand-drawn controls and the Lu et al. options above.
  • grasshopper_userobjects_src/ — Python 3 script source and input/output reference. Re-export the user object from Grasshopper after editing the script (right-click → Save User Object…).

Install the stippler package into the Rhino CPython environment the component uses (see Installation above), then paste or sync the script into a GH Python 3 component.

Example Parameters

Parameters used for the Donut example

Parameter Value
Resolution 1920 x 1080 px
NumDots 100000
Iterations 12
Gamma 1.0
RMin 1.0
RMax 6.0
SizeJitter 0.15
PositionJitter 0.1
EdgeNoise 0.1
Seed 42
DPI 300
Boundary false
SilhouetteDensity false
Interior false
Lighting false
DepthAttenuation false
GradientSize false
SilhouetteCurves true
CurveWidth 0.5

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stippler-0.2.1.1.tar.gz (24.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stippler-0.2.1.1-py3-none-any.whl (23.6 kB view details)

Uploaded Python 3

File details

Details for the file stippler-0.2.1.1.tar.gz.

File metadata

  • Download URL: stippler-0.2.1.1.tar.gz
  • Upload date:
  • Size: 24.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stippler-0.2.1.1.tar.gz
Algorithm Hash digest
SHA256 41129c84a5c98b8a8f7db9bc955c3ef3d103e1beb36a06ca96b1be2231611c11
MD5 4a0bbac2a302aa504b6f62a1a06f9e98
BLAKE2b-256 332d2c0ddac46156dec4f377682b9b69170382d875d68a635f075952a9eb9597

See more details on using hashes here.

Provenance

The following attestation bundles were made for stippler-0.2.1.1.tar.gz:

Publisher: publish-pypi.yml on fstwn/stippler

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stippler-0.2.1.1-py3-none-any.whl.

File metadata

  • Download URL: stippler-0.2.1.1-py3-none-any.whl
  • Upload date:
  • Size: 23.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stippler-0.2.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d73a6223a51ca59e620eb74de32ded5de98cd8c20c0237c3d4970f1f366ac31c
MD5 17a2277fe51407cd4716af615dd47cf7
BLAKE2b-256 93e075e9e2f261eec43645917da4e21d4e44ce7a330715a766e15e623a315ccc

See more details on using hashes here.

Provenance

The following attestation bundles were made for stippler-0.2.1.1-py3-none-any.whl:

Publisher: publish-pypi.yml on fstwn/stippler

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page