From 322f8c3ff2ec11731b48c2f8dc736cfaacb12f90 Mon Sep 17 00:00:00 2001 From: as Date: Sat, 2 Dec 2017 00:39:56 -0800 Subject: [PATCH 1/4] experiment where any string drawn to the screen is cached --- font/draw.go | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/font/draw.go b/font/draw.go index 2219bf4..f322bcb 100644 --- a/font/draw.go +++ b/font/draw.go @@ -7,17 +7,55 @@ import ( "unicode/utf8" ) +type rgba struct{ + r,g,b,a uint32 +} +type signature struct{ + s string + dy int + rgba +} +var cache map[signature]*image.RGBA +func init(){ + cache = make(map[signature]*image.RGBA) +} + func StringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft *Font, s []byte, bg image.Image, bgp image.Point) int { + quad := rgba{} + { + r,g,b,a := src.(*image.Uniform).RGBA() + quad=rgba{r,g,b,a} + } + sig := signature{ + s: string(s), + dy: ft.Dy(), + rgba: quad, + } + if img, ok := cache[sig]; ok{ + draw.Draw(dst, img.Bounds().Add(p), img, img.Bounds().Min, draw.Src) + return p.X+img.Bounds().Dx() //Add(image.Pt(img.Bounds().Dx(), 0)) + } + r0 := image.Rectangle{p, p} for _, b := range s { mask := ft.Char(b) if mask == nil { panic("StringBG") } r := mask.Bounds() - //draw.Draw(dst, r.Add(p), bg, bgp, draw.Src) + if r0.Min == image.ZP{ + r0.Min = r.Add(p).Min + } draw.DrawMask(dst, r.Add(p), src, sp, mask, mask.Bounds().Min, draw.Over) p.X += r.Dx() + ft.stride + r0.Max.X += r.Dx() + ft.stride + if r.Dy() > r0.Dy(){ + r0.Max.Y=r.Dy() + } } + img := image.NewRGBA(image.Rect(0,0,r0.Max.X,r0.Max.Y)) + draw.Draw(img, img.Bounds(), bg, bgp, draw.Src) + draw.Draw(img, img.Bounds(), dst, r0.Min, draw.Src) + cache[sig]=img return p.X } From 427e0e6a260cb844dec4aea92f70508e7f2a3019 Mon Sep 17 00:00:00 2001 From: as Date: Sat, 2 Dec 2017 01:10:38 -0800 Subject: [PATCH 2/4] cache every letter with bg color --- font/draw.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/font/draw.go b/font/draw.go index f322bcb..7b3232e 100644 --- a/font/draw.go +++ b/font/draw.go @@ -11,7 +11,7 @@ type rgba struct{ r,g,b,a uint32 } type signature struct{ - s string + b byte dy int rgba } @@ -27,16 +27,17 @@ func StringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft quad=rgba{r,g,b,a} } sig := signature{ - s: string(s), dy: ft.Dy(), rgba: quad, } - if img, ok := cache[sig]; ok{ - draw.Draw(dst, img.Bounds().Add(p), img, img.Bounds().Min, draw.Src) - return p.X+img.Bounds().Dx() //Add(image.Pt(img.Bounds().Dx(), 0)) - } r0 := image.Rectangle{p, p} for _, b := range s { + sig.b = b + if img, ok := cache[sig]; ok{ + draw.Draw(dst, img.Bounds().Add(p), img, img.Bounds().Min, draw.Src) + p.X+=img.Bounds().Dx()+ft.stride //Add(image.Pt(img.Bounds().Dx(), 0)) + continue + } mask := ft.Char(b) if mask == nil { panic("StringBG") @@ -46,16 +47,17 @@ func StringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft r0.Min = r.Add(p).Min } draw.DrawMask(dst, r.Add(p), src, sp, mask, mask.Bounds().Min, draw.Over) + img := image.NewRGBA(r) + draw.Draw(img, img.Bounds(), bg, bgp, draw.Src) + draw.Draw(img, img.Bounds(), dst, r.Add(p).Min, draw.Src) + cache[sig]=img + p.X += r.Dx() + ft.stride r0.Max.X += r.Dx() + ft.stride if r.Dy() > r0.Dy(){ r0.Max.Y=r.Dy() } } - img := image.NewRGBA(image.Rect(0,0,r0.Max.X,r0.Max.Y)) - draw.Draw(img, img.Bounds(), bg, bgp, draw.Src) - draw.Draw(img, img.Bounds(), dst, r0.Min, draw.Src) - cache[sig]=img return p.X } From 73e5bff6129ab1d4baa3dab603296b8208ee7d64 Mon Sep 17 00:00:00 2001 From: as Date: Sat, 2 Dec 2017 05:59:59 -0800 Subject: [PATCH 3/4] cache image data --- box/run.go | 10 +++---- font/decon.go | 59 ++++++++++++++++++++++++++++++++++++++++ font/draw.go | 34 ++++++++--------------- font/font.go | 63 ++++++++++++++++++++++++++++++++++--------- font/srv.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 198 insertions(+), 42 deletions(-) create mode 100644 font/decon.go create mode 100644 font/srv.go diff --git a/box/run.go b/box/run.go index 5977ff7..222ad1f 100644 --- a/box/run.go +++ b/box/run.go @@ -6,7 +6,7 @@ import ( ) // MaxBytes is the largest capacity of bytes in a box -var MaxBytes = 512 + 3 +var MaxBytes = 256 + 3 func NewRun(minDx, maxDx int, ft *font.Font, newRulerFunc ...func([]byte, *font.Font) Ruler) Run { fn := NewByteRuler @@ -20,7 +20,6 @@ func NewRun(minDx, maxDx int, ft *font.Font, newRulerFunc ...func([]byte, *font. Font: ft, newRulerFunc: fn, br: fn(make([]byte, MaxBytes), ft), - br2: fn(make([]byte, MaxBytes), ft), } } @@ -39,7 +38,6 @@ type Run struct { newRulerFunc func([]byte, *font.Font) Ruler br Ruler - br2 Ruler } func (f *Run) Combine(g *Run, n int) { @@ -118,14 +116,14 @@ func (f *Run) Split(bn, n int) { } func (f *Run) MeasureBytes(p []byte) int { - br := f.newRulerFunc(p, f.Font) + f.br.Reset(p)//f.newRulerFunc(p, f.Font) for { - _, _, err := br.Next() + _, _, err := f.br.Next() if err != nil { break } } - return br.Width() + return f.br.Width() } // Chop drops the first n chars in box b diff --git a/font/decon.go b/font/decon.go new file mode 100644 index 0000000..d3d012b --- /dev/null +++ b/font/decon.go @@ -0,0 +1,59 @@ +package font + +import "image" + +func deconvolveGlyph(g *Glyph) *Glyph { + if g.mask == nil { + panic("deconvolveGlyph: nil g.mask") + } + g.mask = deconvolve(g.mask) + return g +} + +func ampGlyph(g *Glyph) *Glyph { + if g.mask == nil { + panic("ampGlyph: nil g.mask") + } + g.mask = amp(g.mask) + return g +} +func deconvolve(m *image.Alpha) *image.Alpha { + av := func(x, y int) uint8 { + a := m.AlphaAt(x, y) + return a.A + } + for y := 0; y < m.Bounds().Dy(); y++ { + for x := 0; x < m.Bounds().Dx(); x++ { + a := m.AlphaAt(x, y) + mean := int( + av(x-1, y-1) + av(x, y-1) + av(x+1, y-1)+ + av(x-1, y-0) + av(x, y-0) + av(x+1, y-0)+ + av(x-1, y+1) + av(x, y+1) + av(x+1, y+1)) / 8 + if a.A-uint8(mean) > a.A { + a.A = 0 + } else { + a.A -= uint8(mean) + } + defer m.SetAlpha(x, y, a) + } + } + return m +} + +func amp(m *image.Alpha) *image.Alpha { + for y := 0; y < m.Bounds().Dy(); y++ { + for x := 0; x < m.Bounds().Dx(); x++ { + a := m.AlphaAt(x, y) + if a.A < 64 { + continue + } + if a.A+64 < a.A { + a.A = 255 + } else { + a.A += 64 + } + defer m.SetAlpha(x, y, a) + } + } + return m +} diff --git a/font/draw.go b/font/draw.go index 7b3232e..eea17f6 100644 --- a/font/draw.go +++ b/font/draw.go @@ -7,35 +7,23 @@ import ( "unicode/utf8" ) -type rgba struct{ - r,g,b,a uint32 -} -type signature struct{ - b byte - dy int - rgba -} -var cache map[signature]*image.RGBA -func init(){ - cache = make(map[signature]*image.RGBA) -} - func StringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft *Font, s []byte, bg image.Image, bgp image.Point) int { + cache := ft.imgCache quad := rgba{} { - r,g,b,a := src.(*image.Uniform).RGBA() - quad=rgba{r,g,b,a} + r, g, b, a := bg.(*image.Uniform).RGBA() + quad = rgba{r, g, b, a} } sig := signature{ - dy: ft.Dy(), + dy: ft.Dy(), rgba: quad, } r0 := image.Rectangle{p, p} for _, b := range s { sig.b = b - if img, ok := cache[sig]; ok{ + if img, ok := cache[sig]; ok { draw.Draw(dst, img.Bounds().Add(p), img, img.Bounds().Min, draw.Src) - p.X+=img.Bounds().Dx()+ft.stride //Add(image.Pt(img.Bounds().Dx(), 0)) + p.X += img.Bounds().Dx() + ft.stride //Add(image.Pt(img.Bounds().Dx(), 0)) continue } mask := ft.Char(b) @@ -43,19 +31,19 @@ func StringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft panic("StringBG") } r := mask.Bounds() - if r0.Min == image.ZP{ + if r0.Min == image.ZP { r0.Min = r.Add(p).Min } draw.DrawMask(dst, r.Add(p), src, sp, mask, mask.Bounds().Min, draw.Over) img := image.NewRGBA(r) draw.Draw(img, img.Bounds(), bg, bgp, draw.Src) draw.Draw(img, img.Bounds(), dst, r.Add(p).Min, draw.Src) - cache[sig]=img - + cache[sig] = img + p.X += r.Dx() + ft.stride r0.Max.X += r.Dx() + ft.stride - if r.Dy() > r0.Dy(){ - r0.Max.Y=r.Dy() + if r.Dy() > r0.Dy() { + r0.Max.Y = r.Dy() } } return p.X diff --git a/font/font.go b/font/font.go index 3eb01fa..fd9fb8d 100644 --- a/font/font.go +++ b/font/font.go @@ -1,7 +1,6 @@ package font import ( - "fmt" "image" "image/draw" "unicode" @@ -11,6 +10,7 @@ import ( "golang.org/x/image/font/basicfont" "golang.org/x/image/font/gofont/gomedium" "golang.org/x/image/font/gofont/gomono" + "golang.org/x/image/font/gofont/gomonobold" "golang.org/x/image/font/gofont/goregular" "golang.org/x/image/math/fixed" ) @@ -29,6 +29,16 @@ type Font struct { cache Cache hexCache Cache decCache Cache + imgCache map[signature]*image.RGBA +} + +type rgba struct { + r, g, b, a uint32 +} +type signature struct { + b byte + dy int + rgba } func NewGoRegular(size int) *Font { @@ -39,6 +49,10 @@ func NewGoMedium(size int) *Font { return NewTTF(gomedium.TTF, size) } +func NewGoMonoBold(size int) *Font { + return NewTTF(gomonobold.TTF, size) +} + func NewGoMono(size int) *Font { return NewTTF(gomono.TTF, size) } @@ -58,24 +72,38 @@ func NewBasic(size int) *Font { ft.dy = ft.ascent + ft.descent + ft.size hexFt := fromTTF(gomono.TTF, ft.Dy()/4+3) ft.hexDx = ft.genChar('_').Bounds().Dx() + helper := mkhelper(hexFt) for i := 0; i != 256; i++ { ft.cache[i] = ft.genChar(byte(i)) if ft.cache[i] == nil { - ft.cache[i] = hexFt.genHexChar(ft.Dy(), byte(i)) + ft.cache[i] = hexFt.genHexCharTest(ft.Dy(), byte(i), helper) } } return ft } +const hexbytes = "0123456789abcdef" + +func mkhelper(hexFt *Font) []*Glyph { + helper := make([]*Glyph, 0, len(hexbytes)) + for _, s := range hexbytes { + helper = append(helper, hexFt.genChar(byte(s))) + } + return helper +} + func NewTTF(data []byte, size int) *Font { + //return makefont(data, size) ft := fromTTF(data, size) - hexFt := fromTTF(gomono.TTF, ft.Dy()/4+3) - + hexFt := fromTTF(gomono.TTF, ft.Dy()/3+3) + //hexFt := fromTTF(gomono.TTF, ft.Dy()/4+3) ft.hexDx = ft.genChar('_').Bounds().Dx() + + helper := mkhelper(hexFt) for i := 0; i != 256; i++ { ft.cache[i] = ft.genChar(byte(i)) if ft.cache[i] == nil { - ft.cache[i] = hexFt.genHexChar(ft.Dy(), byte(i)) + ft.cache[i] = hexFt.genHexChar(ft.Dy(), byte(i), helper) } } return ft @@ -88,15 +116,14 @@ func fromTTF(data []byte, size int) *Font { ft := &Font{ Face: truetype.NewFace(f, &truetype.Options{ - Size: float64(size), - GlyphCacheEntries: 512 * 2, - SubPixelsX: 1, + Size: float64(size), }), size: size, ascent: 2, descent: +(size / 3), stride: 0, data: data, + imgCache: make(map[signature]*image.RGBA), } ft.dy = ft.ascent + ft.descent + ft.size return ft @@ -145,20 +172,19 @@ func (f *Font) SetLetting(px int) { } func (f *Font) genChar(b byte) *Glyph { - dr, mask, maskp, adv, _ := f.Face.Glyph(fixed.P(0, f.size), rune(b)) if !f.Printable(b) { return nil } + dr, mask, maskp, adv, _ := f.Face.Glyph(fixed.P(0, f.size), rune(b)) r := image.Rect(0, 0, Fix(adv), f.Dy()) m := image.NewAlpha(r) r = r.Add(image.Pt(dr.Min.X, dr.Min.Y)) draw.Draw(m, r, mask, maskp, draw.Src) return &Glyph{mask: m, Rectangle: m.Bounds()} } -func (f *Font) genHexChar(dy int, b byte) *Glyph { - s := fmt.Sprintf("%02x", b) - g0 := f.genChar(s[0]) - g1 := f.genChar(s[1]) +func (f *Font) genHexCharTest(dy int, b byte, helper []*Glyph) *Glyph { + g0 := helper[b/16] + g1 := helper[b%16] r := image.Rect(2, f.descent+f.ascent, g0.Bounds().Dx()+g1.Bounds().Dx()+6, dy) m := image.NewAlpha(r) draw.Draw(m, r, g0.Mask(), image.ZP, draw.Over) @@ -167,6 +193,17 @@ func (f *Font) genHexChar(dy int, b byte) *Glyph { return &Glyph{mask: m, Rectangle: m.Bounds()} } +func (f *Font) genHexChar(dy int, b byte, helper []*Glyph) *Glyph { + g0 := helper[b/16] + g1 := helper[b%16] + r := image.Rect(2, f.descent+f.ascent-3, g0.Bounds().Dx()+g1.Bounds().Dx()+7, dy) + m := image.NewAlpha(r) + draw.Draw(m, r, g0.Mask(), image.ZP, draw.Over) + r.Min.X += g0.Mask().Bounds().Dx() + draw.Draw(m, r.Add(image.Pt(-f.descent/4, f.descent+f.descent/2)), g1.Mask(), image.ZP, draw.Over) + return &Glyph{mask: m, Rectangle: m.Bounds()} +} + func (f *Font) Char(b byte) (mask *image.Alpha) { return f.cache[b].mask } diff --git a/font/srv.go b/font/srv.go new file mode 100644 index 0000000..b54f7db --- /dev/null +++ b/font/srv.go @@ -0,0 +1,74 @@ +package font + +import ( + "hash/crc32" + "image" + + "github.com/golang/freetype/truetype" +) + +var fontIRQ chan fontPKT + +type fontPKT struct { + id string + data []byte + reply chan interface{} +} + +func fontsrv() { + parsedTTF := make(map[string]*truetype.Font) + for { + select { + case in := <-fontIRQ: + func() { + defer close(in.reply) + + if v, ok := parsedTTF[in.id]; ok { + in.reply <- v + return + } + + f, err := truetype.Parse(in.data) + if err != nil { + in.reply <- err + return + } + + parsedTTF[in.id] = f + in.reply <- f + }() + } + } +} +func makefont(data []byte, size int) *Font { + if fontIRQ == nil{ + fontIRQ = make(chan fontPKT) + go fontsrv() + } + reply := make(chan interface{}) + fontIRQ <- fontPKT{ + id: string(crc32.NewIEEE().Sum(data)), + reply: reply, + data: data, + } + rx := <-reply + switch rx := rx.(type) { + case error: + println(rx) + return nil + case *truetype.Font: + return &Font{ + Face: truetype.NewFace(rx, + &truetype.Options{ + Size: float64(size), + }), + size: size, + ascent: 2, + descent: +(size / 3), + stride: 0, + data: data, + imgCache: make(map[signature]*image.RGBA), + } + } + panic("makefont") +} From cf1c30b743d85f6367819bf2a1274d2d7721305f Mon Sep 17 00:00:00 2001 From: as Date: Thu, 7 Dec 2017 01:27:09 -0800 Subject: [PATCH 4/4] update comment; test first then uncomment --- draw.go | 7 +++++++ font/decon.go | 6 +++--- font/font.go | 23 ++++++++++++----------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/draw.go b/draw.go index e077356..e38d9e6 100644 --- a/draw.go +++ b/draw.go @@ -133,6 +133,13 @@ func (f *Frame) drawsel(pt image.Point, p0, p1 int64, back, text image.Image) im if f.PlainBox(nb) { f.stringNBG(f.b, pt, text, image.ZP, f.Font, ptr) } +// TODO(as): replace the above with the snippet below: reason: +// stringBG is more efficient now that it does an src bitblt for an already-known character +// if f.PlainBox(nb){ +// f.stringBG(f.b, pt, text, image.ZP, f.Font, ptr) +// } else { +// f.Draw(f.b, image.Rect(pt.X, pt.Y, min(pt.X+w, f.r.Max.X), pt.Y+f.Font.Dy()), back, pt, f.op) +// } pt.X += w if q0 += len(ptr); q0 >= p1 { diff --git a/font/decon.go b/font/decon.go index d3d012b..113df39 100644 --- a/font/decon.go +++ b/font/decon.go @@ -26,9 +26,9 @@ func deconvolve(m *image.Alpha) *image.Alpha { for x := 0; x < m.Bounds().Dx(); x++ { a := m.AlphaAt(x, y) mean := int( - av(x-1, y-1) + av(x, y-1) + av(x+1, y-1)+ - av(x-1, y-0) + av(x, y-0) + av(x+1, y-0)+ - av(x-1, y+1) + av(x, y+1) + av(x+1, y+1)) / 8 + av(x-1, y-1)+av(x, y-1)+av(x+1, y-1)+ + av(x-1, y-0)+av(x, y-0)+av(x+1, y-0)+ + av(x-1, y+1)+av(x, y+1)+av(x+1, y+1)) / 8 if a.A-uint8(mean) > a.A { a.A = 0 } else { diff --git a/font/font.go b/font/font.go index fd9fb8d..029900d 100644 --- a/font/font.go +++ b/font/font.go @@ -62,12 +62,13 @@ func NewBasic(size int) *Font { f := basicfont.Face7x13 size = 13 ft := &Font{ - Face: f, - size: size, - ascent: 2, - descent: 1, - letting: 0, - stride: 0, + Face: f, + size: size, + ascent: 2, + descent: 1, + letting: 0, + stride: 0, + imgCache: make(map[signature]*image.RGBA), } ft.dy = ft.ascent + ft.descent + ft.size hexFt := fromTTF(gomono.TTF, ft.Dy()/4+3) @@ -118,11 +119,11 @@ func fromTTF(data []byte, size int) *Font { &truetype.Options{ Size: float64(size), }), - size: size, - ascent: 2, - descent: +(size / 3), - stride: 0, - data: data, + size: size, + ascent: 2, + descent: +(size / 3), + stride: 0, + data: data, imgCache: make(map[signature]*image.RGBA), } ft.dy = ft.ascent + ft.descent + ft.size