moved math code to backendbase package so that backends can also use it

This commit is contained in:
Thomas Friedel 2020-03-22 10:07:18 +01:00
parent 066f4f55bb
commit cc9247c627
9 changed files with 364 additions and 357 deletions

140
backend/backendbase/math.go Normal file
View file

@ -0,0 +1,140 @@
package backendbase
import (
"fmt"
"math"
)
type Vec [2]float64
func (v Vec) String() string {
return fmt.Sprintf("[%f,%f]", v[0], v[1])
}
func (v Vec) Add(v2 Vec) Vec {
return Vec{v[0] + v2[0], v[1] + v2[1]}
}
func (v Vec) Sub(v2 Vec) Vec {
return Vec{v[0] - v2[0], v[1] - v2[1]}
}
func (v Vec) Mul(v2 Vec) Vec {
return Vec{v[0] * v2[0], v[1] * v2[1]}
}
func (v Vec) Mulf(f float64) Vec {
return Vec{v[0] * f, v[1] * f}
}
func (v Vec) MulMat(m Mat) Vec {
return Vec{
m[0]*v[0] + m[2]*v[1] + m[4],
m[1]*v[0] + m[3]*v[1] + m[5]}
}
func (v Vec) MulMat2(m Mat2) Vec {
return Vec{m[0]*v[0] + m[2]*v[1], m[1]*v[0] + m[3]*v[1]}
}
func (v Vec) Div(v2 Vec) Vec {
return Vec{v[0] / v2[0], v[1] / v2[1]}
}
func (v Vec) Divf(f float64) Vec {
return Vec{v[0] / f, v[1] / f}
}
func (v Vec) Dot(v2 Vec) float64 {
return v[0]*v2[0] + v[1]*v2[1]
}
func (v Vec) Len() float64 {
return math.Sqrt(v[0]*v[0] + v[1]*v[1])
}
func (v Vec) LenSqr() float64 {
return v[0]*v[0] + v[1]*v[1]
}
func (v Vec) Norm() Vec {
return v.Mulf(1.0 / v.Len())
}
func (v Vec) Atan2() float64 {
return math.Atan2(v[1], v[0])
}
func (v Vec) Angle() float64 {
return math.Pi*0.5 - math.Atan2(v[1], v[0])
}
func (v Vec) AngleTo(v2 Vec) float64 {
return math.Acos(v.Norm().Dot(v2.Norm()))
}
type Mat [6]float64
func (m *Mat) String() string {
return fmt.Sprintf("[%f,%f,0,\n %f,%f,0,\n %f,%f,1,]", m[0], m[2], m[4], m[1], m[3], m[5])
}
var MatIdentity = Mat{
1, 0,
0, 1,
0, 0}
func MatTranslate(v Vec) Mat {
return Mat{
1, 0,
0, 1,
v[0], v[1]}
}
func MatScale(v Vec) Mat {
return Mat{
v[0], 0,
0, v[1],
0, 0}
}
func MatRotate(radians float64) Mat {
s, c := math.Sincos(radians)
return Mat{
c, s,
-s, c,
0, 0}
}
func (m Mat) Mul(m2 Mat) Mat {
return Mat{
m[0]*m2[0] + m[1]*m2[2],
m[0]*m2[1] + m[1]*m2[3],
m[2]*m2[0] + m[3]*m2[2],
m[2]*m2[1] + m[3]*m2[3],
m[4]*m2[0] + m[5]*m2[2] + m2[4],
m[4]*m2[1] + m[5]*m2[3] + m2[5]}
}
func (m Mat) Invert() Mat {
identity := 1.0 / (m[0]*m[3] - m[2]*m[1])
return Mat{
m[3] * identity,
-m[1] * identity,
-m[2] * identity,
m[0] * identity,
(m[2]*m[5] - m[3]*m[4]) * identity,
(m[1]*m[4] - m[0]*m[5]) * identity,
}
}
type Mat2 [4]float64
func (m Mat) Mat2() Mat2 {
return Mat2{m[0], m[1], m[2], m[3]}
}
func (m *Mat2) String() string {
return fmt.Sprintf("[%f,%f,\n %f,%f]", m[0], m[2], m[1], m[3])
}

View file

@ -35,7 +35,7 @@ type Canvas struct {
}
type drawState struct {
transform mat
transform backendbase.Mat
fill drawStyle
stroke drawStyle
font *Font
@ -150,7 +150,7 @@ func New(backend backendbase.Backend) *Canvas {
cv.state.globalAlpha = 1
cv.state.fill.color = color.RGBA{A: 255}
cv.state.stroke.color = color.RGBA{A: 255}
cv.state.transform = matIdentity
cv.state.transform = backendbase.MatIdentity
cv.path.cv = cv
return cv
}
@ -170,8 +170,8 @@ func (cv *Canvas) Height() int {
// Size returns the internal width and height of the canvas
func (cv *Canvas) Size() (int, int) { return cv.b.Size() }
func (cv *Canvas) tf(v vec) vec {
return v.mulMat(cv.state.transform)
func (cv *Canvas) tf(v backendbase.Vec) backendbase.Vec {
return v.MulMat(cv.state.transform)
}
const alphaTexSize = 2048
@ -380,7 +380,7 @@ func (cv *Canvas) Restore() {
cv.b.ClearClip()
for _, st := range cv.stateStack {
if len(st.clip.p) > 0 {
cv.clip(&st.clip, matIdentity)
cv.clip(&st.clip, backendbase.MatIdentity)
}
}
cv.state = cv.stateStack[l-1]
@ -389,27 +389,27 @@ func (cv *Canvas) Restore() {
// Scale updates the current transformation with a scaling by the given values
func (cv *Canvas) Scale(x, y float64) {
cv.state.transform = matScale(vec{x, y}).mul(cv.state.transform)
cv.state.transform = backendbase.MatScale(backendbase.Vec{x, y}).Mul(cv.state.transform)
}
// Translate updates the current transformation with a translation by the given values
func (cv *Canvas) Translate(x, y float64) {
cv.state.transform = matTranslate(vec{x, y}).mul(cv.state.transform)
cv.state.transform = backendbase.MatTranslate(backendbase.Vec{x, y}).Mul(cv.state.transform)
}
// Rotate updates the current transformation with a rotation by the given angle
func (cv *Canvas) Rotate(angle float64) {
cv.state.transform = matRotate(angle).mul(cv.state.transform)
cv.state.transform = backendbase.MatRotate(angle).Mul(cv.state.transform)
}
// Transform updates the current transformation with the given matrix
func (cv *Canvas) Transform(a, b, c, d, e, f float64) {
cv.state.transform = mat{a, b, c, d, e, f}.mul(cv.state.transform)
cv.state.transform = backendbase.Mat{a, b, c, d, e, f}.Mul(cv.state.transform)
}
// SetTransform replaces the current transformation with the given matrix
func (cv *Canvas) SetTransform(a, b, c, d, e, f float64) {
cv.state.transform = mat{a, b, c, d, e, f}
cv.state.transform = backendbase.Mat{a, b, c, d, e, f}
}
// SetShadowColor sets the color of the shadow. If it is fully transparent (default)
@ -456,14 +456,14 @@ func (cv *Canvas) IsPointInStroke(x, y float64) bool {
}
var triBuf [500][2]float64
tris := cv.strokeTris(&cv.path, cv.state.transform.invert(), true, triBuf[:0])
tris := cv.strokeTris(&cv.path, cv.state.transform.Invert(), true, triBuf[:0])
pt := vec{x, y}
pt := backendbase.Vec{x, y}
for i := 0; i < len(tris); i += 3 {
a := vec{tris[i][0], tris[i][1]}
b := vec{tris[i+1][0], tris[i+1][1]}
c := vec{tris[i+2][0], tris[i+2][1]}
a := backendbase.Vec{tris[i][0], tris[i][1]}
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
if triangleContainsPoint(a, b, c, pt) {
return true
}

View file

@ -13,7 +13,7 @@ import (
// will correspond to a straight line
type LinearGradient struct {
cv *Canvas
from, to vec
from, to backendbase.Vec
created bool
loaded bool
opaque bool
@ -27,7 +27,7 @@ type LinearGradient struct {
// will correspond to a circle
type RadialGradient struct {
cv *Canvas
from, to vec
from, to backendbase.Vec
radFrom float64
radTo float64
created bool
@ -44,8 +44,8 @@ func (cv *Canvas) CreateLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
lg := &LinearGradient{
cv: cv,
opaque: true,
from: vec{x0, y0},
to: vec{x1, y1},
from: backendbase.Vec{x0, y0},
to: backendbase.Vec{x1, y1},
data: make(backendbase.Gradient, 0, 20),
}
runtime.SetFinalizer(lg, func(*LinearGradient) {
@ -62,8 +62,8 @@ func (cv *Canvas) CreateRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGr
rg := &RadialGradient{
cv: cv,
opaque: true,
from: vec{x0, y0},
to: vec{x1, y1},
from: backendbase.Vec{x0, y0},
to: backendbase.Vec{x1, y1},
radFrom: r0,
radTo: r1,
data: make(backendbase.Gradient, 0, 20),

View file

@ -182,10 +182,10 @@ func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
}
var data [4][2]float64
data[0] = cv.tf(vec{dx, dy})
data[1] = cv.tf(vec{dx, dy + dh})
data[2] = cv.tf(vec{dx + dw, dy + dh})
data[3] = cv.tf(vec{dx + dw, dy})
data[0] = cv.tf(backendbase.Vec{dx, dy})
data[1] = cv.tf(backendbase.Vec{dx, dy + dh})
data[2] = cv.tf(backendbase.Vec{dx + dw, dy + dh})
data[3] = cv.tf(backendbase.Vec{dx + dw, dy})
cv.drawShadow(data[:], nil, false)
@ -207,7 +207,7 @@ func (cv *Canvas) PutImageData(img *image.RGBA, x, y int) {
type ImagePattern struct {
cv *Canvas
img *Image
tf mat
tf backendbase.Mat
rep imagePatternRepeat
ip backendbase.ImagePattern
}
@ -222,8 +222,8 @@ const (
NoRepeat = imagePatternRepeat(backendbase.NoRepeat)
)
func (ip *ImagePattern) data(tf mat) backendbase.ImagePatternData {
m := tf.invert().mul(ip.tf.invert())
func (ip *ImagePattern) data(tf backendbase.Mat) backendbase.ImagePatternData {
m := tf.Invert().Mul(ip.tf.Invert())
return backendbase.ImagePatternData{
Image: ip.img.img,
Transform: [9]float64{
@ -239,7 +239,7 @@ func (ip *ImagePattern) data(tf mat) backendbase.ImagePatternData {
// to the given matrix. The matrix is a 3x3 matrix, but three
// of the values are always identity values
func (ip *ImagePattern) SetTransform(tf [6]float64) {
ip.tf = mat(tf)
ip.tf = backendbase.Mat(tf)
}
// CreatePattern creates a new image pattern with the specified
@ -249,7 +249,7 @@ func (cv *Canvas) CreatePattern(src interface{}, repeat imagePatternRepeat) *Ima
cv: cv,
img: cv.getImage(src),
rep: repeat,
tf: mat{1, 0, 0, 1, 0, 0},
tf: backendbase.Mat{1, 0, 0, 1, 0, 0},
}
if ip.img != nil {
ip.ip = cv.b.LoadImagePattern(ip.data(cv.state.transform))

140
math.go
View file

@ -1,140 +0,0 @@
package canvas
import (
"fmt"
"math"
)
type vec [2]float64
func (v vec) String() string {
return fmt.Sprintf("[%f,%f]", v[0], v[1])
}
func (v vec) add(v2 vec) vec {
return vec{v[0] + v2[0], v[1] + v2[1]}
}
func (v vec) sub(v2 vec) vec {
return vec{v[0] - v2[0], v[1] - v2[1]}
}
func (v vec) mul(v2 vec) vec {
return vec{v[0] * v2[0], v[1] * v2[1]}
}
func (v vec) mulf(f float64) vec {
return vec{v[0] * f, v[1] * f}
}
func (v vec) mulMat(m mat) vec {
return vec{
m[0]*v[0] + m[2]*v[1] + m[4],
m[1]*v[0] + m[3]*v[1] + m[5]}
}
func (v vec) mulMat2(m mat2) vec {
return vec{m[0]*v[0] + m[2]*v[1], m[1]*v[0] + m[3]*v[1]}
}
func (v vec) div(v2 vec) vec {
return vec{v[0] / v2[0], v[1] / v2[1]}
}
func (v vec) divf(f float64) vec {
return vec{v[0] / f, v[1] / f}
}
func (v vec) dot(v2 vec) float64 {
return v[0]*v2[0] + v[1]*v2[1]
}
func (v vec) len() float64 {
return math.Sqrt(v[0]*v[0] + v[1]*v[1])
}
func (v vec) lenSqr() float64 {
return v[0]*v[0] + v[1]*v[1]
}
func (v vec) norm() vec {
return v.mulf(1.0 / v.len())
}
func (v vec) atan2() float64 {
return math.Atan2(v[1], v[0])
}
func (v vec) angle() float64 {
return math.Pi*0.5 - math.Atan2(v[1], v[0])
}
func (v vec) angleTo(v2 vec) float64 {
return math.Acos(v.norm().dot(v2.norm()))
}
type mat [6]float64
func (m *mat) String() string {
return fmt.Sprintf("[%f,%f,0,\n %f,%f,0,\n %f,%f,1,]", m[0], m[2], m[4], m[1], m[3], m[5])
}
var matIdentity = mat{
1, 0,
0, 1,
0, 0}
func matTranslate(v vec) mat {
return mat{
1, 0,
0, 1,
v[0], v[1]}
}
func matScale(v vec) mat {
return mat{
v[0], 0,
0, v[1],
0, 0}
}
func matRotate(radians float64) mat {
s, c := math.Sincos(radians)
return mat{
c, s,
-s, c,
0, 0}
}
func (m mat) mul(m2 mat) mat {
return mat{
m[0]*m2[0] + m[1]*m2[2],
m[0]*m2[1] + m[1]*m2[3],
m[2]*m2[0] + m[3]*m2[2],
m[2]*m2[1] + m[3]*m2[3],
m[4]*m2[0] + m[5]*m2[2] + m2[4],
m[4]*m2[1] + m[5]*m2[3] + m2[5]}
}
func (m mat) invert() mat {
identity := 1.0 / (m[0]*m[3] - m[2]*m[1])
return mat{
m[3] * identity,
-m[1] * identity,
-m[2] * identity,
m[0] * identity,
(m[2]*m[5] - m[3]*m[4]) * identity,
(m[1]*m[4] - m[0]*m[5]) * identity,
}
}
type mat2 [4]float64
func (m mat) mat2() mat2 {
return mat2{m[0], m[1], m[2], m[3]}
}
func (m *mat2) String() string {
return fmt.Sprintf("[%f,%f,\n %f,%f]", m[0], m[2], m[1], m[3])
}

132
path2d.go
View file

@ -2,6 +2,8 @@ package canvas
import (
"math"
"github.com/tfriedel6/canvas/backend/backendbase"
)
// Path2D is a type that holds a predefined path which can be drawn
@ -9,13 +11,13 @@ import (
type Path2D struct {
cv *Canvas
p []pathPoint
move vec
move backendbase.Vec
cwSum float64
}
type pathPoint struct {
pos vec
next vec
pos backendbase.Vec
next backendbase.Vec
flags pathPointFlag
}
@ -40,12 +42,12 @@ func (cv *Canvas) NewPath2D() *Path2D {
// MoveTo (see equivalent function on canvas type)
func (p *Path2D) MoveTo(x, y float64) {
if len(p.p) > 0 && isSamePoint(p.p[len(p.p)-1].pos, vec{x, y}, 0.1) {
if len(p.p) > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
return
}
p.p = append(p.p, pathPoint{pos: vec{x, y}, flags: pathMove | pathIsConvex})
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}, flags: pathMove | pathIsConvex})
p.cwSum = 0
p.move = vec{x, y}
p.move = backendbase.Vec{x, y}
}
// LineTo (see equivalent function on canvas type)
@ -55,7 +57,7 @@ func (p *Path2D) LineTo(x, y float64) {
func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
count := len(p.p)
if count > 0 && isSamePoint(p.p[len(p.p)-1].pos, vec{x, y}, 0.1) {
if count > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
return
}
if count == 0 {
@ -63,9 +65,9 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
return
}
prev := &p.p[count-1]
prev.next = vec{x, y}
prev.next = backendbase.Vec{x, y}
prev.flags |= pathAttach
p.p = append(p.p, pathPoint{pos: vec{x, y}})
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}})
newp := &p.p[count]
if prev.flags&pathIsConvex > 0 {
@ -84,9 +86,9 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
prev2 := &p.p[count-2]
cw := (prev.flags & pathIsClockwise) > 0
ln := prev.pos.sub(prev2.pos)
lo := vec{ln[1], -ln[0]}
dot := newp.pos.sub(prev2.pos).dot(lo)
ln := prev.pos.Sub(prev2.pos)
lo := backendbase.Vec{ln[1], -ln[0]}
dot := newp.pos.Sub(prev2.pos).Dot(lo)
if (cw && dot <= 0) || (!cw && dot >= 0) {
newp.flags |= pathIsConvex
@ -97,8 +99,8 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
newp.flags |= pathSelfIntersects
} else if newp.flags&pathIsConvex == 0 && newp.flags&pathSelfIntersects == 0 && checkSelfIntersection && !Performance.IgnoreSelfIntersections {
cuts := false
var cutPoint vec
b0, b1 := prev.pos, vec{x, y}
var cutPoint backendbase.Vec
b0, b1 := prev.pos, backendbase.Vec{x, y}
for i := 1; i < count; i++ {
a0, a1 := p.p[i-1].pos, p.p[i].pos
var r1, r2 float64
@ -108,7 +110,7 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
break
}
}
if cuts && !isSamePoint(cutPoint, vec{x, y}, samePointTolerance) {
if cuts && !isSamePoint(cutPoint, backendbase.Vec{x, y}, samePointTolerance) {
newp.flags |= pathSelfIntersects
}
}
@ -116,19 +118,19 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
// Arc (see equivalent function on canvas type)
func (p *Path2D) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
p.arc(x, y, radius, startAngle, endAngle, anticlockwise, matIdentity, true)
p.arc(x, y, radius, startAngle, endAngle, anticlockwise, backendbase.MatIdentity, true)
}
func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool, m mat, ident bool) {
func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool, m backendbase.Mat, ident bool) {
checkSelfIntersection := len(p.p) > 0
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
if endAngle == startAngle {
s, c := math.Sincos(endAngle)
pt := vec{x + radius*c, y + radius*s}
pt := backendbase.Vec{x + radius*c, y + radius*s}
if !ident {
pt = pt.mulMat(m)
pt = pt.MulMat(m)
}
p.lineTo(pt[0], pt[1], checkSelfIntersection)
@ -163,26 +165,26 @@ func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise b
if !anticlockwise {
for a := startAngle; a < endAngle; a += step {
s, c := math.Sincos(a)
pt := vec{x + radius*c, y + radius*s}
pt := backendbase.Vec{x + radius*c, y + radius*s}
if !ident {
pt = pt.mulMat(m)
pt = pt.MulMat(m)
}
p.lineTo(pt[0], pt[1], checkSelfIntersection)
}
} else {
for a := startAngle; a > endAngle; a -= step {
s, c := math.Sincos(a)
pt := vec{x + radius*c, y + radius*s}
pt := backendbase.Vec{x + radius*c, y + radius*s}
if !ident {
pt = pt.mulMat(m)
pt = pt.MulMat(m)
}
p.lineTo(pt[0], pt[1], checkSelfIntersection)
}
}
s, c := math.Sincos(endAngle)
pt := vec{x + radius*c, y + radius*s}
pt := backendbase.Vec{x + radius*c, y + radius*s}
if !ident {
pt = pt.mulMat(m)
pt = pt.MulMat(m)
}
p.lineTo(pt[0], pt[1], checkSelfIntersection)
@ -193,34 +195,34 @@ func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise b
// ArcTo (see equivalent function on canvas type)
func (p *Path2D) ArcTo(x1, y1, x2, y2, radius float64) {
p.arcTo(x1, y1, x2, y2, radius, matIdentity, true)
p.arcTo(x1, y1, x2, y2, radius, backendbase.MatIdentity, true)
}
func (p *Path2D) arcTo(x1, y1, x2, y2, radius float64, m mat, ident bool) {
func (p *Path2D) arcTo(x1, y1, x2, y2, radius float64, m backendbase.Mat, ident bool) {
if len(p.p) == 0 {
return
}
p0, p1, p2 := p.p[len(p.p)-1].pos, vec{x1, y1}, vec{x2, y2}
p0, p1, p2 := p.p[len(p.p)-1].pos, backendbase.Vec{x1, y1}, backendbase.Vec{x2, y2}
if !ident {
p0 = p0.mulMat(m.invert())
p0 = p0.MulMat(m.Invert())
}
v0, v1 := p0.sub(p1).norm(), p2.sub(p1).norm()
angle := math.Acos(v0.dot(v1))
v0, v1 := p0.Sub(p1).Norm(), p2.Sub(p1).Norm()
angle := math.Acos(v0.Dot(v1))
// should be in the range [0-pi]. if parallel, use a straight line
if angle <= 0 || angle >= math.Pi {
p.LineTo(x2, y2)
return
}
// cv0 and cv1 are vectors that point to the center of the circle
cv0 := vec{-v0[1], v0[0]}
cv1 := vec{v1[1], -v1[0]}
x := cv1.sub(cv0).div(v0.sub(v1))[0] * radius
cv0 := backendbase.Vec{-v0[1], v0[0]}
cv1 := backendbase.Vec{v1[1], -v1[0]}
x := cv1.Sub(cv0).Div(v0.Sub(v1))[0] * radius
if x < 0 {
cv0 = cv0.mulf(-1)
cv1 = cv1.mulf(-1)
cv0 = cv0.Mulf(-1)
cv1 = cv1.Mulf(-1)
}
center := p1.add(v0.mulf(math.Abs(x))).add(cv0.mulf(radius))
a0, a1 := cv0.mulf(-1).atan2(), cv1.mulf(-1).atan2()
center := p1.Add(v0.Mulf(math.Abs(x))).Add(cv0.Mulf(radius))
a0, a1 := cv0.Mulf(-1).Atan2(), cv1.Mulf(-1).Atan2()
if x > 0 {
if a1-a0 > 0 {
a0 += math.Pi * 2
@ -239,17 +241,17 @@ func (p *Path2D) QuadraticCurveTo(x1, y1, x2, y2 float64) {
return
}
p0 := p.p[len(p.p)-1].pos
p1 := vec{x1, y1}
p2 := vec{x2, y2}
v0 := p1.sub(p0)
v1 := p2.sub(p1)
p1 := backendbase.Vec{x1, y1}
p2 := backendbase.Vec{x2, y2}
v0 := p1.Sub(p0)
v1 := p2.Sub(p1)
const step = 0.01
for r := 0.0; r < 1; r += step {
i0 := v0.mulf(r).add(p0)
i1 := v1.mulf(r).add(p1)
pt := i1.sub(i0).mulf(r).add(i0)
i0 := v0.Mulf(r).Add(p0)
i1 := v1.Mulf(r).Add(p1)
pt := i1.Sub(i0).Mulf(r).Add(i0)
p.LineTo(pt[0], pt[1])
}
p.LineTo(x2, y2)
@ -261,24 +263,24 @@ func (p *Path2D) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
return
}
p0 := p.p[len(p.p)-1].pos
p1 := vec{x1, y1}
p2 := vec{x2, y2}
p3 := vec{x3, y3}
v0 := p1.sub(p0)
v1 := p2.sub(p1)
v2 := p3.sub(p2)
p1 := backendbase.Vec{x1, y1}
p2 := backendbase.Vec{x2, y2}
p3 := backendbase.Vec{x3, y3}
v0 := p1.Sub(p0)
v1 := p2.Sub(p1)
v2 := p3.Sub(p2)
const step = 0.01
for r := 0.0; r < 1; r += step {
i0 := v0.mulf(r).add(p0)
i1 := v1.mulf(r).add(p1)
i2 := v2.mulf(r).add(p2)
iv0 := i1.sub(i0)
iv1 := i2.sub(i1)
j0 := iv0.mulf(r).add(i0)
j1 := iv1.mulf(r).add(i1)
pt := j1.sub(j0).mulf(r).add(j0)
i0 := v0.Mulf(r).Add(p0)
i1 := v1.Mulf(r).Add(p1)
i2 := v2.Mulf(r).Add(p2)
iv0 := i1.Sub(i0)
iv1 := i2.Sub(i1)
j0 := iv0.Mulf(r).Add(i0)
j1 := iv1.Mulf(r).Add(i1)
pt := j1.Sub(j0).Mulf(r).Add(j0)
p.LineTo(pt[0], pt[1])
}
p.LineTo(x3, y3)
@ -434,7 +436,7 @@ func (p *Path2D) IsPointInPath(x, y float64, rule pathRule) bool {
num := 0
prev := sp[len(sp)-1].pos
for _, pt := range sp {
r, dir := pointIsRightOfLine(prev, pt.pos, vec{x, y})
r, dir := pointIsRightOfLine(prev, pt.pos, backendbase.Vec{x, y})
prev = pt.pos
if !r {
continue
@ -464,14 +466,14 @@ func (p *Path2D) IsPointInStroke(x, y float64) bool {
}
var triBuf [500][2]float64
tris := p.cv.strokeTris(p, mat{}, false, triBuf[:0])
tris := p.cv.strokeTris(p, backendbase.Mat{}, false, triBuf[:0])
pt := vec{x, y}
pt := backendbase.Vec{x, y}
for i := 0; i < len(tris); i += 3 {
a := vec{tris[i][0], tris[i][1]}
b := vec{tris[i+1][0], tris[i+1][1]}
c := vec{tris[i+2][0], tris[i+2][1]}
a := backendbase.Vec{tris[i][0], tris[i][1]}
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
if triangleContainsPoint(a, b, c, pt) {
return true
}

184
paths.go
View file

@ -2,6 +2,8 @@ package canvas
import (
"math"
"github.com/tfriedel6/canvas/backend/backendbase"
)
// BeginPath clears the current path and starts a new one
@ -12,19 +14,19 @@ func (cv *Canvas) BeginPath() {
cv.path.p = cv.path.p[:0]
}
func isSamePoint(a, b vec, maxDist float64) bool {
func isSamePoint(a, b backendbase.Vec, maxDist float64) bool {
return math.Abs(b[0]-a[0]) <= maxDist && math.Abs(b[1]-a[1]) <= maxDist
}
// MoveTo adds a gap and moves the end of the path to x/y
func (cv *Canvas) MoveTo(x, y float64) {
tf := cv.tf(vec{x, y})
tf := cv.tf(backendbase.Vec{x, y})
cv.path.MoveTo(tf[0], tf[1])
}
// LineTo adds a line to the end of the path
func (cv *Canvas) LineTo(x, y float64) {
tf := cv.tf(vec{x, y})
tf := cv.tf(backendbase.Vec{x, y})
cv.path.LineTo(tf[0], tf[1])
}
@ -33,7 +35,7 @@ func (cv *Canvas) LineTo(x, y float64) {
// means that the line is added anticlockwise
func (cv *Canvas) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
ax, ay := math.Sincos(startAngle)
startAngle2 := vec{ay, ax}.mulMat2(cv.state.transform.mat2()).atan2()
startAngle2 := backendbase.Vec{ay, ax}.MulMat2(cv.state.transform.Mat2()).Atan2()
endAngle2 := startAngle2 + (endAngle - startAngle)
cv.path.arc(x, y, radius, startAngle2, endAngle2, anticlockwise, cv.state.transform, false)
}
@ -49,17 +51,17 @@ func (cv *Canvas) ArcTo(x1, y1, x2, y2, radius float64) {
// QuadraticCurveTo adds a quadratic curve to the path. It uses the current end
// point of the path, x1/y1 defines the curve, and x2/y2 is the end point
func (cv *Canvas) QuadraticCurveTo(x1, y1, x2, y2 float64) {
tf1 := cv.tf(vec{x1, y1})
tf2 := cv.tf(vec{x2, y2})
tf1 := cv.tf(backendbase.Vec{x1, y1})
tf2 := cv.tf(backendbase.Vec{x2, y2})
cv.path.QuadraticCurveTo(tf1[0], tf1[1], tf2[0], tf2[1])
}
// BezierCurveTo adds a bezier curve to the path. It uses the current end point
// of the path, x1/y1 and x2/y2 define the curve, and x3/y3 is the end point
func (cv *Canvas) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
tf1 := cv.tf(vec{x1, y1})
tf2 := cv.tf(vec{x2, y2})
tf3 := cv.tf(vec{x3, y3})
tf1 := cv.tf(backendbase.Vec{x1, y1})
tf2 := cv.tf(backendbase.Vec{x2, y2})
tf3 := cv.tf(backendbase.Vec{x3, y3})
cv.path.BezierCurveTo(tf1[0], tf1[1], tf2[0], tf2[1], tf3[0], tf3[1])
}
@ -69,9 +71,9 @@ func (cv *Canvas) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
// are angles in radians, and anticlockwise means that the line is added
// anticlockwise
func (cv *Canvas) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, anticlockwise bool) {
tf := cv.tf(vec{x, y})
tf := cv.tf(backendbase.Vec{x, y})
ax, ay := math.Sincos(startAngle)
startAngle2 := vec{ay, ax}.mulMat2(cv.state.transform.mat2()).atan2()
startAngle2 := backendbase.Vec{ay, ax}.MulMat2(cv.state.transform.Mat2()).Atan2()
endAngle2 := startAngle2 + (endAngle - startAngle)
cv.path.Ellipse(tf[0], tf[1], radiusX, radiusY, rotation, startAngle2, endAngle2, anticlockwise)
}
@ -84,7 +86,7 @@ func (cv *Canvas) ClosePath() {
// Stroke uses the current StrokeStyle to draw the current path
func (cv *Canvas) Stroke() {
cv.strokePath(&cv.path, cv.state.transform.invert(), true)
cv.strokePath(&cv.path, cv.state.transform.Invert(), true)
}
// StrokePath uses the current StrokeStyle to draw the given path
@ -94,10 +96,10 @@ func (cv *Canvas) StrokePath(path *Path2D) {
p: make([]pathPoint, len(path.p)),
}
copy(path2.p, path.p)
cv.strokePath(&path2, mat{}, false)
cv.strokePath(&path2, backendbase.Mat{}, false)
}
func (cv *Canvas) strokePath(path *Path2D, inv mat, doInv bool) {
func (cv *Canvas) strokePath(path *Path2D, inv backendbase.Mat, doInv bool) {
if len(path.p) == 0 {
return
}
@ -111,7 +113,7 @@ func (cv *Canvas) strokePath(path *Path2D, inv mat, doInv bool) {
cv.b.Fill(&stl, tris, true)
}
func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]float64) [][2]float64 {
func (cv *Canvas) strokeTris(path *Path2D, inv backendbase.Mat, doInv bool, target [][2]float64) [][2]float64 {
if len(path.p) == 0 {
return target
}
@ -126,8 +128,8 @@ func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]floa
}
for i, pt := range path.p {
pt.pos = pt.pos.mulMat(inv)
pt.next = pt.next.mulMat(inv)
pt.pos = pt.pos.MulMat(inv)
pt.next = pt.next.MulMat(inv)
pcopy.p[i] = pt
}
path = &pcopy
@ -136,7 +138,7 @@ func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]floa
dashedPath := cv.applyLineDash(path.p)
start := true
var p0 vec
var p0 backendbase.Vec
for _, p := range dashedPath {
if p.flags&pathMove != 0 {
p0 = p.pos
@ -145,22 +147,22 @@ func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]floa
}
p1 := p.pos
v0 := p1.sub(p0).norm()
v1 := vec{v0[1], -v0[0]}.mulf(cv.state.lineWidth * 0.5)
v0 = v0.mulf(cv.state.lineWidth * 0.5)
v0 := p1.Sub(p0).Norm()
v1 := backendbase.Vec{v0[1], -v0[0]}.Mulf(cv.state.lineWidth * 0.5)
v0 = v0.Mulf(cv.state.lineWidth * 0.5)
lp0 := p0.add(v1)
lp1 := p1.add(v1)
lp2 := p0.sub(v1)
lp3 := p1.sub(v1)
lp0 := p0.Add(v1)
lp1 := p1.Add(v1)
lp2 := p0.Sub(v1)
lp3 := p1.Sub(v1)
if start {
switch cv.state.lineCap {
case Butt:
// no need to do anything
case Square:
lp0 = lp0.sub(v0)
lp2 = lp2.sub(v0)
lp0 = lp0.Sub(v0)
lp2 = lp2.Sub(v0)
case Round:
target = cv.addCircleTris(p0, cv.state.lineWidth*0.5, target)
}
@ -171,8 +173,8 @@ func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]floa
case Butt:
// no need to do anything
case Square:
lp1 = lp1.add(v0)
lp3 = lp3.add(v0)
lp1 = lp1.Add(v0)
lp3 = lp3.Add(v0)
case Round:
target = cv.addCircleTris(p1, cv.state.lineWidth*0.5, target)
}
@ -209,8 +211,8 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
continue
}
v := pp.pos.sub(lp.pos)
vl := v.len()
v := pp.pos.Sub(lp.pos)
vl := v.Len()
prev := ldo
for vl > 0 {
draw := ldp%2 == 0
@ -220,7 +222,7 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
ldo = 0
dl := cv.state.lineDash[ldp] - prev
dist := dl / vl
newp.pos = lp.pos.add(v.mulf(dist))
newp.pos = lp.pos.Add(v.Mulf(dist))
vl -= dl
ldp++
ldp %= len(cv.state.lineDash)
@ -239,7 +241,7 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
}
lp = newp
v = pp.pos.sub(lp.pos)
v = pp.pos.Sub(lp.pos)
}
lp = pp
}
@ -247,50 +249,50 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
return path2
}
func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec, tris [][2]float64) [][2]float64 {
v2 := p1.sub(p2).norm()
v3 := vec{v2[1], -v2[0]}.mulf(cv.state.lineWidth * 0.5)
func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 backendbase.Vec, tris [][2]float64) [][2]float64 {
v2 := p1.Sub(p2).Norm()
v3 := backendbase.Vec{v2[1], -v2[0]}.Mulf(cv.state.lineWidth * 0.5)
switch cv.state.lineJoin {
case Miter:
l1p0 := p2.sub(v3)
l1p1 := p1.sub(v3)
l1p2 := p2.add(v3)
l1p3 := p1.add(v3)
l1p0 := p2.Sub(v3)
l1p1 := p1.Sub(v3)
l1p2 := p2.Add(v3)
l1p3 := p1.Add(v3)
var ip0, ip1 vec
if l0p1.sub(l1p1).lenSqr() < 0.000000001 {
ip0 = l0p1.sub(l1p1).mulf(0.5).add(l1p1)
var ip0, ip1 backendbase.Vec
if l0p1.Sub(l1p1).LenSqr() < 0.000000001 {
ip0 = l0p1.Sub(l1p1).Mulf(0.5).Add(l1p1)
} else {
var q float64
ip0, _, q = lineIntersection(l0p0, l0p1, l1p1, l1p0)
if q >= 1 {
ip0 = l0p1.add(l1p1).mulf(0.5)
ip0 = l0p1.Add(l1p1).Mulf(0.5)
}
}
if dist := ip0.sub(l0p1).lenSqr(); dist > cv.state.miterLimitSqr {
l1p1 := p1.sub(v3)
l1p3 := p1.add(v3)
if dist := ip0.Sub(l0p1).LenSqr(); dist > cv.state.miterLimitSqr {
l1p1 := p1.Sub(v3)
l1p3 := p1.Add(v3)
tris = append(tris, cv.tf(p1), cv.tf(l0p1), cv.tf(l1p1),
cv.tf(p1), cv.tf(l1p3), cv.tf(l0p3))
return tris
}
if l0p3.sub(l1p3).lenSqr() < 0.000000001 {
ip1 = l0p3.sub(l1p3).mulf(0.5).add(l1p3)
if l0p3.Sub(l1p3).LenSqr() < 0.000000001 {
ip1 = l0p3.Sub(l1p3).Mulf(0.5).Add(l1p3)
} else {
var q float64
ip1, _, q = lineIntersection(l0p2, l0p3, l1p3, l1p2)
if q >= 1 {
ip1 = l0p3.add(l1p3).mulf(0.5)
ip1 = l0p3.Add(l1p3).Mulf(0.5)
}
}
if dist := ip1.sub(l1p1).lenSqr(); dist > cv.state.miterLimitSqr {
l1p1 := p1.sub(v3)
l1p3 := p1.add(v3)
if dist := ip1.Sub(l1p1).LenSqr(); dist > cv.state.miterLimitSqr {
l1p1 := p1.Sub(v3)
l1p3 := p1.Add(v3)
tris = append(tris, cv.tf(p1), cv.tf(l0p1), cv.tf(l1p1),
cv.tf(p1), cv.tf(l1p3), cv.tf(l0p3))
@ -302,8 +304,8 @@ func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec, tris [][2]fl
cv.tf(p1), cv.tf(l1p3), cv.tf(ip1),
cv.tf(p1), cv.tf(ip1), cv.tf(l0p3))
case Bevel:
l1p1 := p1.sub(v3)
l1p3 := p1.add(v3)
l1p1 := p1.Sub(v3)
l1p3 := p1.Add(v3)
tris = append(tris, cv.tf(p1), cv.tf(l0p1), cv.tf(l1p1),
cv.tf(p1), cv.tf(l1p3), cv.tf(l0p3))
@ -314,7 +316,7 @@ func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec, tris [][2]fl
return tris
}
func (cv *Canvas) addCircleTris(center vec, radius float64, tris [][2]float64) [][2]float64 {
func (cv *Canvas) addCircleTris(center backendbase.Vec, radius float64, tris [][2]float64) [][2]float64 {
step := 6 / radius
if step > 0.8 {
step = 0.8
@ -322,26 +324,26 @@ func (cv *Canvas) addCircleTris(center vec, radius float64, tris [][2]float64) [
step = 0.05
}
centertf := cv.tf(center)
p0 := cv.tf(vec{center[0], center[1] + radius})
p0 := cv.tf(backendbase.Vec{center[0], center[1] + radius})
for angle := step; angle <= math.Pi*2+step; angle += step {
s, c := math.Sincos(angle)
p1 := cv.tf(vec{center[0] + s*radius, center[1] + c*radius})
p1 := cv.tf(backendbase.Vec{center[0] + s*radius, center[1] + c*radius})
tris = append(tris, centertf, p0, p1)
p0 = p1
}
return tris
}
func lineIntersection(a0, a1, b0, b1 vec) (vec, float64, float64) {
va := a1.sub(a0)
vb := b1.sub(b0)
func lineIntersection(a0, a1, b0, b1 backendbase.Vec) (backendbase.Vec, float64, float64) {
va := a1.Sub(a0)
vb := b1.Sub(b0)
if (va[0] == 0 && vb[0] == 0) || (va[1] == 0 && vb[1] == 0) || (va[0] == 0 && va[1] == 0) || (vb[0] == 0 && vb[1] == 0) {
return vec{}, float64(math.Inf(1)), float64(math.Inf(1))
return backendbase.Vec{}, float64(math.Inf(1)), float64(math.Inf(1))
}
d := va[1]*vb[0] - va[0]*vb[1]
if d == 0 {
return vec{}, float64(math.Inf(1)), float64(math.Inf(1))
return backendbase.Vec{}, float64(math.Inf(1)), float64(math.Inf(1))
}
p := (vb[1]*(a0[0]-b0[0]) - a0[1]*vb[0] + b0[1]*vb[0]) / d
var q float64
@ -351,21 +353,21 @@ func lineIntersection(a0, a1, b0, b1 vec) (vec, float64, float64) {
q = (a0[0] + p*va[0] - b0[0]) / vb[0]
}
return a0.add(va.mulf(p)), p, q
return a0.Add(va.Mulf(p)), p, q
}
func linePointDistSqr(a, b, p vec) float64 {
v := b.sub(a)
vl := v.len()
vn := v.divf(vl)
d := p.sub(a).dot(vn)
c := a.add(vn.mulf(d))
return p.sub(c).lenSqr()
func linePointDistSqr(a, b, p backendbase.Vec) float64 {
v := b.Sub(a)
vl := v.Len()
vn := v.Divf(vl)
d := p.Sub(a).Dot(vn)
c := a.Add(vn.Mulf(d))
return p.Sub(c).LenSqr()
}
// Fill fills the current path with the current FillStyle
func (cv *Canvas) Fill() {
cv.fillPath(&cv.path, matIdentity)
cv.fillPath(&cv.path, backendbase.MatIdentity)
}
// FillPath fills the given path with the current FillStyle
@ -374,7 +376,7 @@ func (cv *Canvas) FillPath(path *Path2D) {
}
// FillPath fills the given path with the current FillStyle
func (cv *Canvas) fillPath(path *Path2D, tf mat) {
func (cv *Canvas) fillPath(path *Path2D, tf backendbase.Mat) {
if len(path.p) < 3 {
return
}
@ -396,16 +398,16 @@ func (cv *Canvas) fillPath(path *Path2D, tf mat) {
cv.b.Fill(&stl, tris, false)
}
func appendSubPathTriangles(tris [][2]float64, mat mat, path []pathPoint) [][2]float64 {
func appendSubPathTriangles(tris [][2]float64, mat backendbase.Mat, path []pathPoint) [][2]float64 {
last := path[len(path)-1]
if last.flags&pathIsConvex != 0 {
p0, p1 := path[0].pos.mulMat(mat), path[1].pos.mulMat(mat)
p0, p1 := path[0].pos.MulMat(mat), path[1].pos.MulMat(mat)
lastIdx := len(path)
if path[0].pos == path[lastIdx-1].pos {
lastIdx--
}
for i := 2; i < lastIdx; i++ {
p2 := path[i].pos.mulMat(mat)
p2 := path[i].pos.MulMat(mat)
tris = append(tris, p0, p1, p2)
p1 = p2
}
@ -423,10 +425,10 @@ func appendSubPathTriangles(tris [][2]float64, mat mat, path []pathPoint) [][2]f
// Clip uses the current path to clip any further drawing. Use Save/Restore to
// remove the clipping again
func (cv *Canvas) Clip() {
cv.clip(&cv.path, matIdentity)
cv.clip(&cv.path, backendbase.MatIdentity)
}
func (cv *Canvas) clip(path *Path2D, tf mat) {
func (cv *Canvas) clip(path *Path2D, tf backendbase.Mat) {
if len(path.p) < 3 {
return
}
@ -476,10 +478,10 @@ func (cv *Canvas) Rect(x, y, w, h float64) {
// StrokeRect draws a rectangle using the current stroke style
func (cv *Canvas) StrokeRect(x, y, w, h float64) {
v0 := vec{x, y}
v1 := vec{x + w, y}
v2 := vec{x + w, y + h}
v3 := vec{x, y + h}
v0 := backendbase.Vec{x, y}
v1 := backendbase.Vec{x + w, y}
v2 := backendbase.Vec{x + w, y + h}
v3 := backendbase.Vec{x, y + h}
var p [5]pathPoint
p[0] = pathPoint{pos: v0, flags: pathMove | pathAttach, next: v1}
p[1] = pathPoint{pos: v1, next: v2, flags: pathAttach}
@ -487,15 +489,15 @@ func (cv *Canvas) StrokeRect(x, y, w, h float64) {
p[3] = pathPoint{pos: v3, next: v0, flags: pathAttach}
p[4] = pathPoint{pos: v0, next: v1, flags: pathAttach}
path := Path2D{p: p[:]}
cv.strokePath(&path, mat{}, false)
cv.strokePath(&path, backendbase.Mat{}, false)
}
// FillRect fills a rectangle with the active fill style
func (cv *Canvas) FillRect(x, y, w, h float64) {
p0 := cv.tf(vec{x, y})
p1 := cv.tf(vec{x, y + h})
p2 := cv.tf(vec{x + w, y + h})
p3 := cv.tf(vec{x + w, y})
p0 := cv.tf(backendbase.Vec{x, y})
p1 := cv.tf(backendbase.Vec{x, y + h})
p2 := cv.tf(backendbase.Vec{x + w, y + h})
p3 := cv.tf(backendbase.Vec{x + w, y})
data := [4][2]float64{{p0[0], p0[1]}, {p1[0], p1[1]}, {p2[0], p2[1]}, {p3[0], p3[1]}}
@ -507,10 +509,10 @@ func (cv *Canvas) FillRect(x, y, w, h float64) {
// ClearRect sets the color of the rectangle to transparent black
func (cv *Canvas) ClearRect(x, y, w, h float64) {
p0 := cv.tf(vec{x, y})
p1 := cv.tf(vec{x, y + h})
p2 := cv.tf(vec{x + w, y + h})
p3 := cv.tf(vec{x + w, y})
p0 := cv.tf(backendbase.Vec{x, y})
p1 := cv.tf(backendbase.Vec{x, y + h})
p2 := cv.tf(backendbase.Vec{x + w, y + h})
p3 := cv.tf(backendbase.Vec{x + w, y})
data := [4][2]float64{{p0[0], p0[1]}, {p1[0], p1[1]}, {p2[0], p2[1]}, {p3[0], p3[1]}}
cv.b.Clear(data)

25
text.go
View file

@ -12,6 +12,7 @@ import (
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
@ -117,8 +118,8 @@ func (cv *Canvas) FillText(str string, x, y float64) {
return
}
scaleX := vec{cv.state.transform[0], cv.state.transform[1]}.len()
scaleY := vec{cv.state.transform[2], cv.state.transform[3]}.len()
scaleX := backendbase.Vec{cv.state.transform[0], cv.state.transform[1]}.Len()
scaleY := backendbase.Vec{cv.state.transform[2], cv.state.transform[3]}.Len()
scale := (scaleX + scaleY) * 0.5
fontSize := fixed.Int26_6(math.Round(float64(cv.state.fontSize) * scale))
@ -213,10 +214,10 @@ func (cv *Canvas) FillText(str string, x, y float64) {
w, h := cv.b.Size()
fw, fh := float64(w), float64(h)
p0 := cv.tf(vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
p1 := cv.tf(vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
p2 := cv.tf(vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
p3 := cv.tf(vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
p0 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
p1 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
p2 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
p3 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
inside := (p0[0] >= 0 || p1[0] >= 0 || p2[0] >= 0 || p3[0] >= 0) &&
(p0[1] >= 0 || p1[1] >= 0 || p2[1] >= 0 || p3[1] >= 0) &&
(p0[0] < fw || p1[0] < fw || p2[0] < fw || p3[0] < fw) &&
@ -299,10 +300,10 @@ func (cv *Canvas) FillText(str string, x, y float64) {
// render textImage to the screen
var pts [4][2]float64
pts[0] = cv.tf(vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + y})
pts[1] = cv.tf(vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + fstrHeight + y})
pts[2] = cv.tf(vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + fstrHeight + y})
pts[3] = cv.tf(vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + y})
pts[0] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + y})
pts[1] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + fstrHeight + y})
pts[2] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + fstrHeight + y})
pts[3] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + y})
mask := textImage.SubImage(image.Rect(0, 0, strWidth, strHeight)).(*image.Alpha)
@ -345,7 +346,7 @@ func (cv *Canvas) StrokeText(str string, x, y float64) {
continue
}
cv.runePath(rn, vec{x, y})
cv.runePath(rn, backendbase.Vec{x, y})
x += float64(advance) / 64
}
@ -354,7 +355,7 @@ func (cv *Canvas) StrokeText(str string, x, y float64) {
cv.path = prevPath
}
func (cv *Canvas) runePath(rn rune, pos vec) {
func (cv *Canvas) runePath(rn rune, pos backendbase.Vec) {
gb := &truetype.GlyphBuf{}
gb.Load(cv.state.font.font, cv.state.fontSize, cv.state.font.font.Index(rn), font.HintingNone)

View file

@ -3,11 +3,13 @@ package canvas
import (
"math"
"sort"
"github.com/tfriedel6/canvas/backend/backendbase"
)
const samePointTolerance = 1e-20
func pointIsRightOfLine(a, b, p vec) (bool, bool) {
func pointIsRightOfLine(a, b, p backendbase.Vec) (bool, bool) {
if a[1] == b[1] {
return false, false
}
@ -19,13 +21,13 @@ func pointIsRightOfLine(a, b, p vec) (bool, bool) {
if p[1] < a[1] || p[1] >= b[1] {
return false, false
}
v := b.sub(a)
v := b.Sub(a)
r := (p[1] - a[1]) / v[1]
x := a[0] + r*v[0]
return p[0] > x, dir
}
func triangleContainsPoint(a, b, c, p vec) bool {
func triangleContainsPoint(a, b, c, p backendbase.Vec) bool {
// if point is outside triangle bounds, return false
if p[0] < a[0] && p[0] < b[0] && p[0] < c[0] {
return false
@ -56,12 +58,12 @@ func triangleContainsPoint(a, b, c, p vec) bool {
const parallelTolerance = 1e-10
func parallel(a1, b1, a2, b2 vec) bool {
ang := b1.sub(a1).angleTo(b2.sub(a2))
func parallel(a1, b1, a2, b2 backendbase.Vec) bool {
ang := b1.Sub(a1).AngleTo(b2.Sub(a2))
return math.Abs(ang) < parallelTolerance || math.Abs(ang-math.Pi) < parallelTolerance
}
func polygonContainsLine(polygon []vec, ia, ib int, a, b vec) bool {
func polygonContainsLine(polygon []backendbase.Vec, ia, ib int, a, b backendbase.Vec) bool {
for i := range polygon {
if i == ia || i == ib {
continue
@ -80,7 +82,7 @@ func polygonContainsLine(polygon []vec, ia, ib int, a, b vec) bool {
const onLineToleranceSqr = 1e-20
func polygonContainsPoint(polygon []vec, p vec) bool {
func polygonContainsPoint(polygon []backendbase.Vec, p backendbase.Vec) bool {
a := polygon[len(polygon)-1]
count := 0
for _, b := range polygon {
@ -95,15 +97,15 @@ func polygonContainsPoint(polygon []vec, p vec) bool {
return count%2 == 1
}
func triangulatePath(path []pathPoint, mat mat, target [][2]float64) [][2]float64 {
func triangulatePath(path []pathPoint, mat backendbase.Mat, target [][2]float64) [][2]float64 {
if path[0].pos == path[len(path)-1].pos {
path = path[:len(path)-1]
}
var buf [500]vec
var buf [500]backendbase.Vec
polygon := buf[:0]
for _, p := range path {
polygon = append(polygon, p.pos.mulMat(mat))
polygon = append(polygon, p.pos.MulMat(mat))
}
for len(polygon) > 3 {
@ -117,7 +119,7 @@ func triangulatePath(path []pathPoint, mat mat, target [][2]float64) [][2]float6
if isSamePoint(a, c, math.SmallestNonzeroFloat64) {
break
}
if len(polygon) > 3 && !polygonContainsPoint(polygon, a.add(c).divf(2)) {
if len(polygon) > 3 && !polygonContainsPoint(polygon, a.Add(c).Divf(2)) {
continue
}
if !polygonContainsLine(polygon, i, ic, a, c) {
@ -156,7 +158,7 @@ type tessNet struct {
}
type tessVert struct {
pos vec
pos backendbase.Vec
attached []int
count int
}
@ -183,7 +185,7 @@ func cutIntersections(path []pathPoint) tessNet {
type cut struct {
from, to int
ratio float64
point vec
point backendbase.Vec
}
var cutBuf [50]cut
@ -296,8 +298,8 @@ func cutIntersections(path []pathPoint) tessNet {
func setPathLeftRightInside(net *tessNet) {
for i, e1 := range net.edges {
a1, b1 := net.verts[e1.a], net.verts[e1.b]
diff := b1.pos.sub(a1.pos)
mid := a1.pos.add(diff.mulf(0.5))
diff := b1.pos.Sub(a1.pos)
mid := a1.pos.Add(diff.Mulf(0.5))
left, right := 0, 0
if math.Abs(diff[1]) > math.Abs(diff[0]) {
@ -315,7 +317,7 @@ func setPathLeftRightInside(net *tessNet) {
if mid[1] < a2[1] || mid[1] > b2[1] {
continue
}
v := b2.sub(a2)
v := b2.Sub(a2)
r := (mid[1] - a2[1]) / v[1]
x := a2[0] + r*v[0]
if mid[0] > x {
@ -342,7 +344,7 @@ func setPathLeftRightInside(net *tessNet) {
if mid[0] < a2[0] || mid[0] > b2[0] {
continue
}
v := b2.sub(a2)
v := b2.Sub(a2)
r := (mid[0] - a2[0]) / v[0]
y := a2[1] + r*v[1]
if mid[1] > y {
@ -404,7 +406,7 @@ func selfIntersectingPathParts(p []pathPoint, partFn func(sp []pathPoint) bool)
for limit := 0; limit < len(net.edges); limit++ {
ecur := net.edges[from]
acur, bcur := net.verts[ecur.a], net.verts[ecur.b]
dir := bcur.pos.sub(acur.pos)
dir := bcur.pos.Sub(acur.pos)
dirAngle := math.Atan2(dir[1], dir[0])
minAngleDiff := math.Pi * 2
var next, nextEdge int
@ -421,7 +423,7 @@ func selfIntersectingPathParts(p []pathPoint, partFn func(sp []pathPoint) bool)
if e.b == cur {
na, nb = nb, na
}
ndir := nb.pos.sub(na.pos)
ndir := nb.pos.Sub(na.pos)
nextAngle := math.Atan2(ndir[1], ndir[0]) + math.Pi
if nextAngle < dirAngle {
nextAngle += math.Pi * 2