Skip to content

eliheuer/img2bez

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

img2bez

Trace bitmap glyph images to bezier contours and insert them directly into UFO font sources.

Designed for agentic AI type design pipelines — takes scanned or rendered letterforms and produces cubic outlines ready for editing or compilation into fonts.

Built on the Linebender ecosystem: kurbo for bezier curve math and optimal curve fitting via fit_to_bezpath_opt, and norad for UFO reading/writing.

Installation

cargo install --git https://github.com/eliheuer/img2bez

Autoresearch visualizations (optional)

The autoresearch loop generates per-glyph bezier structure comparison images. This requires designbot:

cargo install --git https://github.com/eliheuer/designbot designbot-cli

Once installed, autoresearch/run_experiment.sh will automatically render viz_uni<XXXX>.png files for the weakest glyphs after each experiment run. If designbot is not on your PATH the viz step is silently skipped.

Or to use as a library, add to your Cargo.toml:

[dependencies]
img2bez = { git = "https://github.com/eliheuer/img2bez" }

Quick start

# Basic usage: trace a glyph image and insert it into a UFO source
img2bez --input glyph.png \
  --output MyFont.ufo \    # target UFO (must already exist)
  --name A \               # glyph name in the UFO
  --unicode 0041           # Unicode codepoint in hex

# With font metrics: scale and position the outline to match your UPM
img2bez --input glyph.png \
  --output MyFont.ufo \
  --name A \
  --unicode 0041 \
  --target-height 1024 \    # ascender - descender in font units
  --y-offset -256 \         # shift down by the descender value
  --grid 2                  # snap coordinates to a 2-unit grid

# Compare the traced output against a reference glyph
img2bez --input glyph.png \
  --output MyFont.ufo \
  --name A \
  --unicode 0041 \
  --reference MyFont.ufo/glyphs/A_.glif

CLI options

Flag Default Description
-i, --input required Input image (PNG, JPEG, BMP)
-o, --output required Output UFO path
-n, --name required Glyph name
-u, --unicode Unicode codepoint (hex, e.g. 0041)
-w, --width auto Advance width (auto-computed from bbox if omitted)
--target-height 1000 Target height in font units (ascender - descender)
--y-offset 0 Y offset after scaling (typically the descender)
--grid 0 Grid size for coordinate snapping (0 = off)
--accuracy 4.0 Curve fitting accuracy in font units (smaller = tighter)
--alphamax 1.0 Corner detection (0.6-0.8 for geometric type, 1.0 for organic)
--smooth 3 Polygon smoothing iterations (0 = off)
--chamfer 0 Chamfer size (0 = off)
--threshold Otsu Fixed brightness threshold (0-255), overrides Otsu
--invert false Invert image before tracing
--reference Reference .glif for quality evaluation

Library usage

use img2bez::{trace, TracingConfig};
use std::path::Path;

let config = TracingConfig {
    target_height: 1088.0,
    y_offset: -256.0,
    grid: 2,
    ..TracingConfig::default()
};

let result = trace(Path::new("glyph.png"), &config)?;
// result.paths: Vec<kurbo::BezPath>
// result.advance_width: f64
// result.contour_types: Vec<ContourType>

Pipeline overview

 Input image
      |
 1. Threshold (Otsu or fixed) ─── bitmap.rs
      |
 2. Pixel-edge contour extraction (dual grid) ─── vectorize/decompose.rs
      |
 3. Optimal polygon approximation (DP) ─── vectorize/polygon.rs
      |
 4. Sub-pixel vertex refinement ─── vectorize/polygon.rs
      |
 5. Corner detection + curve fitting ─── vectorize/curve.rs
      |
 6. Post-processing ─── cleanup/
      ├── Contour direction (CCW outer, CW counter)
      ├── Grid snapping
      ├── H/V handle correction
      └── Optional chamfer insertion
      |
 7. Reposition + advance width ─── metrics.rs
      |
 8. UFO output ─── ufo.rs

Features

  • Potrace-style pipeline: decompose, polygon, curve — adapted for font outlines
  • Extrema-aware splitting: curves are split at bounding-box extrema so handles naturally align H/V
  • Grid snapping: on-curve points snap to an integer grid while off-curve handles preserve curve accuracy
  • Rayon parallelism: contours are fitted in parallel
  • Raster IoU evaluation: compare traced output against a reference with pixel-level accuracy

Cargo features

Feature Default Description
ufo yes UFO output via norad
cli yes Command-line binary (implies ufo)

Debug environment variables

Prefix your command with VAR=1 to enable debug output for a single run:

IMG2BEZ_DEBUG_SPLITS=1 img2bez --input glyph.png --output MyFont.ufo --name A
Variable Effect
IMG2BEZ_DEBUG_BITMAP Save thresholded bitmap as debug_threshold.png
IMG2BEZ_DEBUG_PIXELS Print raw pixel contour stats
IMG2BEZ_DEBUG_RAW_CONTOUR Skip polygon optimization, use raw pixel points
IMG2BEZ_DEBUG_NO_ADJUST Skip sub-pixel vertex refinement
IMG2BEZ_DEBUG_POLYGON Output polygon as straight lines (no curve fitting)
IMG2BEZ_DEBUG_SPLITS Print split-point analysis per contour
IMG2BEZ_DEBUG_FIT Print per-section curve fitting details
IMG2BEZ_DEBUG_NO_CLEANUP Skip all post-processing
IMG2BEZ_DEBUG_PIXELDIFF Save 1:1 pixel diff image

Autoresearch

An overnight research loop that autonomously improves img2bez by tracing hand-drawn reference glyphs and comparing raster + vector quality metrics. Inspired by karpathy/autoresearch.

How it works

reference.ufo (hand-drawn glyphs)
      |
render_glyph.py  →  glyph.png   (drawbot-skia renders each .glif to bitmap)
      |
img2bez          →  traced.glif  (trace the bitmap back to bezier outlines)
      |
--reference flag →  metrics     (raster IoU + weighted vector quality score)
      |
agent loop       →  keep/discard  (Claude proposes changes, keeps if mean_iou improves)

Running a session

# 1. Create a research branch
git checkout -b autoresearch/$(date +%b%d | tr A-Z a-z)

# 2. Activate the Python venv (needs ufoLib2 + drawbot-skia)
source ~/Py/venvs/basic-fonts/bin/activate

# 3. Run the experiment harness
./autoresearch/run_experiment.sh

# 4. Tell Claude to follow the instructions and run overnight
# "Follow autoresearch/program.md and improve img2bez"

Current baseline

Reference font: Virtua Grotesk Regular (autoresearch/reference.ufo → symlink to ~/GH/repos/virtua-grotesk/sources/VirtuaGrotesk-Regular.ufo)

Glyphs tested: A–Z + a–z (52 glyphs, all with real hand-drawn outlines)

Metric Baseline
mean_iou 90.39%
mean_score 0.804
glyphs_ok 51/52

Established on commit 82acffa (2026-03-20). Results logged to autoresearch/results.tsv (gitignored).

Switching reference fonts

# Point at a different UFO
ln -sf /path/to/other.ufo autoresearch/reference.ufo

# Or override per-run without changing the symlink
REFERENCE_UFO=/path/to/other.ufo ./autoresearch/run_experiment.sh

# Run a focused subset of glyphs
GLYPHS_FILTER="O S G" ./autoresearch/run_experiment.sh

Files

File Purpose
autoresearch/program.md Full instructions for Claude to follow as the research agent
autoresearch/run_experiment.sh Experiment harness: render → trace → evaluate → report
autoresearch/render_glyph.py Render a .glif to PNG via drawbot-skia
autoresearch/setup.sh One-time setup check
autoresearch/results.tsv Experiment log (gitignored, maintained by the agent)
autoresearch/reference.ufo Symlink to reference UFO (gitignored)

License

MIT

About

Trace raster images to font-ready bezier outlines — Rust library and CLI tool.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors