From cc9247c627adbfd99a6b093e1552dff833a746af Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Sun, 22 Mar 2020 10:07:18 +0100 Subject: [PATCH] moved math code to backendbase package so that backends can also use it --- backend/backendbase/math.go | 140 +++++++++++++++++++++++++++ canvas.go | 30 +++--- gradients.go | 12 +-- images.go | 18 ++-- math.go | 140 --------------------------- path2d.go | 132 +++++++++++++------------- paths.go | 184 ++++++++++++++++++------------------ text.go | 25 ++--- triangulation.go | 40 ++++---- 9 files changed, 364 insertions(+), 357 deletions(-) create mode 100644 backend/backendbase/math.go delete mode 100644 math.go diff --git a/backend/backendbase/math.go b/backend/backendbase/math.go new file mode 100644 index 0000000..05717bf --- /dev/null +++ b/backend/backendbase/math.go @@ -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]) +} diff --git a/canvas.go b/canvas.go index 8863b5e..3326bc7 100644 --- a/canvas.go +++ b/canvas.go @@ -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 } diff --git a/gradients.go b/gradients.go index 50d2ebb..af815f7 100644 --- a/gradients.go +++ b/gradients.go @@ -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), diff --git a/images.go b/images.go index ce09e52..aac45b5 100644 --- a/images.go +++ b/images.go @@ -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)) diff --git a/math.go b/math.go deleted file mode 100644 index d4fc183..0000000 --- a/math.go +++ /dev/null @@ -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]) -} diff --git a/path2d.go b/path2d.go index 4ad1e4a..aa6293d 100644 --- a/path2d.go +++ b/path2d.go @@ -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 } diff --git a/paths.go b/paths.go index d9b0fb2..6a2b2a3 100644 --- a/paths.go +++ b/paths.go @@ -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) diff --git a/text.go b/text.go index fb92102..0e8649e 100644 --- a/text.go +++ b/text.go @@ -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) diff --git a/triangulation.go b/triangulation.go index 40e8ae3..23ee512 100644 --- a/triangulation.go +++ b/triangulation.go @@ -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