A pixel-grid logo editor that draws on a grid and outputs clean, smooth SVG/PNG — no illustrator required. Paint on a configurable grid, dial in the curve smoothing, then export production-ready vector art in light mode, dark mode, or both at once
The core idea is three stages in a pipeline:
- Draw — paint cells on a pixel grid (8×8 to 128×128). Each cell is either filled with a color or empty.
- Smooth — a marching-squares contour extractor traces the filled regions into polygons, then a cubic Bézier fitter curves them. One slider controls how aggressively the curves deviate from the original grid shape.
- Export — the smoothed paths render into a self-contained SVG or a rasterized PNG at 1×, 2×, or 4× resolution.
No server. Everything runs in the browser.
| Tool | Shortcut | Description |
|---|---|---|
| Draw | D |
Click or drag to fill cells |
| Erase | E |
Click or drag to clear cells |
| Line | L |
Click and drag to draw a straight line |
| Rectangle | R |
Click and drag to draw a rectangle |
| Ellipse | O |
Click and drag to draw an ellipse |
| Fill | F |
Flood-fill a contiguous region |
Brush sizes — 1px to 4px, for quick block fills without tedious cell-by-cell painting.
Symmetry modes — horizontal, vertical, or quad-axis mirroring while you draw. Great for logos that need to be balanced.
Shapes — rectangles and ellipses can be filled or outline-only.
Up to three independent layers, each with its own visibility toggle and rotation control. Layers are smoothed and exported independently, so you can layer a symbol over a background shape and get separate path groups in the SVG.
A single slider from 0 to 1 controls the Bézier tension applied during curve fitting. At 0 you get a clean pixel-art polygon. At 1 you get maximal smoothing — rounded, organic shapes from the same grid data. The preview updates in real time (debounced) as you adjust.
Two modes are available:
- Smooth — full Bézier curve fitting
- Pixel — straight-edged polygons with no curve fitting, for a crisp pixel-art aesthetic
SVG — a valid, self-contained SVG with inline styles. No external dependencies. Each color region is its own <path>, each layer is a <g> group.
PNG — the SVG rasterized at 1×, 2×, or 4× via an offscreen canvas.
Color modes:
- Light — dark foreground on a light background
- Dark — light foreground on a dark background
- Adaptive — a single SVG with embedded
prefers-color-schememedia queries; shows correctly in both light and dark OS themes
Full stroke-level undo/redo (Ctrl+Z / Ctrl+Shift+Z). Grid resizes are also undoable. New strokes after an undo discard the redo stack.
npm install
npm run devOpen http://localhost:5173. No environment variables, no backend.
npm run build # production build → dist/
npm run preview # preview the production build locally
npm test # run the test suitesrc/
├── models/ # Core data types: Grid, LayerManager, Color, Tool
├── canvas/ # Drawing logic, history (undo/redo), resize, symmetry
├── smoothing/ # Contour extraction, Bézier fitting, multi-color/layer pipeline
├── export/ # SVG generation, PNG rasterization, download triggers
└── components/ # React UI: PixelCanvas, Preview, Inspector, ToolPalette
The pipeline is strictly one-directional:
Canvas (grid state) → Smoothing Engine (vector paths) → Export (files)
Neither the smoothing engine nor the export layer mutate grid state.
- React 19 — UI
- Vite 8 — dev server and build
- TypeScript — throughout
- Vitest + Testing Library — unit and integration tests
- Zero runtime dependencies beyond React
Smoothing uses marching squares with 8-connectivity. Saddle point cases (cases 5 and 10) are resolved by corner-average disambiguation rather than a fixed rule, which produces more visually consistent results across different grid shapes. Each color and each layer is extracted separately — there's no compositing of contours across layers.
Grid padding — before contour extraction, the grid is padded by one empty cell on each side. Without this, shapes touching the grid edge produce open (clipped) contours. The padding is virtual; it doesn't modify the stored grid.
History operates at stroke granularity. A stroke is one complete interaction: a full drag, a single click, a resize, etc. Individual cell changes within a drag are batched into a single DrawStrokeCommand, so one undo always reverses exactly one user action.
Export color modes work by parameterizing foreground and background colors at export time rather than baking them into the grid. The same smoothed paths render into all three modes from a single call.
MIT