From 9d1e5b306a169792c6b3debe6be0cbf2acfc175e Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Sat, 21 Mar 2020 11:36:27 +0100 Subject: [PATCH] improved cache code --- canvas.go | 79 ++++++++++++++++++++++++++++++++--------------- images.go | 91 +++++++++++++++---------------------------------------- text.go | 40 +++++++++++++++++++++++- 3 files changed, 118 insertions(+), 92 deletions(-) diff --git a/canvas.go b/canvas.go index 3e5cb4a..043fc05 100644 --- a/canvas.go +++ b/canvas.go @@ -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 + } +} diff --git a/images.go b/images.go index 2be024e..a3c0a9c 100644 --- a/images.go +++ b/images.go @@ -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 + 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 diff --git a/text.go b/text.go index 3782a76..01611c5 100644 --- a/text.go +++ b/text.go @@ -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) {