Knitters have a geometry problem that most charting software ignores: a knitted stitch is not square. I build a browser-based knitting chart tool, and three small pieces of code did most of the work of getting charts right: a gauge-based cell aspect ratio, a photo-to-chart pipeline that thinks in stitches instead of pixels, and a row walker that turns the grid into written instructions. Here is how each one works.
1. Stitches are wider than they are tall
Knitters measure gauge: how many stitches and how many rows fit in 10 cm (4 in). A typical worsted-weight gauge is 18 stitches × 24 rows. That means one stitch is 10/18 ≈ 0.56 cm wide but only 10/24 ≈ 0.42 cm tall.
If you draw a motif on square graph paper, every row is drawn taller than it will knit. A circle charted on squares knits up as a squashed oval, and lettering looks short and wide. The fix is to draw each cell at the stitch's real proportions:
/** Height of one stitch relative to its width, from the gauge. */ export const stitchAspect = (gauge: { stitches: number; rows: number }) => gauge.stitches / gauge.rows; // layout const cellH = proportional ? cellW * stitchAspect(chart.gauge) : cellW; With 18 × 24, stitchAspect is 0.75, so a 20 px wide cell is drawn 15 px tall. Everything else (hit testing, row numbers, outlines) has to use cellW and cellH separately instead of a single cellSize:
const x = Math.floor((px - layout.left) / layout.cellW); const y = Math.floor((py - layout.top) / layout.cellH); It sounds trivial, but once cells are not square you find every place in the code that quietly assumed they were. There is still a toggle for square cells for people who prefer them, but the default is proportional.
2. Photo to chart: resample in stitches, not pixels
The naive way to turn a photo into a colorwork chart is to shrink it to N × M pixels and posterize. That gets the proportions wrong for the same reason as above: if the chart is 60 stitches wide, the number of rows has to come from the photo's aspect ratio and the gauge:
export const rowsForStitches = (img: HTMLImageElement, stitches: number, gauge: Gauge) => Math.max(1, Math.round( stitches * (img.naturalHeight / img.naturalWidth) * (gauge.rows / gauge.stitches) )); A square photo at 60 stitches and 18 × 24 gauge becomes 60 × 80: more rows than stitches, because rows are shorter. Knit it and the result is square again.
The rest of the pipeline runs on a canvas in the browser, so the photo never leaves the device:
- Draw the image into a canvas sized
stitches × rowswithimageSmoothingQuality = 'high'. Each pixel is now one stitch. - Build a palette with image-q using Wu quantization and BT.709-weighted Euclidean distance. Wu is fast and deterministic, which matters when someone drags a "colors" slider and expects the same result each time.
- Map every pixel to its nearest palette color, with no dithering.
That last point is knitting-specific. Dithering makes photos look better on screen, but in stranded colorwork every isolated stitch of a different color is a strand you have to carry and catch. A dithered chart is technically accurate and miserable to knit.
Cleaning up single stitches
Even without dithering, quantized photos are full of lone stitches. A small cleanup pass looks at each stitch's four neighbours: if none of them share its color, and one color holds the majority (at least three of the four, or all of them at an edge), the stitch takes that color:
if (same || neighbours < 2) continue; // keep stitches that touch their own color if (bestCount >= Math.min(3, neighbours)) out[i] = best; Running it twice catches the pairs the first pass exposes. Finally the palette is sorted by how many stitches use each color, so the most-used color becomes color A, the main color, and unused colors are dropped. Knitters expect A to be the background.
3. Turning the grid into written instructions
Many knitters prefer written rows to a chart. Generating them is a run-length encoding problem with one twist: direction.
When knitting flat, right-side rows are worked from right to left across the chart, and wrong-side rows from left to right, with every stitch turned into its wrong-side equivalent (a knit seen from the back is a purl). In the round, every round reads right to left.
const ws = isWrongSide(chart, y); const xs = ws ? [...Array(width).keys()] : [...Array(width).keys()].reverse(); Then walk xs, merge consecutive cells with the same stitch and color into a run, and format each run in standard abbreviations: k5 A, k2 B, p3 A. Cells marked "no stitch" (used when stitch counts change across a chart) are skipped and not counted, so each row ends with its true stitch total, which is the first thing a knitter checks when something goes wrong.
What I'd tell anyone building tools for crafters
- Model the physical object, not the picture. Gauge, direction of work and "no stitch" cells are the domain; pixels are an implementation detail.
- Optimise for the person holding the needles. No dithering, a cleanup pass, and the main color as A are all choices that make the output worse as an image and better as a pattern.
- Keep it local. Everything above runs in the browser. People upload family photos to make sweaters; not sending those to a server is a feature.
You can try all of this in Knitting Chart, the free tool I built: draw a chart in true stitch proportions, or use the photo to knitting chart converter and download a PDF with the key and written rows. Disclosure: I made it, and it has an optional one-time paid upgrade for cloud sync; everything described in this post is in the free version.
