mirror of
https://github.com/talgo-cloud/bimg.git
synced 2026-03-07 21:48:13 -08:00
Set an bimg.Options OutputICC to an absolute path to the desired output ICC profile. If an embedded ICC profile is found in VipsImage, it is converted to the output ICC profile. Fixes #50
661 lines
16 KiB
Go
661 lines
16 KiB
Go
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
|
|
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)
|
|
|
|
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, 1, quality)
|
|
case PNG:
|
|
saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, 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, 1, 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 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 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) == 0 {
|
|
return UNKNOWN
|
|
}
|
|
if buf[0] == 0x89 && buf[1] == 0x50 && buf[2] == 0x4E && buf[3] == 0x47 {
|
|
return PNG
|
|
}
|
|
if buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF {
|
|
return JPEG
|
|
}
|
|
if IsTypeSupported(WEBP) && buf[8] == 0x57 && buf[9] == 0x45 && buf[10] == 0x42 && buf[11] == 0x50 {
|
|
return WEBP
|
|
}
|
|
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(GIF) && buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 {
|
|
return GIF
|
|
}
|
|
if IsTypeSupported(PDF) && buf[0] == 0x25 && buf[1] == 0x50 && buf[2] == 0x44 && buf[3] == 0x46 {
|
|
return PDF
|
|
}
|
|
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
|
|
}
|