forked from h2non/bimg
/
vips.go
379 lines (301 loc) · 8.71 KB
/
vips.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
package bimg
/*
#cgo pkg-config: vips
#include "vips.h"
*/
import "C"
import (
"errors"
"os"
"runtime"
"strings"
"sync"
"unsafe"
)
const (
maxCacheMem = 100 * 1024 * 1024
maxCacheSize = 500
)
var (
m sync.Mutex
initialized bool
)
type VipsMemoryInfo struct {
Memory int64
MemoryHighwater int64
Allocations int64
}
type vipsSaveOptions struct {
Quality int
Compression int
Type ImageType
Interlace bool
NoProfile bool
}
type vipsWatermarkOptions struct {
Width C.int
DPI C.int
Margin C.int
NoReplicate C.int
Opacity C.float
Background [3]C.double
}
type vipsWatermarkTextOptions struct {
Text *C.char
Font *C.char
}
func init() {
Initialize()
}
// Explicit thread-safe start of libvips.
// Only call this function if you've previously shutdown libvips
func Initialize() {
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
panic("unsupported libvips version!")
}
m.Lock()
runtime.LockOSThread()
defer m.Unlock()
defer runtime.UnlockOSThread()
err := C.vips_init(C.CString("bimg"))
if err != 0 {
panic("unable to start vips!")
}
// Set libvips cache params
C.vips_cache_set_max_mem(maxCacheMem)
C.vips_cache_set_max(maxCacheSize)
// Define a custom libvips thread concurrency limit (this may generate thread-unsafe issues)
// See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414
if os.Getenv("VIPS_CONCURRENCY") == "" {
C.vips_concurrency_set(1)
}
// Enable libvips cache tracing
if os.Getenv("VIPS_TRACE") != "" {
C.vips_enable_cache_set_trace()
}
initialized = true
}
// Thread-safe function to shutdown libvips. You could call this to drop caches as well.
// If libvips was already initialized, the function is no-op
func Shutdown() {
m.Lock()
defer m.Unlock()
if initialized {
C.vips_shutdown()
initialized = false
}
}
// Output to stdout vips collected data. Useful for debugging
func VipsDebugInfo() {
C.im__print_all()
}
// Get memory info stats from vips (cache size, memory allocs...)
func VipsMemory() VipsMemoryInfo {
return VipsMemoryInfo{
Memory: int64(C.vips_tracked_get_mem()),
MemoryHighwater: int64(C.vips_tracked_get_mem_highwater()),
Allocations: int64(C.vips_tracked_get_allocs()),
}
}
func vipsExifOrientation(image *C.struct__VipsImage) int {
return int(C.vips_exif_orientation(image))
}
func vipsHasAlpha(image *C.struct__VipsImage) bool {
return int(C.has_alpha_channel(image)) > 0
}
func vipsHasProfile(image *C.struct__VipsImage) bool {
return int(C.has_profile_embed(image)) > 0
}
func vipsWindowSize(name string) float64 {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return float64(C.interpolator_window_size(cname))
}
func vipsSpace(image *C.struct__VipsImage) string {
return C.GoString(C.vips_enum_nick_bridge(image))
}
func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) {
var out *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(image))
err := C.vips_rotate(image, &out, C.int(angle))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func vipsFlip(image *C.struct__VipsImage, direction Direction) (*C.struct__VipsImage, error) {
var out *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(image))
err := C.vips_flip_bridge(image, &out, C.int(direction))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func vipsZoom(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) {
var out *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(image))
err := C.vips_zoom_bridge(image, &out, C.int(zoom), C.int(zoom))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func vipsWatermark(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) {
var out *C.struct__VipsImage
// Defaults
noReplicate := 0
if w.NoReplicate {
noReplicate = 1
}
text := C.CString(w.Text)
font := C.CString(w.Font)
background := [3]C.double{C.double(w.Background.R), C.double(w.Background.G), C.double(w.Background.B)}
textOpts := vipsWatermarkTextOptions{text, font}
opts := vipsWatermarkOptions{C.int(w.Width), C.int(w.DPI), C.int(w.Margin), C.int(noReplicate), C.float(w.Opacity), background}
defer C.free(unsafe.Pointer(text))
defer C.free(unsafe.Pointer(font))
err := C.vips_watermark(image, &out, (*C.WatermarkTextOptions)(unsafe.Pointer(&textOpts)), (*C.WatermarkOptions)(unsafe.Pointer(&opts)))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) {
var image *C.struct__VipsImage
imageType := vipsImageType(buf)
if imageType == UNKNOWN {
return nil, UNKNOWN, errors.New("Unsupported image format")
}
length := C.size_t(len(buf))
imageBuf := unsafe.Pointer(&buf[0])
err := C.vips_init_image(imageBuf, length, C.int(imageType), &image)
if err != 0 {
return nil, UNKNOWN, catchVipsError()
}
return image, imageType, nil
}
func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) {
length := C.size_t(0)
err := C.int(0)
interlace := C.int(boolToInt(o.Interlace))
// Remove ICC profile metadata
if o.NoProfile {
C.remove_profile(image)
}
// Force RGB color space
var outImage *C.struct__VipsImage
C.vips_colourspace_bridge(image, &outImage)
defer C.g_object_unref(C.gpointer(image))
defer C.g_object_unref(C.gpointer(outImage))
var ptr unsafe.Pointer
switch o.Type {
case PNG:
err = C.vips_pngsave_bridge(outImage, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), interlace)
break
case WEBP:
err = C.vips_webpsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality))
break
default:
err = C.vips_jpegsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality), interlace)
break
}
if int(err) != 0 {
return nil, catchVipsError()
}
buf := C.GoBytes(ptr, C.int(length))
// Clean up
C.g_free(C.gpointer(ptr))
C.vips_error_clear()
return buf, nil
}
func vipsExtract(image *C.struct__VipsImage, left, top, width, height int) (*C.struct__VipsImage, error) {
var buf *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(image))
if width > MAX_SIZE || height > MAX_SIZE {
return nil, errors.New("Maximum image size exceeded")
}
err := C.vips_extract_area_bridge(image, &buf, C.int(left), C.int(top), C.int(width), C.int(height))
if err != 0 {
return nil, catchVipsError()
}
return buf, nil
}
func vipsShrinkJpeg(buf []byte, input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, error) {
var image *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(input))
err := C.vips_jpegload_buffer_shrink(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image, C.int(shrink))
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsShrink(input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, error) {
var image *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(input))
err := C.vips_shrink_bridge(input, &image, C.double(float64(shrink)), C.double(float64(shrink)))
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int) (*C.struct__VipsImage, error) {
var image *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(input))
err := C.vips_embed_bridge(input, &image, C.int(left), C.int(top), C.int(width), C.int(height), C.int(extend))
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*C.struct__VipsImage, error) {
var image *C.struct__VipsImage
cstring := C.CString(i.String())
interpolator := C.vips_interpolate_new(cstring)
defer C.free(unsafe.Pointer(cstring))
defer C.g_object_unref(C.gpointer(interpolator))
err := C.vips_affine_interpolator(input, &image, C.double(residual), 0, 0, C.double(residual), interpolator)
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsImageType(buf []byte) ImageType {
imageType := UNKNOWN
if len(buf) == 0 {
return imageType
}
length := C.size_t(len(buf))
imageBuf := unsafe.Pointer(&buf[0])
bufferType := C.GoString(C.vips_foreign_find_load_buffer(imageBuf, length))
switch {
case strings.HasSuffix(bufferType, "JpegBuffer"):
imageType = JPEG
break
case strings.HasSuffix(bufferType, "PngBuffer"):
imageType = PNG
break
case strings.HasSuffix(bufferType, "TiffBuffer"):
imageType = TIFF
break
case strings.HasSuffix(bufferType, "WebpBuffer"):
imageType = WEBP
break
case strings.HasSuffix(bufferType, "MagickBuffer"):
imageType = MAGICK
break
}
return imageType
}
func catchVipsError() error {
s := C.GoString(C.vips_error_buffer())
C.vips_error_clear()
C.vips_thread_shutdown()
return errors.New(s)
}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}