123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- package bimg
- /*
- #cgo pkg-config: vips
- #include "vips.h"
- */
- import "C"
- import (
- "errors"
- "fmt"
- "math"
- "os"
- "runtime"
- "strings"
- "sync"
- "unsafe"
- d "github.com/tj/go-debug"
- )
- // debug is internally used to
- var debug = d.Debug("bimg")
- // VipsVersion exposes the current libvips semantic version
- const VipsVersion = string(C.VIPS_VERSION)
- // VipsMajorVersion exposes the current libvips major version number
- const VipsMajorVersion = int(C.VIPS_MAJOR_VERSION)
- // VipsMinorVersion exposes the current libvips minor version number
- const VipsMinorVersion = int(C.VIPS_MINOR_VERSION)
- const (
- maxCacheMem = 100 * 1024 * 1024
- maxCacheSize = 500
- )
- var (
- m sync.Mutex
- initialized bool
- )
- // VipsMemoryInfo represents the memory stats provided by libvips.
- type VipsMemoryInfo struct {
- Memory int64
- MemoryHighwater int64
- Allocations int64
- }
- // vipsSaveOptions represents the internal option used to talk with libvips.
- type vipsSaveOptions struct {
- Quality int
- Compression int
- Type ImageType
- Interlace bool
- NoProfile bool
- StripMetadata bool
- OutputICC string // Absolute path to the output ICC profile
- Interpretation Interpretation
- }
- type vipsWatermarkOptions struct {
- Width C.int
- DPI C.int
- Margin C.int
- NoReplicate C.int
- Opacity C.float
- Background [3]C.double
- }
- type vipsWatermarkImageOptions struct {
- Left C.int
- Top C.int
- Opacity C.float
- }
- type vipsWatermarkTextOptions struct {
- Text *C.char
- Font *C.char
- }
- func init() {
- Initialize()
- }
- // Initialize is used to explicitly start libvips in thread-safe way.
- // Only call this function if you have previously turned off 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 thread concurrency limit in libvips (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
- }
- // Shutdown is used to shutdown libvips in a thread-safe way.
- // You can 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
- }
- }
- // VipsCacheSetMaxMem Sets the maximum amount of tracked memory allowed before the vips operation cache
- // begins to drop entries.
- func VipsCacheSetMaxMem(maxCacheMem int) {
- C.vips_cache_set_max_mem(C.size_t(maxCacheMem))
- }
- // VipsCacheSetMax sets the maximum number of operations to keep in the vips operation cache.
- func VipsCacheSetMax(maxCacheSize int) {
- C.vips_cache_set_max(C.int(maxCacheSize))
- }
- // VipsCacheDropAll drops the vips operation cache, freeing the allocated memory.
- func VipsCacheDropAll() {
- C.vips_cache_drop_all()
- }
- // VipsDebugInfo outputs to stdout libvips collected data. Useful for debugging.
- func VipsDebugInfo() {
- C.im__print_all()
- }
- // VipsMemory gets memory info stats from libvips (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()),
- }
- }
- // VipsIsTypeSupported returns true if the given image type
- // is supported by the current libvips compilation.
- func VipsIsTypeSupported(t ImageType) bool {
- if t == JPEG {
- return int(C.vips_type_find_bridge(C.JPEG)) != 0
- }
- if t == WEBP {
- return int(C.vips_type_find_bridge(C.WEBP)) != 0
- }
- if t == PNG {
- return int(C.vips_type_find_bridge(C.PNG)) != 0
- }
- if t == GIF {
- return int(C.vips_type_find_bridge(C.GIF)) != 0
- }
- if t == PDF {
- return int(C.vips_type_find_bridge(C.PDF)) != 0
- }
- if t == SVG {
- return int(C.vips_type_find_bridge(C.SVG)) != 0
- }
- if t == TIFF {
- return int(C.vips_type_find_bridge(C.TIFF)) != 0
- }
- if t == MAGICK {
- return int(C.vips_type_find_bridge(C.MAGICK)) != 0
- }
- return false
- }
- // VipsIsTypeSupportedSave returns true if the given image type
- // is supported by the current libvips compilation for the
- // save operation.
- func VipsIsTypeSupportedSave(t ImageType) bool {
- if t == JPEG {
- return int(C.vips_type_find_save_bridge(C.JPEG)) != 0
- }
- if t == WEBP {
- return int(C.vips_type_find_save_bridge(C.WEBP)) != 0
- }
- if t == PNG {
- return int(C.vips_type_find_save_bridge(C.PNG)) != 0
- }
- if t == TIFF {
- return int(C.vips_type_find_save_bridge(C.TIFF)) != 0
- }
- return false
- }
- func vipsExifOrientation(image *C.VipsImage) int {
- return int(C.vips_exif_orientation(image))
- }
- func vipsHasAlpha(image *C.VipsImage) bool {
- return int(C.has_alpha_channel(image)) > 0
- }
- func vipsHasProfile(image *C.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.VipsImage) string {
- return C.GoString(C.vips_enum_nick_bridge(image))
- }
- func vipsRotate(image *C.VipsImage, angle Angle) (*C.VipsImage, error) {
- var out *C.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.VipsImage, direction Direction) (*C.VipsImage, error) {
- var out *C.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.VipsImage, zoom int) (*C.VipsImage, error) {
- var out *C.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.VipsImage, w Watermark) (*C.VipsImage, error) {
- var out *C.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.VipsImage, ImageType, error) {
- var image *C.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 vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) {
- image, _, err := vipsRead(buf)
- if err != nil {
- return false, err
- }
- C.g_object_unref(C.gpointer(image))
- return vipsColourspaceIsSupported(image), nil
- }
- func vipsColourspaceIsSupported(image *C.VipsImage) bool {
- return int(C.vips_colourspace_issupported_bridge(image)) == 1
- }
- func vipsInterpretationBuffer(buf []byte) (Interpretation, error) {
- image, _, err := vipsRead(buf)
- if err != nil {
- return InterpretationError, err
- }
- C.g_object_unref(C.gpointer(image))
- return vipsInterpretation(image), nil
- }
- func vipsInterpretation(image *C.VipsImage) Interpretation {
- return Interpretation(C.vips_image_guess_interpretation_bridge(image))
- }
- func vipsFlattenBackground(image *C.VipsImage, background Color) (*C.VipsImage, error) {
- var outImage *C.VipsImage
- backgroundC := [3]C.double{
- C.double(background.R),
- C.double(background.G),
- C.double(background.B),
- }
- if vipsHasAlpha(image) {
- err := C.vips_flatten_background_brigde(image, &outImage,
- backgroundC[0], backgroundC[1], backgroundC[2])
- if int(err) != 0 {
- return nil, catchVipsError()
- }
- C.g_object_unref(C.gpointer(image))
- image = outImage
- }
- return image, nil
- }
- func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) {
- var outImage *C.VipsImage
- // Remove ICC profile metadata
- if o.NoProfile {
- C.remove_profile(image)
- }
- // Use a default interpretation and cast it to C type
- if o.Interpretation == 0 {
- o.Interpretation = InterpretationSRGB
- }
- interpretation := C.VipsInterpretation(o.Interpretation)
- // Apply the proper colour space
- if vipsColourspaceIsSupported(image) {
- err := C.vips_colourspace_bridge(image, &outImage, interpretation)
- if int(err) != 0 {
- return nil, catchVipsError()
- }
- image = outImage
- }
- if o.OutputICC != "" && vipsHasProfile(image) {
- debug("Embedded ICC profile found, trying to convert to %s", o.OutputICC)
- outputIccPath := C.CString(o.OutputICC)
- defer C.free(unsafe.Pointer(outputIccPath))
- err := C.vips_icc_transform_bridge(image, &outImage, outputIccPath)
- if int(err) != 0 {
- return nil, catchVipsError()
- }
- image = outImage
- }
- return image, nil
- }
- func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) {
- defer C.g_object_unref(C.gpointer(image))
- tmpImage, err := vipsPreSave(image, &o)
- if err != nil {
- return nil, err
- }
- // When an image has an unsupported color space, vipsPreSave
- // returns the pointer of the image passed to it unmodified.
- // When this occurs, we must take care to not dereference the
- // original image a second time; we may otherwise erroneously
- // free the object twice.
- if tmpImage != image {
- defer C.g_object_unref(C.gpointer(tmpImage))
- }
- length := C.size_t(0)
- saveErr := C.int(0)
- interlace := C.int(boolToInt(o.Interlace))
- quality := C.int(o.Quality)
- strip := C.int(boolToInt(o.StripMetadata))
- if o.Type != 0 && !IsTypeSupportedSave(o.Type) {
- return nil, fmt.Errorf("VIPS cannot save to %#v", ImageTypes[o.Type])
- }
- var ptr unsafe.Pointer
- switch o.Type {
- case WEBP:
- saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, strip, quality)
- case PNG:
- saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, strip, C.int(o.Compression), quality, interlace)
- case TIFF:
- saveErr = C.vips_tiffsave_bridge(tmpImage, &ptr, &length)
- default:
- saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, strip, quality, interlace)
- }
- if int(saveErr) != 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 getImageBuffer(image *C.VipsImage) ([]byte, error) {
- var ptr unsafe.Pointer
- length := C.size_t(0)
- interlace := C.int(0)
- quality := C.int(100)
- err := C.int(0)
- err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, quality, interlace)
- if int(err) != 0 {
- return nil, catchVipsError()
- }
- defer C.g_free(C.gpointer(ptr))
- defer C.vips_error_clear()
- return C.GoBytes(ptr, C.int(length)), nil
- }
- func vipsExtract(image *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) {
- var buf *C.VipsImage
- defer C.g_object_unref(C.gpointer(image))
- if width > MaxSize || height > MaxSize {
- return nil, errors.New("Maximum image size exceeded")
- }
- top, left = max(top), max(left)
- 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 vipsSmartCrop(image *C.VipsImage, width, height int) (*C.VipsImage, error) {
- var buf *C.VipsImage
- defer C.g_object_unref(C.gpointer(image))
- if width > MaxSize || height > MaxSize {
- return nil, errors.New("Maximum image size exceeded")
- }
- err := C.vips_smartcrop_bridge(image, &buf, C.int(width), C.int(height))
- if err != 0 {
- return nil, catchVipsError()
- }
- return buf, nil
- }
- func vipsTrim(image *C.VipsImage) (int, int, int, int, error) {
- defer C.g_object_unref(C.gpointer(image))
- top := C.int(0)
- left := C.int(0)
- width := C.int(0)
- height := C.int(0)
- err := C.vips_find_trim_bridge(image, &top, &left, &width, &height)
- if err != 0 {
- return 0, 0, 0, 0, catchVipsError()
- }
- return int(top), int(left), int(width), int(height), nil
- }
- func vipsShrinkJpeg(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, error) {
- var image *C.VipsImage
- var ptr = unsafe.Pointer(&buf[0])
- defer C.g_object_unref(C.gpointer(input))
- err := C.vips_jpegload_buffer_shrink(ptr, C.size_t(len(buf)), &image, C.int(shrink))
- if err != 0 {
- return nil, catchVipsError()
- }
- return image, nil
- }
- func vipsShrink(input *C.VipsImage, shrink int) (*C.VipsImage, error) {
- var image *C.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 vipsReduce(input *C.VipsImage, xshrink float64, yshrink float64) (*C.VipsImage, error) {
- var image *C.VipsImage
- defer C.g_object_unref(C.gpointer(input))
- err := C.vips_reduce_bridge(input, &image, C.double(xshrink), C.double(yshrink))
- if err != 0 {
- return nil, catchVipsError()
- }
- return image, nil
- }
- func vipsEmbed(input *C.VipsImage, left, top, width, height int, extend Extend, background Color) (*C.VipsImage, error) {
- var image *C.VipsImage
- // Max extend value, see: http://www.vips.ecs.soton.ac.uk/supported/8.4/doc/html/libvips/libvips-conversion.html#VipsExtend
- if extend > 5 {
- extend = ExtendBackground
- }
- 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), C.double(background.R), C.double(background.G), C.double(background.B))
- if err != 0 {
- return nil, catchVipsError()
- }
- return image, nil
- }
- func vipsAffine(input *C.VipsImage, residualx, residualy float64, i Interpolator) (*C.VipsImage, error) {
- var image *C.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(input))
- defer C.g_object_unref(C.gpointer(interpolator))
- err := C.vips_affine_interpolator(input, &image, C.double(residualx), 0, 0, C.double(residualy), interpolator)
- if err != 0 {
- return nil, catchVipsError()
- }
- return image, nil
- }
- func vipsImageType(buf []byte) ImageType {
- if len(buf) < 12 {
- return UNKNOWN
- }
- if buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF {
- return JPEG
- }
- if IsTypeSupported(GIF) && buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 {
- return GIF
- }
- if buf[0] == 0x89 && buf[1] == 0x50 && buf[2] == 0x4E && buf[3] == 0x47 {
- return PNG
- }
- if IsTypeSupported(TIFF) &&
- ((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) ||
- (buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) {
- return TIFF
- }
- if IsTypeSupported(PDF) && buf[0] == 0x25 && buf[1] == 0x50 && buf[2] == 0x44 && buf[3] == 0x46 {
- return PDF
- }
- if IsTypeSupported(WEBP) && buf[8] == 0x57 && buf[9] == 0x45 && buf[10] == 0x42 && buf[11] == 0x50 {
- return WEBP
- }
- if IsTypeSupported(SVG) && IsSVGImage(buf) {
- return SVG
- }
- if IsTypeSupported(MAGICK) && strings.HasSuffix(readImageType(buf), "MagickBuffer") {
- return MAGICK
- }
- return UNKNOWN
- }
- func readImageType(buf []byte) string {
- length := C.size_t(len(buf))
- imageBuf := unsafe.Pointer(&buf[0])
- load := C.vips_foreign_find_load_buffer(imageBuf, length)
- return C.GoString(load)
- }
- 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
- }
- func vipsGaussianBlur(image *C.VipsImage, o GaussianBlur) (*C.VipsImage, error) {
- var out *C.VipsImage
- defer C.g_object_unref(C.gpointer(image))
- err := C.vips_gaussblur_bridge(image, &out, C.double(o.Sigma), C.double(o.MinAmpl))
- if err != 0 {
- return nil, catchVipsError()
- }
- return out, nil
- }
- func vipsSharpen(image *C.VipsImage, o Sharpen) (*C.VipsImage, error) {
- var out *C.VipsImage
- defer C.g_object_unref(C.gpointer(image))
- err := C.vips_sharpen_bridge(image, &out, C.int(o.Radius), C.double(o.X1), C.double(o.Y2), C.double(o.Y3), C.double(o.M1), C.double(o.M2))
- if err != 0 {
- return nil, catchVipsError()
- }
- return out, nil
- }
- func max(x int) int {
- return int(math.Max(float64(x), 0))
- }
- func vipsDrawWatermark(image *C.VipsImage, o WatermarkImage) (*C.VipsImage, error) {
- var out *C.VipsImage
- watermark, _, e := vipsRead(o.Buf)
- if e != nil {
- return nil, e
- }
- opts := vipsWatermarkImageOptions{C.int(o.Left), C.int(o.Top), C.float(o.Opacity)}
- err := C.vips_watermark_image(image, watermark, &out, (*C.WatermarkImageOptions)(unsafe.Pointer(&opts)))
- if err != 0 {
- return nil, catchVipsError()
- }
- return out, nil
- }
|