core

package
v0.21.2 Latest Latest
Warning

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

Go to latest
Published: Jan 28, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package core provides CPU-based rendering primitives for gg.

This package contains the core algorithms for scanline conversion, edge processing, and anti-aliased filling. It is independent of any GPU backend and can be used for pure software rendering.

Architecture

The core package follows the Skia/tiny-skia design where CPU rendering code is separated from GPU code. This allows:

  • Clear separation of concerns (CPU vs GPU)
  • Reuse of CPU algorithms in different contexts
  • Testing of rendering logic without GPU dependencies

Key Components

  • Fixed-point math (FDot6, FDot16) for precise curve rasterization
  • Edge types (Line, Quadratic, Cubic) with forward differencing
  • EdgeBuilder for converting paths to edges
  • AET (Active Edge Table) for scanline processing
  • Filler for analytic anti-aliased rendering
  • AlphaRuns for RLE-compressed coverage storage

Usage

The typical flow is:

  1. Convert path to edges using EdgeBuilder
  2. Process edges through AET (Active Edge Table)
  3. Accumulate coverage using AlphaRuns
  4. Call Filler to render with anti-aliasing

References

Index

Constants

View Source
const (
	// FDot6One is 1.0 in FDot6 representation (2^6 = 64).
	FDot6One FDot6 = 64

	// FDot6Half is 0.5 in FDot6 representation (2^5 = 32).
	FDot6Half FDot6 = 32

	// FDot6Shift is the number of fractional bits in FDot6.
	FDot6Shift = 6

	// FDot6Mask is the mask for the fractional part of FDot6.
	FDot6Mask = FDot6One - 1
)

Fixed-point constants for FDot6.

View Source
const (
	// FDot16One is 1.0 in FDot16 representation (2^16 = 65536).
	FDot16One FDot16 = 1 << 16

	// FDot16Half is 0.5 in FDot16 representation (2^15 = 32768).
	FDot16Half FDot16 = 1 << 15

	// FDot16Shift is the number of fractional bits in FDot16.
	FDot16Shift = 16

	// FDot16Mask is the mask for the fractional part of FDot16.
	FDot16Mask = FDot16One - 1
)

Fixed-point constants for FDot16.

View Source
const Epsilon = 1e-6

Epsilon is a small value for floating point comparison.

View Source
const MaxCoeffShift = 6

MaxCoeffShift limits the number of subdivisions for a curve. We store 1<<shift in a signed byte (int8), so max value is 1<<6 = 64. This limits the number of line segments per curve.

Variables

This section is empty.

Functions

func ChopCubicAtYExtrema

func ChopCubicAtYExtrema(src [4]GeomPoint, dst *[10]GeomPoint) int

ChopCubicAtYExtrema chops a cubic Bezier at its Y extrema (if any).

A cubic Bezier can have up to 2 Y extrema. This function splits the curve at all extrema to produce 1-3 monotonic cubic segments.

Parameters:

  • src: 4 control points [p0, p1, p2, p3]
  • dst: output array, must have capacity for 10 points

Returns:

  • number of chops (0, 1, or 2)
  • 0 means dst[0..4] contains the original (already monotonic)
  • 1 means dst[0..4] and dst[3..7] are two monotonic cubics
  • 2 means dst[0..4], dst[3..7], and dst[6..10] are three monotonic cubics

func ChopQuadAtYExtrema

func ChopQuadAtYExtrema(src [3]GeomPoint, dst *[5]GeomPoint) int

ChopQuadAtYExtrema chops a quadratic Bezier at its Y extremum (if any).

A quadratic Bezier can have at most one Y extremum. If the curve is not monotonic in Y, this function splits it at the extremum point.

Parameters:

  • src: 3 control points [p0, p1, p2]
  • dst: output array, must have capacity for 5 points

Returns:

  • number of chops (0 or 1)
  • 0 means dst[0..3] contains the original (already monotonic)
  • 1 means dst[0..3] and dst[2..5] are two monotonic quads

The control points are structured as:

  • dst[0] = first quad start
  • dst[1] = first quad control
  • dst[2] = first quad end = second quad start (shared)
  • dst[3] = second quad control
  • dst[4] = second quad end

func CubicIsYMonotonic

func CubicIsYMonotonic(p0, p1, p2, p3 GeomPoint) bool

CubicIsYMonotonic returns true if the cubic is monotonic in Y. A cubic is Y-monotonic if it has no Y extrema in (0, 1).

func FDot6CanConvertToFDot16

func FDot6CanConvertToFDot16(v FDot6) bool

FDot6CanConvertToFDot16 returns true if the FDot6 value can be converted to FDot16 without overflow.

func FDot6Ceil

func FDot6Ceil(v FDot6) int32

FDot6Ceil returns the ceiling of an FDot6.

func FDot6Floor

func FDot6Floor(v FDot6) int32

FDot6Floor returns the integer part (floor) of an FDot6.

func FDot6Round

func FDot6Round(v FDot6) int32

FDot6Round returns the nearest integer to an FDot6.

func FDot6SmallScale

func FDot6SmallScale(value uint8, dot6 FDot6) uint8

FDot6SmallScale scales a byte value by an FDot6 factor (0 to 64). Used for alpha blending calculations.

func FDot6ToFloat32

func FDot6ToFloat32(v FDot6) float32

FDot6ToFloat32 converts an FDot6 to float32.

func FDot6ToFloat64

func FDot6ToFloat64(v FDot6) float64

FDot6ToFloat64 converts an FDot6 to float64.

func FDot6UpShift

func FDot6UpShift(v FDot6, upShift int) int32

FDot6UpShift performs a left shift on an FDot6 value. Used in cubic edge coefficient computation.

func FDot16CeilToInt

func FDot16CeilToInt(v FDot16) int32

FDot16CeilToInt returns the ceiling of an FDot16.

func FDot16FloorToInt

func FDot16FloorToInt(v FDot16) int32

FDot16FloorToInt returns the integer part (floor) of an FDot16.

func FDot16RoundToInt

func FDot16RoundToInt(v FDot16) int32

FDot16RoundToInt returns the nearest integer to an FDot16.

func FDot16ToFloat32

func FDot16ToFloat32(v FDot16) float32

FDot16ToFloat32 converts an FDot16 to float32.

func FDot16ToFloat64

func FDot16ToFloat64(v FDot16) float64

FDot16ToFloat64 converts an FDot16 to float64.

func FillPath

func FillPath(
	eb *EdgeBuilder,
	width, height int,
	fillRule FillRule,
	callback func(y int, runs *AlphaRuns),
)

FillPath is a convenience function that creates a filler and fills a path. For repeated fills, create a filler once and reuse it.

func FillToBuffer

func FillToBuffer(
	eb *EdgeBuilder,
	width, height int,
	fillRule FillRule,
	buffer []uint8,
)

FillToBuffer fills a path and writes coverage to a buffer. The buffer must have width * height elements. Coverage values are written as 0-255 alpha values.

func QuadIsYMonotonic

func QuadIsYMonotonic(p0, p1, p2 GeomPoint) bool

QuadIsYMonotonic returns true if the quadratic is monotonic in Y. A quadratic is Y-monotonic if its control point's Y is between the endpoints' Y values. This is more permissive than isNotMonotonic to handle curves flattened at extrema.

Types

type ActiveEdge

type ActiveEdge struct {
	Edge *Edge
	X    float32 // Current X position at current scanline
}

ActiveEdge holds an edge with its current X position.

type AlphaRun

type AlphaRun struct {
	X     int   // Starting X position
	Alpha uint8 // Coverage value (0-255)
	Count int   // Run length
}

AlphaRun represents a single run of coverage. Used for iteration output.

type AlphaRuns

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

AlphaRuns provides run-length-encoded (RLE) storage for coverage values.

This is efficient for paths with long horizontal spans of constant coverage. Instead of storing per-pixel alpha values, it stores runs of consecutive pixels with the same alpha.

The implementation follows tiny-skia's alpha_runs.rs pattern but with Go 1.25+ iterators for efficient traversal.

Usage:

ar := NewAlphaRuns(width)
ar.Reset()
ar.Add(10, 128, 20, 128) // Add coverage from x=10, 20 pixels wide
for x, alpha := range ar.Iter() {
    // Process pixel at x with alpha value
}

func NewAlphaRuns

func NewAlphaRuns(width int) *AlphaRuns

NewAlphaRuns creates a new AlphaRuns buffer for the given width.

func (*AlphaRuns) Add

func (ar *AlphaRuns) Add(x int, startAlpha uint8, middleCount int, endAlpha uint8)

Add inserts coverage into the buffer.

Parameters:

  • x: starting x coordinate
  • startAlpha: alpha for first pixel (fractional left edge)
  • middleCount: number of full-coverage pixels
  • endAlpha: alpha for last pixel (fractional right edge)

The method accumulates coverage - multiple Add calls can contribute to the same pixels. This is essential for correct winding rule handling.

func (*AlphaRuns) AddWithCoverage

func (ar *AlphaRuns) AddWithCoverage(x int, startAlpha uint8, middleCount int, endAlpha uint8, maxValue uint8)

AddWithCoverage inserts coverage with a specified maximum value. This is useful when the coverage itself represents partial opacity.

func (*AlphaRuns) Clear

func (ar *AlphaRuns) Clear()

Clear sets all alpha values to zero and resets to a single run.

func (*AlphaRuns) CopyTo

func (ar *AlphaRuns) CopyTo(dst []uint8)

CopyTo copies the coverage values to a destination slice. The destination must have at least ar.width elements.

func (*AlphaRuns) GetAlpha

func (ar *AlphaRuns) GetAlpha(x int) uint8

GetAlpha returns the alpha value at position x. Returns 0 if x is out of bounds.

func (*AlphaRuns) IsEmpty

func (ar *AlphaRuns) IsEmpty() bool

IsEmpty returns true if the scanline contains only a single run of alpha 0.

func (*AlphaRuns) Iter

func (ar *AlphaRuns) Iter() iter.Seq2[int, uint8]

Iter returns an iterator over all runs with non-zero alpha. Each iteration yields the X position and alpha value. This uses Go 1.25+ iter.Seq2 for efficient iteration.

func (*AlphaRuns) IterRuns

func (ar *AlphaRuns) IterRuns() iter.Seq[AlphaRun]

IterRuns returns an iterator over runs (not individual pixels). More efficient when processing entire runs at once.

func (*AlphaRuns) Reset

func (ar *AlphaRuns) Reset()

Reset reinitializes the buffer for a new scanline. This is O(1) - it doesn't clear the entire buffer.

func (*AlphaRuns) SetOffset

func (ar *AlphaRuns) SetOffset(offset int)

SetOffset sets the offset for the next Add call. Use 0 when starting a new scanline.

func (*AlphaRuns) Width

func (ar *AlphaRuns) Width() int

Width returns the scanline width.

type AnalyticFiller

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

AnalyticFiller computes per-pixel coverage using exact geometric calculations.

Unlike supersampling approaches that sample multiple points per pixel, analytic AA computes the exact area of the shape within each pixel using trapezoidal integration. This provides higher quality anti-aliasing with no supersampling overhead.

The algorithm is based on vello's CPU fine rasterizer (fine.rs), which uses the following approach:

  1. For each edge crossing a pixel row, compute the Y range it covers
  2. Find the X intersections at the top and bottom of the pixel
  3. Compute the trapezoidal area within the pixel bounds
  4. Accumulate coverage based on winding direction

Usage:

filler := NewAnalyticFiller(width, height)
filler.Fill(edgeBuilder, FillRuleNonZero, func(y int, runs *AlphaRuns) {
    // Blend alpha runs to the destination row
})

func NewAnalyticFiller

func NewAnalyticFiller(width, height int) *AnalyticFiller

NewAnalyticFiller creates a new analytic filler for the given dimensions.

func (*AnalyticFiller) AlphaRuns

func (af *AnalyticFiller) AlphaRuns() *AlphaRuns

AlphaRuns returns the alpha runs for the last processed scanline.

func (*AnalyticFiller) Coverage

func (af *AnalyticFiller) Coverage() []float32

Coverage returns the raw coverage buffer for the last processed scanline. Values are in [0, 1] range. The buffer is reused between scanlines.

func (*AnalyticFiller) Fill

func (af *AnalyticFiller) Fill(
	eb *EdgeBuilder,
	fillRule FillRule,
	callback func(y int, runs *AlphaRuns),
)

Fill renders a path using analytic coverage calculation.

Parameters:

  • eb: EdgeBuilder containing the path edges
  • fillRule: NonZero or EvenOdd fill rule
  • callback: called for each scanline with the alpha runs

The callback receives the Y coordinate and AlphaRuns for that scanline. The caller is responsible for blending the runs to the destination.

func (*AnalyticFiller) Height

func (af *AnalyticFiller) Height() int

Height returns the filler height.

func (*AnalyticFiller) Reset

func (af *AnalyticFiller) Reset()

Reset clears the filler state for reuse.

func (*AnalyticFiller) Width

func (af *AnalyticFiller) Width() int

Width returns the filler width.

type CubicEdge

type CubicEdge struct {
	// TopY is the curve's overall top scanline (for AET insertion timing).
	// This is set once at creation and never changes, unlike line.FirstY
	// which changes as we step through curve segments.
	TopY int32

	// BottomY is the curve's overall bottom scanline.
	BottomY int32
	// contains filtered or unexported fields
}

CubicEdge represents a cubic Bezier curve for scanline conversion. Uses forward differencing for O(1) per-step evaluation.

A cubic Bezier is defined by:

p(t) = (1-t)^3 * p0 + 3*t*(1-t)^2 * p1 + 3*t^2*(1-t) * p2 + t^3 * p3

Rewritten in polynomial form:

p(t) = A*t^3 + B*t^2 + C*t + D
where:
A = -p0 + 3*p1 - 3*p2 + p3
B = 3*p0 - 6*p1 + 3*p2
C = -3*p0 + 3*p1
D = p0

func NewCubicEdge

func NewCubicEdge(p0, p1, p2, p3 CurvePoint, shift int) *CubicEdge

NewCubicEdge creates a cubic edge from control points. Returns nil if the curve has no vertical extent.

Parameters:

  • p0: start point
  • p1: first control point
  • p2: second control point
  • p3: end point
  • shift: AA shift (0 for no AA, 2 for 4x AA quality)

func (*CubicEdge) CurveCount

func (c *CubicEdge) CurveCount() int8

CurveCount returns the remaining number of segments. For cubic, this is negative (counts up to 0).

func (*CubicEdge) Line

func (c *CubicEdge) Line() *LineEdge

Line returns the current line segment for AET processing.

func (*CubicEdge) Update

func (c *CubicEdge) Update() bool

Update advances the cubic curve to the next line segment. Returns true if a valid segment was produced.

Forward differencing for cubic:

newx = oldx + (dx >> dshift)
dx += ddx >> ddshift
ddx += dddx  // Third derivative is constant!

func (*CubicEdge) Winding

func (c *CubicEdge) Winding() int8

Winding returns the winding direction.

type CurveAwareAET

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

CurveAwareAET is an Active Edge Table that handles all edge types.

Unlike a simple line-based AET, this table can hold LineEdge, QuadraticEdge, and CubicEdge types. Curve edges are stepped through their segments during scanline traversal using forward differencing.

The AET maintains edges sorted by their current X position, which is essential for correct scanline rasterization and winding number calculation.

Usage:

aet := NewCurveAwareAET()
for y := yMin; y < yMax; y++ {
    aet.RemoveExpired(y)
    for edge := range eb.EdgesStartingAt(y) {
        aet.Insert(edge)
    }
    aet.StepCurves()
    aet.SortByX()
    // Process edges in X order
}

func NewCurveAwareAET

func NewCurveAwareAET() *CurveAwareAET

NewCurveAwareAET creates a new curve-aware Active Edge Table.

func (*CurveAwareAET) AdvanceX

func (aet *CurveAwareAET) AdvanceX()

AdvanceX advances all edges to the next scanline. This updates the X position based on the slope (DX).

func (*CurveAwareAET) ComputeSpans

func (aet *CurveAwareAET) ComputeSpans(_ int32, fillRule FillRule, callback func(x, width int, coverage float32))

ComputeSpans computes the horizontal spans for the current scanline. This implements the core of scanline rasterization:

  1. For each pair of edges, compute the winding contribution
  2. Apply fill rule to determine coverage
  3. Return spans with their coverage values

The callback is called for each span with x, width, and coverage.

func (*CurveAwareAET) EdgeAt

func (aet *CurveAwareAET) EdgeAt(i int) *CurveEdgeVariant

EdgeAt returns the edge at index i. Panics if i is out of bounds.

func (*CurveAwareAET) Edges

func (aet *CurveAwareAET) Edges() []CurveEdgeVariant

Edges returns the slice of edge variants for processing. The edges are in X-sorted order after SortByX() is called.

func (*CurveAwareAET) ForEach

func (aet *CurveAwareAET) ForEach(fn func(edge *CurveEdgeVariant) bool)

ForEach calls fn for each edge in the AET. Iteration stops if fn returns false.

func (*CurveAwareAET) Insert

func (aet *CurveAwareAET) Insert(e CurveEdgeVariant)

Insert adds an edge to the AET. The edge's current line segment is used for initial positioning.

func (*CurveAwareAET) IsEmpty

func (aet *CurveAwareAET) IsEmpty() bool

IsEmpty returns true if there are no active edges.

func (*CurveAwareAET) Len

func (aet *CurveAwareAET) Len() int

Len returns the number of active edges.

func (*CurveAwareAET) RemoveExpired

func (aet *CurveAwareAET) RemoveExpired(y int32)

RemoveExpired removes edges that have ended before scanline y. An edge is expired when its LastY < y.

func (*CurveAwareAET) RemoveExpiredSubpixel

func (aet *CurveAwareAET) RemoveExpiredSubpixel(ySubpixel int32)

RemoveExpiredSubpixel removes edges that have completely ended. Uses the edge's overall BottomY (not current segment) for expiration.

This is used when coordinates are in sub-pixel space with AA scaling. An edge is expired when its BottomY <= ySubpixel.

func (*CurveAwareAET) Reset

func (aet *CurveAwareAET) Reset()

Reset clears the AET for reuse.

func (*CurveAwareAET) SortByX

func (aet *CurveAwareAET) SortByX()

SortByX sorts all edges by their current X position. This is essential for correct scanline processing.

func (*CurveAwareAET) StepCurves

func (aet *CurveAwareAET) StepCurves()

StepCurves advances curve edges to their next line segment. This should be called once per scanline after RemoveExpired.

For each curve edge whose current segment has ended, Update() is called to compute the next segment. This is where forward differencing happens.

type CurveEdgeVariant

type CurveEdgeVariant struct {
	Type      EdgeType
	Line      *LineEdge
	Quadratic *QuadraticEdge
	Cubic     *CubicEdge
}

CurveEdgeVariant wraps different edge types for uniform handling in the AET. This is Go's equivalent of Rust's enum Edge { Line, Quadratic, Cubic }.

func NewCubicEdgeVariant

func NewCubicEdgeVariant(p0, p1, p2, p3 CurvePoint, shift int) *CurveEdgeVariant

NewCubicEdgeVariant creates a CurveEdgeVariant for a cubic.

func NewLineEdgeVariant

func NewLineEdgeVariant(p0, p1 CurvePoint, shift int) *CurveEdgeVariant

NewLineEdgeVariant creates a CurveEdgeVariant for a line.

func NewQuadraticEdgeVariant

func NewQuadraticEdgeVariant(p0, p1, p2 CurvePoint, shift int) *CurveEdgeVariant

NewQuadraticEdgeVariant creates a CurveEdgeVariant for a quadratic.

func (*CurveEdgeVariant) AsLine

func (e *CurveEdgeVariant) AsLine() *LineEdge

AsLine returns the LineEdge for this edge, regardless of type. All edge types contain a LineEdge for AET compatibility.

func (*CurveEdgeVariant) BottomY

func (e *CurveEdgeVariant) BottomY() int32

BottomY returns the curve's overall bottom Y coordinate. For line edges, this is the same as LastY + 1. For curve edges, this is the curve's original bottom Y.

func (*CurveEdgeVariant) TopY

func (e *CurveEdgeVariant) TopY() int32

TopY returns the curve's overall top Y coordinate (for AET insertion timing). For line edges, this is the same as FirstY. For curve edges, this is the curve's original top Y before stepping.

func (*CurveEdgeVariant) Update

func (e *CurveEdgeVariant) Update() bool

Update advances a curve edge to the next line segment. Returns true if a valid segment was produced. For line edges, always returns false (no more segments).

type CurveEdger

type CurveEdger interface {
	// Update advances the curve to the next line segment.
	// Returns true if a valid segment was produced, false if done.
	Update() bool

	// Line returns the current line segment for AET processing.
	Line() *LineEdge

	// CurveCount returns the remaining number of segments.
	CurveCount() int8

	// Winding returns the winding direction (+1 or -1).
	Winding() int8
}

CurveEdger is the interface implemented by curve edges (quadratic, cubic). It allows polymorphic handling of different curve types in the AET.

type CurvePoint

type CurvePoint struct {
	X, Y float32
}

CurvePoint represents a 2D point for curve edge construction. Using separate type to avoid coupling with scene.Point.

type Edge

type Edge struct {
	// YMin is the minimum Y coordinate (top of edge)
	YMin float32

	// YMax is the maximum Y coordinate (bottom of edge)
	YMax float32

	// XAtYMin is the X coordinate at YMin
	XAtYMin float32

	// DXDY is the inverse slope: change in X per unit Y
	DXDY float32

	// Winding indicates the direction: +1 for downward, -1 for upward
	Winding int8
}

Edge represents a line segment for scanline conversion. Edges are derived from path segments (lines, curves flattened to lines) and used by the Active Edge Table algorithm.

func NewEdge

func NewEdge(x0, y0, x1, y1 float32) *Edge

NewEdge creates a new edge from two points. Returns nil if the edge is horizontal (no Y extent).

func NewEdgeWithWinding

func NewEdgeWithWinding(x0, y0, x1, y1 float32, winding int8) *Edge

NewEdgeWithWinding creates a new edge with explicit winding.

func (*Edge) ContainsY

func (e *Edge) ContainsY(y float32) bool

ContainsY returns true if Y is within the edge's Y range (inclusive).

func (*Edge) Height

func (e *Edge) Height() float32

Height returns the vertical extent of the edge.

func (*Edge) IsActiveAt

func (e *Edge) IsActiveAt(y float32) bool

IsActiveAt returns true if the edge is active at the given Y coordinate. An edge is active when YMin <= y < YMax.

func (*Edge) XAtY

func (e *Edge) XAtY(y float32) float32

XAtY calculates the X coordinate at a given Y value. This is the core calculation for scanline intersection.

type EdgeBuilder

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

EdgeBuilder converts paths to typed edges for analytic anti-aliasing.

By default, EdgeBuilder preserves curve information (QuadraticEdge, CubicEdge) which enables higher quality anti-aliasing by evaluating curve coverage analytically.

Alternatively, with flattenCurves=true, all curves are converted to line segments at build time. This is simpler and more reliable for the AnalyticFiller.

The builder ensures all edges are Y-monotonic by chopping curves at their Y extrema before creating edge objects.

Usage:

eb := NewEdgeBuilder(2) // 4x AA quality
eb.SetFlattenCurves(true) // Flatten curves to lines
eb.BuildFromPath(path, IdentityTransform{})

for edge := range eb.AllEdges() {
    // Process edges sorted by top Y
}

Reference: tiny-skia/src/edge_builder.rs

func NewEdgeBuilder

func NewEdgeBuilder(aaShift int) *EdgeBuilder

NewEdgeBuilder creates a new edge builder with specified AA quality.

Parameters:

  • aaShift: anti-aliasing shift (0 = no AA, 2 = 4x AA quality)

Higher shift values provide better AA quality but require more memory and computation.

func (*EdgeBuilder) AAShift

func (eb *EdgeBuilder) AAShift() int

AAShift returns the anti-aliasing shift value.

func (*EdgeBuilder) AllEdges

func (eb *EdgeBuilder) AllEdges() iter.Seq[CurveEdgeVariant]

AllEdges returns an iterator over all edges sorted by top Y coordinate.

This uses Go 1.25+ iter.Seq for efficient iteration. Edges are yielded in scanline order (top to bottom), which is required for Active Edge Table processing.

Usage:

for edge := range eb.AllEdges() {
    line := edge.AsLine()
    // Process edge.Line().FirstY, etc.
}

func (*EdgeBuilder) Bounds

func (eb *EdgeBuilder) Bounds() Rect

Bounds returns the bounding rectangle of all edges.

func (*EdgeBuilder) BuildFromPath

func (eb *EdgeBuilder) BuildFromPath(path PathLike, transform Transform)

BuildFromPath processes a PathLike and creates typed edges.

This is the main entry point for path processing. It:

  1. Iterates through path verbs
  2. Applies transform to all points
  3. Chops curves at Y extrema for monotonicity
  4. Creates appropriate edge types

Parameters:

  • path: the path to process (implements PathLike interface)
  • transform: transformation to apply to all points

func (*EdgeBuilder) CubicEdgeCount

func (eb *EdgeBuilder) CubicEdgeCount() int

CubicEdgeCount returns the number of cubic edges.

func (*EdgeBuilder) CubicEdges

func (eb *EdgeBuilder) CubicEdges() iter.Seq[*CubicEdge]

CubicEdges returns an iterator over cubic edges only.

func (*EdgeBuilder) EdgeCount

func (eb *EdgeBuilder) EdgeCount() int

EdgeCount returns the total number of edges.

func (*EdgeBuilder) FlattenCurves

func (eb *EdgeBuilder) FlattenCurves() bool

FlattenCurves returns whether curve flattening is enabled.

func (*EdgeBuilder) IsEmpty

func (eb *EdgeBuilder) IsEmpty() bool

IsEmpty returns true if no edges have been added.

func (*EdgeBuilder) LineEdgeCount

func (eb *EdgeBuilder) LineEdgeCount() int

LineEdgeCount returns the number of line edges.

func (*EdgeBuilder) LineEdges

func (eb *EdgeBuilder) LineEdges() iter.Seq[*LineEdge]

LineEdges returns an iterator over line edges only.

func (*EdgeBuilder) QuadraticEdgeCount

func (eb *EdgeBuilder) QuadraticEdgeCount() int

QuadraticEdgeCount returns the number of quadratic edges.

func (*EdgeBuilder) QuadraticEdges

func (eb *EdgeBuilder) QuadraticEdges() iter.Seq[*QuadraticEdge]

QuadraticEdges returns an iterator over quadratic edges only.

func (*EdgeBuilder) Reset

func (eb *EdgeBuilder) Reset()

Reset clears the builder for reuse without deallocating memory.

func (*EdgeBuilder) SetFlattenCurves

func (eb *EdgeBuilder) SetFlattenCurves(flatten bool)

SetFlattenCurves enables or disables curve flattening mode. When enabled, all curves are converted to line segments at build time. This is simpler and more reliable for AnalyticFiller.

type EdgeList

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

EdgeList is a collection of edges with utility methods.

func NewEdgeList

func NewEdgeList() *EdgeList

NewEdgeList creates a new empty edge list.

func (*EdgeList) Add

func (el *EdgeList) Add(e *Edge)

Add adds an edge to the list.

func (*EdgeList) AddLine

func (el *EdgeList) AddLine(x0, y0, x1, y1 float32)

AddLine adds a line segment as an edge.

func (*EdgeList) Bounds

func (el *EdgeList) Bounds() (minX, minY, maxX, maxY float32)

Bounds returns the bounding rectangle of all edges.

func (*EdgeList) Edges

func (el *EdgeList) Edges() []Edge

Edges returns the underlying slice.

func (*EdgeList) Len

func (el *EdgeList) Len() int

Len returns the number of edges.

func (*EdgeList) Reset

func (el *EdgeList) Reset()

Reset clears the edge list for reuse.

func (*EdgeList) SortByYMin

func (el *EdgeList) SortByYMin()

SortByYMin sorts edges by their minimum Y coordinate.

type EdgeRange

type EdgeRange struct {
	StartX    int32   // Left X of the span
	EndX      int32   // Right X of the span
	Winding   int32   // Accumulated winding number
	Coverage  float32 // Accumulated coverage
	EdgeCount int     // Number of edges in this range
}

EdgeRange represents a range of edges for a scanline span.

type EdgeType

type EdgeType int

EdgeType represents the type of an edge for polymorphic handling.

const (
	// EdgeTypeLine represents a simple line edge.
	EdgeTypeLine EdgeType = iota

	// EdgeTypeQuadratic represents a quadratic Bezier edge.
	EdgeTypeQuadratic

	// EdgeTypeCubic represents a cubic Bezier edge.
	EdgeTypeCubic
)

type FDot6

type FDot6 = int32

FDot6 is a 26.6 fixed-point number (6 fractional bits). Used for intermediate calculations in edge setup where sub-pixel precision (1/64 pixel) is sufficient.

Range: approximately -33 million to +33 million with 1/64 precision.

func FDot6FromFloat32

func FDot6FromFloat32(f float32) FDot6

FDot6FromFloat32 converts a float32 to FDot6. Values are scaled by 64 and truncated toward zero.

func FDot6FromFloat64

func FDot6FromFloat64(f float64) FDot6

FDot6FromFloat64 converts a float64 to FDot6.

func FDot6FromInt

func FDot6FromInt(n int32) FDot6

FDot6FromInt converts an integer to FDot6. The integer must fit in 26 bits (approximately -33M to +33M).

type FDot8

type FDot8 = int32

FDot8 is a 24.8 fixed-point number (8 fractional bits). Used for anti-aliased coverage values.

func FDot8FromFDot16

func FDot8FromFDot16(v FDot16) FDot8

FDot8FromFDot16 converts an FDot16 to FDot8 with rounding.

type FDot16

type FDot16 = int32

FDot16 is a 16.16 fixed-point number (16 fractional bits). Used for positions, slopes, and forward differencing coefficients where higher precision is needed.

Range: approximately -32768 to +32768 with 1/65536 precision.

func FDot6Div

func FDot6Div(a, b FDot6) FDot16

FDot6Div divides two FDot6 values and returns an FDot16. This is used for computing slopes (dx / dy). If the numerator fits in 16 bits, uses fast path.

func FDot6ToFDot16

func FDot6ToFDot16(v FDot6) FDot16

FDot6ToFDot16 converts an FDot6 to FDot16. This is a left shift by 10 bits (16 - 6 = 10).

func FDot6ToFixedDiv2

func FDot6ToFixedDiv2(v FDot6) FDot16

FDot6ToFixedDiv2 converts an FDot6 to FDot16 divided by 2. This is used in quadratic edge setup to avoid overflow. The result is (value / 2) in FDot16 representation.

func FDot16Div

func FDot16Div(numer, denom int32) FDot16

FDot16Div divides two FDot6/FDot16 values and returns an FDot16. Uses 64-bit intermediate to avoid overflow.

func FDot16FastDiv

func FDot16FastDiv(a, b FDot6) FDot16

FDot16FastDiv divides two FDot6 values using fast 32-bit math. Only valid when the numerator fits in 16 bits.

func FDot16FromFloat32

func FDot16FromFloat32(f float32) FDot16

FDot16FromFloat32 converts a float32 to FDot16.

func FDot16FromFloat64

func FDot16FromFloat64(f float64) FDot16

FDot16FromFloat64 converts a float64 to FDot16.

func FDot16Mul

func FDot16Mul(a, b FDot16) FDot16

FDot16Mul multiplies two FDot16 values. Uses 64-bit intermediate to avoid overflow, then truncates.

type FillRule

type FillRule int

FillRule determines how overlapping paths are filled.

const (
	// FillRuleNonZero fills regions where the winding number is non-zero.
	// This is the default fill rule and handles self-intersecting paths well.
	FillRuleNonZero FillRule = iota

	// FillRuleEvenOdd fills regions where the winding number is odd.
	// This creates a checkerboard pattern for self-intersecting paths.
	FillRuleEvenOdd
)

func (FillRule) String

func (fr FillRule) String() string

String returns the string representation of the fill rule.

type GeomPoint

type GeomPoint struct {
	X, Y float32
}

GeomPoint represents a 2D point for geometry calculations. Using a local type to avoid coupling with scene.Point and CurvePoint.

func ZeroGeomPoint

func ZeroGeomPoint() GeomPoint

ZeroGeomPoint returns the origin point.

type IdentityTransform

type IdentityTransform struct{}

IdentityTransform is a no-op transform that returns points unchanged.

func (IdentityTransform) TransformPoint

func (IdentityTransform) TransformPoint(x, y float32) (float32, float32)

TransformPoint returns the point unchanged.

type LineEdge

type LineEdge struct {
	// Linked list pointers (indices into edge array).
	// Using Option<u32> pattern from Rust as nullable int32.
	Prev int32
	Next int32

	// X is the current X position in FDot16 (16.16 fixed-point).
	X FDot16

	// DX is the slope: change in X per scanline (in FDot16).
	DX FDot16

	// FirstY is the first scanline this edge covers.
	FirstY int32

	// LastY is the last scanline this edge covers (inclusive).
	LastY int32

	// Winding indicates direction: +1 for downward, -1 for upward.
	Winding int8
}

LineEdge represents a single line segment in the Active Edge Table. This is the base type that QuadraticEdge and CubicEdge use internally. Derived from tiny-skia's LineEdge.

func NewLineEdge

func NewLineEdge(p0, p1 CurvePoint, shift int) *LineEdge

NewLineEdge creates a new line edge from two points. Returns nil if the edge is horizontal (no Y extent).

Parameters:

  • p0, p1: endpoints in pixel coordinates
  • shift: AA shift (0 for no AA, 2 for 4x AA quality)

func (*LineEdge) IsVertical

func (e *LineEdge) IsVertical() bool

IsVertical returns true if the edge has zero slope.

type PathLike

type PathLike interface {
	// IsEmpty returns true if the path has no commands.
	IsEmpty() bool

	// Verbs returns the verb stream.
	Verbs() []PathVerb

	// Points returns the point data stream (pairs of float32 x,y coordinates).
	Points() []float32
}

PathLike is the interface for path-like objects that can be processed by EdgeBuilder. This allows core package to work with any path implementation without importing scene.

type PathVerb

type PathVerb uint8

PathVerb represents a path construction command. Mirrors scene.PathVerb for core package independence.

const (
	// VerbMoveTo moves the current point without drawing.
	VerbMoveTo PathVerb = iota
	// VerbLineTo draws a line to the specified point.
	VerbLineTo
	// VerbQuadTo draws a quadratic Bezier curve.
	VerbQuadTo
	// VerbCubicTo draws a cubic Bezier curve.
	VerbCubicTo
	// VerbClose closes the current subpath.
	VerbClose
)

Path verb constants.

type QuadraticEdge

type QuadraticEdge struct {
	// TopY is the curve's overall top scanline (for AET insertion timing).
	// This is set once at creation and never changes, unlike line.FirstY
	// which changes as we step through curve segments.
	TopY int32

	// BottomY is the curve's overall bottom scanline.
	BottomY int32
	// contains filtered or unexported fields
}

QuadraticEdge represents a quadratic Bezier curve for scanline conversion. Uses forward differencing for O(1) per-step evaluation.

A quadratic Bezier is defined by:

p(t) = (1-t)^2 * p0 + 2*t*(1-t) * p1 + t^2 * p2

Rewritten in polynomial form:

p(t) = A*t^2 + B*t + C
where A = p0 - 2*p1 + p2, B = 2*(p1 - p0), C = p0

func NewQuadraticEdge

func NewQuadraticEdge(p0, p1, p2 CurvePoint, shift int) *QuadraticEdge

NewQuadraticEdge creates a quadratic edge from control points. Returns nil if the curve has no vertical extent.

Parameters:

  • p0: start point
  • p1: control point
  • p2: end point
  • shift: AA shift (0 for no AA, 2 for 4x AA quality)

func (*QuadraticEdge) CurveCount

func (q *QuadraticEdge) CurveCount() int8

CurveCount returns the remaining number of segments.

func (*QuadraticEdge) Line

func (q *QuadraticEdge) Line() *LineEdge

Line returns the current line segment for AET processing.

func (*QuadraticEdge) Update

func (q *QuadraticEdge) Update() bool

Update advances the quadratic curve to the next line segment. Returns true if a valid segment was produced.

This is the core of the forward differencing algorithm:

newx = oldx + (dx >> shift)
dx += ddx  // Second derivative is constant!

func (*QuadraticEdge) Winding

func (q *QuadraticEdge) Winding() int8

Winding returns the winding direction.

type Rect

type Rect struct {
	MinX, MinY float32
	MaxX, MaxY float32
}

Rect represents a bounding rectangle.

func EmptyRect

func EmptyRect() Rect

EmptyRect returns an empty rectangle (inverted bounds for union operations).

func (Rect) IsEmpty

func (r Rect) IsEmpty() bool

IsEmpty returns true if the rectangle has no area.

type ScenePathAdapter

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

ScenePathAdapter adapts a scene-like path to the core.PathLike interface. This allows EdgeBuilder to work with any path implementation without creating an import cycle.

Usage:

// In a package that can import scene:
type scenePathWrapper struct {
    path *scene.Path
}
func (w *scenePathWrapper) IsEmpty() bool { return w.path.IsEmpty() }
func (w *scenePathWrapper) Verbs() []core.PathVerb {
    // Convert scene.PathVerb to core.PathVerb
    return convertVerbs(w.path.Verbs())
}
func (w *scenePathWrapper) Points() []float32 { return w.path.Points() }

Then create an EdgeBuilder and use:

eb.BuildFromPath(&scenePathWrapper{path}, core.IdentityTransform{})

func NewScenePathAdapter

func NewScenePathAdapter(isEmpty bool, verbs []PathVerb, points []float32) *ScenePathAdapter

NewScenePathAdapter creates a new adapter from verb and point data.

This is a low-level constructor. Higher-level packages should provide convenience functions that convert from their path types.

func (*ScenePathAdapter) IsEmpty

func (a *ScenePathAdapter) IsEmpty() bool

IsEmpty returns true if the path has no commands.

func (*ScenePathAdapter) Points

func (a *ScenePathAdapter) Points() []float32

Points returns the point data stream.

func (*ScenePathAdapter) Verbs

func (a *ScenePathAdapter) Verbs() []PathVerb

Verbs returns the verb stream.

type SimpleAET

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

SimpleAET manages active edges during scanline conversion. This is the simple linear edge table (not curve-aware).

func NewSimpleAET

func NewSimpleAET() *SimpleAET

NewSimpleAET creates a new simple active edge table.

func (*SimpleAET) Active

func (aet *SimpleAET) Active() []ActiveEdge

Active returns the list of active edges for iteration.

func (*SimpleAET) InsertEdge

func (aet *SimpleAET) InsertEdge(e *Edge, y float32)

InsertEdge adds an edge to the active list.

func (*SimpleAET) Len

func (aet *SimpleAET) Len() int

Len returns the number of active edges.

func (*SimpleAET) RemoveExpired

func (aet *SimpleAET) RemoveExpired(y float32)

RemoveExpired removes edges that end at or before the given Y.

func (*SimpleAET) Reset

func (aet *SimpleAET) Reset()

Reset clears the active edge table.

func (*SimpleAET) SortByX

func (aet *SimpleAET) SortByX()

SortByX sorts active edges by their current X position.

func (*SimpleAET) UpdateX

func (aet *SimpleAET) UpdateX(y float32)

UpdateX updates X positions for all active edges at the new Y.

type Transform

type Transform interface {
	// TransformPoint transforms a point (x, y) and returns the result.
	TransformPoint(x, y float32) (float32, float32)
}

Transform is the interface for affine transformations. This allows core package to work with any transform implementation.

Jump to

Keyboard shortcuts

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