1061 lines
29 KiB
Go
1061 lines
29 KiB
Go
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
|
|
}
|