forked from nanmu42/orly
-
Notifications
You must be signed in to change notification settings - Fork 0
/
imageprovider.go
149 lines (135 loc) · 3.7 KB
/
imageprovider.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
* Copyright (c) 2018 LI Zhennan
*
* Use of this work is governed by an MIT License.
* You may find a license copy in project root.
*
*/
package orly
import (
"image"
"image/draw"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"github.com/disintegration/imaging"
"github.com/pkg/errors"
"golang.org/x/image/tiff"
)
// ImageProvider is an loader and holder for assets of their various size.
// If the size asked does not exist, provider will load the origin file from
// file system, resize it and cache product in memory.
//
// ImageProvider is safe for concurrent use.
//
// For now, ImageProvider does not handle memory growing.
type ImageProvider struct {
cache *ImageCache
loadLock *loadLock
// originImgLoader if image does not exist,
// provider calls this func to get original image for resize.
originImgLoader func(fileName string) (image.Image, error)
// miss record cache miss num
miss uint64
}
// NewImageProvider factory of ImageProvider
func NewImageProvider(Loader func(fileName string) (image.Image, error)) *ImageProvider {
return &ImageProvider{
cache: NewImageCache(),
loadLock: newLoadLock(),
originImgLoader: Loader,
miss: 0,
}
}
// Load get image of specific size from cache or file system
func (i *ImageProvider) Load(fileName string, size image.Rectangle) (img image.Image, err error) {
// check cache first
key := encodeKey(fileName, size.Dx(), size.Dy())
img, found := i.cache.Load(key)
if found {
return
}
// needs load from file system
atomic.AddUint64(&i.miss, 1)
// is loading?
if i.loadLock.Lock(key) {
err = errors.Errorf("%s is already under loading", key)
return
}
// got the lock, now do loading
defer i.loadLock.Unlock(key)
origin, err := i.originImgLoader(fileName)
if err != nil {
err = errors.Wrap(err, "originImgLoader failed")
return
}
// normally we are doing downscaling
fitted := imaging.Fit(origin, size.Dx(), size.Dy(), imaging.Box)
// converted to RGBA
rgba := image.NewRGBA(fitted.Bounds())
draw.Draw(rgba, rgba.Bounds(), fitted, image.ZP, draw.Src)
// push to cache
i.cache.Store(key, rgba)
img = rgba
return
}
// Miss report the num of cache miss
func (i *ImageProvider) Miss() uint64 {
return atomic.LoadUint64(&i.miss)
}
// LoadTIFFFromFolder func factory
func LoadTIFFFromFolder(folderPath string) func(fileName string) (image.Image, error) {
return func(fileName string) (img image.Image, err error) {
var strBuild strings.Builder
strBuild.WriteString(folderPath)
strBuild.WriteRune(os.PathSeparator)
strBuild.WriteString(fileName)
f, err := os.Open(strBuild.String())
if err != nil {
err = errors.Wrap(err, "Open failed")
return
}
defer f.Close()
img, err = tiff.Decode(f)
if err != nil {
err = errors.Wrap(err, "tiff.Decode")
return
}
return
}
}
// encodeKey encodes the key
func encodeKey(fileName string, width, height int) string {
return strconv.FormatInt(int64(width), 10) + "&" + strconv.FormatInt(int64(height), 10) + "&" + fileName
}
// loadLock is a set of key-status pair which is concurrently safe.
type loadLock struct {
mu sync.Mutex
dict map[string]*int64
}
// newLoadLock factory of loadLock
func newLoadLock() *loadLock {
return &loadLock{
mu: sync.Mutex{},
dict: make(map[string]*int64),
}
}
// status get key's status pointer
func (m *loadLock) status(key string) *int64 {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.dict[key]; !ok {
m.dict[key] = new(int64)
}
return m.dict[key]
}
// Lock locks key's loadlock
func (m *loadLock) Lock(key string) (alreadyLocked bool) {
return !atomic.CompareAndSwapInt64(m.status(key), 0, 1)
}
// Unlock unlocks key's loadlock
func (m *loadLock) Unlock(key string) {
atomic.StoreInt64(m.status(key), 0)
}