summaryrefslogtreecommitdiff
path: root/misc/pylib/robofab/pens/filterPen.py
diff options
context:
space:
mode:
Diffstat (limited to 'misc/pylib/robofab/pens/filterPen.py')
-rwxr-xr-xmisc/pylib/robofab/pens/filterPen.py407
1 files changed, 0 insertions, 407 deletions
diff --git a/misc/pylib/robofab/pens/filterPen.py b/misc/pylib/robofab/pens/filterPen.py
deleted file mode 100755
index f7c84c082..000000000
--- a/misc/pylib/robofab/pens/filterPen.py
+++ /dev/null
@@ -1,407 +0,0 @@
-"""A couple of point pens to filter contours in various ways."""
-
-from fontTools.pens.basePen import AbstractPen, BasePen
-
-from robofab.pens.pointPen import AbstractPointPen
-from robofab.objects.objectsRF import RGlyph as _RGlyph
-from robofab.objects.objectsBase import _interpolatePt
-
-import math
-
-#
-# threshold filtering
-#
-
-def distance(pt1, pt2):
- return math.hypot(pt1[0]-pt2[0], pt1[1]-pt2[1])
-
-class ThresholdPointPen(AbstractPointPen):
-
- """
- Rewrite of the ThresholdPen as a PointPen
- so that we can preserve named points and other arguments.
- This pen will add components from the original glyph, but
- but it won't filter those components.
-
- "move", "line", "curve" or "qcurve"
-
- """
- def __init__(self, otherPointPen, threshold=10):
- self.threshold = threshold
- self._lastPt = None
- self._offCurveBuffer = []
- self.otherPointPen = otherPointPen
-
- def beginPath(self):
- """Start a new sub path."""
- self.otherPointPen.beginPath()
- self._lastPt = None
-
- def endPath(self):
- """End the current sub path."""
- self.otherPointPen.endPath()
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- """Add a point to the current sub path."""
- if segmentType in ['curve', 'qcurve']:
- # it's an offcurve, let's buffer them until we get another oncurve
- # and we know what to do with them
- self._offCurveBuffer.append((pt, segmentType, smooth, name, kwargs))
- return
-
- elif segmentType == "move":
- # start of an open contour
- self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs?
- self._lastPt = pt
- self._offCurveBuffer = []
-
- elif segmentType == "line":
- if self._lastPt is None:
- self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs?
- self._lastPt = pt
- elif distance(pt, self._lastPt) >= self.threshold:
- # we're oncurve and far enough from the last oncurve
- if self._offCurveBuffer:
- # empty any buffered offcurves
- for buf_pt, buf_segmentType, buf_smooth, buf_name, buf_kwargs in self._offCurveBuffer:
- self.otherPointPen.addPoint(buf_pt, buf_segmentType, buf_smooth, buf_name) # how to add kwargs?
- self._offCurveBuffer = []
- # finally add the oncurve.
- self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs?
- self._lastPt = pt
- else:
- # we're too short, so we're not going to make it.
- # we need to clear out the offcurve buffer.
- self._offCurveBuffer = []
-
- def addComponent(self, baseGlyphName, transformation):
- """Add a sub glyph. Note: this way components are not filtered."""
- self.otherPointPen.addComponent(baseGlyphName, transformation)
-
-
-class ThresholdPen(AbstractPen):
-
- """Removes segments shorter in length than the threshold value."""
-
- def __init__(self, otherPen, threshold=10):
- self.threshold = threshold
- self._lastPt = None
- self.otherPen = otherPen
-
- def moveTo(self, pt):
- self._lastPt = pt
- self.otherPen.moveTo(pt)
-
- def lineTo(self, pt, smooth=False):
- if self.threshold <= distance(pt, self._lastPt):
- self.otherPen.lineTo(pt)
- self._lastPt = pt
-
- def curveTo(self, pt1, pt2, pt3):
- if self.threshold <= distance(pt3, self._lastPt):
- self.otherPen.curveTo(pt1, pt2, pt3)
- self._lastPt = pt3
-
- def qCurveTo(self, *points):
- if self.threshold <= distance(points[-1], self._lastPt):
- self.otherPen.qCurveTo(*points)
- self._lastPt = points[-1]
-
- def closePath(self):
- self.otherPen.closePath()
-
- def endPath(self):
- self.otherPen.endPath()
-
- def addComponent(self, glyphName, transformation):
- self.otherPen.addComponent(glyphName, transformation)
-
-
-def thresholdGlyph(aGlyph, threshold=10):
- """ Convenience function that handles the filtering. """
- from robofab.pens.adapterPens import PointToSegmentPen
- new = _RGlyph()
- filterpen = ThresholdPen(new.getPen(), threshold)
- wrappedPen = PointToSegmentPen(filterpen)
- aGlyph.drawPoints(wrappedPen)
- aGlyph.clear()
- aGlyph.appendGlyph(new)
- aGlyph.update()
- return aGlyph
-
-def thresholdGlyphPointPen(aGlyph, threshold=10):
- """ Same a thresholdGlyph, but using the ThresholdPointPen, which should respect anchors."""
- from robofab.pens.adapterPens import PointToSegmentPen
- new = _RGlyph()
- wrappedPen = new.getPointPen()
- filterpen = ThresholdPointPen(wrappedPen, threshold)
- aGlyph.drawPoints(filterpen)
- aGlyph.clear()
- new.drawPoints(aGlyph.getPointPen())
- aGlyph.update()
- return aGlyph
-
-
-#
-# Curve flattening
-#
-
-def _estimateCubicCurveLength(pt0, pt1, pt2, pt3, precision=10):
- """Estimate the length of this curve by iterating
- through it and averaging the length of the flat bits.
- """
- points = []
- length = 0
- step = 1.0/precision
- factors = range(0, precision+1)
- for i in factors:
- points.append(_getCubicPoint(i*step, pt0, pt1, pt2, pt3))
- for i in range(len(points)-1):
- pta = points[i]
- ptb = points[i+1]
- length += distance(pta, ptb)
- return length
-
-def _mid((x0, y0), (x1, y1)):
- """(Point, Point) -> Point\nReturn the point that lies in between the two input points."""
- return 0.5 * (x0 + x1), 0.5 * (y0 + y1)
-
-def _getCubicPoint(t, pt0, pt1, pt2, pt3):
- if t == 0:
- return pt0
- if t == 1:
- return pt3
- if t == 0.5:
- a = _mid(pt0, pt1)
- b = _mid(pt1, pt2)
- c = _mid(pt2, pt3)
- d = _mid(a, b)
- e = _mid(b, c)
- return _mid(d, e)
- else:
- cx = (pt1[0] - pt0[0]) * 3
- cy = (pt1[1] - pt0[1]) * 3
- bx = (pt2[0] - pt1[0]) * 3 - cx
- by = (pt2[1] - pt1[1]) * 3 - cy
- ax = pt3[0] - pt0[0] - cx - bx
- ay = pt3[1] - pt0[1] - cy - by
- t3 = t ** 3
- t2 = t * t
- x = ax * t3 + bx * t2 + cx * t + pt0[0]
- y = ay * t3 + by * t2 + cy * t + pt0[1]
- return x, y
-
-
-class FlattenPen(BasePen):
-
- """Process the contours into a series of straight lines by flattening the curves.
- """
-
- def __init__(self, otherPen, approximateSegmentLength=5, segmentLines=False, filterDoubles=True):
- self.approximateSegmentLength = approximateSegmentLength
- BasePen.__init__(self, {})
- self.otherPen = otherPen
- self.currentPt = None
- self.firstPt = None
- self.segmentLines = segmentLines
- self.filterDoubles = filterDoubles
-
- def _moveTo(self, pt):
- self.otherPen.moveTo(pt)
- self.currentPt = pt
- self.firstPt = pt
-
- def _lineTo(self, pt):
- if self.filterDoubles:
- if pt == self.currentPt:
- return
- if not self.segmentLines:
- self.otherPen.lineTo(pt)
- self.currentPt = pt
- return
- d = distance(self.currentPt, pt)
- maxSteps = int(round(d / self.approximateSegmentLength))
- if maxSteps < 1:
- self.otherPen.lineTo(pt)
- self.currentPt = pt
- return
- step = 1.0/maxSteps
- factors = range(0, maxSteps+1)
- for i in factors[1:]:
- self.otherPen.lineTo(_interpolatePt(self.currentPt, pt, i*step))
- self.currentPt = pt
-
- def _curveToOne(self, pt1, pt2, pt3):
- est = _estimateCubicCurveLength(self.currentPt, pt1, pt2, pt3)/self.approximateSegmentLength
- maxSteps = int(round(est))
- falseCurve = (pt1==self.currentPt) and (pt2==pt3)
- if maxSteps < 1 or falseCurve:
- self.otherPen.lineTo(pt3)
- self.currentPt = pt3
- return
- step = 1.0/maxSteps
- factors = range(0, maxSteps+1)
- for i in factors[1:]:
- pt = _getCubicPoint(i*step, self.currentPt, pt1, pt2, pt3)
- self.otherPen.lineTo(pt)
- self.currentPt = pt3
-
- def _closePath(self):
- self.lineTo(self.firstPt)
- self.otherPen.closePath()
- self.currentPt = None
-
- def _endPath(self):
- self.otherPen.endPath()
- self.currentPt = None
-
- def addComponent(self, glyphName, transformation):
- self.otherPen.addComponent(glyphName, transformation)
-
-
-def flattenGlyph(aGlyph, threshold=10, segmentLines=True):
-
- """Replace curves with series of straight l ines."""
-
- from robofab.pens.adapterPens import PointToSegmentPen
- if len(aGlyph.contours) == 0:
- return
- new = _RGlyph()
- writerPen = new.getPen()
- filterpen = FlattenPen(writerPen, threshold, segmentLines)
- wrappedPen = PointToSegmentPen(filterpen)
- aGlyph.drawPoints(wrappedPen)
- aGlyph.clear()
- aGlyph.appendGlyph(new)
- aGlyph.update()
- return aGlyph
-
-
-def spikeGlyph(aGlyph, segmentLength=20, spikeLength=40, patternFunc=None):
-
- """Add narly spikes or dents to the glyph.
- patternFunc is an optional function which recalculates the offset."""
-
- from math import atan2, sin, cos, pi
-
- new = _RGlyph()
- new.appendGlyph(aGlyph)
- new.width = aGlyph.width
-
- if len(new.contours) == 0:
- return
- flattenGlyph(new, segmentLength, segmentLines=True)
- for contour in new:
- l = len(contour.points)
- lastAngle = None
- for i in range(0, len(contour.points), 2):
- prev = contour.points[i-1]
- cur = contour.points[i]
- next = contour.points[(i+1)%l]
- angle = atan2(prev.x - next.x, prev.y - next.y)
- lastAngle = angle
- if patternFunc is not None:
- thisSpikeLength = patternFunc(spikeLength)
- else:
- thisSpikeLength = spikeLength
- cur.x -= sin(angle+.5*pi)*thisSpikeLength
- cur.y -= cos(angle+.5*pi)*thisSpikeLength
- new.update()
- aGlyph.clear()
- aGlyph.appendGlyph(new)
- aGlyph.update()
- return aGlyph
-
-
-def halftoneGlyph(aGlyph, invert=False):
-
- """Convert the glyph into some sort of halftoning pattern.
- Measure a bunch of inside/outside points to simulate grayscale levels.
- Slow.
- """
- print 'halftoneGlyph is running...'
- grid = {}
- drawing = {}
- dataDistance = 10
- scan = 2
- preload = 0
- cellDistance = dataDistance * 5
- overshoot = dataDistance * 2
- (xMin, yMin, xMax, yMax) = aGlyph.box
- for x in range(xMin-overshoot, xMax+overshoot, dataDistance):
- print 'scanning..', x
- for y in range(yMin-overshoot, yMax+overshoot, dataDistance):
- if aGlyph.pointInside((x, y)):
- grid[(x, y)] = True
- else:
- grid[(x, y)] = False
- #print 'gathering data', x, y, grid[(x, y)]
- print 'analyzing..'
- for x in range(xMin-overshoot, xMax+overshoot, cellDistance):
- for y in range(yMin-overshoot, yMax+overshoot, cellDistance):
- total = preload
- for scanx in range(-scan, scan):
- for scany in range(-scan, scan):
- if grid.get((x+scanx*dataDistance, y+scany*dataDistance)):
- total += 1
- if invert:
- drawing[(x, y)] = 2*scan**2 - float(total)
- else:
- drawing[(x, y)] = float(total)
- aGlyph.clear()
- print drawing
- for (x,y) in drawing.keys():
- size = drawing[(x,y)] / float(2*scan**2) * 5
- pen = aGlyph.getPen()
- pen.moveTo((x-size, y-size))
- pen.lineTo((x+size, y-size))
- pen.lineTo((x+size, y+size))
- pen.lineTo((x-size, y+size))
- pen.lineTo((x-size, y-size))
- pen.closePath()
- aGlyph.update()
-
-
-if __name__ == "__main__":
- from robofab.pens.pointPen import PrintingPointPen
- pp = PrintingPointPen()
- #pp.beginPath()
- #pp.addPoint((100, 100))
- #pp.endPath()
-
- tpp = ThresholdPointPen(pp, threshold=20)
- tpp.beginPath()
- #segmentType=None, smooth=False, name=None
- tpp.addPoint((100, 100), segmentType="line", smooth=True)
- # section that should be too small
- tpp.addPoint((100, 102), segmentType="line", smooth=True)
- tpp.addPoint((200, 200), segmentType="line", smooth=True)
- # curve section with final point that's far enough, but with offcurves that are under the threshold
- tpp.addPoint((200, 205), segmentType="curve", smooth=True)
- tpp.addPoint((300, 295), segmentType="curve", smooth=True)
- tpp.addPoint((300, 300), segmentType="line", smooth=True)
- # curve section with final point that is not far enough
- tpp.addPoint((550, 350), segmentType="curve", smooth=True)
- tpp.addPoint((360, 760), segmentType="curve", smooth=True)
- tpp.addPoint((310, 310), segmentType="line", smooth=True)
-
- tpp.addPoint((400, 400), segmentType="line", smooth=True)
- tpp.addPoint((100, 100), segmentType="line", smooth=True)
- tpp.endPath()
-
- # couple of single points with names
- tpp.beginPath()
- tpp.addPoint((500, 500), segmentType="move", smooth=True, name="named point")
- tpp.addPoint((600, 500), segmentType="move", smooth=True, name="named point")
- tpp.addPoint((601, 501), segmentType="move", smooth=True, name="named point")
- tpp.endPath()
-
- # open path
- tpp.beginPath()
- tpp.addPoint((500, 500), segmentType="move", smooth=True)
- tpp.addPoint((501, 500), segmentType="line", smooth=True)
- tpp.addPoint((101, 500), segmentType="line", smooth=True)
- tpp.addPoint((101, 100), segmentType="line", smooth=True)
- tpp.addPoint((498, 498), segmentType="line", smooth=True)
- tpp.endPath()
- \ No newline at end of file