fill.go

  1// Copyright 2018 by the rasterx Authors. All rights reserved.
  2//_
  3// Created 2017 by S.R.Wiley
  4
  5package rasterx
  6
  7import (
  8	"image"
  9	"image/color"
 10	"math"
 11
 12	"golang.org/x/image/math/fixed"
 13)
 14
 15type (
 16	// ColorFunc maps a color to x y coordinates
 17	ColorFunc func(x, y int) color.Color
 18	// Scanner interface for path generating types
 19	Scanner interface {
 20		Start(a fixed.Point26_6)
 21		Line(b fixed.Point26_6)
 22		Draw()
 23		GetPathExtent() fixed.Rectangle26_6
 24		SetBounds(w, h int)
 25		SetColor(color interface{})
 26		SetWinding(useNonZeroWinding bool)
 27		Clear()
 28
 29		// SetClip sets an optional clipping rectangle to restrict rendering
 30		// only to that region -- if size is 0 then ignored (set to image.ZR
 31		// to clear)
 32		SetClip(rect image.Rectangle)
 33	}
 34	// Adder interface for types that can accumlate path commands
 35	Adder interface {
 36		// Start starts a new curve at the given point.
 37		Start(a fixed.Point26_6)
 38		// Line adds a line segment to the path
 39		Line(b fixed.Point26_6)
 40		// QuadBezier adds a quadratic bezier curve to the path
 41		QuadBezier(b, c fixed.Point26_6)
 42		// CubeBezier adds a cubic bezier curve to the path
 43		CubeBezier(b, c, d fixed.Point26_6)
 44		// Closes the path to the start point if closeLoop is true
 45		Stop(closeLoop bool)
 46	}
 47	// Rasterx extends the adder interface to include lineF and joinF functions
 48	Rasterx interface {
 49		Adder
 50		lineF(b fixed.Point26_6)
 51		joinF()
 52	}
 53
 54	// Filler satisfies Rasterx
 55	Filler struct {
 56		Scanner
 57		a, first fixed.Point26_6
 58	}
 59)
 60
 61// Start starts a new path at the given point.
 62func (r *Filler) Start(a fixed.Point26_6) {
 63	r.a = a
 64	r.first = a
 65	r.Scanner.Start(a)
 66}
 67
 68// Stop sends a path at the given point.
 69func (r *Filler) Stop(isClosed bool) {
 70	if r.first != r.a {
 71		r.Line(r.first)
 72	}
 73}
 74
 75// QuadBezier adds a quadratic segment to the current curve.
 76func (r *Filler) QuadBezier(b, c fixed.Point26_6) {
 77	r.QuadBezierF(r, b, c)
 78}
 79
 80// QuadTo flattens the quadratic Bezier curve into lines through the LineTo func
 81// This functions is adapted from the version found in
 82// golang.org/x/image/vector
 83func QuadTo(ax, ay, bx, by, cx, cy float32, LineTo func(dx, dy float32)) {
 84	devsq := devSquared(ax, ay, bx, by, cx, cy)
 85	if devsq >= 0.333 {
 86		const tol = 3
 87		n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq))))
 88		t, nInv := float32(0), 1/float32(n)
 89		for i := 0; i < n-1; i++ {
 90			t += nInv
 91
 92			mt := 1 - t
 93			t1 := mt * mt
 94			t2 := mt * t * 2
 95			t3 := t * t
 96			LineTo(
 97				ax*t1+bx*t2+cx*t3,
 98				ay*t1+by*t2+cy*t3)
 99		}
100	}
101	LineTo(cx, cy)
102}
103
104// CubeTo flattens the cubic Bezier curve into lines through the LineTo func
105// This functions is adapted from the version found in
106// golang.org/x/image/vector
107func CubeTo(ax, ay, bx, by, cx, cy, dx, dy float32, LineTo func(ex, ey float32)) {
108	devsq := devSquared(ax, ay, bx, by, dx, dy)
109	if devsqAlt := devSquared(ax, ay, cx, cy, dx, dy); devsq < devsqAlt {
110		devsq = devsqAlt
111	}
112	if devsq >= 0.333 {
113		const tol = 3
114		n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq))))
115		t, nInv := float32(0), 1/float32(n)
116		for i := 0; i < n-1; i++ {
117			t += nInv
118
119			tsq := t * t
120			mt := 1 - t
121			mtsq := mt * mt
122			t1 := mtsq * mt
123			t2 := mtsq * t * 3
124			t3 := mt * tsq * 3
125			t4 := tsq * t
126			LineTo(
127				ax*t1+bx*t2+cx*t3+dx*t4,
128				ay*t1+by*t2+cy*t3+dy*t4)
129		}
130	}
131	LineTo(dx, dy)
132}
133
134// devSquared returns a measure of how curvy the sequence (ax, ay) to (bx, by)
135// to (cx, cy) is. It determines how many line segments will approximate a
136// Bรฉzier curve segment. This functions is copied from the version found in
137// golang.org/x/image/vector as are the below comments.
138//
139// http://lists.nongnu.org/archive/html/freetype-devel/2016-08/msg00080.html
140// gives the rationale for this evenly spaced heuristic instead of a recursive
141// de Casteljau approach:
142//
143// The reason for the subdivision by n is that I expect the "flatness"
144// computation to be semi-expensive (it's done once rather than on each
145// potential subdivision) and also because you'll often get fewer subdivisions.
146// Taking a circular arc as a simplifying assumption (ie a spherical cow),
147// where I get n, a recursive approach would get 2^โŒˆlg nโŒ‰, which, if I haven't
148// made any horrible mistakes, is expected to be 33% more in the limit.
149func devSquared(ax, ay, bx, by, cx, cy float32) float32 {
150	devx := ax - 2*bx + cx
151	devy := ay - 2*by + cy
152	return devx*devx + devy*devy
153}
154
155// QuadBezierF adds a quadratic segment to the sgm Rasterizer.
156func (r *Filler) QuadBezierF(sgm Rasterx, b, c fixed.Point26_6) {
157	// check for degenerate bezier
158	if r.a == b || b == c {
159		sgm.Line(c)
160		return
161	}
162	sgm.joinF()
163	QuadTo(float32(r.a.X), float32(r.a.Y), // Pts are x64, but does not matter.
164		float32(b.X), float32(b.Y),
165		float32(c.X), float32(c.Y),
166		func(dx, dy float32) {
167			sgm.lineF(fixed.Point26_6{X: fixed.Int26_6(dx), Y: fixed.Int26_6(dy)})
168		})
169
170}
171
172// CubeBezier adds a cubic bezier to the curve
173func (r *Filler) CubeBezier(b, c, d fixed.Point26_6) {
174	r.CubeBezierF(r, b, c, d)
175}
176
177// joinF is a no-op for a filling rasterizer. This is used in stroking and dashed
178// stroking
179func (r *Filler) joinF() {
180
181}
182
183// Line for a filling rasterizer is just the line call in scan
184func (r *Filler) Line(b fixed.Point26_6) {
185	r.lineF(b)
186}
187
188// lineF for a filling rasterizer is just the line call in scan
189func (r *Filler) lineF(b fixed.Point26_6) {
190	r.Scanner.Line(b)
191	r.a = b
192}
193
194// CubeBezierF adds a cubic bezier to the curve. sending the line calls the the
195// sgm Rasterizer
196func (r *Filler) CubeBezierF(sgm Rasterx, b, c, d fixed.Point26_6) {
197	if (r.a == b && c == d) || (r.a == b && b == c) || (c == b && d == c) {
198		sgm.Line(d)
199		return
200	}
201	sgm.joinF()
202	CubeTo(float32(r.a.X), float32(r.a.Y),
203		float32(b.X), float32(b.Y),
204		float32(c.X), float32(c.Y),
205		float32(d.X), float32(d.Y),
206		func(ex, ey float32) {
207			sgm.lineF(fixed.Point26_6{X: fixed.Int26_6(ex), Y: fixed.Int26_6(ey)})
208		})
209}
210
211// Clear resets the filler
212func (r *Filler) Clear() {
213	r.a = fixed.Point26_6{}
214	r.first = r.a
215	r.Scanner.Clear()
216}
217
218// SetBounds sets the maximum width and height of the rasterized image and
219// calls Clear. The width and height are in pixels, not fixed.Int26_6 units.
220func (r *Filler) SetBounds(width, height int) {
221	if width < 0 {
222		width = 0
223	}
224	if height < 0 {
225		height = 0
226	}
227	r.Scanner.SetBounds(width, height)
228	r.Clear()
229}
230
231// NewFiller returns a Filler ptr with default values.
232// A Filler in addition to rasterizing lines like a Scann,
233// can also rasterize quadratic and cubic bezier curves.
234// If Scanner is nil default scanner ScannerGV is used
235func NewFiller(width, height int, scanner Scanner) *Filler {
236	r := new(Filler)
237	r.Scanner = scanner
238	r.SetBounds(width, height)
239	r.SetWinding(true)
240	return r
241}