// curviest2 returns the value of t for which the quadratic parametric curve // (1-t)²*a + 2*t*(1-t).b + t²*c has maximum curvature. // // The curvature of the parametric curve f(t) = (x(t), y(t)) is // |x′y″-y′x″| / (x′²+y′²)^(3/2). // // Let d = b-a and e = c-2*b+a, so that f′(t) = 2*d+2*e*t and f″(t) = 2*e. // The curvature's numerator is (2*dx+2*ex*t)*(2*ey)-(2*dy+2*ey*t)*(2*ex), // which simplifies to 4*dx*ey-4*dy*ex, which is constant with respect to t. // // Thus, curvature is extreme where the denominator is extreme, i.e. where // (x′²+y′²) is extreme. The first order condition is that // 2*x′*x″+2*y′*y″ = 0, or (dx+ex*t)*ex + (dy+ey*t)*ey = 0. // Solving for t gives t = -(dx*ex+dy*ey) / (ex*ex+ey*ey). func curviest2(a, b, c fixed.Point26_6) fixed.Int52_12 { dx := int64(b.X - a.X) dy := int64(b.Y - a.Y) ex := int64(c.X - 2*b.X + a.X) ey := int64(c.Y - 2*b.Y + a.Y) if ex == 0 && ey == 0 { return 2048 } return fixed.Int52_12(-4096 * (dx*ex + dy*ey) / (ex*ex + ey*ey)) }
// pDot returns the dot product p·q. func pDot(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 { px, py := int64(p.X), int64(p.Y) qx, qy := int64(q.X), int64(q.Y) return fixed.Int52_12(px*qx + py*qy) }
// Add2 adds a quadratic segment to the stroker. func (k *stroker) Add2(b, c fixed.Point26_6) { ab := b.Sub(k.a) bc := c.Sub(b) abnorm := pRot90CCW(pNorm(ab, k.u)) if len(k.r) == 0 { k.p.Start(k.a.Add(abnorm)) k.r.Start(k.a.Sub(abnorm)) } else { k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, abnorm) } // Approximate nearly-degenerate quadratics by linear segments. abIsSmall := pDot(ab, ab) < epsilon bcIsSmall := pDot(bc, bc) < epsilon if abIsSmall || bcIsSmall { acnorm := pRot90CCW(pNorm(c.Sub(k.a), k.u)) k.p.Add1(c.Add(acnorm)) k.r.Add1(c.Sub(acnorm)) k.a, k.anorm = c, acnorm return } // The quadratic segment (k.a, b, c) has a point of maximum curvature. // If this occurs at an end point, we process the segment as a whole. t := curviest2(k.a, b, c) if t <= 0 || 4096 <= t { k.addNonCurvy2(b, c) return } // Otherwise, we perform a de Casteljau decomposition at the point of // maximum curvature and process the two straighter parts. mab := interpolate(k.a, b, t) mbc := interpolate(b, c, t) mabc := interpolate(mab, mbc, t) // If the vectors ab and bc are close to being in opposite directions, // then the decomposition can become unstable, so we approximate the // quadratic segment by two linear segments joined by an arc. bcnorm := pRot90CCW(pNorm(bc, k.u)) if pDot(abnorm, bcnorm) < -fixed.Int52_12(k.u)*fixed.Int52_12(k.u)*2047/2048 { pArc := pDot(abnorm, bc) < 0 k.p.Add1(mabc.Add(abnorm)) if pArc { z := pRot90CW(abnorm) addArc(k.p, mabc, abnorm, z) addArc(k.p, mabc, z, bcnorm) } k.p.Add1(mabc.Add(bcnorm)) k.p.Add1(c.Add(bcnorm)) k.r.Add1(mabc.Sub(abnorm)) if !pArc { z := pRot90CW(abnorm) addArc(&k.r, mabc, pNeg(abnorm), z) addArc(&k.r, mabc, z, pNeg(bcnorm)) } k.r.Add1(mabc.Sub(bcnorm)) k.r.Add1(c.Sub(bcnorm)) k.a, k.anorm = c, bcnorm return } // Process the decomposed parts. k.addNonCurvy2(mab, mabc) k.addNonCurvy2(mbc, c) }
// addNonCurvy2 adds a quadratic segment to the stroker, where the segment // defined by (k.a, b, c) achieves maximum curvature at either k.a or c. func (k *stroker) addNonCurvy2(b, c fixed.Point26_6) { // We repeatedly divide the segment at its middle until it is straight // enough to approximate the stroke by just translating the control points. // ds and ps are stacks of depths and points. t is the top of the stack. const maxDepth = 5 var ( ds [maxDepth + 1]int ps [2*maxDepth + 3]fixed.Point26_6 t int ) // Initially the ps stack has one quadratic segment of depth zero. ds[0] = 0 ps[2] = k.a ps[1] = b ps[0] = c anorm := k.anorm var cnorm fixed.Point26_6 for { depth := ds[t] a := ps[2*t+2] b := ps[2*t+1] c := ps[2*t+0] ab := b.Sub(a) bc := c.Sub(b) abIsSmall := pDot(ab, ab) < fixed.Int52_12(1<<12) bcIsSmall := pDot(bc, bc) < fixed.Int52_12(1<<12) if abIsSmall && bcIsSmall { // Approximate the segment by a circular arc. cnorm = pRot90CCW(pNorm(bc, k.u)) mac := midpoint(a, c) addArc(k.p, mac, anorm, cnorm) addArc(&k.r, mac, pNeg(anorm), pNeg(cnorm)) } else if depth < maxDepth && angleGreaterThan45(ab, bc) { // Divide the segment in two and push both halves on the stack. mab := midpoint(a, b) mbc := midpoint(b, c) t++ ds[t+0] = depth + 1 ds[t-1] = depth + 1 ps[2*t+2] = a ps[2*t+1] = mab ps[2*t+0] = midpoint(mab, mbc) ps[2*t-1] = mbc continue } else { // Translate the control points. bnorm := pRot90CCW(pNorm(c.Sub(a), k.u)) cnorm = pRot90CCW(pNorm(bc, k.u)) k.p.Add2(b.Add(bnorm), c.Add(cnorm)) k.r.Add2(b.Sub(bnorm), c.Sub(cnorm)) } if t == 0 { k.a, k.anorm = c, cnorm return } t-- anorm = cnorm } panic("unreachable") }
// interpolate returns the point (1-t)*a + t*b. func interpolate(a, b fixed.Point26_6, t fixed.Int52_12) fixed.Point26_6 { s := 1<<12 - t x := s*fixed.Int52_12(a.X) + t*fixed.Int52_12(b.X) y := s*fixed.Int52_12(a.Y) + t*fixed.Int52_12(b.Y) return fixed.Point26_6{fixed.Int26_6(x >> 12), fixed.Int26_6(y >> 12)} }
// Copyright 2010 The Freetype-Go Authors. All rights reserved. // Use of this source code is governed by your choice of either the // FreeType License or the GNU General Public License version 2 (or // any later version), both of which can be found in the LICENSE file. package raster import ( "golang.org/x/image/math/fixed" ) // Two points are considered practically equal if the square of the distance // between them is less than one quarter (i.e. 1024 / 4096). const epsilon = fixed.Int52_12(1024) // A Capper signifies how to begin or end a stroked path. type Capper interface { // Cap adds a cap to p given a pivot point and the normal vector of a // terminal segment. The normal's length is half of the stroke width. Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) } // The CapperFunc type adapts an ordinary function to be a Capper. type CapperFunc func(Adder, fixed.Int26_6, fixed.Point26_6, fixed.Point26_6) func (f CapperFunc) Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) { f(p, halfWidth, pivot, n1) } // A Joiner signifies how to join interior nodes of a stroked path. type Joiner interface {