vector

package
v0.36.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: BSD-3-Clause Imports: 5 Imported by: 3

Documentation

Overview

Package vector is go-gfx's pure-Go 2-D vector rasterizer: it turns arbitrary outlines (move / line / quadratic / cubic / close) into per-pixel coverage, with an anti-aliased scanline accumulator and a round-join / round-cap stroker.

It computes COVERAGE, not colour: a Rasterizer hands back a float64 grid of per-pixel coverage (0..1) over an integer box, which a consumer composites into its own pixel buffer however it likes (honouring its own clip, blend mode, and colour). Keeping the geometry and coverage here — decoupled from any particular surface — lets go-widgets/painter, go-webengine and any other renderer share one exact rasterizer rather than hand-rolling their own.

Coordinates are float64 pixel units. Curves are flattened to line segments by an error tolerance at fill / stroke time, so a consumer never deals with beziers directly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Composite added in v0.3.0

func Composite(dst *raster.Image, cov []float64, ox, oy, w, h int, p Paint)

Composite lays a Paint onto dst through the coverage grid cov (row-major w*h, values 0..1) positioned at (ox, oy) on the surface, using straight-alpha source-over blending. This is the colour-bearing companion to Rasterizer.Fill / Rasterizer.Stroke, which return exactly such a grid: the rasterizer decides SHAPE (per-pixel coverage) and Composite decides COLOUR (a flat SolidPaint, a LinearGradient or a RadialGradient).

The effective source alpha at a pixel is the paint's own alpha scaled by the pixel's coverage; a paint colour is sampled at the pixel centre. The box [ox, oy, w, h] is assumed to lie within dst's bounds — Rasterizer.Fill and Rasterizer.Stroke clamp it to the surface passed as clampW/clampH. Pixels with zero coverage are left untouched.

Types

type FillRule

type FillRule int

FillRule selects how a path's winding count decides which side of the outline is "inside" and therefore filled.

const (
	// NonZero fills a point when the signed edge-crossing count around it is
	// non-zero. Overlapping sub-paths that wind the same way merge into a solid
	// region; this is the intuitive rule for most icon shapes.
	NonZero FillRule = iota

	// EvenOdd fills a point when the crossing count is odd. Overlapping regions
	// alternate filled/empty, so a shape drawn over itself punches a hole — the
	// rule that turns a self-overlapping star's centre into a cut-out.
	EvenOdd
)

type LineCap added in v0.9.0

type LineCap int

A LineCap says how the open end of a stroke is finished.

const (
	// ButtCap stops the stroke square at the end point.
	ButtCap LineCap = iota
	// RoundCap finishes it with a half disc.
	RoundCap
	// SquareCap carries it half a width past the end point.
	SquareCap
)

The three caps every drawing model has.

type LineJoin added in v0.9.0

type LineJoin int

A LineJoin says how two segments of a stroke meet.

const (
	// MiterJoin carries the two outer edges to where they cross, unless that
	// point is further away than the miter limit allows, in which case the
	// join is bevelled instead.
	MiterJoin LineJoin = iota
	// RoundJoin fills the corner with a disc.
	RoundJoin
	// BevelJoin closes the corner with a straight line.
	BevelJoin
)

The three joins every drawing model has.

type LinearGradient added in v0.3.0

type LinearGradient struct {
	X0, Y0, X1, Y1 float64
	Spread         SpreadMethod
	// contains filtered or unexported fields
}

LinearGradient is a Paint whose colour varies along the axis from (X0, Y0) to (X1, Y1): a point's parameter is its projection onto that axis, 0 at the start and 1 at the end, extended past the ends by the Spread method.

func NewLinearGradient added in v0.3.0

func NewLinearGradient(x0, y0, x1, y1 float64, spread SpreadMethod, stops ...Stop) *LinearGradient

NewLinearGradient returns a linear gradient along (x0,y0)->(x1,y1) with the given spread method and colour stops (copied and sorted by offset).

func (*LinearGradient) ColorAt added in v0.3.0

func (g *LinearGradient) ColorAt(x, y float64) color.RGBA

ColorAt returns the gradient colour at (x, y).

type Paint added in v0.3.0

type Paint interface {
	// ColorAt returns the straight-alpha colour at surface point (x, y).
	ColorAt(x, y float64) color.RGBA
}

A Paint is a per-pixel colour source: given a point in surface coordinates it returns the straight-alpha colour to lay down there. Composite pushes a Paint through a coverage grid onto a pixel buffer, so the rasterizer stays colour-agnostic while gradients and solid fills plug in as Paints.

type Path

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

Path is a mutable 2-D outline: a sequence of sub-paths built from move / line / quadratic / cubic / close commands. A Path carries no colour or width — it is handed to a Rasterizer's Fill / Stroke together with a clamp box. The builder methods return the receiver so calls chain:

pth := vector.NewPath().MoveTo(0, 0).LineTo(10, 0).LineTo(5, 8).Close()

Coordinates are float64 pixel units. Curves are flattened to line segments by an error tolerance at fill/stroke time, so a consumer never deals with beziers directly.

func NewPath

func NewPath() *Path

NewPath returns an empty Path ready to build.

func (*Path) Close

func (p *Path) Close() *Path

Close marks the current sub-path closed — a straight segment joins its last point back to its start. A fill implicitly closes every sub-path regardless; Close matters to Stroke, where it turns end caps into a join.

func (*Path) CubicTo

func (p *Path) CubicTo(cx1, cy1, cx2, cy2, x, y float64) *Path

CubicTo adds a cubic bezier from the current point through controls (cx1, cy1) and (cx2, cy2) to (x, y).

func (*Path) LineTo

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

LineTo adds a straight segment from the current point to (x, y).

func (*Path) MoveTo

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

MoveTo starts a new sub-path at (x, y).

func (*Path) QuadTo

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

QuadTo adds a quadratic bezier from the current point through control (cx, cy) to (x, y).

type RadialGradient added in v0.3.0

type RadialGradient struct {
	CX, CY, R float64
	Spread    SpreadMethod
	// contains filtered or unexported fields
}

RadialGradient is a Paint whose colour varies with distance from the centre (CX, CY): a point's parameter is its distance divided by radius R, 0 at the centre and 1 on the circle, extended past the edge by the Spread method.

func NewRadialGradient added in v0.3.0

func NewRadialGradient(cx, cy, r float64, spread SpreadMethod, stops ...Stop) *RadialGradient

NewRadialGradient returns a radial gradient centred at (cx,cy) with radius r, the given spread method and colour stops (copied and sorted by offset).

func (*RadialGradient) ColorAt added in v0.3.0

func (g *RadialGradient) ColorAt(x, y float64) color.RGBA

ColorAt returns the gradient colour at (x, y).

type Rasterizer

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

Rasterizer turns paths into per-pixel coverage grids. It owns reusable scratch buffers (the coverage accumulator, a per-stroke-segment temporary, and a scanline crossings list), grown on demand and reused across Fill / Stroke calls so a steady stream of vector draws amortises to ~zero allocation. The scratch carries no state between calls — each use re-zeroes / resets the region it touches — but a returned coverage slice aliases the accumulator, so a caller must consume it before the next Fill / Stroke.

The zero Rasterizer is ready to use; it is not safe for concurrent use.

func (*Rasterizer) Fill

func (rz *Rasterizer) Fill(pth *Path, rule FillRule, clampW, clampH int) (cov []float64, ox, oy, w, h int, ok bool)

Fill rasterizes pth's interior under rule into a per-pixel coverage grid (row-major, values 0..1) over the integer box [ox,oy,w,h] clamped to the [0,clampW) x [0,clampH) surface. It returns ok=false — and an untouched grid — for a nil path, a path enclosing no area, or one whose bounds fall entirely off the clamp surface. Curves are flattened; corner and edge pixels get fractional coverage (anti-aliased). The returned grid aliases the Rasterizer's scratch and is valid only until the next Fill / Stroke.

func (*Rasterizer) Stroke

func (rz *Rasterizer) Stroke(pth *Path, width float64, clampW, clampH int) (cov []float64, ox, oy, w, h int, ok bool)

Stroke rasterises the path as a line of the given width into a per-pixel coverage grid (row-major, values 0..1) over the integer box [ox,oy,w,h] clamped to the [0,clampW) x [0,clampH) surface, with round caps and round joins. It is Rasterizer.StrokeWith with the style that needs no decisions.

It returns ok=false — and an untouched grid — for a nil path, width <= 0, a path with no strokeable segment, or one whose bounds fall entirely off the clamp surface. The returned grid aliases the Rasterizer's scratch and is valid only until the next Fill or Stroke.

func (*Rasterizer) StrokeWith added in v0.9.0

func (rz *Rasterizer) StrokeWith(pth *Path, style StrokeStyle, clampW, clampH int) (cov []float64, ox, oy, w, h int, ok bool)

StrokeWith rasterises the path as a line drawn in the given style. A stroke is made of a piece per segment, a piece per corner and a piece per end; they all go into one edge list, wound the same way round, and the whole of it is filled once under the nonzero rule.

Rasterising each piece on its own and keeping the greater coverage would look like the same thing and is not. Two pieces that meet along a shared edge each cover part of the pixel that edge cuts, and the greater of two halves is a half. A finely cut curve — the way every plotting program writes one — is nothing but such seams, and would come out at half its colour, combed through with lighter notches at every vertex.

type Rect added in v0.33.0

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

A Rect is an axis-aligned rectangle whose coverage is worked out rather than rasterised.

It exists because a consumer that knows its shape is a rectangle before it draws one -- a PDF renderer meets `re W n`, and 91% of the clips in two corpora of 3215 documents are exactly that -- pays for a coverage grid it could have computed: the edge list, the sweep, and w*h floats to store a shape four numbers describe.

What Rect.At returns is what Rasterizer.Fill would have put in that grid, BIT FOR BIT, which is what makes it a substitution rather than an approximation. That is not free to arrange: the fill samples pathSS vertical sub-scanlines a row at y+(s+0.5)/pathSS and adds a weight for each one that is inside, with the horizontal overlap worked out the way addSpan works it out. The closed form has to sample the same places and do the same arithmetic; TestRectAgreesWithFill holds that down, and catches a sub-scanline moved by half a step or an edge compared the other way round.

The weight is ACCUMULATED rather than multiplied by the count. At pathSS = 4 the two agree for every rectangle that test tries -- a sum of at most four equal doubles is rounded once, as the product is -- so this is not what keeps it exact today. It is written as the rasteriser writes it so that it still matches if pathSS ever stops being small.

It lives beside the rasteriser it has to agree with, so that a change to one is made in sight of the other.

func (Rect) At added in v0.33.0

func (r Rect) At(px, py int) float64

At is how much of pixel (px,py) the rectangle covers, 0..1.

func (Rect) Box added in v0.33.0

func (r Rect) Box(clampW, clampH int) (ox, oy, w, h int, ok bool)

Box is the integer box Rasterizer.Fill would return for this rectangle, clamped to the [0,clampW) x [0,clampH) surface, and ok=false when nothing of it is on it. It is the same rule the rasteriser uses, called rather than restated.

func (Rect) Whole added in v0.34.0

func (r Rect) Whole(clampW, clampH int) (ox, oy, w, h int, ok bool)

Whole is the box of pixels the rectangle covers ENTIRELY, where Rect.At returns exactly 1. A caller that asks about many pixels -- a clip consulted once per pixel painted through it -- answers most of them with two comparisons and no arithmetic at all.

ok=false when there is no such pixel, which is every rectangle thinner than one in either direction.

type SolidPaint added in v0.3.0

type SolidPaint struct{ Color color.RGBA }

SolidPaint is a Paint that returns one flat colour everywhere.

func (SolidPaint) ColorAt added in v0.3.0

func (s SolidPaint) ColorAt(_, _ float64) color.RGBA

ColorAt returns the flat colour, ignoring position.

type SpreadMethod added in v0.3.0

type SpreadMethod int

SpreadMethod decides the colour of points whose gradient parameter falls outside the [0, 1] range spanned by the stops.

const (
	// Pad clamps out-of-range parameters, extending the end stops' colours.
	Pad SpreadMethod = iota
	// Repeat tiles the gradient, wrapping the parameter modulo 1.
	Repeat
	// Reflect mirrors the gradient on every other tile (a triangle wave).
	Reflect
)

type Stop added in v0.3.0

type Stop struct {
	Offset float64
	Color  color.RGBA
}

Stop is one colour stop of a gradient at parameter Offset (nominally in [0, 1]).

type StrokeStyle added in v0.9.0

type StrokeStyle struct {
	Width      float64
	Cap        LineCap
	Join       LineJoin
	MiterLimit float64 // zero means the usual ten
	// Dash is the pattern of on and off lengths, beginning with an on length.
	// An odd pattern repeats to become even, so a single length means equal
	// dashes and gaps. An empty pattern draws a solid line.
	Dash      []float64
	DashPhase float64
}

A StrokeStyle is everything about a stroke except where it goes.

The zero value is butt caps and miter joins, which is what PostScript, PDF, SVG and Canvas all start from — set Width to something useful.

Jump to

Keyboard shortcuts

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