// Package scanline rasterizes D2's closed vector paths with non-zero winding. // // The implementation is deliberately specialized for D2's renderer: paths are // consumed in device coordinates and painted as a uniform color, written to an // Alpha image, or exposed row-wise for direct paint compositing. Each directed // edge contributes its exact signed area to the pixels it crosses; a row-sized // difference buffer carries winding across interior spans. This avoids both // supersampling and a canvas-sized accumulation image.
package scanline import ( ) const ( // Curves are subdivided until their control points are within three-eighths // of a device pixel from the chord. At that scale, analytic edge coverage // absorbs the remaining geometric error without visible faceting. curveTolerance = 0.375 maxCurveDepth = 18 // Long inner loops check cancellation at this power-of-two interval. Row // loops also check periodically, so small paths do not pay a per-pixel // context lookup. contextCheckInterval = 4_096 ) var ( // ErrEdgeLimit reports that path flattening would exceed the caller's // retained edge-storage budget. ErrEdgeLimit = errors.New("scanline edge limit exceeded") // ErrCurveLimit reports that a curve remains unresolved at the safety depth. // Returning an error avoids silently replacing extreme geometry with an // inaccurate chord. ErrCurveLimit = errors.New("scanline curve subdivision limit exceeded") // ErrWorkLimit reports that scan conversion exhausted its explicit operation // budget. The budget is shared across rasterizer calls for one frame. ErrWorkLimit = errors.New("scanline work limit exceeded") ) // WorkBudget accounts scan-conversion operations across rasterizer calls. It // is intentionally not concurrency-safe; one renderer owns it for one frame. type WorkBudget struct { remaining int64 } // NewWorkBudget returns a budget with limit available operation units. func ( int64) WorkBudget { if < 0 { = 0 } return WorkBudget{remaining: } } // Remaining reports the unconsumed operation units. func ( *WorkBudget) () int64 { if == nil { return 0 } return .remaining } func ( *WorkBudget) ( int64) error { if == nil || < 0 || > .remaining { return ErrWorkLimit } .remaining -= return nil } type point struct { x float64 y float64 } type edge struct { from point to point } type scanEdge struct { end int next int x float64 y float64 bottom float64 slope float64 inverseSlope float64 winding int8 } // Rasterizer retains reusable path and scanline storage between Reset calls. // It is not safe for concurrent use. type Rasterizer struct { width int height int edges []edge scanEdges []scanEdge active []int rowHeads []int partial []float32 difference []float32 first point current point hasPath bool originOffset point touchedMin int touchedMax int touched bool countOnly bool edgeCount int edgeLimit int pathErr error workVisibleEdges int64 workActiveRows int64 workHorizontalCrossing int64 workFirstRow int workLastRow int workTouchedMin int workTouchedMax int workTouched bool } // NewRasterizer returns an empty rasterizer for a width by height target. func (, int) *Rasterizer { := &Rasterizer{edgeLimit: -1} .Reset(, ) return } // NewCounter returns a rasterizer that performs the same path flattening as a // drawing rasterizer without allocating edge or scanline storage. At most // edgeLimit edges are counted before ErrEdgeLimit is reported. func (, , int) *Rasterizer { := &Rasterizer{countOnly: true, edgeLimit: max(, 0)} .Reset(, ) return } // Reset clears the current paths while retaining bounded scratch capacity. func ( *Rasterizer) (, int) { // Successful scanlines clear their touched range immediately. If drawing was // interrupted, clear that same range while the old dimensions still describe // the retained buffers; untouched storage is already zero. if !.countOnly { .clearTouched() } if < 0 { = 0 } if < 0 { = 0 } .width = .height = .edges = .edges[:0] .scanEdges = .scanEdges[:0] .active = .active[:0] .hasPath = false .originOffset = point{} .edgeCount = 0 .pathErr = nil .workVisibleEdges = 0 .workActiveRows = 0 .workHorizontalCrossing = 0 .workFirstRow = .workLastRow = 0 .workTouchedMin = .workTouchedMax = 0 .workTouched = false if !.countOnly { .resizeRows() } .resetTouched() } // ReserveEdges bounds path construction and reserves the corresponding // reusable edge, scan-edge, and active-edge capacities. func ( *Rasterizer) ( int) { if < 0 { = 0 } .edgeLimit = if .countOnly { return } if cap(.edges) < { .edges = make([]edge, 0, ) } if cap(.scanEdges) < { .scanEdges = make([]scanEdge, 0, ) } if cap(.active) < { .active = make([]int, 0, ) } } // EdgeCount reports the number of non-horizontal finite edges in the current // path, capped at the configured limit. func ( *Rasterizer) () int { return .edgeCount } // Err reports any edge-budget or curve-complexity error from path construction. func ( *Rasterizer) () error { return .pathErr } func ( *Rasterizer) ( int) { if cap(.partial) < { .partial = make([]float32, ) } else { .partial = .partial[:] } if cap(.difference) < +1 { .difference = make([]float32, +1) } else { .difference = .difference[:+1] } } // MoveTo starts a new subpath at x, y. func ( *Rasterizer) (, float32) { .moveTo(point{x: float64(), y: float64()}) } // SetOriginOffset sets the translation applied by the 64-bit path methods // after their coordinates have been rounded to float32. This lets a cropped // target preserve the coordinate rounding of a larger reference target. // Reset clears the translation; the original float32 methods ignore it. func ( *Rasterizer) (, float64) { .originOffset = point{x: , y: } } func ( *Rasterizer) (, float64) point { return point{x: float64(float32()) + .originOffset.x, y: float64(float32()) + .originOffset.y} } // MoveTo64 starts a subpath, rounding relative to the reference origin before // applying the translation configured by SetOriginOffset. func ( *Rasterizer) (, float64) { .moveTo(.referencePoint(, )) } func ( *Rasterizer) ( point) { .first = .current = .hasPath = true } // LineTo appends a straight edge to the current subpath. func ( *Rasterizer) (, float32) { .lineTo(point{x: float64(), y: float64()}) } // LineTo64 appends an edge using the reference rounding and origin translation // described by MoveTo64. func ( *Rasterizer) (, float64) { .lineTo(.referencePoint(, )) } func ( *Rasterizer) ( point) { if !.hasPath { .first = .current = .hasPath = true return } .addEdge(.current, ) .current = } // CubeTo appends a cubic Bezier curve. Curves are flattened in device space so // the tolerance is independent of the source transform. func ( *Rasterizer) (, , , , , float32) { := point{x: float64(), y: float64()} := point{x: float64(), y: float64()} := point{x: float64(), y: float64()} .cubeTo(, , ) } // CubeTo64 appends a cubic curve using reference rounding for its control and // end points before applying the configured origin translation. func ( *Rasterizer) (, , , , , float64) { .cubeTo(.referencePoint(, ), .referencePoint(, ), .referencePoint(, )) } func ( *Rasterizer) (, , point) { if !.hasPath { .first = .current = .hasPath = true return } .flattenCube(.current, , , , 0) .current = } // ClosePath closes the current subpath with a straight edge. func ( *Rasterizer) () { if !.hasPath { return } .addEdge(.current, .first) .current = .first .hasPath = false } func ( *Rasterizer) (, point) { if .pathErr != nil { return } if == || .y == .y { return } if !finitePoint() || !finitePoint() { return } if .edgeLimit >= 0 && .edgeCount >= .edgeLimit { .pathErr = ErrEdgeLimit return } .edgeCount++ if !.recordEdgeWork(, ) { .pathErr = ErrWorkLimit return } if .countOnly { return } .edges = append(.edges, edge{from: , to: }) } // recordEdgeWork retains only aggregate geometry metrics. This gives preflight // and drawing the same conservative work bound without storing attacker-sized // edge slices in count-only mode. func ( *Rasterizer) (, point) bool { if .y > .y { , = , } if .y <= 0 || .y >= float64(.height) { return true } := int(max(.y, 0)) := int(math.Ceil(min(.y, float64(.height)))) if >= { return true } := int64( - ) , := checkedAddInt64(.workVisibleEdges, 1) if ! { return false } , := checkedAddInt64(.workActiveRows, ) if ! { return false } // The complete raw edge contains its vertically clipped portion, so using // its endpoints avoids solving the line a second time and remains a safe // (occasionally looser) bound for both horizontal travel and touched X. := min(max(.x, 0), float64(.width)) := min(max(.x, 0), float64(.width)) // Splitting an edge at scanline boundaries can add at most one partial // segment per active row beyond the monotone horizontal travel. := - if < 0 { = - } // Truncation plus one also covers an exact-integer span and is cheaper than // a second floating-point ceil in this path-submission hot loop. := int64() + 1 , = checkedAddInt64(, ) if ! { return false } , = checkedAddInt64(.workHorizontalCrossing, ) if ! { return false } , := 0, 0 , := min(.x, .x), max(.x, .x) switch { case <= 0: , = 0, 0 case >= float64(.width): , = .width, .width default: = int(max(, 0)) if >= float64(.width) { = .width } else { = int() + 1 } } if !.workTouched { .workTouchedMin, .workTouchedMax = , .workTouched = true } else { .workTouchedMin = min(.workTouchedMin, ) .workTouchedMax = max(.workTouchedMax, ) } .workVisibleEdges = .workActiveRows = .workHorizontalCrossing = .workFirstRow = min(.workFirstRow, ) .workLastRow = max(.workLastRow, ) return true } func finitePoint( point) bool { return !math.IsNaN(.x) && !math.IsNaN(.y) && !math.IsInf(.x, 0) && !math.IsInf(.y, 0) } func ( *Rasterizer) (, , , point, int) { if .pathErr != nil { return } // A cubic stays inside the convex hull of its controls. Curves wholly above // or below the target cannot affect coverage and need no subdivision. if max(.y, .y, .y, .y) <= 0 || min(.y, .y, .y, .y) >= float64(.height) { return } if cubeFlatEnough(, , , ) { .addEdge(, ) return } if >= maxCurveDepth { .pathErr = ErrCurveLimit return } := midpoint(, ) := midpoint(, ) := midpoint(, ) := midpoint(, ) := midpoint(, ) := midpoint(, ) .(, , , , +1) .(, , , , +1) } func midpoint(, point) point { return point{x: .x + (.x-.x)*0.5, y: .y + (.y-.y)*0.5} } func cubeFlatEnough(, , , point) bool { := .x - .x := .y - .y := * + * if == 0 { , := .x-.x, .y-.y , := .x-.x, .y-.y return math.Max(*+*, *+*) <= curveTolerance*curveTolerance } := curveTolerance * curveTolerance * := curveTolerance * math.Sqrt() := 0.0 for , := range [...]point{, } { := .x - .x := .y - .y := * + * := * - * := 0.0 if < 0 { = - } else if > { = - } if *+* > || < - { return false } = } return true } // DrawRGBA paints the current paths over dst using a uniform, unpremultiplied // color. Rasterizer coordinates start at dst.Bounds().Min. Work is charged to // budget and cancellation is checked within long scan loops. func ( *Rasterizer) ( context.Context, *WorkBudget, *image.RGBA, color.NRGBA) error { if := .Err(); != nil { return } if := .Err(); != nil { return } if == nil || .width == 0 || .height == 0 || len(.edges) == 0 || .A == 0 { return nil } // Sparse paths spend most of their painted rows in event-free interiors, // where filling an opaque span is cheaper than per-pixel compositing. Dense // compound paths have short spans and favor the general loop. if .A == 0xff && len(.edges) <= max(.height, 8) { , , , := .RGBA() := uint32(.R) | uint32(.G)<<8 | uint32(.B)<<16 | uint32(0xff)<<24 return .rasterize(, , func(, , int) error { := .PixOffset(.Rect.Min.X+, .Rect.Min.Y+) var float32 for := ; < ; { := min(+contextCheckInterval, ) if != { if := .Err(); != nil { return } } for < { += .difference[] := coverage(.partial[] + ) if != 0 { := .Pix[ : +4 : +4] if == 0xff { binary.LittleEndian.PutUint32(, ) } else { := uint32() |= << 8 := (uint32(0xffff) - ) * 0x101 [0] = uint8((uint32([0])* + *) / 0xffff >> 8) [1] = uint8((uint32([1])* + *) / 0xffff >> 8) [2] = uint8((uint32([2])* + *) / 0xffff >> 8) [3] = uint8((uint32([3])* + uint32(0xffff)*) / 0xffff >> 8) } } ++ += 4 // Interior spans have no partial area or winding event. Consume // each fully opaque or transparent span once instead of repeating // coverage work for every pixel. if < && zeroCoverageValues(.partial[], .difference[]) { := coverage() if == 0 || == 0xff { := + 1 if - >= 8 { = zeroCoverageRunEnd(.partial, .difference, , ) } else { for < && zeroCoverageValues(.partial[], .difference[]) { ++ } } := + (-)*4 if == 0xff { if - >= 8 { fillPackedRGBA(.Pix[:], ) } else { for := ; < ; += 4 { binary.LittleEndian.PutUint32(.Pix[:+4], ) } } } = = } } } } return nil }) } // Dense opaque paths do not benefit from searching for interior spans, but // they still avoid the general source-alpha multiply and replace fully // covered pixels directly. if .A == 0xff { , , , := .RGBA() := uint32(.R) | uint32(.G)<<8 | uint32(.B)<<16 | uint32(0xff)<<24 return .rasterize(, , func(, , int) error { := .PixOffset(.Rect.Min.X+, .Rect.Min.Y+) var float32 for := ; < ; { := min(+contextCheckInterval, ) if != { if := .Err(); != nil { return } } for ; < ; ++ { += .difference[] := coverage(.partial[] + ) if == 0 { += 4 continue } := .Pix[ : +4 : +4] if == 0xff { binary.LittleEndian.PutUint32(, ) } else { := uint32() |= << 8 := (uint32(0xffff) - ) * 0x101 [0] = uint8((uint32([0])* + *) / 0xffff >> 8) [1] = uint8((uint32([1])* + *) / 0xffff >> 8) [2] = uint8((uint32([2])* + *) / 0xffff >> 8) [3] = uint8((uint32([3])* + uint32(0xffff)*) / 0xffff >> 8) } += 4 } } return nil }) } , , , := .RGBA() if len(.edges) <= max(.height, 8) { return .rasterize(, , func(, , int) error { := .PixOffset(.Rect.Min.X+, .Rect.Min.Y+) var float32 for := ; < ; { := min(+contextCheckInterval, ) if != { if := .Err(); != nil { return } } for < { += .difference[] := coverage(.partial[] + ) if != 0 { := uint32() |= << 8 := (uint32(0xffff) - */0xffff) * 0x101 := .Pix[ : +4 : +4] [0] = uint8((uint32([0])* + *) / 0xffff >> 8) [1] = uint8((uint32([1])* + *) / 0xffff >> 8) [2] = uint8((uint32([2])* + *) / 0xffff >> 8) [3] = uint8((uint32([3])* + *) / 0xffff >> 8) } ++ += 4 // Between winding events, coverage is constant. Skip transparent // interiors in chunks; for painted spans, reuse the exact source-over // coefficients while checking for the next event. if < && zeroCoverageValues(.partial[], .difference[]) { := coverage() if == 0 { := + 1 if - >= 8 { = zeroCoverageRunEnd(.partial, .difference, , ) } else { for < && zeroCoverageValues(.partial[], .difference[]) { ++ } } += ( - ) * 4 = continue } := uint32() |= << 8 := (uint32(0xffff) - */0xffff) * 0x101 := * := * := * := * for < && zeroCoverageValues(.partial[], .difference[]) { := .Pix[ : +4 : +4] [0] = uint8((uint32([0])* + ) / 0xffff >> 8) [1] = uint8((uint32([1])* + ) / 0xffff >> 8) [2] = uint8((uint32([2])* + ) / 0xffff >> 8) [3] = uint8((uint32([3])* + ) / 0xffff >> 8) ++ += 4 } } } } return nil }) } return .rasterize(, , func(, , int) error { := .PixOffset(.Rect.Min.X+, .Rect.Min.Y+) var float32 for := ; < ; { := min(+contextCheckInterval, ) if != { if := .Err(); != nil { return } } for ; < ; ++ { += .difference[] := coverage(.partial[] + ) if == 0 { += 4 continue } := uint32() |= << 8 := (uint32(0xffff) - */0xffff) * 0x101 := .Pix[ : +4 : +4] [0] = uint8((uint32([0])* + *) / 0xffff >> 8) [1] = uint8((uint32([1])* + *) / 0xffff >> 8) [2] = uint8((uint32([2])* + *) / 0xffff >> 8) [3] = uint8((uint32([3])* + *) / 0xffff >> 8) += 4 } } return nil }) } func fillPackedRGBA( []byte, uint32) { if len() == 0 { return } binary.LittleEndian.PutUint32([:4], ) for := 4; < len(); { += copy([:], [:]) } } // WriteAlpha writes opaque path coverage into a freshly zeroed dst. Rasterizer // coordinates start at dst.Bounds().Min. Work is charged to budget and // cancellation is checked within long scan loops. func ( *Rasterizer) ( context.Context, *WorkBudget, *image.Alpha) error { if := .Err(); != nil { return } if := .Err(); != nil { return } if == nil || .width == 0 || .height == 0 || len(.edges) == 0 { return nil } // Wide, sparse masks contain enough event-free interior to amortize span // discovery. Short rows and dense compound paths favor the scalar loop. if .width >= 256 && len(.edges) <= max(.height, 8) { return .rasterize(, , func(, , int) error { := .PixOffset(.Rect.Min.X+, .Rect.Min.Y+) var float32 for := ; < ; { := min(+contextCheckInterval, ) if != { if := .Err(); != nil { return } } for < { += .difference[] .Pix[] = coverage(.partial[] + ) ++ ++ if < && zeroCoverageValues(.partial[], .difference[]) { := coverage() if == 0 || == 0xff { := + 1 if - >= 8 { = zeroCoverageRunEnd(.partial, .difference, , ) } else { for < && zeroCoverageValues(.partial[], .difference[]) { ++ } } if == 0xff { if - >= 8 { fillBytes(.Pix[:+-], 0xff) } else { for := ; < +-; ++ { .Pix[] = 0xff } } } += - = } } } } return nil }) } return .rasterize(, , func(, , int) error { := .PixOffset(.Rect.Min.X+, .Rect.Min.Y+) var float32 for := ; < ; { := min(+contextCheckInterval, ) if != { if := .Err(); != nil { return } } for ; < ; ++ { += .difference[] .Pix[] = coverage(.partial[] + ) ++ } } return nil }) } // WalkCoverage rasterizes the current paths and visits each row segment that // can contain non-zero coverage. partial and difference are transient views // into the Rasterizer's reusable accumulation storage, aligned so index zero // corresponds to minX. The callback must consume them before returning and // must not retain either slice. // // Callers reconstruct each pixel's coverage in increasing index order with: // // winding += difference[index] // alpha := QuantizeCoverage(partial[index] + winding) // // Rasterizer coordinates start at the caller's target origin. Work is charged // to budget and cancellation is checked during scan conversion; callbacks that // perform substantial per-pixel work should also check their context. func ( *Rasterizer) ( context.Context, *WorkBudget, func(, int, , []float32) error) error { if := .Err(); != nil { return } if := .Err(); != nil { return } if .width == 0 || .height == 0 || len(.edges) == 0 { return nil } return .rasterize(, , func(, , int) error { return (, , .partial[:], .difference[:]) }) } // QuantizeCoverage converts a signed analytic pixel area into the same 8-bit // absolute coverage used by DrawRGBA and WriteAlpha. func ( float32) uint8 { return coverage() } func fillBytes( []byte, byte) { if len() == 0 { return } [0] = for := 1; < len(); { += copy([:], [:]) } } var retainedBytesPerEdge = uint64(unsafe.Sizeof(edge{})) + uint64(unsafe.Sizeof(scanEdge{})) + uint64(unsafe.Sizeof(int(0))) // RetainedBytes returns the backing-storage bytes retained after visiting // targets up to maxWidth and maxHeight and paths up to maxEdges. Width and // height are independent maxima because Reset retains both capacities. func (, , int) (int64, bool) { if < 0 || < 0 || < 0 { return 0, false } if == 0 && == 0 && == 0 { return 0, true } , := checkedMultiplyUint64(uint64(), 2*uint64(unsafe.Sizeof(float32(0)))) if ! { return 0, false } , = checkedAddUint64(, uint64(unsafe.Sizeof(float32(0)))) if ! { return 0, false } , := checkedMultiplyUint64(uint64(), uint64(unsafe.Sizeof(int(0)))) if ! { return 0, false } , := checkedMultiplyUint64(uint64(), retainedBytesPerEdge) if ! { return 0, false } , := checkedAddUint64(, ) if { , = checkedAddUint64(, ) } if ! || > math.MaxInt64 { return 0, false } return int64(), true } func checkedMultiplyUint64(, uint64) (uint64, bool) { if != 0 && > ^uint64(0)/ { return 0, false } return * , true } func checkedAddUint64(, uint64) (uint64, bool) { if > ^uint64(0)- { return 0, false } return + , true } func checkedMultiplyInt64(, int64) (int64, bool) { if < 0 || < 0 || != 0 && > math.MaxInt64/ { return 0, false } return * , true } func checkedAddInt64(, int64) (int64, bool) { if < 0 || < 0 || > math.MaxInt64- { return 0, false } return + , true } // MaxEdgesForBytes returns the largest edge count whose retained edge arrays // fit in bytes. It is used to stop count-only flattening before preflight can // itself exceed the configured offscreen budget. func ( int64) int { if <= 0 { return 0 } := uint64() / retainedBytesPerEdge := uint64(^uint(0) >> 1) if > { return int() } return int() } // WorkBound returns the worst-case operation units for rasterizing edges into // a width by height target. Callers that submitted geometry should prefer the // Rasterizer method, whose O(1) aggregate metrics preserve the same safety // proof without multiplying every small path by the complete target bounds. func (, , int) (int64, bool) { if < 0 || < 0 || < 0 { return 0, false } if == 0 || == 0 || == 0 { return 0, true } , , := uint64(), uint64(), uint64() := [...]struct { uint64 uint64 uint64 }{ {3, , 1}, // row-head initialization, row visits, difference tails {3, , 1}, // edge preparation, scan-edge creation, and insertion {3, , }, // removal, accumulation, and per-row edge fragments {1, , }, // monotone horizontal travel {3, , }, // paint, partial clear, and difference clear spans } var uint64 for , := range { , := checkedMultiplyUint64(., .) if { , = checkedMultiplyUint64(, .) } if ! { return 0, false } , = checkedAddUint64(, ) if ! { return 0, false } } if > math.MaxInt64 { return 0, false } return int64(), true } // WorkBound returns a conservative bound derived from aggregate edge metrics. // The rasterizer records only counts, clipped row spans, monotone horizontal // travel, and a touched rectangle, so count-only preflight remains O(1) space. func ( *Rasterizer) () (int64, bool) { if == nil || .edgeCount == 0 { return 0, true } := int64(0) if .workFirstRow < .workLastRow { = int64(.workLastRow - .workFirstRow) } := int64(0) if .workTouchedMin < .workTouchedMax { = int64(.workTouchedMax - .workTouchedMin) } , := checkedMultiplyInt64(, ) if { , = checkedMultiplyInt64(, 3) } if ! { return 0, false } , := checkedMultiplyInt64(.workVisibleEdges, 2) if ! { return 0, false } , := checkedMultiplyInt64(.workActiveRows, 2) if ! { return 0, false } , := checkedMultiplyInt64(, 2) if ! { return 0, false } := [...]int64{ int64(.height), // row-head initialization int64(.edgeCount), // raw-edge preparation , // scan-edge creation and row insertion , // expiration and accumulation visits .workHorizontalCrossing, // horizontal travel plus row fragments , // row iteration and difference tail , // painted and cleared touched spans } := int64(0) for , := range { if < 0 { return 0, false } , = checkedAddInt64(, ) if ! { return 0, false } } return , true } func ( *Rasterizer) ( context.Context, *WorkBudget, func(, , int) error) error { , := .WorkBound() if ! { return ErrWorkLimit } if := .consume(); != nil { return } , , := .prepareScanEdges() if != nil { return } if >= { return .Err() } // Consume row heads as the scan advances. This preserves an all-zero idle // buffer without clearing the full target height for every small path. // Clear any unvisited suffix on cancellation or a destination error. := defer func() { .clearRowHeads(, ) }() .active = .active[:0] for := ; < ; ++ { if := - ; &(31) == 0 { if := .Err(); != nil { return } } .removeExpired() := 0 := .rowHeads[] .rowHeads[] = 0 = + 1 for != 0 { := - 1 if != 0 && &(contextCheckInterval-1) == 0 { if := .Err(); != nil { return } } .active = append(.active, ) ++ = .scanEdges[].next } for := 0; < len(.active); += contextCheckInterval { if != 0 { if := .Err(); != nil { return } } := min(+contextCheckInterval, len(.active)) for := ; < ; ++ { if := .accumulateEdgeRow(, &.scanEdges[.active[]], ); != nil { return } } } if .touchedMin < .touchedMax { if := (, .touchedMin, .touchedMax); != nil { return } } .clearTouched() } return .Err() } func ( *Rasterizer) ( context.Context) (int, int, error) { .scanEdges = .scanEdges[:0] if cap(.rowHeads) < .height { .rowHeads = make([]int, .height) } else { .rowHeads = .rowHeads[:.height] } := .height := 0 for , := range .edges { if != 0 && &(contextCheckInterval-1) == 0 { if := .Err(); != nil { .clearRowHeads(, ) return 0, 0, } } , := .from, .to := int8(1) if .y > .y { , = , = -1 } if .y <= 0 || .y >= float64(.height) { continue } := int(math.Floor(max(.y, 0))) := int(math.Ceil(min(.y, float64(.height)))) if >= { continue } := (.x - .x) / (.y - .y) := max(.y, float64()) := math.FMA(-.y, , .x) := 0.0 if != 0 { = 1 / } := len(.scanEdges) .scanEdges = append(.scanEdges, scanEdge{ end: , next: .rowHeads[], x: , y: , bottom: .y, slope: , inverseSlope: , winding: , }) // Zero is the empty-head sentinel; stored indices are one-based. .rowHeads[] = + 1 if < { = } if > { = } } return , , nil } func ( *Rasterizer) (, int) { if >= { return } clear(.rowHeads[:]) } func ( *Rasterizer) ( int) { for := 0; < len(.active); { if .scanEdges[.active[]].end > { ++ continue } := len(.active) - 1 .active[] = .active[] .active = .active[:] } } func ( *Rasterizer) ( context.Context, *scanEdge, int) error { := min(.bottom, float64(+1)) := - .y if <= 0 { return nil } := .x := math.FMA(, .slope, ) := .y .x = .y = := float64(.winding) := - if == 0 { if < 0 { .addDifferenceEvent(0, *) } else if >= float64(.width) { .addDifferenceEvent(.width, *) } else { := int(math.Floor()) .accumulateVisibleEdgePortion(, , , , ) .touch(, +1) } return nil } := .inverseSlope , := , if > 0 { if < 0 { if <= 0 { .addDifferenceEvent(0, *) return nil } := math.FMA(-, , ) .addDifferenceEvent(0, *(-)) , = 0, } if >= float64(.width) { .addDifferenceEvent(.width, *(-)) return nil } := min(, float64(.width)) := math.FMA(-, , ) .touch(int(math.Floor()), min(.width, int(math.Floor())+1)) := math.Floor() + 1 if < { := math.FMA(-, , ) .accumulateVisibleEdgePortion(int()-1, , , , -) , = , ++ := float32( * * 0.5) := float32( * ) := 0 for < { if != 0 && &(contextCheckInterval-1) == 0 { if := .Err(); != nil { return } } := int() - 1 .partial[] += .difference[+1] += = += ++ ++ } } .accumulateVisibleEdgePortion(int(math.Floor((+)*0.5)), , , , -) if < { .addDifferenceEvent(.width, *(-)) } return nil } if > float64(.width) { if >= float64(.width) { .addDifferenceEvent(.width, *) return nil } := math.FMA(float64(.width)-, , ) .addDifferenceEvent(.width, *(-)) , = float64(.width), } if <= 0 { .addDifferenceEvent(0, *(-)) return nil } := max(, 0) := math.FMA(-, , ) .touch(int(math.Floor()), min(.width, int(math.Floor())+1)) := math.Ceil() - 1 if > { := math.FMA(-, , ) .accumulateVisibleEdgePortion(int(), , , , -) , = , -- := - := float32( * * 0.5) := float32( * ) := 0 for > { if != 0 && &(contextCheckInterval-1) == 0 { if := .Err(); != nil { return } } := int() .partial[] += .difference[+1] += = += -- ++ } } .accumulateVisibleEdgePortion(int(math.Floor((+)*0.5)), , , , -) if < { .addDifferenceEvent(0, *(-)) } return nil } func ( *Rasterizer) ( int, , , , float64) { if <= 0 { return } := ( + ) * 0.5 .partial[] += float32( * * (float64(+1) - )) .difference[+1] += float32( * ) } func ( *Rasterizer) ( int, float64) { if == 0 { return } .difference[] += float32() .touchEvent() } func coverage( float32) uint8 { if < 0 { = - } if <= 0 { return 0 } if >= 1 { return 255 } return uint8(*255 + 0.5) } func zeroCoverageValues(, float32) bool { // Ignore the sign bit so negative zero retains ordinary floating-point == 0 // semantics. Combining the bits avoids two floating-point comparisons in // the sparse-span discovery loop. return (math.Float32bits()|math.Float32bits())<<1 == 0 } func zeroCoverageRunEnd(, []float32, , int) int { := for +4 <= { := math.Float32bits([]) | math.Float32bits([]) | math.Float32bits([+1]) | math.Float32bits([+1]) | math.Float32bits([+2]) | math.Float32bits([+2]) | math.Float32bits([+3]) | math.Float32bits([+3]) if <<1 != 0 { break } += 4 } for < && zeroCoverageValues([], []) { ++ } return } func ( *Rasterizer) () { .touchedMin = .width .touchedMax = 0 .touched = false } func ( *Rasterizer) (, int) { .touched = true if < .touchedMin { .touchedMin = } if > .touchedMax { .touchedMax = } } func ( *Rasterizer) ( int) { .touched = true if < .touchedMin { .touchedMin = } if > .touchedMax { .touchedMax = } } func ( *Rasterizer) () { if !.touched { return } := min(.touchedMax, .width) if .touchedMin < { clear(.partial[.touchedMin:]) } clear(.difference[.touchedMin : min(.touchedMax, .width)+1]) .resetTouched() }