A pure-Go library for Photoshop-style layered image compositing: blend modes,
masks, vector paths, gradients, blur, and non-destructive layer effects.
Intentionally built with CGO_ENABLED=0 and no dependencies outside the
standard library.
This is just a library and not a full application, so there is no CLI, and no
opinion about file formats beyond decoding into and encoding out of image.Image.
This way, a poster renderer, a game's UI layer, or a template service should all
find it equally usable.
Go has pieces of this already. image/draw does Porter-Duff compositing,
x/image/vector fills paths. Neither does Photoshop's blend math, layer
effects, or dithered gradients, and the alternatives all mean CGO bindings to
Skia or Cairo.
There are definitely better alternatives outside Go, but I'm stubborn and wanted to try filling out that missing middle piece that Go didn't seem to have.
Compositing happens in linear light, premultiplied, at float32. Source pixels decode from sRGB on ingest and re-encode on output, so compositing always happens in linear space.
Rendering is deterministic. The same input gives byte-identical output every run. Parallel work is split into fixed row bands rather than scheduled dynamically.
Expensive operations are concurrent. Splits across GOMAXPROCS goroutines.
Inputs are immutable. Effects and masks work on copies, so a layer can be re-composited with different settings without its content changing.
Effects are declarative. A layer effect is a parameter struct rather than an imperative call, and the renderer decides how and when to rasterize it.
Lower layers never import higher ones, so each package works on its own.
| Package | Responsibility |
|---|---|
raster |
The Buffer type, sRGB/linear conversion, 8/16-bit image ingest and egress with ordered dithering |
blend |
The blend-mode formulas from ISO 32000-2, per-pixel and buffer-level |
mask |
Raster and vector masks, coverage composition |
path |
Bezier construction, anti-aliased fill (nonzero/even-odd), and stroking (joins, caps, dashes) |
gradient |
Linear, radial, angle, reflected, and diamond gradients with per-stop opacity |
blur |
3-pass box blur as a separable Gaussian approximation |
effects |
The declarative layer styles |
canvas |
Layer stack, groups, and Render |
Most callers import only canvas.
go get github.com/odevine/impasto
Requires Go 1.24+.
doc := &canvas.Document{
Width: 200, Height: 120,
Root: canvas.Group{
PassThrough: true, Opacity: 1,
Layers: []canvas.Node{
&canvas.Layer{Content: bg, Opacity: 1, Mode: blend.Normal},
&canvas.Layer{
Content: logo,
Opacity: 1,
Mask: mask.NewVectorMask(shape, 200, 120, path.NonZero),
Effects: []effects.Effect{
&effects.DropShadow{Color: color.Black, Opacity: 0.75,
Angle: 2.36, Distance: 12, BlurRadius: 18, Mode: blend.Multiply},
&effects.Stroke{Width: 4, Color: color.White,
Alignment: effects.StrokeOutside},
},
},
},
},
}
out := canvas.MustRender(doc)
png.Encode(w, out.ToImage(8))Layer content is document-sized. Place a smaller image with canvas.Place.
The API reference lives on pkg.go.dev, with runnable examples attached to the symbols that need them.
The docs/ directory covers things beyond just references. Things like
how the pieces all fit together, why the model works the way it does, and when
the obvious thing might be the wrong thing.
| Guide | Covers |
|---|---|
| raster | The color model, Buffer, ingest and egress, dithering |
| blend | The 16 modes, what each is for, the compositing formula |
| mask | Raster, vector, procedural, and combined coverage |
| path | Bezier construction, fill rules, anti-aliasing, stroking |
| gradient | The five geometries, stops, opacity, spread modes |
| blur | The three-box Gaussian approximation, sigma vs radius |
| effects | The eight layer styles, their parameters, stacking order |
| canvas | Documents, layers, groups, clipping, rendering |
New to the library? Read canvas, then raster.
The examples/ directory has runnable programs that render PNGs
showing off each part of the library. See
examples/README.md for a walk-through of every mode,
effect, and gradient with the image beside it.
| Program | Renders |
|---|---|
blendmodes |
All 16 blend modes as a chart |
gradients |
The five gradient types plus a transparency fade |
effects |
The eight layer styles on a badge |
paths |
Stroke caps, joins, dashes, fill rules, and beziers |
showcase |
A poster combining gradients, masks, groups, and every effect |
banner |
The README banner at the top of this page |
Run one with go run ./examples/<name>, or regenerate all of them with
make examples.
This library does not shape text. That's something that a package like go-text/typesetting
does very well already, so the recommendation is to rasterize glyph outlines yourself,
pass them into the path package, and hand the resulting buffer to canvas as an
ordinary layer.
Nothing below canvas needs to know a layer's pixels came from text, so rasterized
text gets shadows and strokes like anything else.
Blend modes are checked against the ISO formulas computed independently, per
channel, across a grid that includes the discontinuity points. Path coverage is
validated by area conservation and exact partial-pixel coverage. Golden-image
regression runs over a full document, regenerated with go test ./canvas -update.
Fuzzing covers the path rasterizer, stroker, and gradient sampling, which is where untrusted or degenerate input first reaches numeric code:
go test ./path -fuzz=FuzzRasterize
Benchmarks track blend throughput, blur throughput, and full-document render:
go test ./... -bench=.
A Makefile wraps the common tasks, and make ci runs exactly what CI runs:
make ci # gofmt check, vet, build, race tests
make test # plain test suite
make cover # coverage summary
make fuzz # short fuzz smoke run
make examples # regenerate the example images
make hooks # enable the pre-commit hook (fmt + vet + test)
CI runs on every push and pull request: a test matrix on the minimum and current
Go versions, govulncheck, and a fuzz smoke run. Releases are automated with
release-please. Commits follow
Conventional Commits, and merging the
release PR tags a semver version and publishes a GitHub Release.
Seven of the eight effects are built to spec-level accuracy. Bevel and emboss is close-and-configurable rather than pixel-exact, by design: it is the one effect with no clean public spec, so it derives a normal map from the layer's alpha and applies a directional lighting model. Expect it to keep iterating against real reference renders.
No font shaping, no CMYK or print color management, no GPU backend, no animation.

