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
import (
"fmt"
"image"
"image/color"
"os"
"sort"
"github.com/golang/freetype/truetype"
"github.com/tfriedel6/canvas/backend/backendbase"
@ -26,7 +25,9 @@ type Canvas struct {
state drawState
stateStack []drawState
images map[interface{}]*Image
images map[interface{}]*Image
fonts map[interface{}]*Font
fontCtxs map[fontKey]*frContext
shadowBuf [][2]float64
}
@ -122,9 +123,11 @@ const (
var Performance = struct {
IgnoreSelfIntersections 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.
@ -136,6 +139,7 @@ func New(backend backendbase.Backend) *Canvas {
b: backend,
stateStack: make([]drawState, 0, 20),
images: make(map[interface{}]*Image),
fonts: make(map[interface{}]*Font),
}
cv.state.lineWidth = 1
cv.state.lineAlpha = 1
@ -286,25 +290,26 @@ func (cv *Canvas) SetFont(src interface{}, size float64) {
if src == nil {
cv.state.font = defaultFont
} else {
switch v := src.(type) {
case *Font:
cv.state.font = v
case *truetype.Font:
cv.state.font = &Font{font: v}
case string:
if f, ok := fonts[v]; ok {
cv.state.font = f
} else {
f, err := cv.LoadFont(v)
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading font %s: %v\n", v, err)
fonts[v] = nil
} else {
fonts[v] = f
cv.state.font = f
}
}
}
cv.state.font = cv.getFont(src)
// switch v := src.(type) {
// case *Font:
// cv.state.font = v
// case *truetype.Font:
// cv.state.font = &Font{font: v}
// case string:
// if f, ok := fonts[v]; ok {
// cv.state.font = f
// } else {
// f, err := cv.LoadFont(v)
// if err != nil {
// fmt.Fprintf(os.Stderr, "Error loading font %s: %v\n", v, err)
// fonts[v] = nil
// } else {
// fonts[v] = f
// cv.state.font = f
// }
// }
// }
}
cv.state.fontSize = size
@ -481,3 +486,29 @@ func (cv *Canvas) IsPointInStroke(x, y float64) bool {
}
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"
"io/ioutil"
"os"
"sort"
"strings"
"time"
@ -22,32 +21,6 @@ type Image struct {
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
// 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
@ -62,7 +35,7 @@ func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
return img, nil
}
}
cv.reduceCache(Performance.ImageCacheSize)
cv.reduceCache(Performance.CacheSize)
var srcImg image.Image
switch v := src.(type) {
case image.Image:
@ -100,54 +73,38 @@ func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
}
func (cv *Canvas) getImage(src interface{}) *Image {
if img, ok := src.(*Image); ok {
return img
} else if img, ok := cv.images[src]; ok {
return img
}
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
if cv2, ok := src.(*Canvas); ok {
if !cv.b.CanUseAsImage(cv2.b) {
w, h := cv2.Size()
return cv.getImage(cv2.GetImageData(0, 0, w, h))
}
cv.images[v] = img
return img
case string:
img, err := cv.LoadImage(v)
if err != nil {
bimg := cv2.b.AsImage()
if bimg == nil {
w, h := cv2.Size()
return cv.getImage(cv2.GetImageData(0, 0, w, h))
}
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") {
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 {
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\n", v, err)
}
cv.images[src] = nil
return nil
default:
fmt.Fprintf(os.Stderr, "Failed to load image: %v\n", err)
}
cv.images[v] = img
return 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
} else {
cv.images[src] = img
}
fmt.Fprintf(os.Stderr, "Unknown image type: %T\n", src)
cv.images[src] = nil
return nil
return img
}
// Width returns the width of the image

40
text.go
View file

@ -2,9 +2,11 @@ package canvas
import (
"errors"
"fmt"
"image"
"image/draw"
"io/ioutil"
"os"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
@ -20,7 +22,16 @@ type Font struct {
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 textImage *image.Alpha
@ -29,6 +40,14 @@ var defaultFont *Font
// LoadFont loads a font and returns the result. The font
// can be a file name or a byte slice in TTF format
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
switch v := src.(type) {
case *truetype.Font:
@ -55,9 +74,28 @@ func (cv *Canvas) LoadFont(src interface{}) (*Font, error) {
if defaultFont == nil {
defaultFont = f
}
if _, ok := src.([]byte); !ok {
cv.fonts[src] = f
}
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
// using the currently set font and font height
func (cv *Canvas) FillText(str string, x, y float64) {