mirror of https://github.com/talgo-cloud/bimg.git
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.
372 lines
8.6 KiB
372 lines
8.6 KiB
package bimg
|
|
|
|
/*
|
|
#cgo pkg-config: vips
|
|
#include "vips.h"
|
|
#include "stdlib.h"
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"errors"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"unsafe"
|
|
)
|
|
|
|
var (
|
|
m sync.Mutex
|
|
initialized bool = false
|
|
)
|
|
|
|
type VipsMemoryInfo struct {
|
|
Memory int64
|
|
MemoryHighwater int64
|
|
Allocations int64
|
|
}
|
|
|
|
type vipsSaveOptions struct {
|
|
Quality int
|
|
Compression int
|
|
Type ImageType
|
|
}
|
|
|
|
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() {
|
|
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
|
|
panic("unsupported old vips version!")
|
|
}
|
|
|
|
Initialize()
|
|
}
|
|
|
|
// Explicit thread-safe start of libvips.
|
|
// Only call this function if you've previously shutdown libvips
|
|
func Initialize() {
|
|
m.Lock()
|
|
runtime.LockOSThread()
|
|
defer m.Unlock()
|
|
defer runtime.UnlockOSThread()
|
|
|
|
err := C.vips_init(C.CString("bimg"))
|
|
if err != 0 {
|
|
Shutdown()
|
|
panic("unable to start vips!")
|
|
}
|
|
|
|
C.vips_concurrency_set(0) // default
|
|
C.vips_cache_set_max_mem(100 * 1024 * 1024) // 100 MB
|
|
C.vips_cache_set_max(500) // 500 operations
|
|
initialized = true
|
|
}
|
|
|
|
// Explicit thread-safe libvips shutdown. Call this to drop caches.
|
|
// If libvips was already initialized, the function is no-op
|
|
func Shutdown() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
if initialized == true {
|
|
C.vips_shutdown()
|
|
initialized = false
|
|
}
|
|
}
|
|
|
|
// Output to stdout vips collected data. Useful for debugging
|
|
func VipsDebug() {
|
|
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 {
|
|
return float64(C.interpolator_window_size(C.CString(name)))
|
|
}
|
|
|
|
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 vipsColorSpace(image *C.struct__VipsImage) (*C.struct__VipsImage, error) {
|
|
var out *C.struct__VipsImage
|
|
var temp *C.struct__VipsImage
|
|
var max *C.double
|
|
var x *C.int
|
|
var y *C.int
|
|
|
|
defer C.g_object_unref(C.gpointer(image))
|
|
|
|
err := C.vips_colorspace_bridge(image, &out)
|
|
if err != 0 {
|
|
return nil, catchVipsError()
|
|
}
|
|
|
|
err = C.vips_hist_find_ndim_bridge(out, &temp)
|
|
if err != 0 {
|
|
return nil, catchVipsError()
|
|
}
|
|
|
|
err = C.vips_max_bridge(temp, max, &x, &y)
|
|
if err != 0 {
|
|
return nil, catchVipsError()
|
|
}
|
|
debug("MAX VALUE %dx%d", x, y)
|
|
|
|
return temp, 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) {
|
|
var ptr unsafe.Pointer
|
|
length := C.size_t(0)
|
|
err := C.int(0)
|
|
|
|
defer C.g_object_unref(C.gpointer(image))
|
|
|
|
switch {
|
|
case o.Type == PNG:
|
|
err = C.vips_pngsave_bridge(image, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), 0)
|
|
break
|
|
case o.Type == WEBP:
|
|
err = C.vips_webpsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
|
break
|
|
default:
|
|
err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
|
break
|
|
}
|
|
|
|
if int(err) != 0 {
|
|
return nil, catchVipsError()
|
|
}
|
|
|
|
buf := C.GoBytes(ptr, C.int(length))
|
|
|
|
// Cleanup
|
|
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
|
|
istring := C.CString(i.String())
|
|
interpolator := C.vips_interpolate_new(istring)
|
|
|
|
defer C.free(unsafe.Pointer(istring))
|
|
defer C.g_object_unref(C.gpointer(input))
|
|
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)
|
|
}
|