stilus

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 30, 2026 License: MIT Imports: 8 Imported by: 0

README

stilus

A sparse CPU rasterizer for 2D vector graphics in pure Go: no cgo, no dependencies, all GOOS/GOARCH including js/wasm, MIT licensed.

The cost of a path is edge length in pixels plus covered spans – never the area of its bounding box or the width of the canvas. After warm-up, drawing performs zero allocations.

stilus is the rasterizer core (milestone M1) of the PDF renderer described in the spec “PDF-Renderer für Go (Testballon)”. It knows nothing about PDF, fonts or color spaces, so it can equally serve lux, figure or act as the CPU filler behind gogpu/gg.

API in three layers

// 1. Spans – plug into any compositor
type Blitter interface {
    BlitRun(y, x0, x1 int, alpha uint8)   // interior: constant runs, no pixel loop
    BlitCoverage(y, x int, cov []uint8)   // antialiased edge pixels
}
r := stilus.NewRasterizer(clip)
r.Fill(path, matrix, stilus.NonZero, blitter)    // or AddLine/AddPath + Rasterize

// 2. Geometry – strokes in user space, exact under anisotropic CTMs
var s stilus.Stroker
s.Stroke(r, path, matrix, &stilus.StrokeStyle{Width: 1, Cap: stilus.RoundCap, Dash: []float64{3, 2}})
r.Rasterize(stilus.NonZero, blitter)

// 3. Canvas – the display-list device: Fill/Stroke/ClipPath/ClipRect/PopClip
c := stilus.NewCanvas(img)                        // *image.RGBA owned by the caller
c.Reset(img, band)                                // tile/band/viewport rendering
c.ClipRect(stilus.Rect{X0: 0, Y0: 0, X1: 842, Y1: 595}, ctm)
c.Stroke(path, ctm, style, &stilus.Paint{Color: color.RGBA{0, 0, 0, 255}})
c.Fill(path, ctm, stilus.EvenOdd, &stilus.Paint{Shader: myShader}) // per-span callback
c.PopClip()

Integration points for other renderers: implement Blitter (own pixel format, GPU upload, coverage masks), Shader (gradients, images – called per span, never per pixel) or consume Stroker output through a LineSink.

How it is fast

  • Band accumulation: edges are accumulated as area/cover into a 32-row band of cells (24.8 fixed point), not a page-sized buffer. Paths are clipped analytically first; curves are flattened in device space (0.1 px) after culling against the clip.
  • Sparse sweep: narrow rows (the common case: strokes, glyphs) are integrated over their touched cell range; wide rows use a dirty bitset and emit the constant stretches between edges as runs. The sweep zeroes what it reads – there is no clearing pass.
  • Hairlines (thinner than one device pixel) are stroked one pixel wide in device space by the same stroker, with exact area coverage.
  • Analytic strokes: between the corners of a polyline (joins, caps, inner notches), each segment is bounded by two parallel lines, so each pixel's area is the difference of two closed-form half-plane areas – no cells, no sweep. Only the bands of rows around corners go through the accumulator, with the stroke's real outline edges, so corners, caps, round joins and segments meeting end to end are exactly as on the outline path. Covers polylines, flattened curves, closed shapes and straight dashes; within 2/255 of the outline path (TestPolylineFastPath).
  • Rectangle clips (91 % of real PDF clips) cost nothing per pixel; other clips are rasterized once into a mask that is never cleared as a whole and keeps fully opaque runs as runs.
  • Compositing on premultiplied RGBA8 with SWAR arithmetic (four channels per 64-bit multiply, no divisions), memmove-speed opaque fills.

Numbers

A3 landscape at 150 dpi (2481×1754 px), rectangle page clip, cloud Xeon @ 2.1 GHz, go run ./cmd/stilus-scenes (minimum of 15 runs):

scene 1 core 4 cores (bands) spec target (1 core)
2 000 long hatch lines, hairline 83 ms – ≤ 60 ms ✗
2 000 long hatch lines, 0.35 mm 96 ms – –
20 000 short strokes, 0.35 mm 50 ms – ≤ 40 ms ✗
20 000 short strokes, hairline 43 ms – –
30 000 glyph outlines, uncached 64 ms – –
mixed drawing (fills with alpha, dashes, curves) 57 ms – –
3 000 polylines + 600 circles, 0.35 mm 110 ms – –
2 000 hatch lines through an elliptic mask clip 60 ms – –

Hairlines are drawn one device pixel wide with exact area coverage, like PDFium (an earlier approximation was faster but 14/255 off on dense hatching). Against PDFium on the synthetic drawings of the harness: mean deviation 0.3–1.5/255, SSIM ≥ 0.998 (see harness/).

Allocations per rendered scene after warm-up: 0.

Same machine, same scenes, other pure-Go rasterizers (bench/, separate module):

scene stilus x/image/vector¹ gogpu/gg v0.52.5 (CPU)²
2 000 hatch lines 70 ms 33 400 ms 14 800 ms
20 000 short strokes 55 ms 111 ms 2 090 ms

¹ stilus stroker outlines, filled with one vector.Rasterizer sized to each outline's bounding box. ² No clip; gg's rectangle clip is slower still.

SIMD (experimental, Go 1.26 simd/archsimd)

Built with GOEXPERIMENT=simd on amd64, the compositing kernels (covOpaque, covOver, runOver) use AVX2 (4 px per iteration) or AVX-512 with VBMI (8 px, VPMOVWB pack, VPERMB coverage broadcast), chosen at runtime. Results are bit-identical to the scalar path (TestSpanKernels). Any other build uses the scalar kernels unchanged.

GOTOOLCHAIN=go1.26.8 GOEXPERIMENT=simd go test ./...
GOTOOLCHAIN=go1.26.8 GOEXPERIMENT=simd go test -run XXX -bench SpanKernels .
STILUS_SIMD=0 | 256   # at run time: scalar only | AVX2 only

Kernel throughput, ns per span (same Xeon):

span kernel scalar AVX2 AVX-512
4 px opaque coverage 13 16 17
16 px opaque coverage 36 16 14
64 px coverage over (alpha) 282 71 35
256 px run with alpha 517 196 74
1024 px opaque coverage 2423 846 483

Spans shorter than 16 px stay scalar: one vector block plus VZEROUPPER costs more than a few scalar pixels. On the scenes this means: fills with transparency (mixed drawing) −15…20 %, stroke-dominated scenes unchanged, because their spans are 2–6 px wide.

Pitfalls found on the way (Go 1.26), all avoided in simd_amd64.go:

  • The compiler emits no VZEROUPPER; without archsimd.ClearAVXUpperBits() at the end of each kernel, the rasterizer's legacy-SSE float code ran 3–5× slower afterwards.
  • ShiftAllRight with a count compiles to a legacy-SSE MOVQ inside the AVX loop; division by 255 therefore uses MulHigh ((x+128)·257 >> 16).
  • Vectors inside a struct are copied with legacy-SSE MOVUPS; constants are kept in plain local variables.

Accuracy and robustness

  • Coverage is exact-area accumulation (like FreeType, AGG/PDFium, Skia); tests compare against a supersampling reference integrating the winding number (mean < 0.5/255, max ≤ 16/255 on random polygons).
  • Stroker: AGG-style outlines (inner corners at the offset intersection, no overlap on ordinary polylines), butt/round/square caps, miter/round/bevel joins with miter limit, dashes with phase and zero-length dots. Strokes below one device pixel are drawn one pixel wide, like PDFium.
  • NaN/Inf rejected, huge coordinates clipped analytically, budgets for edges per path, curve/arc subdivision, dash counts and clip depth. Canvas never panics; errors are reported through Err(). Dash patterns beyond the subdivision budget skip or truncate the affected subpath and report ErrDashBudget; Stroker.Truncated() exposes the same state when using the stroker directly. Patterns too dense to resolve (a device period of at most 1/4 px, or more than 32 entries per pixel) are drawn as a solid stroke at the pattern's mean coverage, caps included, so their cost is that of a solid stroke; Stroker.Coverage() reports the factor. Walking a pattern dash by dash is bounded by 32 transitions per device pixel of the subpath.
  • Fuzz targets: FuzzFill, FuzzStroke.

Development

go test ./...                                  # tests, reference comparisons, alloc gates
go test -run XXX -bench BenchmarkScenes .      # scene benchmarks at 72/150/300 dpi
go run ./cmd/stilus-scenes -dpi 150 -out /tmp/scenes   # timings + PNGs
go run ./cmd/stilus-scenes -threads 4          # band-parallel rendering
(cd bench && go test -bench .)                 # comparison with x/image/vector and gg
go test -fuzz FuzzStroke                       # fuzzing

Not in this milestone

Blend modes, transparency groups and soft masks (M6), shadings and images (M5/M7), glyph cache (M4), display list and PDF interpretation (M3). The Blitter/Shader interfaces are where these plug in.

Next speed levers: most remaining stroke time is compositing latency on the destination (cache misses per row); a band-sorted display list (M3) is where that can be attacked. Vectorizing the sweep helps wide fills.

Documentation

Overview

Package stilus is a sparse CPU rasterizer for 2D vector graphics.

Its cost per path is the length of the path's edges in pixels plus the covered spans, never the area of the bounding box or the width of the canvas. It knows nothing about PDF, fonts or color spaces and has no dependencies, so it can serve as the CPU backend of a PDF renderer, a UI toolkit or another 2D library (for example as the CPU filler behind gogpu/gg).

Layers

Each layer can be used on its own:

  • Rasterizer turns device-space paths (Fill, or AddLine/AddPath and Rasterize) into coverage spans delivered to a Blitter: BlitRun for constant interior runs, BlitCoverage for antialiased edge pixels. Implement Blitter to composite into any pixel format.
  • Stroker turns a stroke (width, caps, joins, miter limit, dashes; all in user space, exact under anisotropic transforms) into fill geometry for a LineSink, such as a Rasterizer or a PathSink.
  • Canvas combines both with a clip stack and compositing onto a caller-owned *image.RGBA. Its methods map one to one onto a display-list device: Fill, Stroke, ClipPath, ClipRect, PopClip.

Performance model

Edges are accumulated as signed area and cover into a band of cells (32 rows × clip width) in 24.8 fixed point. Per row, the touched cell range is integrated directly when it is narrow; wide rows are swept with a dirty bitset that skips untouched cells, and the constant stretches between edges are emitted as runs that blitters fill without a per-pixel loop. Nothing is cleared as a whole: the sweep zeroes what it reads.

Strokes are filled analytically between their corners: rows bounded only by a segment's two parallel sides get each pixel's exact area in closed form; only the rows around joins and caps go through the accumulator. Strokes thinner than one device pixel are drawn one pixel wide. Rectangle clips cost nothing per pixel; other clips are rasterized once into a mask over their bounds.

All buffers grow on demand and are retained: after warm-up, drawing performs no allocations. Rasterizer, Stroker and Canvas are not safe for concurrent use; use one per worker and give each worker its own band or tile of the destination (Canvas.Reset takes a region).

Robustness

NaN and infinite coordinates are rejected, huge ones are clipped analytically, curve and arc subdivision and dash counts are bounded, and the edge count per path is budgeted (Rasterizer.MaxEdges). Canvas never panics; it reports problems through Err and keeps drawing.

Example
package main

import (
	"fmt"
	"image"
	"image/color"

	"github.com/timzifer/stilus"
)

func main() {
	img := image.NewRGBA(image.Rect(0, 0, 200, 100))
	c := stilus.NewCanvas(img)

	// Page space in points, drawn at 144 dpi.
	m := stilus.Scale(2, 2)
	c.ClipRect(stilus.Rect{X0: 5, Y0: 5, X1: 95, Y1: 45}, m)

	var p stilus.Path
	p.MoveTo(10, 40)
	p.CubicTo(30, 0, 70, 80, 90, 10)
	c.Stroke(&p, m, &stilus.StrokeStyle{Width: 1.5, Cap: stilus.RoundCap}, &stilus.Paint{Color: color.RGBA{0, 0, 0, 255}})

	var r stilus.Path
	r.Ellipse(50, 25, 20, 12)
	c.Fill(&r, m, stilus.NonZero, &stilus.Paint{Color: color.RGBA{0, 0, 128, 128}}) // premultiplied

	c.PopClip()
	fmt.Println(c.Err())
}
Output:
<nil>

Index

Examples

Constants

View Source
const DefaultMaxClipDepth = 256

DefaultMaxClipDepth bounds the clip stack of a Canvas.

View Source
const DefaultMaxDashes = 1 << 20

DefaultMaxDashes bounds dash transitions per subpath. Subpaths estimated to exceed the budget are skipped; the subdivision loop also stops at the budget. Stroker.Truncated reports either condition.

View Source
const (

	// DefaultMaxEdges is the default edge budget per path.
	DefaultMaxEdges = 1 << 22
)

Variables

View Source
var (
	ErrEdgeBudget = errors.New("stilus: edge budget exceeded, path truncated")
	ErrDashBudget = errors.New("stilus: dash budget exceeded, stroke truncated")
	ErrClipDepth  = errors.New("stilus: clip stack too deep")
	ErrInternal   = errors.New("stilus: internal error")
)

Errors reported by Canvas.Err.

View Source
var Identity = Matrix{1, 0, 0, 1, 0, 0}

Identity is the identity transform.

Functions

func IsHairline

func IsHairline(m Matrix, st *StrokeStyle) bool

IsHairline reports whether st under m is thinner than one device pixel in every direction. Such strokes are drawn one pixel wide in device space, like PDFium.

func PackRGBA

func PackRGBA(c color.RGBA) uint32

PackRGBA packs a premultiplied color into the native pixel layout used by Shader.ShadeSpan.

func SIMD

func SIMD() bool

SIMD reports whether vectorized span kernels are in use. They require GOEXPERIMENT=simd (Go 1.26+) on amd64 with AVX2.

func UnpackRGBA

func UnpackRGBA(v uint32) color.RGBA

UnpackRGBA is the inverse of PackRGBA.

Types

type Blitter

type Blitter interface {
	// BlitRun paints the constant coverage alpha over [x0, x1) on row y.
	// Interior pixels of a shape arrive here, never through BlitCoverage.
	BlitRun(y, x0, x1 int, alpha uint8)
	// BlitCoverage paints per-pixel coverage for [x, x+len(cov)) on row y.
	BlitCoverage(y, x int, cov []uint8)
}

Blitter receives the output of the rasterizer, one span at a time, in increasing y order and, within a row, in increasing x order. Spans never overlap and always lie inside the rasterizer's clip.

Implementations must not retain cov after the call returns.

type Canvas

type Canvas struct {
	// MaxClipDepth overrides DefaultMaxClipDepth when non-zero.
	MaxClipDepth int
	// contains filtered or unexported fields
}

Canvas draws paths onto a caller-owned *image.RGBA (premultiplied, as image.RGBA always is). It combines the rasterizer, the stroker and a clip stack, and maps one to one onto a display-list device: Fill/Stroke/ClipPath/ClipRect/PopClip.

Device space is the pixel space of the destination image, so a tile or band is rendered by passing an image whose Rect is that tile, or by restricting Reset's region. A Canvas is not safe for concurrent use; use one per worker. After warm-up a Canvas does not allocate.

func NewCanvas

func NewCanvas(dst *image.RGBA) *Canvas

NewCanvas returns a canvas drawing onto dst.

func (*Canvas) Clip

func (c *Canvas) Clip() image.Rectangle

Clip returns the device-space bounds of the current clip.

func (*Canvas) ClipDepth

func (c *Canvas) ClipDepth() int

ClipDepth returns the number of clips pushed since Reset.

func (*Canvas) ClipPath

func (c *Canvas) ClipPath(p *Path, m Matrix, rule FillRule)

ClipPath intersects the clip with p under m. Rectangles ("re W n") take the rectangle fast path; other shapes are rasterized once into a mask over their bounds.

func (*Canvas) ClipRect

func (c *Canvas) ClipRect(r Rect, m Matrix)

ClipRect intersects the clip with rectangle r in user space. An axis-aligned result costs nothing per pixel; a rotated one becomes a mask.

func (*Canvas) Err

func (c *Canvas) Err() error

Err returns the first error since Reset, if any. Drawing continues after an error; the error only reports that output may be incomplete.

func (*Canvas) Fill

func (c *Canvas) Fill(p *Path, m Matrix, rule FillRule, paint *Paint)

Fill fills p, transformed by m into device space, with the fill rule.

func (*Canvas) PopClip

func (c *Canvas) PopClip()

PopClip removes the most recent clip.

func (*Canvas) Reset

func (c *Canvas) Reset(dst *image.RGBA, region image.Rectangle)

Reset retargets the canvas to dst, limited to region (in dst's coordinates), clears the clip stack and the error.

func (*Canvas) Stroke

func (c *Canvas) Stroke(p *Path, m Matrix, st *StrokeStyle, paint *Paint)

Stroke strokes p with style st (in user space) under m. Strokes thinner than a device pixel are drawn one device pixel wide.

type Cap

type Cap uint8

Cap is the shape of open stroke ends.

const (
	ButtCap Cap = iota
	RoundCap
	SquareCap
)

type FillRule

type FillRule uint8

FillRule selects how winding numbers map to inside/outside.

const (
	NonZero FillRule = iota
	EvenOdd
)

type Join

type Join uint8

Join is the shape of stroke corners.

const (
	MiterJoin Join = iota
	RoundJoin
	BevelJoin
)

type LineSink

type LineSink interface {
	AddLine(x0, y0, x1, y1 float64)
}

LineSink receives device-space line segments. Segments emitted by the Stroker form closed polygons that must be filled with the NonZero rule. *Rasterizer implements LineSink.

type MaskBlitter

type MaskBlitter struct {
	Mask *image.Alpha
}

MaskBlitter accumulates coverage into an 8-bit alpha image (for clip masks, soft masks and tests). Coverage is combined with "max", which is exact for the non-overlapping spans of a single rasterization.

func (*MaskBlitter) BlitCoverage

func (b *MaskBlitter) BlitCoverage(y, x int, cov []uint8)

func (*MaskBlitter) BlitRun

func (b *MaskBlitter) BlitRun(y, x0, x1 int, alpha uint8)

type Matrix

type Matrix [6]float64

Matrix is an affine transform in PDF order [a b c d e f]:

x' = a·x + c·y + e
y' = b·x + d·y + f

func Rotate

func Rotate(rad float64) Matrix

Rotate returns a rotation by rad radians.

func Scale

func Scale(sx, sy float64) Matrix

Scale returns a scaling transform.

func Translate

func Translate(tx, ty float64) Matrix

Translate returns a translation.

func (Matrix) Apply

func (m Matrix) Apply(x, y float64) (float64, float64)

Apply transforms (x, y).

func (Matrix) ApplyVec

func (m Matrix) ApplyVec(x, y float64) (float64, float64)

ApplyVec transforms the vector (x, y), ignoring translation.

func (Matrix) Det

func (m Matrix) Det() float64

Det returns the determinant of the linear part.

func (Matrix) Invert

func (m Matrix) Invert() (inv Matrix, ok bool)

Invert returns the inverse transform; ok is false if m is singular.

func (Matrix) Mul

func (m Matrix) Mul(n Matrix) Matrix

Mul returns the transform that applies m first and then n.

type Paint

type Paint struct {
	Color  color.RGBA // premultiplied
	Shader Shader
}

Paint describes what a fill or stroke paints: a solid premultiplied color, or a Shader when Shader is non-nil.

type Path

type Path struct {
	Verbs  []Verb
	Points []Point
}

Path is a sequence of subpaths. The zero value is an empty path ready to use. Reset keeps the backing storage, so a Path can be rebuilt many times without allocating.

func (*Path) Bounds

func (p *Path) Bounds() Rect

Bounds returns the bounding box of all points (including control points).

func (*Path) Close

func (p *Path) Close()

Close closes the current subpath.

func (*Path) CubicTo

func (p *Path) CubicTo(c1x, c1y, c2x, c2y, x, y float32)

CubicTo adds a cubic Bézier segment.

func (*Path) Ellipse

func (p *Path) Ellipse(cx, cy, rx, ry float32)

Ellipse appends a closed ellipse centred at (cx, cy).

func (*Path) Empty

func (p *Path) Empty() bool

Empty reports whether the path has no drawing commands.

func (*Path) LineTo

func (p *Path) LineTo(x, y float32)

LineTo adds a line segment to (x, y).

func (*Path) MoveTo

func (p *Path) MoveTo(x, y float32)

MoveTo starts a new subpath at (x, y).

func (*Path) QuadTo

func (p *Path) QuadTo(cx, cy, x, y float32)

QuadTo adds a quadratic Bézier segment.

func (*Path) Rect

func (p *Path) Rect(x, y, w, h float32)

Rect appends a closed rectangle subpath (PDF "re").

func (*Path) Reset

func (p *Path) Reset()

Reset empties the path, keeping its storage.

type PathSink

type PathSink struct{ P *Path }

PathSink collects line segments into a Path.

func (PathSink) AddLine

func (s PathSink) AddLine(x0, y0, x1, y1 float64)

type Point

type Point struct{ X, Y float32 }

Point is a 2D point. Path coordinates are stored as float32 to keep paths (and display lists built on top of them) compact.

type Rasterizer

type Rasterizer struct {

	// MaxEdges bounds the number of edges per path. Edges beyond it are
	// dropped and Truncated reports true. Zero means DefaultMaxEdges.
	MaxEdges int
	// contains filtered or unexported fields
}

Rasterizer converts paths into coverage spans. Its cost per path is the length of the path's edges in pixels plus the number of covered spans; it never touches the area of the bounding box.

A Rasterizer is not safe for concurrent use; use one per worker. All buffers grow on demand and are retained, so steady-state use performs no allocations.

Example
package main

import (
	"fmt"
	"image"

	"github.com/timzifer/stilus"
)

// areaBlitter shows the span interface used to plug the rasterizer into
// another compositor: it just integrates the coverage.
type areaBlitter struct{ area int }

func (b *areaBlitter) BlitRun(y, x0, x1 int, alpha uint8) { b.area += (x1 - x0) * int(alpha) }
func (b *areaBlitter) BlitCoverage(y, x int, cov []uint8) {
	for _, a := range cov {
		b.area += int(a)
	}
}

func main() {
	r := stilus.NewRasterizer(image.Rect(0, 0, 100, 100))
	var p stilus.Path
	p.Rect(10.5, 10.5, 50, 30)
	p.Rect(20, 60, 400, 10) // wide: interior rows arrive as runs
	var b areaBlitter
	r.Fill(&p, stilus.Identity, stilus.NonZero, &b)
	fmt.Printf("%.0f px²\n", float64(b.area)/255)
}
Output:
2300 px²

func NewRasterizer

func NewRasterizer(clip image.Rectangle) *Rasterizer

NewRasterizer returns a rasterizer clipped to clip.

func (*Rasterizer) AddLine

func (r *Rasterizer) AddLine(x0, y0, x1, y1 float64)

AddLine adds a device-space line segment. Segments are clipped analytically: parts above or below the clip are dropped, parts right of it are dropped, and parts left of it become vertical edges on the clip's left border, which preserves the winding of everything inside.

func (*Rasterizer) AddPath

func (r *Rasterizer) AddPath(p *Path, m Matrix)

AddPath adds the edges of p transformed by m. Open subpaths are closed implicitly. Curves are flattened in device space with a tolerance of a quarter pixel. Paths containing NaN or infinite values are ignored.

func (*Rasterizer) Clip

func (r *Rasterizer) Clip() image.Rectangle

Clip returns the current clip rectangle.

func (*Rasterizer) Fill

func (r *Rasterizer) Fill(p *Path, m Matrix, rule FillRule, b Blitter)

Fill rasterizes p transformed by m with the given fill rule. It is equivalent to Reset, AddPath and Rasterize.

func (*Rasterizer) Rasterize

func (r *Rasterizer) Rasterize(rule FillRule, b Blitter)

Rasterize converts all added edges into spans and resets the rasterizer.

func (*Rasterizer) Reset

func (r *Rasterizer) Reset()

Reset discards all accumulated edges and the Truncated state.

func (*Rasterizer) SetAntialias

func (r *Rasterizer) SetAntialias(aa bool)

SetAntialias enables (default) or disables antialiasing. Without antialiasing a pixel is covered when at least half of it is inside.

func (*Rasterizer) SetClip

func (r *Rasterizer) SetClip(clip image.Rectangle)

SetClip sets the device-space clip rectangle. Every span produced lies inside it. Buffers are resized only when the clip grows.

func (*Rasterizer) Truncated

func (r *Rasterizer) Truncated() bool

Truncated reports whether the last path exceeded the edge budget.

type Rect

type Rect struct{ X0, Y0, X1, Y1 float64 }

Rect is an axis-aligned rectangle [X0,X1)×[Y0,Y1).

func (Rect) Empty

func (r Rect) Empty() bool

Empty reports whether r has no area. NaN bounds count as empty.

func (Rect) Intersect

func (r Rect) Intersect(s Rect) Rect

Intersect returns the intersection of r and s.

type Shader

type Shader interface {
	ShadeSpan(y, x int, dst []uint32)
}

Shader produces premultiplied colors (in PackRGBA layout) for the pixels [x, x+len(dst)) on row y. It is called once per span, never per pixel.

type ShaderBlitter

type ShaderBlitter struct {
	// contains filtered or unexported fields
}

ShaderBlitter composites the output of a Shader onto an image.RGBA.

func NewShaderBlitter

func NewShaderBlitter(dst *image.RGBA, s Shader) *ShaderBlitter

NewShaderBlitter returns a blitter painting s onto dst.

func (*ShaderBlitter) BlitCoverage

func (b *ShaderBlitter) BlitCoverage(y, x int, cov []uint8)

func (*ShaderBlitter) BlitRun

func (b *ShaderBlitter) BlitRun(y, x0, x1 int, alpha uint8)

func (*ShaderBlitter) Reset

func (b *ShaderBlitter) Reset(dst *image.RGBA, s Shader)

Reset retargets the blitter without allocating.

type SolidBlitter

type SolidBlitter struct {
	// contains filtered or unexported fields
}

SolidBlitter composites a single premultiplied color onto an image.RGBA.

func NewSolidBlitter

func NewSolidBlitter(dst *image.RGBA, c color.RGBA) *SolidBlitter

NewSolidBlitter returns a blitter painting c (premultiplied) onto dst.

func (*SolidBlitter) BlitCoverage

func (b *SolidBlitter) BlitCoverage(y, x int, cov []uint8)

func (*SolidBlitter) BlitRun

func (b *SolidBlitter) BlitRun(y, x0, x1 int, alpha uint8)

func (*SolidBlitter) Reset

func (b *SolidBlitter) Reset(dst *image.RGBA, c color.RGBA)

Reset retargets the blitter without allocating.

func (*SolidBlitter) SetColor

func (b *SolidBlitter) SetColor(c color.RGBA)

SetColor changes the paint color (premultiplied).

type StrokeStyle

type StrokeStyle struct {
	Width      float64 // 0 means the thinnest line the device can render
	Cap        Cap
	Join       Join
	MiterLimit float64   // PDF default 10; values < 1 are treated as 1
	Dash       []float64 // on/off lengths; empty means solid
	DashPhase  float64
}

StrokeStyle describes a stroke in user space (the space the path is defined in), like the PDF graphics state.

type Stroker

type Stroker struct {
	// MaxDashes overrides DefaultMaxDashes when non-zero.
	MaxDashes int
	// contains filtered or unexported fields
}

Stroker converts strokes into fill geometry (closed outlines to be filled with NonZero). Offsets are computed in user space and transformed afterwards, which is exact for anisotropic transforms.

A Stroker is not safe for concurrent use. Buffers are retained.

func (*Stroker) Coverage added in v0.1.1

func (s *Stroker) Coverage() float64

Coverage reports the factor to apply to the coverage of the last stroke's outline: the mean on-fraction of a dash pattern emitted as a solid stroke, else 1.

func (*Stroker) Stroke

func (s *Stroker) Stroke(sink LineSink, p *Path, m Matrix, st *StrokeStyle)

Stroke emits the outline of p stroked with st under m into sink.

A dash pattern too dense to resolve in device space is emitted as a solid stroke; Coverage then reports the fraction the caller must scale the resulting coverage by.

func (*Stroker) Truncated

func (s *Stroker) Truncated() bool

Truncated reports whether the last stroke exceeded its dash budget. Canvas reports this condition as ErrDashBudget.

type Verb

type Verb uint8

Verb is a path construction command.

const (
	MoveTo  Verb = iota // 1 point
	LineTo              // 1 point
	QuadTo              // 2 points
	CubicTo             // 3 points
	Close               // 0 points
)

Directories

Path Synopsis
cmd
stilus-scenes command
Command stilus-scenes renders the benchmark scenes, prints timings and allocations and optionally writes PNGs for visual inspection.
Command stilus-scenes renders the benchmark scenes, prints timings and allocations and optionally writes PNGs for visual inspection.
internal
scenes
Package scenes provides deterministic benchmark scenes modelled on technical drawings: an A3 landscape page in PDF points, drawn under a scale to the requested resolution with a rectangular page clip.
Package scenes provides deterministic benchmark scenes modelled on technical drawings: an A3 landscape page in PDF points, drawn under a scale to the requested resolution with a rectangular page clip.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL