← All posts

cgPaint: Classic CG Algorithms, Pixel by Pixel

For my computer graphics mini project I built a paint app where every primitive is implemented from scratch. No ctx.lineTo for lines. No ctx.arc for circles. Just the raw algorithms, plotting one pixel at a time.

The most useful part turned out to be the speed slider: slow an algorithm down to 100ms per step and you can watch Bresenham decide whether to step right or diagonally. That made the theory click in a way lecture notes didn’t.

DDA vs Bresenham: two ways to draw a line

DDA (Digital Differential Analyzer) is the intuitive approach — divide the total change in x and y by the number of steps, then increment:

JS  DDA Line — try changing the endpoints

Bresenham eliminates the floating point entirely using an integer error term. At each step, the error tells you whether the next pixel should be directly right or diagonally up-right:

JS  Bresenham Line — inspect the error term

Run both on the same line: they produce identical pixels. The difference shows up at scale — Bresenham avoids floating-point rounding that accumulates across millions of lines in a real renderer.

Bresenham’s circle: computing one octant, mirroring eight

A circle has 8-fold symmetry. Bresenham’s algorithm exploits this: compute one octant (45°), then mirror it to all eight positions simultaneously. For a radius of r, you only do ~r iterations to draw the full circle:

JS  Bresenham Circle — 8-way symmetry

Scan-line polygon fill

Given an arbitrary polygon, the scan-line algorithm sweeps horizontal lines top to bottom. For each scan line it finds every edge intersection, sorts them, and fills between pairs. The tricky part is the edge table — you only activate edges whose yMin the current scan line has reached:

JS  Scan-line fill — watch the sweep

The blocks in the output show the actual fill span per scan line. You can see the triangle widen as y increases.

2D transformations and why order matters

Translate, rotate, and scale are applied via the canvas transform stack. The important thing: the order you apply them changes the result. Rotate-then-translate is not the same as translate-then-rotate:

JS  Transform order — rotate vs translate first

In the actual app, each shape stores its own transform state and ctx.save()/ctx.restore() isolates it so one object’s rotation doesn’t bleed into the next.

👉 github.com/prathameshrp