package vips // #cgo CFLAGS: -std=c99 // #include "operations.h" import "C" import ( "errors" "os" "runtime" "strings" "unsafe" ) // Arithmetic // https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-find-trim func vipsFindTrim(in *C.VipsImage, threshold float64, backgroundColor *Color) (int, int, int, int, error) { incOpCounter("findTrim") var left, top, width, height C.int if err := C.find_trim(in, &left, &top, &width, &height, C.double(threshold), C.double(backgroundColor.R), C.double(backgroundColor.G), C.double(backgroundColor.B)); err != 0 { return -1, -1, -1, -1, handleVipsError() } return int(left), int(top), int(width), int(height), nil } // https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-getpoint func vipsGetPoint(in *C.VipsImage, n int, x int, y int) ([]float64, error) { incOpCounter("getpoint") var out *C.double if err := C.getpoint(in, &out, C.int(n), C.int(x), C.int(y)); err != 0 { return nil, handleVipsError() } // Copy from C memory into a Go slice, then free the C allocation. result := make([]float64, n) copy(result, (*[4]float64)(unsafe.Pointer(out))[:n:n]) gFreePointer(unsafe.Pointer(out)) return result, nil } // https://www.libvips.org/API/current/libvips-arithmetic.html#vips-min func vipsMin(in *C.VipsImage) (float64, int, int, error) { incOpCounter("min") var out C.double var x, y C.int if err := C.minOp(in, &out, &x, &y, C.int(1)); err != 0 { return 0, 0, 0, handleVipsError() } return float64(out), int(x), int(y), nil } // Color // Color represents an RGB type Color struct { R, G, B uint8 } // ColorRGBA represents an RGB with alpha channel (A) type ColorRGBA struct { R, G, B, A uint8 } // Interpretation represents VIPS_INTERPRETATION type type Interpretation int // Interpretation enum const ( InterpretationError Interpretation = C.VIPS_INTERPRETATION_ERROR InterpretationMultiband Interpretation = C.VIPS_INTERPRETATION_MULTIBAND InterpretationBW Interpretation = C.VIPS_INTERPRETATION_B_W InterpretationHistogram Interpretation = C.VIPS_INTERPRETATION_HISTOGRAM InterpretationXYZ Interpretation = C.VIPS_INTERPRETATION_XYZ InterpretationLAB Interpretation = C.VIPS_INTERPRETATION_LAB InterpretationCMYK Interpretation = C.VIPS_INTERPRETATION_CMYK InterpretationLABQ Interpretation = C.VIPS_INTERPRETATION_LABQ InterpretationRGB Interpretation = C.VIPS_INTERPRETATION_RGB InterpretationRGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 InterpretationCMC Interpretation = C.VIPS_INTERPRETATION_CMC InterpretationLCH Interpretation = C.VIPS_INTERPRETATION_LCH InterpretationLABS Interpretation = C.VIPS_INTERPRETATION_LABS InterpretationSRGB Interpretation = C.VIPS_INTERPRETATION_sRGB InterpretationYXY Interpretation = C.VIPS_INTERPRETATION_YXY InterpretationFourier Interpretation = C.VIPS_INTERPRETATION_FOURIER InterpretationGrey16 Interpretation = C.VIPS_INTERPRETATION_GREY16 InterpretationMatrix Interpretation = C.VIPS_INTERPRETATION_MATRIX InterpretationScRGB Interpretation = C.VIPS_INTERPRETATION_scRGB InterpretationHSV Interpretation = C.VIPS_INTERPRETATION_HSV ) // Intent represents VIPS_INTENT type type Intent int // Intent enum const ( IntentPerceptual Intent = C.VIPS_INTENT_PERCEPTUAL IntentRelative Intent = C.VIPS_INTENT_RELATIVE IntentSaturation Intent = C.VIPS_INTENT_SATURATION IntentAbsolute Intent = C.VIPS_INTENT_ABSOLUTE IntentLast Intent = C.VIPS_INTENT_LAST ) func vipsIsColorSpaceSupported(in *C.VipsImage) bool { return C.is_colorspace_supported(in) == 1 } // https://libvips.github.io/libvips/API/current/libvips-colour.html#vips-colourspace func vipsToColorSpace(in *C.VipsImage, interpretation Interpretation) (*C.VipsImage, error) { incOpCounter("to_colorspace") var out *C.VipsImage inter := C.VipsInterpretation(interpretation) if err := C.to_colorspace(in, &out, inter); err != 0 { return nil, handleImageError(out) } return out, nil } func vipsICCTransform(in *C.VipsImage, outputProfile string, inputProfile string, intent Intent, depth int, embedded bool) (*C.VipsImage, error) { var out *C.VipsImage var cInputProfile *C.char var cEmbedded C.gboolean cOutputProfile := C.CString(outputProfile) defer freeCString(cOutputProfile) if inputProfile != "" { cInputProfile = C.CString(inputProfile) defer freeCString(cInputProfile) } if embedded { cEmbedded = C.TRUE } if res := C.icc_transform(in, &out, cOutputProfile, cInputProfile, C.VipsIntent(intent), C.int(depth), cEmbedded); res != 0 { return nil, handleImageError(out) } return out, nil } // Composite // ImageComposite image to composite param type ImageComposite struct { Image *ImageRef BlendMode BlendMode X, Y int } func toVipsCompositeStructs(r *ImageRef, datas []*ImageComposite) ([]*C.VipsImage, []C.int, []C.int, []C.int) { ins := []*C.VipsImage{r.image} modes := []C.int{} xs := []C.int{} ys := []C.int{} for _, image := range datas { ins = append(ins, image.Image.image) modes = append(modes, C.int(image.BlendMode)) xs = append(xs, C.int(image.X)) ys = append(ys, C.int(image.Y)) } return ins, modes, xs, ys } // Conversion // BandFormat represents VIPS_FORMAT type type BandFormat int // BandFormat enum const ( BandFormatNotSet BandFormat = C.VIPS_FORMAT_NOTSET BandFormatUchar BandFormat = C.VIPS_FORMAT_UCHAR BandFormatChar BandFormat = C.VIPS_FORMAT_CHAR BandFormatUshort BandFormat = C.VIPS_FORMAT_USHORT BandFormatShort BandFormat = C.VIPS_FORMAT_SHORT BandFormatUint BandFormat = C.VIPS_FORMAT_UINT BandFormatInt BandFormat = C.VIPS_FORMAT_INT BandFormatFloat BandFormat = C.VIPS_FORMAT_FLOAT BandFormatComplex BandFormat = C.VIPS_FORMAT_COMPLEX BandFormatDouble BandFormat = C.VIPS_FORMAT_DOUBLE BandFormatDpComplex BandFormat = C.VIPS_FORMAT_DPCOMPLEX ) // BlendMode gives the various Porter-Duff and PDF blend modes. // See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode type BlendMode int // Constants define the various Porter-Duff and PDF blend modes. // See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode const ( BlendModeClear BlendMode = C.VIPS_BLEND_MODE_CLEAR BlendModeSource BlendMode = C.VIPS_BLEND_MODE_SOURCE BlendModeOver BlendMode = C.VIPS_BLEND_MODE_OVER BlendModeIn BlendMode = C.VIPS_BLEND_MODE_IN BlendModeOut BlendMode = C.VIPS_BLEND_MODE_OUT BlendModeAtop BlendMode = C.VIPS_BLEND_MODE_ATOP BlendModeDest BlendMode = C.VIPS_BLEND_MODE_DEST BlendModeDestOver BlendMode = C.VIPS_BLEND_MODE_DEST_OVER BlendModeDestIn BlendMode = C.VIPS_BLEND_MODE_DEST_IN BlendModeDestOut BlendMode = C.VIPS_BLEND_MODE_DEST_OUT BlendModeDestAtop BlendMode = C.VIPS_BLEND_MODE_DEST_ATOP BlendModeXOR BlendMode = C.VIPS_BLEND_MODE_XOR BlendModeAdd BlendMode = C.VIPS_BLEND_MODE_ADD BlendModeSaturate BlendMode = C.VIPS_BLEND_MODE_SATURATE BlendModeMultiply BlendMode = C.VIPS_BLEND_MODE_MULTIPLY BlendModeScreen BlendMode = C.VIPS_BLEND_MODE_SCREEN BlendModeOverlay BlendMode = C.VIPS_BLEND_MODE_OVERLAY BlendModeDarken BlendMode = C.VIPS_BLEND_MODE_DARKEN BlendModeLighten BlendMode = C.VIPS_BLEND_MODE_LIGHTEN BlendModeColorDodge BlendMode = C.VIPS_BLEND_MODE_COLOUR_DODGE BlendModeColorBurn BlendMode = C.VIPS_BLEND_MODE_COLOUR_BURN BlendModeHardLight BlendMode = C.VIPS_BLEND_MODE_HARD_LIGHT BlendModeSoftLight BlendMode = C.VIPS_BLEND_MODE_SOFT_LIGHT BlendModeDifference BlendMode = C.VIPS_BLEND_MODE_DIFFERENCE BlendModeExclusion BlendMode = C.VIPS_BLEND_MODE_EXCLUSION ) // Gravity represents VIPS_GRAVITY type type Gravity int // Gravity enum const ( GravityCentre Gravity = C.VIPS_COMPASS_DIRECTION_CENTRE GravityNorth Gravity = C.VIPS_COMPASS_DIRECTION_NORTH GravityEast Gravity = C.VIPS_COMPASS_DIRECTION_EAST GravitySouth Gravity = C.VIPS_COMPASS_DIRECTION_SOUTH GravityWest Gravity = C.VIPS_COMPASS_DIRECTION_WEST GravityNorthEast Gravity = C.VIPS_COMPASS_DIRECTION_NORTH_EAST GravityNorthWest Gravity = C.VIPS_COMPASS_DIRECTION_NORTH_WEST GravitySouthEast Gravity = C.VIPS_COMPASS_DIRECTION_SOUTH_EAST GravitySouthWest Gravity = C.VIPS_COMPASS_DIRECTION_SOUTH_WEST ) // Direction represents VIPS_DIRECTION type type Direction int // Direction enum const ( DirectionHorizontal Direction = C.VIPS_DIRECTION_HORIZONTAL DirectionVertical Direction = C.VIPS_DIRECTION_VERTICAL ) // Angle represents VIPS_ANGLE type type Angle int // Angle enum const ( Angle0 Angle = C.VIPS_ANGLE_D0 Angle90 Angle = C.VIPS_ANGLE_D90 Angle180 Angle = C.VIPS_ANGLE_D180 Angle270 Angle = C.VIPS_ANGLE_D270 ) // Angle45 represents VIPS_ANGLE45 type type Angle45 int // Angle45 enum const ( Angle45_0 Angle45 = C.VIPS_ANGLE45_D0 Angle45_45 Angle45 = C.VIPS_ANGLE45_D45 Angle45_90 Angle45 = C.VIPS_ANGLE45_D90 Angle45_135 Angle45 = C.VIPS_ANGLE45_D135 Angle45_180 Angle45 = C.VIPS_ANGLE45_D180 Angle45_225 Angle45 = C.VIPS_ANGLE45_D225 Angle45_270 Angle45 = C.VIPS_ANGLE45_D270 Angle45_315 Angle45 = C.VIPS_ANGLE45_D315 ) // ExtendStrategy represents VIPS_EXTEND type type ExtendStrategy int // ExtendStrategy enum const ( ExtendBlack ExtendStrategy = C.VIPS_EXTEND_BLACK ExtendCopy ExtendStrategy = C.VIPS_EXTEND_COPY ExtendRepeat ExtendStrategy = C.VIPS_EXTEND_REPEAT ExtendMirror ExtendStrategy = C.VIPS_EXTEND_MIRROR ExtendWhite ExtendStrategy = C.VIPS_EXTEND_WHITE ExtendBackground ExtendStrategy = C.VIPS_EXTEND_BACKGROUND ) // Interesting represents VIPS_INTERESTING type // https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsInteresting type Interesting int // Interesting constants represent areas of interest which smart cropping will crop based on. const ( InterestingNone Interesting = C.VIPS_INTERESTING_NONE InterestingCentre Interesting = C.VIPS_INTERESTING_CENTRE InterestingEntropy Interesting = C.VIPS_INTERESTING_ENTROPY InterestingAttention Interesting = C.VIPS_INTERESTING_ATTENTION InterestingLow Interesting = C.VIPS_INTERESTING_LOW InterestingHigh Interesting = C.VIPS_INTERESTING_HIGH InterestingAll Interesting = C.VIPS_INTERESTING_ALL InterestingLast Interesting = C.VIPS_INTERESTING_LAST ) // https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-embed func vipsEmbed(in *C.VipsImage, left, top, width, height int, extend ExtendStrategy) (*C.VipsImage, error) { incOpCounter("embed") var out *C.VipsImage if err := C.embed_image(in, &out, C.int(left), C.int(top), C.int(width), C.int(height), C.int(extend)); err != 0 { return nil, handleImageError(out) } return out, nil } // https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-embed func vipsEmbedBackground(in *C.VipsImage, left, top, width, height int, backgroundColor *ColorRGBA) (*C.VipsImage, error) { incOpCounter("embed") var out *C.VipsImage if err := C.embed_image_background(in, &out, C.int(left), C.int(top), C.int(width), C.int(height), C.double(backgroundColor.R), C.double(backgroundColor.G), C.double(backgroundColor.B), C.double(backgroundColor.A)); err != 0 { return nil, handleImageError(out) } return out, nil } func vipsEmbedMultiPage(in *C.VipsImage, left, top, width, height int, extend ExtendStrategy) (*C.VipsImage, error) { incOpCounter("embedMultiPage") var out *C.VipsImage if err := C.embed_multi_page_image(in, &out, C.int(left), C.int(top), C.int(width), C.int(height), C.int(extend)); err != 0 { return nil, handleImageError(out) } return out, nil } func vipsEmbedMultiPageBackground(in *C.VipsImage, left, top, width, height int, backgroundColor *ColorRGBA) (*C.VipsImage, error) { incOpCounter("embedMultiPageBackground") var out *C.VipsImage if err := C.embed_multi_page_image_background(in, &out, C.int(left), C.int(top), C.int(width), C.int(height), C.double(backgroundColor.R), C.double(backgroundColor.G), C.double(backgroundColor.B), C.double(backgroundColor.A)); err != 0 { return nil, handleImageError(out) } return out, nil } // https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-flip func vipsFlip(in *C.VipsImage, direction Direction) (*C.VipsImage, error) { return vipsGenFlip(in, direction) } // https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-extract-area func vipsExtractArea(in *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) { return vipsGenExtractArea(in, left, top, width, height) } func vipsExtractAreaMultiPage(in *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) { incOpCounter("extractAreaMultiPage") pageHeight := vipsGetPageHeight(in) nPages := int(in.Ysize) / pageHeight pages := make([]*C.VipsImage, nPages) for i := 0; i < nPages; i++ { page, err := vipsGenExtractArea(in, left, pageHeight*i+top, width, height) if err != nil { for j := 0; j < i; j++ { clearImage(pages[j]) } return nil, err } pages[i] = page } across := 1 joined, err := vipsGenArrayjoin(pages, &ArrayjoinOptions{Across: &across}) for _, p := range pages { clearImage(p) } if err != nil { return nil, err } out, err := vipsGenCopy(joined, nil) clearImage(joined) if err != nil { return nil, err } vipsSetPageHeight(out, height) return out, nil } // http://libvips.github.io/libvips/API/current/libvips-resample.html#vips-similarity func vipsSimilarity(in *C.VipsImage, scale float64, angle float64, color *ColorRGBA, idx float64, idy float64, odx float64, ody float64) (*C.VipsImage, error) { incOpCounter("similarity") var out *C.VipsImage if err := C.similarity(in, &out, C.double(scale), C.double(angle), C.double(color.R), C.double(color.G), C.double(color.B), C.double(color.A), C.double(idx), C.double(idy), C.double(odx), C.double(ody)); err != 0 { return nil, handleImageError(out) } return out, nil } // http://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-smartcrop func vipsSmartCrop(in *C.VipsImage, width int, height int, interesting Interesting) (*C.VipsImage, error) { _, out, _, err := vipsGenSmartcrop(in, width, height, &SmartcropOptions{Interesting: &interesting}) return out, err } // http://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-crop func vipsCrop(in *C.VipsImage, left int, top int, width int, height int) (*C.VipsImage, error) { incOpCounter("crop") var out *C.VipsImage if err := C.crop(in, &out, C.int(left), C.int(top), C.int(width), C.int(height)); err != 0 { return nil, handleImageError(out) } return out, nil } // https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-composite func vipsComposite(ins []*C.VipsImage, modes []C.int, xs, ys []C.int) (*C.VipsImage, error) { if len(ins) == 0 || len(modes) == 0 || len(xs) == 0 || len(ys) == 0 { return nil, errors.New("vipsComposite: empty input slice") } incOpCounter("composite_multi") var out *C.VipsImage if err := C.composite_image(&ins[0], &out, C.int(len(ins)), &modes[0], &xs[0], &ys[0]); err != 0 { return nil, handleImageError(out) } return out, nil } // https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-join func vipsJoin(input1 *C.VipsImage, input2 *C.VipsImage, dir Direction) (*C.VipsImage, error) { incOpCounter("join") var out *C.VipsImage defer C.g_object_unref(C.gpointer(input1)) defer C.g_object_unref(C.gpointer(input2)) if err := C.join(input1, input2, &out, C.int(dir)); err != 0 { return nil, handleVipsError() } return out, nil } func vipsAddAlpha(in *C.VipsImage) (*C.VipsImage, error) { incOpCounter("addalpha") var out *C.VipsImage if err := C.add_alpha(in, &out); err != 0 { return nil, handleImageError(out) } return out, nil } // Create type TextWrap int type TextParams struct { Text string Font string Width int Height int Alignment Align DPI int RGBA bool Justify bool Spacing int Wrap TextWrap } type vipsTextOptions struct { Text *C.char Font *C.char Width C.int Height C.int DPI C.int RGBA C.gboolean Justify C.gboolean Spacing C.int Alignment C.VipsAlign Wrap C.VipsTextWrap } // TextWrap enum const ( TextWrapWord TextWrap = C.VIPS_TEXT_WRAP_WORD TextWrapChar TextWrap = C.VIPS_TEXT_WRAP_CHAR TextWrapWordChar TextWrap = C.VIPS_TEXT_WRAP_WORD_CHAR TextWrapNone TextWrap = C.VIPS_TEXT_WRAP_NONE ) // https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text func vipsText(params *TextParams) (*C.VipsImage, error) { var out *C.VipsImage text := C.CString(params.Text) defer freeCString(text) font := C.CString(params.Font) defer freeCString(font) opts := vipsTextOptions{ Text: text, Font: font, Width: C.int(params.Width), Height: C.int(params.Height), DPI: C.int(params.DPI), Alignment: C.VipsAlign(params.Alignment), Spacing: C.int(params.Spacing), Wrap: C.VipsTextWrap(params.Wrap), } if params.RGBA { opts.RGBA = C.TRUE } if params.Justify { opts.Justify = C.TRUE } err := C.text(&out, (*C.TextOptions)(unsafe.Pointer(&opts))) if err != 0 { return nil, handleImageError(out) } return out, nil } // Draw // https://libvips.github.io/libvips/API/current/libvips-draw.html#vips-draw-rect func vipsDrawRect(in *C.VipsImage, color ColorRGBA, left int, top int, width int, height int, fill bool) error { incOpCounter("draw_rect") fillBit := 0 if fill { fillBit = 1 } if err := C.draw_rect(in, C.double(color.R), C.double(color.G), C.double(color.B), C.double(color.A), C.int(left), C.int(top), C.int(width), C.int(height), C.int(fillBit)); err != 0 { return handleImageError(in) } return nil } // Header func vipsHasICCProfile(in *C.VipsImage) bool { return int(C.has_icc_profile(in)) != 0 } func vipsGetICCProfile(in *C.VipsImage) ([]byte, bool) { var bufPtr unsafe.Pointer var dataLength C.size_t if int(C.get_icc_profile(in, &bufPtr, &dataLength)) != 0 { return nil, false } buf := C.GoBytes(bufPtr, C.int(dataLength)) return buf, true } func vipsRemoveICCProfile(in *C.VipsImage) bool { return fromGboolean(C.remove_icc_profile(in)) } func vipsHasIPTC(in *C.VipsImage) bool { return int(C.has_iptc(in)) != 0 } func vipsImageGetFields(in *C.VipsImage) (fields []string) { const maxFields = 256 rawFields := C.image_get_fields(in) defer C.g_strfreev(rawFields) cFields := (*[maxFields]*C.char)(unsafe.Pointer(rawFields))[:maxFields:maxFields] for _, field := range cFields { if field == nil { break } fields = append(fields, C.GoString(field)) } return } func vipsImageGetExifData(in *C.VipsImage) map[string]string { fields := vipsImageGetFields(in) exifData := map[string]string{} for _, field := range fields { if strings.HasPrefix(field, "exif") { exifData[field] = vipsImageGetString(in, field) } } return exifData } func vipsRemoveMetadata(in *C.VipsImage, keep ...string) { fields := vipsImageGetFields(in) retain := append(keep, technicalMetadata...) for _, field := range fields { if contains(retain, field) { continue } cField := C.CString(field) C.remove_field(in, cField) C.free(unsafe.Pointer(cField)) } } var technicalMetadata = []string{ C.VIPS_META_ICC_NAME, C.VIPS_META_ORIENTATION, C.VIPS_META_N_PAGES, C.VIPS_META_PAGE_HEIGHT, "delay", "loop", } func contains(a []string, x string) bool { for _, n := range a { if x == n { return true } } return false } func vipsGetMetaOrientation(in *C.VipsImage) int { return int(C.get_meta_orientation(in)) } func vipsRemoveMetaOrientation(in *C.VipsImage) { C.remove_meta_orientation(in) } func vipsSetMetaOrientation(in *C.VipsImage, orientation int) { C.set_meta_orientation(in, C.int(orientation)) } func vipsGetImageNPages(in *C.VipsImage) int { return int(C.get_image_n_pages(in)) } func vipsSetImageNPages(in *C.VipsImage, pages int) { C.set_image_n_pages(in, C.int(pages)) } func vipsGetPageHeight(in *C.VipsImage) int { return int(C.get_page_height(in)) } func vipsSetPageHeight(in *C.VipsImage, height int) { C.set_page_height(in, C.int(height)) } func vipsImageGetMetaLoader(in *C.VipsImage) (string, bool) { var out *C.char code := int(C.get_meta_loader(in, &out)) return C.GoString(out), code == 0 } func vipsImageGetDelay(in *C.VipsImage, n int) ([]int, error) { incOpCounter("imageGetDelay") var out *C.int if err := C.get_image_delay(in, &out); err != 0 { return nil, handleVipsError() } return fromCArrayInt(out, n), nil } func vipsImageSetDelay(in *C.VipsImage, data []C.int) error { incOpCounter("imageSetDelay") if n := len(data); n > 0 { C.set_image_delay(in, &data[0], C.int(n)) } return nil } func vipsImageGetLoop(in *C.VipsImage) int { return int(C.get_image_loop(in)) } func vipsImageSetLoop(in *C.VipsImage, loop int) { C.set_image_loop(in, C.int(loop)) } func vipsImageGetBackground(in *C.VipsImage) ([]float64, error) { incOpCounter("imageGetBackground") var out *C.double var n C.int if err := C.get_background(in, &out, &n); err != 0 { return nil, handleVipsError() } return fromCArrayDouble(out, int(n)), nil } // vipsDetermineImageTypeFromMetaLoader determine the image type from vips-loader metadata func vipsDetermineImageTypeFromMetaLoader(in *C.VipsImage) ImageType { vipsLoader, ok := vipsImageGetMetaLoader(in) if vipsLoader == "" || !ok { return ImageTypeUnknown } if strings.HasPrefix(vipsLoader, "jpeg") { return ImageTypeJPEG } if strings.HasPrefix(vipsLoader, "png") { return ImageTypePNG } if strings.HasPrefix(vipsLoader, "gif") { return ImageTypeGIF } if strings.HasPrefix(vipsLoader, "svg") { return ImageTypeSVG } if strings.HasPrefix(vipsLoader, "webp") { return ImageTypeWEBP } if strings.HasPrefix(vipsLoader, "jp2k") { return ImageTypeJP2K } if strings.HasPrefix(vipsLoader, "jxl") { return ImageTypeJXL } if strings.HasPrefix(vipsLoader, "magick") { return ImageTypeMagick } if strings.HasPrefix(vipsLoader, "tiff") { return ImageTypeTIFF } if strings.HasPrefix(vipsLoader, "heif") { return ImageTypeHEIF } if strings.HasPrefix(vipsLoader, "pdf") { return ImageTypePDF } return ImageTypeUnknown } func vipsImageSetBlob(in *C.VipsImage, name string, data []byte) { cData := unsafe.Pointer(&data) cDataLength := C.size_t(len(data)) cField := C.CString(name) defer freeCString(cField) C.image_set_blob(in, cField, cData, cDataLength) } func vipsImageGetBlob(in *C.VipsImage, name string) []byte { var bufPtr unsafe.Pointer var dataLength C.size_t cField := C.CString(name) defer freeCString(cField) if int(C.image_get_blob(in, cField, &bufPtr, &dataLength)) != 0 { return nil } buf := C.GoBytes(bufPtr, C.int(dataLength)) return buf } func vipsImageSetDouble(in *C.VipsImage, name string, f float64) { cField := C.CString(name) defer freeCString(cField) cDouble := C.double(f) C.image_set_double(in, cField, cDouble) } func vipsImageGetDouble(in *C.VipsImage, name string) float64 { cField := C.CString(name) defer freeCString(cField) var cDouble C.double if int(C.image_get_double(in, cField, &cDouble)) == 0 { return float64(cDouble) } return 0 } func vipsImageSetInt(in *C.VipsImage, name string, i int) { cField := C.CString(name) defer freeCString(cField) cInt := C.int(i) C.image_set_int(in, cField, cInt) } func vipsImageGetInt(in *C.VipsImage, name string) int { cField := C.CString(name) defer freeCString(cField) var cInt C.int if int(C.image_get_int(in, cField, &cInt)) == 0 { return int(cInt) } return 0 } func vipsImageSetString(in *C.VipsImage, name string, str string) { cField := C.CString(name) defer freeCString(cField) cStr := C.CString(str) defer freeCString(cStr) C.image_set_string(in, cField, cStr) } func vipsImageGetString(in *C.VipsImage, name string) string { cField := C.CString(name) defer freeCString(cField) var cFieldValue *C.char if int(C.image_get_string(in, cField, &cFieldValue)) == 0 { return C.GoString(cFieldValue) } return "" } func vipsImageGetAsString(in *C.VipsImage, name string) string { cField := C.CString(name) defer freeCString(cField) var cFieldValue *C.char defer func() { freeCString(cFieldValue) }() if int(C.image_get_as_string(in, cField, &cFieldValue)) == 0 { return C.GoString(cFieldValue) } return "" } // Label // Align represents VIPS_ALIGN type Align int // Direction enum const ( AlignLow Align = C.VIPS_ALIGN_LOW AlignCenter Align = C.VIPS_ALIGN_CENTRE AlignHigh Align = C.VIPS_ALIGN_HIGH ) // DefaultFont is the default font to be used for label texts created by govips const DefaultFont = "sans 10" // LabelParams represents a text-based label type LabelParams struct { Text string Font string Width Scalar Height Scalar OffsetX Scalar OffsetY Scalar Opacity float32 Color Color Alignment Align } type vipsLabelOptions struct { Text *C.char Font *C.char Width C.int Height C.int OffsetX C.int OffsetY C.int Alignment C.VipsAlign DPI C.int Margin C.int Opacity C.float Color [3]C.double } func labelImage(in *C.VipsImage, params *LabelParams) (*C.VipsImage, error) { incOpCounter("label") var out *C.VipsImage text := C.CString(params.Text) defer freeCString(text) font := C.CString(params.Font) defer freeCString(font) // todo: release color? color := [3]C.double{C.double(params.Color.R), C.double(params.Color.G), C.double(params.Color.B)} w := params.Width.GetRounded(int(in.Xsize)) h := params.Height.GetRounded(int(in.Ysize)) offsetX := params.OffsetX.GetRounded(int(in.Xsize)) offsetY := params.OffsetY.GetRounded(int(in.Ysize)) opts := vipsLabelOptions{ Text: text, Font: font, Width: C.int(w), Height: C.int(h), OffsetX: C.int(offsetX), OffsetY: C.int(offsetY), Alignment: C.VipsAlign(params.Alignment), Opacity: C.float(params.Opacity), Color: color, } // todo: release inline pointer? err := C.label(in, &out, (*C.LabelOptions)(unsafe.Pointer(&opts))) if err != 0 { return nil, handleImageError(out) } return out, nil } // Resample // Kernel represents VipsKernel type type Kernel int // Kernel enum const ( KernelAuto Kernel = -1 KernelNearest Kernel = C.VIPS_KERNEL_NEAREST KernelLinear Kernel = C.VIPS_KERNEL_LINEAR KernelCubic Kernel = C.VIPS_KERNEL_CUBIC KernelLanczos2 Kernel = C.VIPS_KERNEL_LANCZOS2 KernelLanczos3 Kernel = C.VIPS_KERNEL_LANCZOS3 KernelMitchell Kernel = C.VIPS_KERNEL_MITCHELL ) // Size represents VipsSize type type Size int const ( SizeBoth Size = C.VIPS_SIZE_BOTH SizeUp Size = C.VIPS_SIZE_UP SizeDown Size = C.VIPS_SIZE_DOWN SizeForce Size = C.VIPS_SIZE_FORCE SizeLast Size = C.VIPS_SIZE_LAST ) // https://libvips.github.io/libvips/API/current/libvips-resample.html#vips-resize func vipsResizeWithVScale(in *C.VipsImage, hscale, vscale float64, kernel Kernel) (*C.VipsImage, error) { incOpCounter("resize") var out *C.VipsImage // libvips recommends Lanczos3 as the default kernel if kernel == KernelAuto { kernel = KernelLanczos3 } if err := C.resize_image(in, &out, C.double(hscale), C.double(vscale), C.int(kernel)); err != 0 { return nil, handleImageError(out) } return out, nil } func vipsThumbnail(in *C.VipsImage, width, height int, crop Interesting, size Size) (*C.VipsImage, error) { incOpCounter("thumbnail") var out *C.VipsImage if err := C.thumbnail_image(in, &out, C.int(width), C.int(height), C.int(crop), C.int(size)); err != 0 { return nil, handleImageError(out) } return out, nil } // https://www.libvips.org/API/current/libvips-resample.html#vips-thumbnail func vipsThumbnailFromFile(filename string, width, height int, crop Interesting, size Size, params *ImportParams) (*C.VipsImage, ImageType, error) { var out *C.VipsImage filenameOption := filename if params != nil { filenameOption += "[" + params.OptionString() + "]" } cFileName := C.CString(filenameOption) defer freeCString(cFileName) if err := C.thumbnail(cFileName, &out, C.int(width), C.int(height), C.int(crop), C.int(size)); err != 0 { err := handleImageError(out) if src, err2 := os.ReadFile(filename); err2 == nil { return vipsThumbnailFromBuffer(src, width, height, crop, size, params) } return nil, ImageTypeUnknown, err } imageType := vipsDetermineImageTypeFromMetaLoader(out) return out, imageType, nil } // https://www.libvips.org/API/current/libvips-resample.html#vips-thumbnail-buffer func vipsThumbnailFromBuffer(buf []byte, width, height int, crop Interesting, size Size, params *ImportParams) (*C.VipsImage, ImageType, error) { src := buf // Reference src here so it's not garbage collected during image initialization. defer runtime.KeepAlive(src) var out *C.VipsImage var err C.int if params == nil { err = C.thumbnail_buffer(unsafe.Pointer(&src[0]), C.size_t(len(src)), &out, C.int(width), C.int(height), C.int(crop), C.int(size)) } else { cOptionString := C.CString(params.OptionString()) defer freeCString(cOptionString) err = C.thumbnail_buffer_with_option(unsafe.Pointer(&src[0]), C.size_t(len(src)), &out, C.int(width), C.int(height), C.int(crop), C.int(size), cOptionString) } if err != 0 { err := handleImageError(out) return nil, ImageTypeUnknown, err } imageType := vipsDetermineImageTypeFromMetaLoader(out) return out, imageType, nil }