improved cache code
This commit is contained in:
parent
896af05ba4
commit
9d1e5b306a
3 changed files with 119 additions and 93 deletions
79
canvas.go
79
canvas.go
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
93
images.go
93
images.go
|
@ -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
40
text.go
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue