improved cache code

This commit is contained in:
Thomas Friedel 2020-03-21 11:36:27 +01:00
parent 896af05ba4
commit 9d1e5b306a
3 changed files with 119 additions and 93 deletions

View file

@ -3,10 +3,9 @@
package canvas package canvas
import ( import (
"fmt"
"image" "image"
"image/color" "image/color"
"os" "sort"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/tfriedel6/canvas/backend/backendbase" "github.com/tfriedel6/canvas/backend/backendbase"
@ -26,7 +25,9 @@ type Canvas struct {
state drawState state drawState
stateStack []drawState stateStack []drawState
images map[interface{}]*Image images map[interface{}]*Image
fonts map[interface{}]*Font
fontCtxs map[fontKey]*frContext
shadowBuf [][2]float64 shadowBuf [][2]float64
} }
@ -122,9 +123,11 @@ const (
var Performance = struct { var Performance = struct {
IgnoreSelfIntersections bool IgnoreSelfIntersections bool
AssumeConvex bool AssumeConvex bool
ImageCacheSize int
// CacheSize is only approximate
CacheSize int
}{ }{
ImageCacheSize: 16_000_000, CacheSize: 16_000_000,
} }
// New creates a new canvas with the given viewport coordinates. // New creates a new canvas with the given viewport coordinates.
@ -136,6 +139,7 @@ func New(backend backendbase.Backend) *Canvas {
b: backend, b: backend,
stateStack: make([]drawState, 0, 20), stateStack: make([]drawState, 0, 20),
images: make(map[interface{}]*Image), images: make(map[interface{}]*Image),
fonts: make(map[interface{}]*Font),
} }
cv.state.lineWidth = 1 cv.state.lineWidth = 1
cv.state.lineAlpha = 1 cv.state.lineAlpha = 1
@ -286,25 +290,26 @@ func (cv *Canvas) SetFont(src interface{}, size float64) {
if src == nil { if src == nil {
cv.state.font = defaultFont cv.state.font = defaultFont
} else { } else {
switch v := src.(type) { cv.state.font = cv.getFont(src)
case *Font: // switch v := src.(type) {
cv.state.font = v // case *Font:
case *truetype.Font: // cv.state.font = v
cv.state.font = &Font{font: v} // case *truetype.Font:
case string: // cv.state.font = &Font{font: v}
if f, ok := fonts[v]; ok { // case string:
cv.state.font = f // if f, ok := fonts[v]; ok {
} else { // cv.state.font = f
f, err := cv.LoadFont(v) // } else {
if err != nil { // f, err := cv.LoadFont(v)
fmt.Fprintf(os.Stderr, "Error loading font %s: %v\n", v, err) // if err != nil {
fonts[v] = nil // fmt.Fprintf(os.Stderr, "Error loading font %s: %v\n", v, err)
} else { // fonts[v] = nil
fonts[v] = f // } else {
cv.state.font = f // fonts[v] = f
} // cv.state.font = f
} // }
} // }
// }
} }
cv.state.fontSize = size cv.state.fontSize = size
@ -481,3 +486,29 @@ func (cv *Canvas) IsPointInStroke(x, y float64) bool {
} }
return false return false
} }
func (cv *Canvas) reduceCache(keepSize int) {
var total int
for _, img := range cv.images {
w, h := img.img.Size()
total += w * h * 4
}
if total <= keepSize {
return
}
list := make([]*Image, 0, len(cv.images))
for _, img := range cv.images {
list = append(list, img)
}
sort.Slice(list, func(i, j int) bool {
return list[i].lastUsed.Before(list[j].lastUsed)
})
pos := 0
for total > keepSize {
img := list[pos]
pos++
delete(cv.images, img.src)
w, h := img.img.Size()
total -= w * h * 4
}
}

View file

@ -7,7 +7,6 @@ import (
"image" "image"
"io/ioutil" "io/ioutil"
"os" "os"
"sort"
"strings" "strings"
"time" "time"
@ -22,32 +21,6 @@ type Image struct {
lastUsed time.Time lastUsed time.Time
} }
func (cv *Canvas) reduceCache(keepSize int) {
var total int
for _, img := range cv.images {
w, h := img.img.Size()
total += w * h * 4
}
if total <= keepSize {
return
}
list := make([]*Image, 0, len(cv.images))
for _, img := range cv.images {
list = append(list, img)
}
sort.Slice(list, func(i, j int) bool {
return list[i].lastUsed.Before(list[j].lastUsed)
})
pos := 0
for total > keepSize {
img := list[pos]
pos++
delete(cv.images, img.src)
w, h := img.img.Size()
total -= w * h * 4
}
}
// LoadImage loads an image. The src parameter can be either an image from the // LoadImage loads an image. The src parameter can be either an image from the
// standard image package, a byte slice that will be loaded, or a file name // standard image package, a byte slice that will be loaded, or a file name
// string. If you want the canvas package to load the image, make sure you // string. If you want the canvas package to load the image, make sure you
@ -62,7 +35,7 @@ func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
return img, nil return img, nil
} }
} }
cv.reduceCache(Performance.ImageCacheSize) cv.reduceCache(Performance.CacheSize)
var srcImg image.Image var srcImg image.Image
switch v := src.(type) { switch v := src.(type) {
case image.Image: case image.Image:
@ -100,54 +73,38 @@ func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
} }
func (cv *Canvas) getImage(src interface{}) *Image { func (cv *Canvas) getImage(src interface{}) *Image {
if img, ok := src.(*Image); ok { if cv2, ok := src.(*Canvas); ok {
return img if !cv.b.CanUseAsImage(cv2.b) {
} else if img, ok := cv.images[src]; ok { w, h := cv2.Size()
return img return cv.getImage(cv2.GetImageData(0, 0, w, h))
}
cv.reduceCache(Performance.ImageCacheSize)
switch v := src.(type) {
case *Image:
return v
case image.Image:
img, err := cv.LoadImage(v)
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
cv.images[src] = nil
return nil
} }
cv.images[v] = img bimg := cv2.b.AsImage()
return img if bimg == nil {
case string: w, h := cv2.Size()
img, err := cv.LoadImage(v) return cv.getImage(cv2.GetImageData(0, 0, w, h))
if err != nil { }
return &Image{cv: cv, img: bimg}
}
img, err := cv.LoadImage(src)
if err != nil {
cv.images[src] = nil
switch v := src.(type) {
case image.Image:
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
case string:
if strings.Contains(strings.ToLower(err.Error()), "format") { if strings.Contains(strings.ToLower(err.Error()), "format") {
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\nIt may be necessary to import the appropriate decoder, e.g.\nimport _ \"image/jpeg\"\n", v, err) fmt.Fprintf(os.Stderr, "Error loading image %s: %v\nIt may be necessary to import the appropriate decoder, e.g.\nimport _ \"image/jpeg\"\n", v, err)
} else { } else {
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\n", v, err) fmt.Fprintf(os.Stderr, "Error loading image %s: %v\n", v, err)
} }
cv.images[src] = nil default:
return nil fmt.Fprintf(os.Stderr, "Failed to load image: %v\n", err)
} }
cv.images[v] = img } else {
return img cv.images[src] = img
case *Canvas:
if !cv.b.CanUseAsImage(v.b) {
w, h := v.Size()
return cv.getImage(v.GetImageData(0, 0, w, h))
}
bimg := v.b.AsImage()
if bimg == nil {
w, h := v.Size()
return cv.getImage(v.GetImageData(0, 0, w, h))
}
img := &Image{cv: cv, img: bimg}
cv.images[v] = img
return img
} }
fmt.Fprintf(os.Stderr, "Unknown image type: %T\n", src) return img
cv.images[src] = nil
return nil
} }
// Width returns the width of the image // Width returns the width of the image

40
text.go
View file

@ -2,9 +2,11 @@ package canvas
import ( import (
"errors" "errors"
"fmt"
"image" "image"
"image/draw" "image/draw"
"io/ioutil" "io/ioutil"
"os"
"github.com/golang/freetype" "github.com/golang/freetype"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
@ -20,7 +22,16 @@ type Font struct {
font *truetype.Font font *truetype.Font
} }
var fonts = make(map[string]*Font) type fontKey struct {
font *Font
size fixed.Int26_6
}
type fontCache struct {
font *Font
frctx *frContext
}
var zeroes [alphaTexSize]byte var zeroes [alphaTexSize]byte
var textImage *image.Alpha var textImage *image.Alpha
@ -29,6 +40,14 @@ var defaultFont *Font
// LoadFont loads a font and returns the result. The font // LoadFont loads a font and returns the result. The font
// can be a file name or a byte slice in TTF format // can be a file name or a byte slice in TTF format
func (cv *Canvas) LoadFont(src interface{}) (*Font, error) { func (cv *Canvas) LoadFont(src interface{}) (*Font, error) {
if f, ok := src.(*Font); ok {
return f, nil
} else if _, ok := src.([]byte); !ok {
if f, ok := cv.fonts[src]; ok {
return f, nil
}
}
var f *Font var f *Font
switch v := src.(type) { switch v := src.(type) {
case *truetype.Font: case *truetype.Font:
@ -55,9 +74,28 @@ func (cv *Canvas) LoadFont(src interface{}) (*Font, error) {
if defaultFont == nil { if defaultFont == nil {
defaultFont = f defaultFont = f
} }
if _, ok := src.([]byte); !ok {
cv.fonts[src] = f
}
return f, nil return f, nil
} }
func (cv *Canvas) getFont(src interface{}) *Font {
f, err := cv.LoadFont(src)
if err != nil {
cv.fonts[src] = nil
fmt.Fprintf(os.Stderr, "Error loading font: %v\n", err)
} else {
cv.fonts[src] = f
}
return f
}
// func (cv *Canvas) getFRContext(src interface{}, size float64) *Font {
// }
// FillText draws the given string at the given coordinates // FillText draws the given string at the given coordinates
// using the currently set font and font height // using the currently set font and font height
func (cv *Canvas) FillText(str string, x, y float64) { func (cv *Canvas) FillText(str string, x, y float64) {