You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
2.1 KiB
114 lines
2.1 KiB
package image
|
|
|
|
import (
|
|
"bytes"
|
|
stdimage "image"
|
|
"image/jpeg"
|
|
"os"
|
|
"sync"
|
|
|
|
xdraw "golang.org/x/image/draw"
|
|
)
|
|
|
|
const (
|
|
defaultCacheEntries = 500
|
|
thumbnailWidth = 160
|
|
thumbnailHeight = 90
|
|
)
|
|
|
|
type Cache struct {
|
|
mu sync.Mutex
|
|
max int
|
|
entries map[string][]byte
|
|
order []string
|
|
}
|
|
|
|
func NewCache(max int) *Cache {
|
|
if max <= 0 {
|
|
max = defaultCacheEntries
|
|
}
|
|
return &Cache{
|
|
max: max,
|
|
entries: make(map[string][]byte),
|
|
}
|
|
}
|
|
|
|
func (c *Cache) Thumbnail(absPath string) ([]byte, error) {
|
|
if cached, ok := c.get(absPath); ok {
|
|
return cached, nil
|
|
}
|
|
|
|
file, err := os.Open(absPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
src, err := jpeg.Decode(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dst := resizeToFit(src, thumbnailWidth, thumbnailHeight)
|
|
var out bytes.Buffer
|
|
if err := jpeg.Encode(&out, dst, &jpeg.Options{Quality: 75}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
thumb := out.Bytes()
|
|
c.add(absPath, thumb)
|
|
return append([]byte(nil), thumb...), nil
|
|
}
|
|
|
|
func (c *Cache) get(key string) ([]byte, bool) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
value, ok := c.entries[key]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return append([]byte(nil), value...), true
|
|
}
|
|
|
|
func (c *Cache) add(key string, value []byte) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if _, ok := c.entries[key]; !ok {
|
|
c.order = append(c.order, key)
|
|
}
|
|
c.entries[key] = append([]byte(nil), value...)
|
|
|
|
for len(c.order) > c.max {
|
|
oldest := c.order[0]
|
|
c.order = c.order[1:]
|
|
delete(c.entries, oldest)
|
|
}
|
|
}
|
|
|
|
func resizeToFit(src stdimage.Image, maxWidth, maxHeight int) stdimage.Image {
|
|
bounds := src.Bounds()
|
|
width := bounds.Dx()
|
|
height := bounds.Dy()
|
|
if width <= 0 || height <= 0 {
|
|
return stdimage.NewRGBA(stdimage.Rect(0, 0, 1, 1))
|
|
}
|
|
|
|
targetWidth := maxWidth
|
|
targetHeight := height * targetWidth / width
|
|
if targetHeight > maxHeight {
|
|
targetHeight = maxHeight
|
|
targetWidth = width * targetHeight / height
|
|
}
|
|
if targetWidth < 1 {
|
|
targetWidth = 1
|
|
}
|
|
if targetHeight < 1 {
|
|
targetHeight = 1
|
|
}
|
|
|
|
dst := stdimage.NewRGBA(stdimage.Rect(0, 0, targetWidth, targetHeight))
|
|
xdraw.BiLinear.Scale(dst, dst.Bounds(), src, bounds, xdraw.Over, nil)
|
|
return dst
|
|
}
|
|
|