Documentation
¶
Overview ¶
Package stilus is a sparse CPU rasterizer for 2D vector graphics.
Its cost per path is the length of the path's edges in pixels plus the covered spans, never the area of the bounding box or the width of the canvas. It knows nothing about PDF, fonts or color spaces and has no dependencies, so it can serve as the CPU backend of a PDF renderer, a UI toolkit or another 2D library (for example as the CPU filler behind gogpu/gg).
Layers ¶
Each layer can be used on its own:
- Rasterizer turns device-space paths (Fill, or AddLine/AddPath and Rasterize) into coverage spans delivered to a Blitter: BlitRun for the constant interior runs of wide shapes, BlitCoverage for antialiased edge pixels and for narrow shapes as a whole. Implement Blitter to composite into any pixel format.
- Stroker turns a stroke (width, caps, joins, miter limit, dashes; all in user space, exact under anisotropic transforms) into fill geometry for a LineSink, such as a Rasterizer or a PathSink.
- Canvas combines both with a clip stack and compositing onto a caller-owned *image.RGBA. Its methods map one to one onto a display-list device: Fill, Stroke, ClipPath, ClipRect, ClipStroke, PopClip.
- Shaders paint what a solid colour cannot, per span: ImageShader (Texture with mip levels, edge-repeating or periodic for pattern tiles), LinearGradient and RadialGradient (a Ramp of colours, evenly spaced or at Knots), MeshShader (Gouraud-shaded triangles), LayerShader (a layer with opacity, mask and BlendMode, removing the backdrop of non-isolated groups), MaskShader and GlyphCache (glyph coverage masks). FillMesh draws Gouraud-shaded triangles into a layer.
Performance model ¶
Edges are accumulated as signed area and cover into a band of cells (32 rows × clip width) in 24.8 fixed point. Per row, the touched cell range is integrated directly when it is narrow; wide rows are swept with a dirty bitset that skips untouched cells, and the constant stretches between edges are emitted as runs that blitters fill without a per-pixel loop. Nothing is cleared as a whole: the sweep zeroes what it reads.
Strokes are filled analytically between their corners: rows bounded only by a segment's two parallel sides get each pixel's exact area in closed form; only the rows around joins and caps go through the accumulator. Strokes thinner than one device pixel are drawn one pixel wide. Rectangle clips cost nothing per pixel; other clips are rasterized once into a mask over their bounds.
All buffers grow on demand and are retained: after warm-up, drawing performs no allocations. Rasterizer, Stroker and Canvas are not safe for concurrent use; use one per worker and give each worker its own band or tile of the destination (Canvas.Reset takes a region).
Robustness ¶
NaN and infinite coordinates are rejected, huge ones are clipped analytically, curve and arc subdivision and dash counts are bounded, and the edge count per path is budgeted (Rasterizer.MaxEdges). Canvas never panics; it reports problems through Err and keeps drawing.
Example ¶
package main
import (
"fmt"
"image"
"image/color"
"github.com/timzifer/stilus"
)
func main() {
img := image.NewRGBA(image.Rect(0, 0, 200, 100))
c := stilus.NewCanvas(img)
// Page space in points, drawn at 144 dpi.
m := stilus.Scale(2, 2)
c.ClipRect(stilus.Rect{X0: 5, Y0: 5, X1: 95, Y1: 45}, m)
var p stilus.Path
p.MoveTo(10, 40)
p.CubicTo(30, 0, 70, 80, 90, 10)
c.Stroke(&p, m, &stilus.StrokeStyle{Width: 1.5, Cap: stilus.RoundCap}, &stilus.Paint{Color: color.RGBA{0, 0, 0, 255}})
var r stilus.Path
r.Ellipse(50, 25, 20, 12)
c.Fill(&r, m, stilus.NonZero, &stilus.Paint{Color: color.RGBA{0, 0, 128, 128}}) // premultiplied
c.PopClip()
fmt.Println(c.Err())
}
Output: <nil>
Index ¶
- Constants
- Variables
- func Blend(bm BlendMode, cb, cs [3]float64) [3]float64
- func FillMesh(dst *image.RGBA, region image.Rectangle, tris []MeshTriangle, m Matrix, ...)
- func IsHairline(m Matrix, st *StrokeStyle) bool
- func PackRGBA(c color.RGBA) uint32
- func SIMD() bool
- func UnpackRGBA(v uint32) color.RGBA
- type BlendMode
- type Blitter
- type Canvas
- func (c *Canvas) Clip() image.Rectangle
- func (c *Canvas) ClipDepth() int
- func (c *Canvas) ClipPath(p *Path, m Matrix, rule FillRule)
- func (c *Canvas) ClipRect(r Rect, m Matrix)
- func (c *Canvas) ClipStroke(p *Path, m Matrix, st *StrokeStyle)
- func (c *Canvas) Err() error
- func (c *Canvas) Fill(p *Path, m Matrix, rule FillRule, paint *Paint)
- func (c *Canvas) FillShape(s *Shape, paint *Paint)
- func (c *Canvas) FillUnion(u *Union, paint *Paint)
- func (c *Canvas) PopClip()
- func (c *Canvas) Reset(dst *image.RGBA, region image.Rectangle)
- func (c *Canvas) Stroke(p *Path, m Matrix, st *StrokeStyle, paint *Paint)
- type Cap
- type FillRule
- type GlyphCache
- type Gradient
- type ImageShader
- func (s *ImageShader) Reset()
- func (s *ImageShader) SetColor(c uint32)
- func (s *ImageShader) SetImage(t *Texture, toDevice Matrix, smooth bool, alpha uint8) bool
- func (s *ImageShader) SetImageWrap(t *Texture, toDevice Matrix, smooth bool, alpha uint8) bool
- func (s *ImageShader) SetMask(t *Texture, toDevice Matrix, smooth bool) bool
- func (s *ImageShader) SetMaskWrap(t *Texture, toDevice Matrix, smooth bool) bool
- func (s *ImageShader) ShadeSpan(y, x int, dst []uint32)
- type Join
- type LayerShader
- type LineSink
- type LinearGradient
- type MaskBlitter
- type MaskShader
- type Matrix
- type MeshShader
- type MeshTriangle
- type MeshVertex
- type Paint
- type Palette
- type Path
- func (p *Path) Bounds() Rect
- func (p *Path) Close()
- func (p *Path) CubicTo(c1x, c1y, c2x, c2y, x, y float32)
- func (p *Path) Ellipse(cx, cy, rx, ry float32)
- func (p *Path) Empty() bool
- func (p *Path) LineTo(x, y float32)
- func (p *Path) MoveTo(x, y float32)
- func (p *Path) QuadTo(cx, cy, x, y float32)
- func (p *Path) Rect(x, y, w, h float32)
- func (p *Path) Reset()
- type PathSink
- type Plane
- type PlaneKind
- type Point
- type RadialGradient
- type Ramp
- type Rasterizer
- func (r *Rasterizer) AddLine(x0, y0, x1, y1 float64)
- func (r *Rasterizer) AddPath(p *Path, m Matrix)
- func (r *Rasterizer) Clip() image.Rectangle
- func (r *Rasterizer) Fill(p *Path, m Matrix, rule FillRule, b Blitter)
- func (r *Rasterizer) Rasterize(rule FillRule, b Blitter)
- func (r *Rasterizer) Reset()
- func (r *Rasterizer) SetAntialias(aa bool)
- func (r *Rasterizer) SetClip(clip image.Rectangle)
- func (r *Rasterizer) Truncated() bool
- type Rect
- type Sampler
- type Shader
- type ShaderBlitter
- type Shape
- type SolidBlitter
- type StrokeStyle
- type Stroker
- type Texture
- type Union
- type Verb
Examples ¶
Constants ¶
const ( // MaxCachedEm is the em size in device pixels above which glyphs are // filled as paths: large glyphs are few, and their masks big. MaxCachedEm = 160 // DefaultGlyphCacheBytes bounds the masks of a cache whose MaxBytes // is zero; a full cache evicts masks to make room. DefaultGlyphCacheBytes = 4 << 20 )
const DefaultMaxClipDepth = 256
DefaultMaxClipDepth bounds the clip stack of a Canvas.
const DefaultMaxDashes = 1 << 20
DefaultMaxDashes bounds dash transitions per subpath. Subpaths estimated to exceed the budget are skipped; the subdivision loop also stops at the budget. Stroker.Truncated reports either condition.
const (
// DefaultMaxEdges is the default edge budget per path.
DefaultMaxEdges = 1 << 22
)
const DefaultShapeBytes = DefaultMaxEdges * shapeEdgeCost
DefaultShapeBytes is the default per-form budget of a Shape: enough for DefaultMaxEdges edges.
Variables ¶
var ( ErrEdgeBudget = errors.New("stilus: edge budget exceeded, path truncated") ErrDashBudget = errors.New("stilus: dash budget exceeded, stroke truncated") ErrClipDepth = errors.New("stilus: clip stack too deep") ErrInternal = errors.New("stilus: internal error") )
Errors reported by Canvas.Err.
var GrayPalette, AlphaPalette = func() (*Palette, *Palette) {
var g, a Palette
for i := range 256 {
v := uint8(i)
g[i] = pack(v, v, v, 255)
a[i] = pack(v, v, v, v)
}
return &g, &a
}()
Shared palettes: opaque greys and levels of alpha (white premultiplied, so all four channels are the level). They must not be modified.
var Identity = Matrix{1, 0, 0, 1, 0, 0}
Identity is the identity transform.
Functions ¶
func Blend ¶ added in v0.5.0
Blend returns B(Cb, Cs), the blend function of bm, for a backdrop and a source colour of straight components in [0, 1].
func FillMesh ¶ added in v0.5.0
FillMesh draws tris, transformed by m into device space, into dst within region, interpolating the colours of their vertices linearly (or, with a non-empty ramp, their parameters, looked up in the ramp). Pixels are replaced, not composited: the mesh is meant to be drawn into a transparent layer that is then composited through a Canvas, for example with a LayerShader, so that the mesh is clipped and blended like any fill. A triangle covers the pixels whose centres lie inside it, by the top-left rule, without antialiasing, so that triangles sharing an edge leave no seam and draw no pixel twice; later triangles overwrite earlier ones where they overlap. MeshShader paints the same pixels without a layer.
func IsHairline ¶
func IsHairline(m Matrix, st *StrokeStyle) bool
IsHairline reports whether st under m is thinner than one device pixel in every direction. Such strokes are drawn one pixel wide in device space, like PDFium.
func PackRGBA ¶
PackRGBA packs a premultiplied color into the native pixel layout used by Shader.ShadeSpan.
Types ¶
type BlendMode ¶ added in v0.5.0
type BlendMode uint8
BlendMode is a separable or non-separable blend mode of the W3C Compositing and Blending specification, which are also those of PDF (ISO 32000-2, 11.3.5) and of SVG and CSS.
type Blitter ¶
type Blitter interface {
// BlitRun paints the constant coverage alpha over [x0, x1) on row y:
// the constant stretches between the edges of wide shapes.
BlitRun(y, x0, x1 int, alpha uint8)
// BlitCoverage paints per-pixel coverage for [x, x+len(cov)) on row y.
// Narrow shapes (strokes, glyphs) arrive here as a whole, fully
// covered pixels included.
BlitCoverage(y, x int, cov []uint8)
}
Blitter receives the output of the rasterizer, one span at a time, in increasing y order and, within a row, in increasing x order. Spans never overlap and always lie inside the rasterizer's clip.
Implementations must not retain cov after the call returns.
type Canvas ¶
type Canvas struct {
// MaxClipDepth overrides DefaultMaxClipDepth when non-zero.
MaxClipDepth int
// contains filtered or unexported fields
}
Canvas draws paths onto a caller-owned *image.RGBA (premultiplied, as image.RGBA always is). It combines the rasterizer, the stroker and a clip stack, and maps one to one onto a display-list device: Fill/Stroke/ClipPath/ClipRect/PopClip.
Device space is the pixel space of the destination image, so a tile or band is rendered by passing an image whose Rect is that tile, or by restricting Reset's region. A Canvas is not safe for concurrent use; use one per worker. After warm-up a Canvas does not allocate.
func (*Canvas) ClipPath ¶
ClipPath intersects the clip with p under m. Rectangles ("re W n") take the rectangle fast path; other shapes are rasterized once into a mask over their bounds.
func (*Canvas) ClipRect ¶
ClipRect intersects the clip with rectangle r in user space. An axis-aligned result costs nothing per pixel; a rotated one becomes a mask.
func (*Canvas) ClipStroke ¶ added in v0.5.0
func (c *Canvas) ClipStroke(p *Path, m Matrix, st *StrokeStyle)
ClipStroke intersects the clip with the area a stroke of p with style st (in user space) under m paints, with the stroke's coverage: text in a stroking clip mode, or a stroke painted with a shader that only reaches the stroke as a clip. The area is rasterized once into a mask.
func (*Canvas) Err ¶
Err returns the first error since Reset, if any. Drawing continues after an error; the error only reports that output may be incomplete.
func (*Canvas) FillShape ¶ added in v0.8.0
FillShape draws the shape s, prepared by SetFill or SetStroke, with paint: the bytes Fill or Stroke draw with the same path, transform and style, at the cost of the records that cross the clip's rows.
func (*Canvas) FillUnion ¶ added in v0.8.0
FillUnion paints the union of the elements of u with paint: their coverage is accumulated in one pass, NonZero, and composited once (see Union). For opaque paint the result is that of drawing them one by one, without the seams between them; translucent paint is applied once where elements overlap.
Strokes take Canvas.Stroke's analytic path, whatever the paint and the clip: the rows between a segment's ends are not edge-walked but their pixels' covered areas computed in closed form, and here they are summed in the accumulator with everything else instead of being composited directly.
type GlyphCache ¶ added in v0.5.0
type GlyphCache struct {
// MaxBytes bounds the masks kept, counted by their allocations plus
// 64 bytes an entry; 0 means DefaultGlyphCacheBytes. A mask larger
// than the budget is drawn but not kept. Evicted masks kept for reuse
// take at most an eighth more; the rasterizer's scratch, sized by the
// largest mask made, and the map are not counted.
MaxBytes int
// contains filtered or unexported fields
}
GlyphCache is a cache of glyph coverage masks. The zero value is ready to use.
func (*GlyphCache) FillGlyph ¶ added in v0.5.0
func (gc *GlyphCache) FillGlyph(c *Canvas, font uint64, glyph int32, outline *Path, m Matrix, paint *Paint)
FillGlyph fills outline (nonzero), transformed by m into device space, with paint through c. The outline is identified by font and glyph: the cache assumes that equal ids have equal outlines. Small glyphs painted with a solid colour are drawn from the cache; large ones, and any glyph painted with a shader, are filled as paths.
FillGlyph makes the glyph's key and looks it up before it learns that the glyph lies outside the clip, and a per-glyph test would need the outline's bounds. When a page is drawn in bands, each playing every glyph, the caller should cull whole text runs against the band (Canvas.Clip) by their box, known from the font's bounding box and the advances, before calling FillGlyph for their glyphs.
type Gradient ¶ added in v0.5.0
type Gradient struct {
// Ramp maps the gradient's parameter to colours.
Ramp Ramp
// Extend continues the colour at t = 0 before the start (Extend[0])
// and the colour at t = 1 past the end (Extend[1]); where the gradient
// does not extend it paints Outside.
Extend [2]bool
// Outside is the premultiplied colour (PackRGBA layout) painted
// where the gradient is not defined; 0 is transparent.
Outside uint32
// Alpha multiplies the gradient's colours; 255 is opaque, 0 paints
// nothing.
Alpha uint8
// Knots, if not empty, are the parameters of the Ramp's entries: as
// many as entries, non-decreasing, within [0, 1]. Colours are then
// interpolated linearly between the two entries around t, and a knot
// given twice is a hard stop exactly there (t at the stop takes the
// later entry); t before the first knot or past the last takes the
// colour of the first or last entry. Without knots the entries are
// evenly spaced and looked up nearest. Set reads the knots: call it
// again after changing them.
Knots []float32
// contains filtered or unexported fields
}
Gradient is what linear and radial gradients share.
type ImageShader ¶ added in v0.5.0
type ImageShader struct {
// contains filtered or unexported fields
}
ImageShader paints a texture (or a solid colour) times a constant alpha, times an optional mask texture whose colours are levels of alpha (all four channels equal, such as AlphaPalette). The zero value paints transparent black; set it up with SetColor or SetImage, and SetMask.
func (*ImageShader) Reset ¶ added in v0.5.0
func (s *ImageShader) Reset()
Reset clears the shader, keeping its buffers, and drops its textures.
func (*ImageShader) SetColor ¶ added in v0.5.0
func (s *ImageShader) SetColor(c uint32)
SetColor paints the premultiplied colour c (PackRGBA layout) instead of a texture: a stencil.
func (*ImageShader) SetImage ¶ added in v0.5.0
SetImage paints t, whose base pixel space toDevice maps to device space, with constant alpha. It reports false if t cannot be drawn so.
func (*ImageShader) SetImageWrap ¶ added in v0.6.0
SetImageWrap is SetImage for a texture that repeats in both directions with the period of its base size (see Sampler.SetupWrap): the tile of a coloured pattern.
func (*ImageShader) SetMask ¶ added in v0.5.0
func (s *ImageShader) SetMask(t *Texture, toDevice Matrix, smooth bool) bool
SetMask multiplies the paint by mask texture t, whose base pixel space toDevice maps to device space. It reports false if t cannot be drawn so.
func (*ImageShader) SetMaskWrap ¶ added in v0.6.0
func (s *ImageShader) SetMaskWrap(t *Texture, toDevice Matrix, smooth bool) bool
SetMaskWrap is SetMask for a repeating mask texture: with SetColor, the stencil tile of an uncoloured pattern.
func (*ImageShader) ShadeSpan ¶ added in v0.5.0
func (s *ImageShader) ShadeSpan(y, x int, dst []uint32)
ShadeSpan implements Shader.
type LayerShader ¶ added in v0.5.0
type LayerShader struct {
// Src is the layer, premultiplied like any image.RGBA; spans must lie
// within its rectangle.
Src *image.RGBA
// Dst is the backdrop the canvas draws onto, read for blend modes
// other than Normal.
Dst *image.RGBA
// Mask, when non-nil, multiplies the layer; it is 0 outside its
// rectangle.
Mask *image.Alpha
// Alpha is the constant opacity of the layer.
Alpha uint8
Blend BlendMode
// Initial and Alone, when both are non-nil, make Src the result of a
// non-isolated group: Initial is the backdrop the group started from
// and Alone the same group drawn onto a transparent image, of which
// only the alpha is read. Both cover Src's rectangle.
Initial, Alone *image.RGBA
}
LayerShader composites a layer, an isolated group drawn into an image of its own, onto the backdrop it is drawn over: the layer's pixels times Alpha and the optional Mask, blended with the backdrop by Blend. Fill the layer's rectangle with it through a Canvas, so that the layer is composited through the clip stack and antialiased at the clip's edges like any fill.
Blend modes are exact under partial coverage: the canvas composites the shader's colour s over the backdrop d with coverage a as a·s + d·(1−a·αs), and the shader returns s = cs·(1−αb) + αs·αb·B(Cb, Cs) with alpha αs, which makes that the compositing formula of the specification interpolated by coverage.
A non-isolated group is drawn onto a copy of its backdrop, so its layer includes the backdrop; blending or knocking out that layer would count the backdrop twice. With Initial and Alone set, the shader first removes the backdrop's contribution as in PDF 2.0, 11.4.8: C = Cn + (Cn − C0)·(α0/αgn − α0), Cn being the layer's colour, C0 and α0 the backdrop's, and αgn the alpha of the group drawn alone; the result, with alpha αgn, is then composited like any layer.
func (*LayerShader) ShadeSpan ¶ added in v0.5.0
func (c *LayerShader) ShadeSpan(y, x int, out []uint32)
ShadeSpan implements Shader.
type LineSink ¶
type LineSink interface {
AddLine(x0, y0, x1, y1 float64)
}
LineSink receives device-space line segments. Segments emitted by the Stroker form closed polygons that must be filled with the NonZero rule. *Rasterizer implements LineSink.
The outline of a stroke winds in one direction everywhere it covers, inner corners, joins, caps and dashes included: its winding number is never positive where another part of it is negative. The direction is that of a stroke under the identity unless the transform mirrors (negative determinant) and the stroke is not a hairline, whose outline is built in device space. So the union of many strokes, oriented alike, is one NonZero fill of their outlines (see Union, TestStrokeOutlineWinding).
type LinearGradient ¶ added in v0.5.0
type LinearGradient struct {
Gradient
// contains filtered or unexported fields
}
LinearGradient is an axial gradient: t runs from 0 at P0 to 1 at P1 and is constant on lines perpendicular to P0P1 (in the gradient's own space, which may be mapped to device space by any affine transform).
func (*LinearGradient) Set ¶ added in v0.5.0
func (g *LinearGradient) Set(x0, y0, x1, y1 float64, m Matrix) bool
Set places the gradient from (x0, y0) to (x1, y1) in a space that m maps to device space, and reports whether it can be drawn: false for coincident points, singular or non-finite transforms and unusable Knots. It keeps Ramp, Extend, Outside, Alpha and Knots.
func (*LinearGradient) ShadeSpan ¶ added in v0.5.0
func (g *LinearGradient) ShadeSpan(y, x int, dst []uint32)
ShadeSpan implements Shader.
type MaskBlitter ¶
MaskBlitter accumulates coverage into an 8-bit alpha image (for clip masks, soft masks and tests). Coverage is combined with "max", which is exact for the non-overlapping spans of a single rasterization.
func (*MaskBlitter) BlitCoverage ¶
func (b *MaskBlitter) BlitCoverage(y, x int, cov []uint8)
func (*MaskBlitter) BlitRun ¶
func (b *MaskBlitter) BlitRun(y, x0, x1 int, alpha uint8)
type MaskShader ¶ added in v0.5.0
MaskShader paints a solid premultiplied colour (PackRGBA layout) through a coverage mask whose origin is at (X, Y) in device space. Spans must lie within the mask's rectangle moved by (X, Y).
func (*MaskShader) ShadeSpan ¶ added in v0.5.0
func (s *MaskShader) ShadeSpan(y, x int, dst []uint32)
ShadeSpan implements Shader.
type Matrix ¶
type Matrix [6]float64
Matrix is an affine transform in PDF order [a b c d e f]:
x' = a·x + c·y + e y' = b·x + d·y + f
type MeshShader ¶ added in v0.7.0
type MeshShader struct {
// Alpha multiplies the mesh's colours; 255 is opaque, 0 paints
// nothing.
Alpha uint8
// contains filtered or unexported fields
}
MeshShader paints triangles, transformed into device space, interpolating their vertex colours (or ramp parameters) barycentrically, without a layer. Pixels covered by no triangle are transparent. Fill the mesh's outline, the caller's path or the clip with it through a Canvas, so that the outer edge is antialiased and clipped like any fill. Inside the mesh a pixel gets exactly the colour FillMesh gives it.
Set transforms and bins the triangles once; ShadeSpan then costs the triangles that cross the span's row, so one shader serves every band of a page. After Set, ShadeSpan may be called by several workers at once.
func (*MeshShader) Set ¶ added in v0.7.0
func (s *MeshShader) Set(tris []MeshTriangle, m Matrix, ramp Ramp) bool
Set prepares the shader for tris, which m maps to device space; with a non-empty ramp the vertices' T is looked up in it. It reports false for singular or non-finite transforms, and keeps its buffers. The shader keeps ramp, not tris.
func (*MeshShader) ShadeSpan ¶ added in v0.7.0
func (s *MeshShader) ShadeSpan(y, x int, dst []uint32)
ShadeSpan implements Shader.
type MeshTriangle ¶ added in v0.5.0
type MeshTriangle [3]MeshVertex
MeshTriangle is a Gouraud-shaded triangle.
type MeshVertex ¶ added in v0.5.0
type MeshVertex struct {
X, Y float32
C uint32 // premultiplied, PackRGBA layout
T float32 // parameter into the ramp, 0 to 1
}
MeshVertex is a vertex of a Gouraud-shaded triangle: a point and either a premultiplied colour or a parameter into a Ramp.
type Paint ¶
Paint describes what a fill or stroke paints: a solid premultiplied color, or a Shader when Shader is non-nil.
type Palette ¶ added in v0.5.0
type Palette [256]uint32
Palette maps samples to premultiplied colours in PackRGBA layout.
type Path ¶
Path is a sequence of subpaths. The zero value is an empty path ready to use. Reset keeps the backing storage, so a Path can be rebuilt many times without allocating.
type Plane ¶ added in v0.5.0
type Plane struct {
Kind PlaneKind
W, H int
Stride int // pixels per row for PlaneRGBA and PlaneIndex, bytes for PlaneBits
Pix8 []uint8
Pix32 []uint32
Pal *Palette
}
Plane is a picture of W × H pixels.
type Point ¶
type Point struct{ X, Y float32 }
Point is a 2D point. Path coordinates are stored as float32 to keep paths (and display lists built on top of them) compact.
type RadialGradient ¶ added in v0.5.0
type RadialGradient struct {
Gradient
// contains filtered or unexported fields
}
RadialGradient is a gradient between two circles: the circle at t has its centre at C0 + t·(C1 − C0) and its radius r0 + t·(r1 − r0), and a point takes the colour of the largest t whose circle passes through it with a radius ≥ 0 (in the extended range, if the gradient extends). This is the two-point conical gradient of PDF (type 3 shadings), of the HTML canvas and of SVG with a focal point.
func (*RadialGradient) Set ¶ added in v0.5.0
func (g *RadialGradient) Set(x0, y0, r0, x1, y1, r1 float64, m Matrix) bool
Set places the gradient between the circles (x0, y0, r0) and (x1, y1, r1) in a space that m maps to device space, and reports whether it can be drawn: false for negative radii, singular or non-finite transforms and unusable Knots. It keeps Ramp, Extend, Outside, Alpha and Knots.
func (*RadialGradient) ShadeSpan ¶ added in v0.5.0
func (g *RadialGradient) ShadeSpan(y, x int, dst []uint32)
ShadeSpan implements Shader.
type Ramp ¶ added in v0.5.0
type Ramp []uint32
Ramp is a colour ramp: premultiplied colours (PackRGBA layout) at evenly spaced parameters from 0 to 1, or at the parameters a gradient's Knots give. A gradient with an empty ramp paints nothing.
type Rasterizer ¶
type Rasterizer struct {
// MaxEdges bounds the number of edges per path. Edges beyond it are
// dropped and Truncated reports true. Zero means DefaultMaxEdges.
MaxEdges int
// contains filtered or unexported fields
}
Rasterizer converts paths into coverage spans. Its cost per path is the length of the path's edges in pixels plus the number of covered spans; it never touches the area of the bounding box.
A Rasterizer is not safe for concurrent use; use one per worker. All buffers grow on demand and are retained, so steady-state use performs no allocations.
Example ¶
package main
import (
"fmt"
"image"
"github.com/timzifer/stilus"
)
// areaBlitter shows the span interface used to plug the rasterizer into
// another compositor: it just integrates the coverage.
type areaBlitter struct{ area int }
func (b *areaBlitter) BlitRun(y, x0, x1 int, alpha uint8) { b.area += (x1 - x0) * int(alpha) }
func (b *areaBlitter) BlitCoverage(y, x int, cov []uint8) {
for _, a := range cov {
b.area += int(a)
}
}
func main() {
r := stilus.NewRasterizer(image.Rect(0, 0, 100, 100))
var p stilus.Path
p.Rect(10.5, 10.5, 50, 30)
p.Rect(20, 60, 400, 10) // wide: interior rows arrive as runs
var b areaBlitter
r.Fill(&p, stilus.Identity, stilus.NonZero, &b)
fmt.Printf("%.0f px²\n", float64(b.area)/255)
}
Output: 2300 px²
func NewRasterizer ¶
func NewRasterizer(clip image.Rectangle) *Rasterizer
NewRasterizer returns a rasterizer clipped to clip.
func (*Rasterizer) AddLine ¶
func (r *Rasterizer) AddLine(x0, y0, x1, y1 float64)
AddLine adds a device-space line segment. Segments are clipped analytically: parts above or below the clip are dropped, parts right of it are dropped, and parts left of it become vertical edges on the clip's left border, which preserves the winding of everything inside.
func (*Rasterizer) AddPath ¶
func (r *Rasterizer) AddPath(p *Path, m Matrix)
AddPath adds the edges of p transformed by m. Open subpaths are closed implicitly. Curves are flattened in device space with a tolerance of a quarter pixel. Paths containing NaN or infinite values are ignored.
func (*Rasterizer) Clip ¶
func (r *Rasterizer) Clip() image.Rectangle
Clip returns the current clip rectangle.
func (*Rasterizer) Fill ¶
func (r *Rasterizer) Fill(p *Path, m Matrix, rule FillRule, b Blitter)
Fill rasterizes p transformed by m with the given fill rule. It is equivalent to Reset, AddPath and Rasterize.
func (*Rasterizer) Rasterize ¶
func (r *Rasterizer) Rasterize(rule FillRule, b Blitter)
Rasterize converts all added edges into spans and resets the rasterizer.
func (*Rasterizer) Reset ¶
func (r *Rasterizer) Reset()
Reset discards all accumulated edges and the Truncated state.
func (*Rasterizer) SetAntialias ¶
func (r *Rasterizer) SetAntialias(aa bool)
SetAntialias enables (default) or disables antialiasing. Without antialiasing a pixel is covered when at least half of it is inside.
func (*Rasterizer) SetClip ¶
func (r *Rasterizer) SetClip(clip image.Rectangle)
SetClip sets the device-space clip rectangle. Every span produced lies inside it. Buffers are resized only when the clip grows.
func (*Rasterizer) Truncated ¶
func (r *Rasterizer) Truncated() bool
Truncated reports whether the last path exceeded the edge budget.
type Rect ¶
type Rect struct{ X0, Y0, X1, Y1 float64 }
Rect is an axis-aligned rectangle [X0,X1)×[Y0,Y1).
type Sampler ¶ added in v0.5.0
type Sampler struct {
// contains filtered or unexported fields
}
Sampler reads a texture at device pixels. It caches what nearest sampling along the device axes shares between pixels (the texel column of every device column, the expanded rows of a magnified texture), so it is not safe for concurrent use; use one per worker.
func (*Sampler) Release ¶ added in v0.5.0
func (s *Sampler) Release()
Release drops the sampler's reference to its texture.
func (*Sampler) Sample ¶ added in v0.5.0
Sample writes the colours of pixels [x, x+len(dst)) of row y. Every pixel's coordinates are computed from its own position rather than accumulated along the span, so that a pixel samples the same texel whichever band, tile or span it is drawn in. Outside the texture the edge pixels repeat, or with SetupWrap the texture itself.
func (*Sampler) Setup ¶ added in v0.5.0
Setup chooses the mip level of t for drawing it with toDevice, which maps its base pixel space to device space, and reports whether t can be drawn so (toDevice is invertible). Magnified textures are sampled at the nearest pixel unless smooth is set; reduced ones bilinearly from the level at most twice as fine as the device.
func (*Sampler) SetupWrap ¶ added in v0.6.0
SetupWrap is Setup for a texture that repeats in both directions with the period of its base size: texture coordinates are taken modulo W and H before sampling, and bilinear samples at the border read the opposite edge. Its mip levels are periodic too, of the base size over 2^k rounded, so that a level still repeats with a whole number of pixels. Textures of 2^30 pixels or more along an axis cannot be repeated.
type Shader ¶
Shader produces premultiplied colors (in PackRGBA layout) for the pixels [x, x+len(dst)) on row y. It is called once per span, never per pixel.
type ShaderBlitter ¶
type ShaderBlitter struct {
// contains filtered or unexported fields
}
ShaderBlitter composites the output of a Shader onto an image.RGBA.
func NewShaderBlitter ¶
func NewShaderBlitter(dst *image.RGBA, s Shader) *ShaderBlitter
NewShaderBlitter returns a blitter painting s onto dst.
func (*ShaderBlitter) BlitCoverage ¶
func (b *ShaderBlitter) BlitCoverage(y, x int, cov []uint8)
func (*ShaderBlitter) BlitRun ¶
func (b *ShaderBlitter) BlitRun(y, x0, x1 int, alpha uint8)
type Shape ¶ added in v0.8.0
type Shape struct {
// MaxBytes bounds the memory of each form a shape makes: its edges,
// its strips and their row bins. A fill makes one form, a stroke up
// to two. Zero means DefaultShapeBytes. It takes effect at the next
// SetFill or SetStroke.
MaxBytes int
// contains filtered or unexported fields
}
Shape is a path prepared for drawing under one transform: its device bounds, and its edges or strips binned by bands of rows, so that a canvas drawing it touches only what crosses its clip's rows. The zero value is empty; SetFill and SetStroke (re)build it, keeping storage. After that it may be drawn by several canvases at once. It copies what it needs from the path and the style and keeps no reference to them.
A shape serves a page rendered in bands, one canvas each, every band playing the whole operation list: the transform of the points, flattening, dashing and the stroke outline with its joins and caps are done once, and a band only replays the edges and analytic strips that cross its rows. They are made by the first FillShape that needs them (a stroke has two forms, for the analytic and the outline path, and a draw uses one), so SetFill and SetStroke cost little more than copying the path, and the work is done by the canvases' workers.
Canvas.FillShape draws the bytes Canvas.Fill or Canvas.Stroke draw with the same path, transform and style, except that Stroke replaces curves lying entirely outside its clip by their chords, which moves a curved stroke's analytic rows (by a few levels of coverage) where the clip cuts it. Drawn in bands, a shape stays within 3/255 of the shape drawn whole; Stroke, per band, does not.
The geometry a form records is bounded by MaxBytes, whatever the clip of the canvas that makes it: recording stops at the budget, before the records and their bins are allocated, and every FillShape and FillUnion drawing the truncated form reports ErrEdgeBudget. The bound belongs to the shape, so canvases with different clips and edge budgets draw the same truncated form.
type SolidBlitter ¶
type SolidBlitter struct {
// contains filtered or unexported fields
}
SolidBlitter composites a single premultiplied color onto an image.RGBA.
func NewSolidBlitter ¶
func NewSolidBlitter(dst *image.RGBA, c color.RGBA) *SolidBlitter
NewSolidBlitter returns a blitter painting c (premultiplied) onto dst.
func (*SolidBlitter) BlitCoverage ¶
func (b *SolidBlitter) BlitCoverage(y, x int, cov []uint8)
func (*SolidBlitter) BlitRun ¶
func (b *SolidBlitter) BlitRun(y, x0, x1 int, alpha uint8)
func (*SolidBlitter) Reset ¶
func (b *SolidBlitter) Reset(dst *image.RGBA, c color.RGBA)
Reset retargets the blitter without allocating.
func (*SolidBlitter) SetColor ¶
func (b *SolidBlitter) SetColor(c color.RGBA)
SetColor changes the paint color (premultiplied).
type StrokeStyle ¶
type StrokeStyle struct {
Width float64 // 0 means the thinnest line the device can render
Cap Cap
Join Join
MiterLimit float64 // PDF default 10; values < 1 are treated as 1
Dash []float64 // on/off lengths; empty means solid
DashPhase float64
}
StrokeStyle describes a stroke in user space (the space the path is defined in), like the PDF graphics state.
type Stroker ¶
type Stroker struct {
// MaxDashes overrides DefaultMaxDashes when non-zero.
MaxDashes int
// contains filtered or unexported fields
}
Stroker converts strokes into fill geometry (closed outlines to be filled with NonZero). Offsets are computed in user space and transformed afterwards, which is exact for anisotropic transforms.
A Stroker is not safe for concurrent use. Buffers are retained.
func (*Stroker) Coverage ¶ added in v0.1.1
Coverage reports the factor to apply to the coverage of the last stroke's outline: the mean on-fraction of a dash pattern emitted as a solid stroke, else 1.
func (*Stroker) Stroke ¶
func (s *Stroker) Stroke(sink LineSink, p *Path, m Matrix, st *StrokeStyle)
Stroke emits the outline of p stroked with st under m into sink.
A dash pattern too dense to resolve in device space is emitted as a solid stroke; Coverage then reports the fraction the caller must scale the resulting coverage by.
type Texture ¶ added in v0.5.0
type Texture struct {
// contains filtered or unexported fields
}
Texture is a Plane and its mip levels, made on demand. It is safe for concurrent use: levels may be requested by several workers at once. The base plane must not change once the texture is made.
func NewTexture ¶ added in v0.5.0
NewTexture returns a texture of p.
func (*Texture) Level ¶ added in v0.5.0
Level returns mip level k (0 is the base), making it if needed.
func (*Texture) Levels ¶ added in v0.5.0
Levels returns the number of levels below the base: halvings until the plane is one pixel, at most 11.
type Union ¶ added in v0.8.0
type Union struct {
// contains filtered or unexported fields
}
Union collects fills and strokes that Canvas.FillUnion paints as one shape: their edges are accumulated together and the result is composited once.
Drawn one after another, shapes of one paint that overlap or abut along antialiased edges leave light seams: each composites its own partial coverage over the last, so two edges that together cover a pixel completely give 1 − (1 − a)(1 − b) < 1. In a union their areas are summed in the accumulator instead, and abutting or overlapping edges cover the pixel completely, as one path with several subpaths filled NonZero does.
That works because every element winds the same way. The outlines the Stroker produces wind in one direction everywhere they cover, joins and caps included (the direction depends only on the sign of the transform's determinant, and hairlines, outlined in device space, always wind the same way). A fill is taken with the NonZero rule; a path whose area winds the other way is reversed as a whole, which keeps its holes. Only a fill whose subpaths wind in both directions where they do not overlap (two separate squares, one clockwise, one counter-clockwise) still has parts that cancel where they overlap other elements.
Elements that cannot be summed are drawn on their own after the union, as Canvas.Fill, Canvas.Stroke or Canvas.FillShape draw them: EvenOdd fills, and strokes whose dash pattern is too dense to resolve (drawn at the pattern's mean coverage).
A Union keeps references to the paths, styles and shapes it is given; they must not change until it is drawn. The zero value is empty; Reset empties it, keeping its storage, so that it does not allocate once warm. FillUnion only reads it, so several canvases may draw one Union at once, one band each.
func (*Union) Fill ¶ added in v0.8.0
Fill adds p, transformed by m into device space, filled with rule.
func (*Union) Reset ¶ added in v0.8.0
func (u *Union) Reset()
Reset empties the union, keeping its storage.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
stilus-scenes
command
Command stilus-scenes renders the benchmark scenes, prints timings and allocations and optionally writes PNGs for visual inspection.
|
Command stilus-scenes renders the benchmark scenes, prints timings and allocations and optionally writes PNGs for visual inspection. |
|
internal
|
|
|
scenes
Package scenes provides deterministic benchmark scenes modelled on technical drawings: an A3 landscape page in PDF points, drawn under a scale to the requested resolution with a rectangular page clip.
|
Package scenes provides deterministic benchmark scenes modelled on technical drawings: an A3 landscape page in PDF points, drawn under a scale to the requested resolution with a rectangular page clip. |