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:
- Convert path to edges using EdgeBuilder
- Process edges through AET (Active Edge Table)
- Accumulate coverage using AlphaRuns
- Call Filler to render with anti-aliasing
References ¶
Index ¶
- Constants
- func ChopCubicAtYExtrema(src [4]GeomPoint, dst *[10]GeomPoint) int
- func ChopQuadAtYExtrema(src [3]GeomPoint, dst *[5]GeomPoint) int
- func CubicIsYMonotonic(p0, p1, p2, p3 GeomPoint) bool
- func FDot6CanConvertToFDot16(v FDot6) bool
- func FDot6Ceil(v FDot6) int32
- func FDot6Floor(v FDot6) int32
- func FDot6Round(v FDot6) int32
- func FDot6SmallScale(value uint8, dot6 FDot6) uint8
- func FDot6ToFloat32(v FDot6) float32
- func FDot6ToFloat64(v FDot6) float64
- func FDot6UpShift(v FDot6, upShift int) int32
- func FDot16CeilToInt(v FDot16) int32
- func FDot16FloorToInt(v FDot16) int32
- func FDot16RoundToInt(v FDot16) int32
- func FDot16ToFloat32(v FDot16) float32
- func FDot16ToFloat64(v FDot16) float64
- func FillPath(eb *EdgeBuilder, width, height int, fillRule FillRule, ...)
- func FillToBuffer(eb *EdgeBuilder, width, height int, fillRule FillRule, buffer []uint8)
- func QuadIsYMonotonic(p0, p1, p2 GeomPoint) bool
- type ActiveEdge
- type AlphaRun
- type AlphaRuns
- func (ar *AlphaRuns) Add(x int, startAlpha uint8, middleCount int, endAlpha uint8)
- func (ar *AlphaRuns) AddWithCoverage(x int, startAlpha uint8, middleCount int, endAlpha uint8, maxValue uint8)
- func (ar *AlphaRuns) Clear()
- func (ar *AlphaRuns) CopyTo(dst []uint8)
- func (ar *AlphaRuns) GetAlpha(x int) uint8
- func (ar *AlphaRuns) IsEmpty() bool
- func (ar *AlphaRuns) Iter() iter.Seq2[int, uint8]
- func (ar *AlphaRuns) IterRuns() iter.Seq[AlphaRun]
- func (ar *AlphaRuns) Reset()
- func (ar *AlphaRuns) SetOffset(offset int)
- func (ar *AlphaRuns) Width() int
- type AnalyticFiller
- func (af *AnalyticFiller) AlphaRuns() *AlphaRuns
- func (af *AnalyticFiller) Coverage() []float32
- func (af *AnalyticFiller) Fill(eb *EdgeBuilder, fillRule FillRule, callback func(y int, runs *AlphaRuns))
- func (af *AnalyticFiller) Height() int
- func (af *AnalyticFiller) Reset()
- func (af *AnalyticFiller) Width() int
- type CubicEdge
- type CurveAwareAET
- func (aet *CurveAwareAET) AdvanceX()
- func (aet *CurveAwareAET) ComputeSpans(_ int32, fillRule FillRule, callback func(x, width int, coverage float32))
- func (aet *CurveAwareAET) EdgeAt(i int) *CurveEdgeVariant
- func (aet *CurveAwareAET) Edges() []CurveEdgeVariant
- func (aet *CurveAwareAET) ForEach(fn func(edge *CurveEdgeVariant) bool)
- func (aet *CurveAwareAET) Insert(e CurveEdgeVariant)
- func (aet *CurveAwareAET) IsEmpty() bool
- func (aet *CurveAwareAET) Len() int
- func (aet *CurveAwareAET) RemoveExpired(y int32)
- func (aet *CurveAwareAET) RemoveExpiredSubpixel(ySubpixel int32)
- func (aet *CurveAwareAET) Reset()
- func (aet *CurveAwareAET) SortByX()
- func (aet *CurveAwareAET) StepCurves()
- type CurveEdgeVariant
- type CurveEdger
- type CurvePoint
- type Edge
- type EdgeBuilder
- func (eb *EdgeBuilder) AAShift() int
- func (eb *EdgeBuilder) AllEdges() iter.Seq[CurveEdgeVariant]
- func (eb *EdgeBuilder) Bounds() Rect
- func (eb *EdgeBuilder) BuildFromPath(path PathLike, transform Transform)
- func (eb *EdgeBuilder) CubicEdgeCount() int
- func (eb *EdgeBuilder) CubicEdges() iter.Seq[*CubicEdge]
- func (eb *EdgeBuilder) EdgeCount() int
- func (eb *EdgeBuilder) FlattenCurves() bool
- func (eb *EdgeBuilder) IsEmpty() bool
- func (eb *EdgeBuilder) LineEdgeCount() int
- func (eb *EdgeBuilder) LineEdges() iter.Seq[*LineEdge]
- func (eb *EdgeBuilder) QuadraticEdgeCount() int
- func (eb *EdgeBuilder) QuadraticEdges() iter.Seq[*QuadraticEdge]
- func (eb *EdgeBuilder) Reset()
- func (eb *EdgeBuilder) SetFlattenCurves(flatten bool)
- type EdgeList
- type EdgeRange
- type EdgeType
- type FDot6
- type FDot8
- type FDot16
- func FDot6Div(a, b FDot6) FDot16
- func FDot6ToFDot16(v FDot6) FDot16
- func FDot6ToFixedDiv2(v FDot6) FDot16
- func FDot16Div(numer, denom int32) FDot16
- func FDot16FastDiv(a, b FDot6) FDot16
- func FDot16FromFloat32(f float32) FDot16
- func FDot16FromFloat64(f float64) FDot16
- func FDot16Mul(a, b FDot16) FDot16
- type FillRule
- type GeomPoint
- type IdentityTransform
- type LineEdge
- type PathLike
- type PathVerb
- type QuadraticEdge
- type Rect
- type ScenePathAdapter
- type SimpleAET
- type Transform
Constants ¶
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.
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.
const Epsilon = 1e-6
Epsilon is a small value for floating point comparison.
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 ¶
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 ¶
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 ¶
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 ¶
FDot6CanConvertToFDot16 returns true if the FDot6 value can be converted to FDot16 without overflow.
func FDot6Floor ¶
FDot6Floor returns the integer part (floor) of an FDot6.
func FDot6Round ¶
FDot6Round returns the nearest integer to an FDot6.
func FDot6SmallScale ¶
FDot6SmallScale scales a byte value by an FDot6 factor (0 to 64). Used for alpha blending calculations.
func FDot6ToFloat32 ¶
FDot6ToFloat32 converts an FDot6 to float32.
func FDot6ToFloat64 ¶
FDot6ToFloat64 converts an FDot6 to float64.
func FDot6UpShift ¶
FDot6UpShift performs a left shift on an FDot6 value. Used in cubic edge coefficient computation.
func FDot16CeilToInt ¶
FDot16CeilToInt returns the ceiling of an FDot16.
func FDot16FloorToInt ¶
FDot16FloorToInt returns the integer part (floor) of an FDot16.
func FDot16RoundToInt ¶
FDot16RoundToInt returns the nearest integer to an FDot16.
func FDot16ToFloat32 ¶
FDot16ToFloat32 converts an FDot16 to float32.
func FDot16ToFloat64 ¶
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 ¶
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 ¶
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 ¶
NewAlphaRuns creates a new AlphaRuns buffer for the given width.
func (*AlphaRuns) Add ¶
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 ¶
CopyTo copies the coverage values to a destination slice. The destination must have at least ar.width elements.
func (*AlphaRuns) GetAlpha ¶
GetAlpha returns the alpha value at position x. Returns 0 if x is out of bounds.
func (*AlphaRuns) IsEmpty ¶
IsEmpty returns true if the scanline contains only a single run of alpha 0.
func (*AlphaRuns) Iter ¶
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 ¶
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.
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:
- For each edge crossing a pixel row, compute the Y range it covers
- Find the X intersections at the top and bottom of the pixel
- Compute the trapezoidal area within the pixel bounds
- 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 ¶
CurveCount returns the remaining number of segments. For cubic, this is negative (counts up to 0).
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:
- For each pair of edges, compute the winding contribution
- Apply fill rule to determine coverage
- 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) 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 ¶
NewEdge creates a new edge from two points. Returns nil if the edge is horizontal (no Y extent).
func NewEdgeWithWinding ¶
NewEdgeWithWinding creates a new edge with explicit winding.
func (*Edge) IsActiveAt ¶
IsActiveAt returns true if the edge is active at the given Y coordinate. An edge is active when YMin <= y < YMax.
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:
- Iterates through path verbs
- Applies transform to all points
- Chops curves at Y extrema for monotonicity
- 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 (*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 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 ¶
FDot6FromFloat32 converts a float32 to FDot6. Values are scaled by 64 and truncated toward zero.
func FDot6FromFloat64 ¶
FDot6FromFloat64 converts a float64 to FDot6.
func FDot6FromInt ¶
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 ¶
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 ¶
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 ¶
FDot6ToFDot16 converts an FDot6 to FDot16. This is a left shift by 10 bits (16 - 6 = 10).
func FDot6ToFixedDiv2 ¶
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 ¶
FDot16Div divides two FDot6/FDot16 values and returns an FDot16. Uses 64-bit intermediate to avoid overflow.
func FDot16FastDiv ¶
FDot16FastDiv divides two FDot6 values using fast 32-bit math. Only valid when the numerator fits in 16 bits.
func FDot16FromFloat32 ¶
FDot16FromFloat32 converts a float32 to FDot16.
func FDot16FromFloat64 ¶
FDot16FromFloat64 converts a float64 to FDot16.
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 )
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.
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 ¶
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 ¶
Rect represents a bounding rectangle.
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 ¶
InsertEdge adds an edge to the active list.
func (*SimpleAET) RemoveExpired ¶
RemoveExpired removes edges that end at or before the given Y.