Skip to content

Commit

Permalink
improved cache code
Browse files Browse the repository at this point in the history
  • Loading branch information
tfriedel6 committed Mar 21, 2020
1 parent 896af05 commit 9d1e5b3
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 92 deletions.
79 changes: 55 additions & 24 deletions canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
package canvas

import (
"fmt"
"image"
"image/color"
"os"
"sort"

"github.com/golang/freetype/truetype"
"github.com/tfriedel6/canvas/backend/backendbase"
Expand All @@ -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
}
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
}
}
91 changes: 24 additions & 67 deletions images.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"image"
"io/ioutil"
"os"
"sort"
"strings"
"time"

Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
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))
}
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}
}
cv.reduceCache(Performance.ImageCacheSize)
switch v := src.(type) {
case *Image:
return v
case image.Image:
img, err := cv.LoadImage(v)
if err != nil {

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)
cv.images[src] = nil
return nil
}
cv.images[v] = img
return img
case string:
img, err := cv.LoadImage(v)
if err != nil {
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
}
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))
default:
fmt.Fprintf(os.Stderr, "Failed to load image: %v\n", err)
}
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
Expand Down
40 changes: 39 additions & 1 deletion text.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package canvas

import (
"errors"
"fmt"
"image"
"image/draw"
"io/ioutil"
"os"

"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
Expand All @@ -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

Expand All @@ -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:
Expand All @@ -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) {
Expand Down

0 comments on commit 9d1e5b3

Please sign in to comment.