github.com/kintar/etxt@v0.0.9/emask/impl_edge_marker.go (about) 1 package emask 2 3 import "math" 4 import "image" 5 6 import "golang.org/x/image/math/fixed" 7 import "golang.org/x/image/font/sfnt" 8 9 // TODO: actually, the default value won't have the cache signature 10 // set properly. Don't recommend using the zero value, and 11 // use NewEdgeMarkerRasterizer instead... unless you explicitly 12 // set the curve threshold and the max splits. 13 14 // An alternative to [DefaultRasterizer] that avoids using 15 // [golang.org/x/image/vector.Rasterizer] under the hood. Results are 16 // visually very similar, but performance is 3 times worse. 17 // 18 // The purpose of this rasterizer is to offer a simpler, more 19 // readable and [well-documented] version of the algorithm used by 20 // vector.Rasterizer that anyone can edit, adapt or learn from. 21 // 22 // The zero-value is usable but will produce jaggy results, as curve 23 // segmentation parameters are not configured. Use [NewStdEdgeMarkerRasterizer]() 24 // if you prefer a pre-configured rasterizer. You may also configure the 25 // rasterizer manually through [EdgeMarkerRasterizer.SetCurveThreshold]() and 26 // [EdgeMarkerRasterizer.SetMaxCurveSplits](). 27 // 28 // [well-documented]: https://github.com/kintar/etxt/blob/main/docs/rasterize-outlines.md 29 type EdgeMarkerRasterizer struct { 30 // All relevant algorithms are implemented inside the unexported 31 // edgeMarker type (see emask/edge_marker.go), except for final 32 // buffer accumulation which is done directly on the Rasterize() 33 // method. The rest is only a wrapper to comply with the 34 // emask.Rasterizer interface. 35 rasterizer edgeMarker 36 onChange func(Rasterizer) 37 cacheSignature uint64 38 rectOffset image.Point 39 normOffset fixed.Point26_6 40 } 41 42 // Creates a new [EdgeMarkerRasterizer] with reasonable default values. 43 func NewStdEdgeMarkerRasterizer() *EdgeMarkerRasterizer { 44 rast := &EdgeMarkerRasterizer{} 45 rast.SetCurveThreshold(0.1) 46 rast.SetMaxCurveSplits(8) // this is excessive for most glyph rendering 47 return rast 48 } 49 50 // Satisfies the [UserCfgCacheSignature] interface. 51 func (self *EdgeMarkerRasterizer) SetHighByte(value uint8) { 52 self.cacheSignature = uint64(value) << 56 53 if self.onChange != nil { 54 self.onChange(self) 55 } 56 } 57 58 // Sets the threshold distance to use when splitting Bézier curves into 59 // linear segments. If a linear segment misses the curve by more than 60 // the threshold value, the curve will be split. Otherwise, the linear 61 // segment will be used to approximate it. 62 // 63 // Values very close to zero could prevent the algorithm from converging 64 // due to floating point instability, but the MaxCurveSplits cutoff will 65 // prevent infinite looping anyway. 66 // 67 // Reasonable values range from 0.01 to 1.0. [NewStdEdgeMarkerRasterizer]() 68 // uses 0.1 by default. 69 func (self *EdgeMarkerRasterizer) SetCurveThreshold(threshold float32) { 70 self.rasterizer.CurveSegmenter.SetThreshold(threshold) 71 bits := math.Float32bits(threshold) 72 self.cacheSignature &= 0xFFFFFFFF00000000 73 self.cacheSignature |= uint64(bits) 74 if self.onChange != nil { 75 self.onChange(self) 76 } 77 } 78 79 // Sets the maximum amount of times a curve can be recursively split 80 // into subsegments while trying to approximate it. 81 // 82 // The maximum number of segments that will approximate a curve is 83 // 2^maxCurveSplits. 84 // 85 // This value is typically used as a cutoff to prevent low curve thresholds 86 // from making the curve splitting process too slow, but it can also be used 87 // creatively to get jaggy results instead of smooth curves. 88 // 89 // Values outside the [0, 255] range will be silently clamped. Reasonable 90 // values range from 0 to 10. [NewStdEdgeMarkerRasterizer]() uses 8 by default. 91 func (self *EdgeMarkerRasterizer) SetMaxCurveSplits(maxCurveSplits int) { 92 segmenter := &self.rasterizer.CurveSegmenter 93 segmenter.SetMaxSplits(maxCurveSplits) 94 self.cacheSignature &= 0xFFFFFF00FFFFFFFF 95 self.cacheSignature |= uint64(segmenter.maxCurveSplits) << 32 96 if self.onChange != nil { 97 self.onChange(self) 98 } 99 } 100 101 // Satisfies the [Rasterizer] interface. 102 func (self *EdgeMarkerRasterizer) SetOnChangeFunc(onChange func(Rasterizer)) { 103 self.onChange = onChange 104 } 105 106 // Satisfies the [Rasterizer] interface. 107 func (self *EdgeMarkerRasterizer) CacheSignature() uint64 { 108 self.cacheSignature &= 0xFF00FFFFFFFFFFFF 109 self.cacheSignature |= 0x00E6000000000000 110 return self.cacheSignature 111 } 112 113 // See [DefaultRasterizer.MoveTo](). 114 func (self *EdgeMarkerRasterizer) MoveTo(point fixed.Point26_6) { 115 x, y := self.fixedToFloat64Coords(point) 116 self.rasterizer.MoveTo(x, y) 117 } 118 119 // See [DefaultRasterizer.LineTo](). 120 func (self *EdgeMarkerRasterizer) LineTo(point fixed.Point26_6) { 121 x, y := self.fixedToFloat64Coords(point) 122 self.rasterizer.LineTo(x, y) 123 } 124 125 // See [DefaultRasterizer.QuadTo](). 126 func (self *EdgeMarkerRasterizer) QuadTo(control, target fixed.Point26_6) { 127 cx, cy := self.fixedToFloat64Coords(control) 128 tx, ty := self.fixedToFloat64Coords(target) 129 self.rasterizer.QuadTo(cx, cy, tx, ty) 130 } 131 132 // See [DefaultRasterizer.CubeTo](). 133 func (self *EdgeMarkerRasterizer) CubeTo(controlA, controlB, target fixed.Point26_6) { 134 cax, cay := self.fixedToFloat64Coords(controlA) 135 cbx, cby := self.fixedToFloat64Coords(controlB) 136 tx, ty := self.fixedToFloat64Coords(target) 137 self.rasterizer.CubeTo(cax, cay, cbx, cby, tx, ty) 138 } 139 140 // Satisfies the [Rasterizer] interface. 141 func (self *EdgeMarkerRasterizer) Rasterize(outline sfnt.Segments, fract fixed.Point26_6) (*image.Alpha, error) { 142 // prepare rasterizer 143 var size image.Point 144 size, self.normOffset, self.rectOffset = figureOutBounds(outline.Bounds(), fract) 145 buffer := &self.rasterizer.Buffer 146 buffer.Resize(size.X, size.Y) 147 148 // process outline 149 processOutline(self, outline) 150 151 // allocate glyph mask and apply buffer accumulation 152 // (this takes around 50% of the time of the process) 153 mask := image.NewAlpha(image.Rect(0, 0, buffer.Width, buffer.Height)) 154 buffer.AccumulateUint8(mask.Pix) 155 156 // translate the mask to its final position 157 mask.Rect = mask.Rect.Add(self.rectOffset) 158 return mask, nil 159 } 160 161 func (self *EdgeMarkerRasterizer) fixedToFloat64Coords(point fixed.Point26_6) (float64, float64) { 162 x := float64(point.X+self.normOffset.X) / 64 163 y := float64(point.Y+self.normOffset.Y) / 64 164 return x, y 165 }