From 8f261497433b2ed47d9ea46dd893d741b05886bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:46:05 +0000 Subject: [PATCH] build(deps): bump github.com/davidbyttow/govips/v2 from 2.17.0 to 2.18.0 Bumps [github.com/davidbyttow/govips/v2](https://github.com/davidbyttow/govips) from 2.17.0 to 2.18.0. - [Release notes](https://github.com/davidbyttow/govips/releases) - [Commits](https://github.com/davidbyttow/govips/compare/v2.17.0...v2.18.0) --- updated-dependencies: - dependency-name: github.com/davidbyttow/govips/v2 dependency-version: 2.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- .../davidbyttow/govips/v2/vips/foreign.c | 2 + .../davidbyttow/govips/v2/vips/foreign.go | 16 +- .../davidbyttow/govips/v2/vips/foreign.h | 1 + .../davidbyttow/govips/v2/vips/govips.go | 29 +- .../davidbyttow/govips/v2/vips/image.go | 1839 +---------------- .../davidbyttow/govips/v2/vips/image_color.go | 173 ++ .../govips/v2/vips/image_composite.go | 70 + .../govips/v2/vips/image_export.go | 392 ++++ .../davidbyttow/govips/v2/vips/image_icc.go | 108 + .../govips/v2/vips/image_metadata.go | 338 +++ .../davidbyttow/govips/v2/vips/image_pixel.go | 390 ++++ .../govips/v2/vips/image_transform.go | 402 ++++ .../davidbyttow/govips/v2/vips/math.go | 2 +- .../davidbyttow/govips/v2/vips/operations.c | 8 +- .../davidbyttow/govips/v2/vips/operations.go | 14 +- vendor/modules.txt | 4 +- 18 files changed, 1988 insertions(+), 1806 deletions(-) create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image_color.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image_composite.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image_export.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image_icc.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image_metadata.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image_pixel.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image_transform.go diff --git a/go.mod b/go.mod index a05593aa71..84cee8c360 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/coreos/go-oidc/v3 v3.18.0 github.com/cs3org/go-cs3apis v0.0.0-20260407125717-5d69ba49048b - github.com/davidbyttow/govips/v2 v2.17.0 + github.com/davidbyttow/govips/v2 v2.18.0 github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e github.com/gabriel-vasile/mimetype v1.4.13 diff --git a/go.sum b/go.sum index e9b7ca6f99..779a29b4ed 100644 --- a/go.sum +++ b/go.sum @@ -273,8 +273,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidbyttow/govips/v2 v2.17.0 h1:TFAjcaEqYpumRoIz2m1Ptr9Fhdg3H0R3FKixKS+4Jo8= -github.com/davidbyttow/govips/v2 v2.17.0/go.mod h1:HF7B5oTeNolUjdBAYVIZQ8S5nhQd1CfDf0rMlYSMJv8= +github.com/davidbyttow/govips/v2 v2.18.0 h1:pZRshWVYvewP/TZx3yZ7YeC42WyLXg53tHy5Qt8nT9E= +github.com/davidbyttow/govips/v2 v2.18.0/go.mod h1:8+nst5zfMoats12PgmmAPh6p5OfjDaXK0BXMFl/vOcM= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/foreign.c b/vendor/github.com/davidbyttow/govips/v2/vips/foreign.c index 0d60b664c3..9f5939e17e 100644 --- a/vendor/github.com/davidbyttow/govips/v2/vips/foreign.c +++ b/vendor/github.com/davidbyttow/govips/v2/vips/foreign.c @@ -114,6 +114,7 @@ int set_webpload_options(VipsOperation *operation, LoadParams *params) { MAYBE_SET_INT(operation, params->page, "page"); MAYBE_SET_INT(operation, params->n, "n"); MAYBE_SET_INT(operation, params->access, "access"); + MAYBE_SET_DOUBLE(operation, params->webpScale, "scale"); return 0; } @@ -527,6 +528,7 @@ LoadParams create_load_params(ImageType inputFormat) { .n = defaultParam, .dpi = defaultParam, .jpegShrink = defaultParam, + .webpScale = defaultParam, .heifThumbnail = defaultParam, .svgUnlimited = defaultParam, .access = defaultParam, diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/foreign.go b/vendor/github.com/davidbyttow/govips/v2/vips/foreign.go index 08762af2f5..36ad04d815 100644 --- a/vendor/github.com/davidbyttow/govips/v2/vips/foreign.go +++ b/vendor/github.com/davidbyttow/govips/v2/vips/foreign.go @@ -146,7 +146,10 @@ func (i ImageType) FileExt() string { // IsTypeSupported checks whether given image type is supported by govips func IsTypeSupported(imageType ImageType) bool { - startupIfNeeded() + if err := startupIfNeeded(); err != nil { + govipsLog("govips", LogLevelError, fmt.Sprintf("failed to start vips: %v", err)) + return false + } // BMP is supported via the magick loader if imageType == ImageTypeBMP { @@ -233,6 +236,7 @@ var heis = []byte("heis") var mif1 = []byte("mif1") var msf1 = []byte("msf1") var avif = []byte("avif") +var avis = []byte("avis") func isHEIF(buf []byte) bool { return bytes.Equal(buf[4:8], ftyp) && (bytes.Equal(buf[8:12], heic) || @@ -245,7 +249,8 @@ func isHEIF(buf []byte) bool { } func isAVIF(buf []byte) bool { - return bytes.Equal(buf[4:8], ftyp) && bytes.Equal(buf[8:12], avif) + return bytes.Equal(buf[4:8], ftyp) && + (bytes.Equal(buf[8:12], avif) || bytes.Equal(buf[8:12], avis)) } var svg = []byte(" r.PageHeight() { - // use animated extract area if more than 1 pages loaded - out, err := vipsExtractAreaMultiPage(r.image, left, top, width, height) - if err != nil { - return err - } - r.setImage(out) - } else { - out, err := vipsExtractArea(r.image, left, top, width, height) - if err != nil { - return err - } - r.setImage(out) - } - return nil -} - -// GetICCProfile retrieves the ICC profile data (if any) from the image. -func (r *ImageRef) GetICCProfile() []byte { - defer runtime.KeepAlive(r) - bytes, _ := vipsGetICCProfile(r.image) - return bytes -} - -// RemoveICCProfile removes the ICC Profile information from the image. -// Typically, browsers and other software assume images without profile to be in the sRGB color space. -func (r *ImageRef) RemoveICCProfile() error { - out, err := vipsGenCopy(r.image, nil) - if err != nil { - return err - } - - vipsRemoveICCProfile(out) - - r.setImage(out) - return nil -} - -// TransformICCProfileWithFallback transforms from the embedded ICC profile of the image to the ICC profile at the given path. -// The fallback ICC profile is used if the image does not have an embedded ICC profile. -func (r *ImageRef) TransformICCProfileWithFallback(targetProfilePath, fallbackProfilePath string) error { - if err := ensureLoadICCPath(&targetProfilePath); err != nil { - return err - } - if err := ensureLoadICCPath(&fallbackProfilePath); err != nil { - return err - } - - depth := 16 - if r.BandFormat() == BandFormatUchar || r.BandFormat() == BandFormatChar || r.BandFormat() == BandFormatNotSet { - depth = 8 - } - - out, err := vipsICCTransform(r.image, targetProfilePath, fallbackProfilePath, IntentPerceptual, depth, true) - if err != nil { - govipsLog("govips", LogLevelError, fmt.Sprintf("failed to do icc transform: %v", err.Error())) - return err - } - - r.setImage(out) - return nil -} - -// TransformICCProfile transforms from the embedded ICC profile of the image to the icc profile at the given path. -func (r *ImageRef) TransformICCProfile(outputProfilePath string) error { - return r.TransformICCProfileWithFallback(outputProfilePath, SRGBIEC6196621ICCProfilePath) -} - -// OptimizeICCProfile optimizes the ICC color profile of the image. -// For two color channel images, it sets a grayscale profile. -// For color images, it sets a CMYK or non-CMYK profile based on the image metadata. -func (r *ImageRef) OptimizeICCProfile() error { - inputProfile := r.determineInputICCProfile() - if !r.HasICCProfile() && (inputProfile == "") { - // No embedded ICC profile in the input image and no input profile determined, nothing to do. - return nil - } - - r.optimizedIccProfile = SRGBV2MicroICCProfilePath - if r.Bands() <= 2 { - r.optimizedIccProfile = SGrayV2MicroICCProfilePath - } - - if err := ensureLoadICCPath(&r.optimizedIccProfile); err != nil { - return err - } - - embedded := r.HasICCProfile() && (inputProfile == "") - - depth := 16 - if r.BandFormat() == BandFormatUchar || r.BandFormat() == BandFormatChar || r.BandFormat() == BandFormatNotSet { - depth = 8 - } - - out, err := vipsICCTransform(r.image, r.optimizedIccProfile, inputProfile, IntentPerceptual, depth, embedded) - if err != nil { - govipsLog("govips", LogLevelError, fmt.Sprintf("failed to do icc transform: %v", err.Error())) - return err - } - - r.setImage(out) - return nil -} - -// RemoveMetadata removes the EXIF metadata from the image. -// N.B. this function won't remove the ICC profile, orientation and pages metadata -// because govips needs it to correctly display the image. -func (r *ImageRef) RemoveMetadata(keep ...string) error { - out, err := vipsGenCopy(r.image, nil) - if err != nil { - return err - } - - vipsRemoveMetadata(out, keep...) - - r.setImage(out) - - return nil -} - -func (r *ImageRef) ImageFields() []string { - return r.GetFields() -} - -func (r *ImageRef) GetFields() []string { - defer runtime.KeepAlive(r) - return vipsImageGetFields(r.image) -} - -func (r *ImageRef) SetBlob(name string, data []byte) { - defer runtime.KeepAlive(r) - vipsImageSetBlob(r.image, name, data) -} - -func (r *ImageRef) GetBlob(name string) []byte { - defer runtime.KeepAlive(r) - return vipsImageGetBlob(r.image, name) -} - -func (r *ImageRef) SetDouble(name string, f float64) { - defer runtime.KeepAlive(r) - vipsImageSetDouble(r.image, name, f) -} - -func (r *ImageRef) GetDouble(name string) float64 { - defer runtime.KeepAlive(r) - return vipsImageGetDouble(r.image, name) -} - -func (r *ImageRef) SetInt(name string, i int) { - defer runtime.KeepAlive(r) - vipsImageSetInt(r.image, name, i) -} - -func (r *ImageRef) GetInt(name string) int { - defer runtime.KeepAlive(r) - return vipsImageGetInt(r.image, name) -} - -func (r *ImageRef) SetString(name string, str string) { - defer runtime.KeepAlive(r) - vipsImageSetString(r.image, name, str) -} - -func (r *ImageRef) GetString(name string) string { - defer runtime.KeepAlive(r) - return vipsImageGetString(r.image, name) -} - -func (r *ImageRef) GetAsString(name string) string { - defer runtime.KeepAlive(r) - return vipsImageGetAsString(r.image, name) -} - -func (r *ImageRef) HasExif() bool { - for _, field := range r.ImageFields() { - if strings.HasPrefix(field, "exif-") { - return true - } - } - - return false -} - -func (r *ImageRef) GetExif() map[string]string { - defer runtime.KeepAlive(r) - return vipsImageGetExifData(r.image) -} - -// ToColorSpace changes the color space of the image to the interpretation supplied as the parameter. -func (r *ImageRef) ToColorSpace(interpretation Interpretation) error { - out, err := vipsToColorSpace(r.image, interpretation) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Flatten removes the alpha channel from the image and replaces it with the background color -func (r *ImageRef) Flatten(backgroundColor *Color) error { - opts := &FlattenOptions{} - if backgroundColor != nil { - opts.Background = []float64{float64(backgroundColor.R), float64(backgroundColor.G), float64(backgroundColor.B)} - } - out, err := vipsGenFlatten(r.image, opts) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// GaussianBlur blurs the image -// add support minAmpl -func (r *ImageRef) GaussianBlur(sigmas ...float64) error { - var ( - sigma = sigmas[0] - minAmpl = GaussBlurDefaultMinAMpl - ) - if len(sigmas) >= 2 { - minAmpl = sigmas[1] - } - out, err := vipsGenGaussblur(r.image, sigma, &GaussblurOptions{MinAmpl: &minAmpl}) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Sharpen sharpens the image -// sigma: sigma of the gaussian -// x1: flat/jaggy threshold -// m2: slope for jaggy areas -func (r *ImageRef) Sharpen(sigma float64, x1 float64, m2 float64) error { - out, err := vipsGenSharpen(r.image, &SharpenOptions{Sigma: &sigma, X1: &x1, M2: &m2}) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Apply Sobel edge detector to the image. -func (r *ImageRef) Sobel() error { - out, err := vipsGenSobel(r.image) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Modulate the colors -func (r *ImageRef) Modulate(brightness, saturation, hue float64) error { - var err error - var multiplications []float64 - var additions []float64 - - colorspace := r.ColorSpace() - if colorspace == InterpretationRGB { - colorspace = InterpretationSRGB - } - - multiplications = []float64{brightness, saturation, 1} - additions = []float64{0, 0, hue} - - if r.HasAlpha() { - multiplications = append(multiplications, 1) - additions = append(additions, 0) - } - - err = r.ToColorSpace(InterpretationLCH) - if err != nil { - return err - } - - err = r.Linear(multiplications, additions) - if err != nil { - return err - } - - err = r.ToColorSpace(colorspace) - if err != nil { - return err - } - - return nil -} - -// ModulateHSV modulates the image HSV values based on the supplier parameters. -func (r *ImageRef) ModulateHSV(brightness, saturation float64, hue int) error { - var err error - var multiplications []float64 - var additions []float64 - - colorspace := r.ColorSpace() - if colorspace == InterpretationRGB { - colorspace = InterpretationSRGB - } - - if r.HasAlpha() { - multiplications = []float64{1, saturation, brightness, 1} - additions = []float64{float64(hue), 0, 0, 0} - } else { - multiplications = []float64{1, saturation, brightness} - additions = []float64{float64(hue), 0, 0} - } - - err = r.ToColorSpace(InterpretationHSV) - if err != nil { - return err - } - - err = r.Linear(multiplications, additions) - if err != nil { - return err - } - - err = r.ToColorSpace(colorspace) - if err != nil { - return err - } - - return nil -} - -// Invert inverts the image -func (r *ImageRef) Invert() error { - out, err := vipsGenInvert(r.image) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Average finds the average value in an image -func (r *ImageRef) Average() (float64, error) { - defer runtime.KeepAlive(r) - out, err := vipsGenAvg(r.image) - if err != nil { - return 0, err - } - return out, nil -} - -// FindTrim returns the bounding box of the non-border part of the image -// Returned values are left, top, width, height -func (r *ImageRef) FindTrim(threshold float64, backgroundColor *Color) (int, int, int, int, error) { - defer runtime.KeepAlive(r) - return vipsFindTrim(r.image, threshold, backgroundColor) -} - -// GetPoint reads a single pixel on an image. -// The pixel values are returned in a slice of length n. -func (r *ImageRef) GetPoint(x int, y int) ([]float64, error) { - defer runtime.KeepAlive(r) - n := 3 - if vipsHasAlpha(r.image) { - n = 4 - } - return vipsGetPoint(r.image, n, x, y) -} - -// Stats find many image statistics in a single pass through the data. Image is changed into a one-band -// `BandFormatDouble` image of at least 10 columns by n + 1 (where n is number of bands in image in) -// rows. Columns are statistics, and are, in order: minimum, maximum, sum, sum of squares, mean, -// standard deviation, x coordinate of minimum, y coordinate of minimum, x coordinate of maximum, -// y coordinate of maximum. -// -// Row 0 has statistics for all bands together, row 1 has stats for band 1, and so on. -// -// If there is more than one maxima or minima, one of them will be chosen at random. -func (r *ImageRef) Stats() error { - out, err := vipsGenStats(r.image) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// HistogramFind find the histogram the image. -// Find the histogram for all bands (producing a one-band histogram). -// char and uchar images are cast to uchar before histogramming, all other image types are cast to ushort. -func (r *ImageRef) HistogramFind() error { - out, err := vipsGenHistFind(r.image, nil) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// HistogramCumulative form cumulative histogram. -func (r *ImageRef) HistogramCumulative() error { - out, err := vipsGenHistCum(r.image) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// HistogramNormalise -// The maximum of each band becomes equal to the maximum index, so for example the max for a uchar -// image becomes 255. Normalise each band separately. -func (r *ImageRef) HistogramNormalise() error { - out, err := vipsGenHistNorm(r.image) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// HistogramEntropy estimate image entropy from a histogram. Entropy is calculated as: -// `-sum(p * log2(p))` -// where p is histogram-value / sum-of-histogram-values. -func (r *ImageRef) HistogramEntropy() (float64, error) { - defer runtime.KeepAlive(r) - return vipsGenHistEntropy(r.image) -} - -// DrawRect draws an (optionally filled) rectangle with a single colour -func (r *ImageRef) DrawRect(ink ColorRGBA, left int, top int, width int, height int, fill bool) error { - defer runtime.KeepAlive(r) - err := vipsDrawRect(r.image, ink, left, top, width, height, fill) - if err != nil { - return err - } - return nil -} - -// Subtract calculate subtract operation between two images. -func (r *ImageRef) Subtract(in2 *ImageRef) error { - out, err := vipsGenSubtract(r.image, in2.image) - if err != nil { - return err - } - - r.setImage(out) - return nil -} - -// Abs calculate abs operation. -func (r *ImageRef) Abs() error { - out, err := vipsGenAbs(r.image) - if err != nil { - return err - } - - r.setImage(out) - return nil -} - -// Project calculate project operation. -func (r *ImageRef) Project() (*ImageRef, *ImageRef, error) { - defer runtime.KeepAlive(r) - col, row, err := vipsGenProject(r.image) - if err != nil { - return nil, nil, err - } - - return newImageRef(col, r.format, r.originalFormat, nil), newImageRef(row, r.format, r.originalFormat, nil), nil -} - -// Min finds the minimum value in an image. -func (r *ImageRef) Min() (float64, int, int, error) { - defer runtime.KeepAlive(r) - return vipsMin(r.image) -} - -// Rank does rank filtering on an image. A window of size width by height is passed over the image. -// At each position, the pixels inside the window are sorted into ascending order and the pixel at position -// index is output. index numbers from 0. -func (r *ImageRef) Rank(width int, height int, index int) error { - out, err := vipsGenRank(r.image, width, height, index) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Resize resizes the image based on the scale, maintaining aspect ratio -func (r *ImageRef) Resize(scale float64, kernel Kernel) error { - return r.ResizeWithVScale(scale, -1, kernel) -} - -// ResizeWithVScale resizes the image with both horizontal and vertical scaling. -// The parameters are the scaling factors. -func (r *ImageRef) ResizeWithVScale(hScale, vScale float64, kernel Kernel) error { - if err := r.PremultiplyAlpha(); err != nil { - return err - } - - pages := r.Pages() - pageHeight := r.GetPageHeight() - - out, err := vipsResizeWithVScale(r.image, hScale, vScale, kernel) - if err != nil { - return err - } - r.setImage(out) - - if pages > 1 { - scale := hScale - if vScale != -1 { - scale = vScale - } - newPageHeight := int(math.Round(float64(pageHeight) * scale)) - if err := r.SetPageHeight(newPageHeight); err != nil { - return err - } - } - - return r.UnpremultiplyAlpha() -} - -// Thumbnail resizes the image to the given width and height. -// crop decides algorithm vips uses to shrink and crop to fill target, -func (r *ImageRef) Thumbnail(width, height int, crop Interesting) error { - out, err := vipsThumbnail(r.image, width, height, crop, SizeBoth) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// ThumbnailWithSize resizes the image to the given width and height. -// crop decides algorithm vips uses to shrink and crop to fill target, -// size controls upsize, downsize, both or force -func (r *ImageRef) ThumbnailWithSize(width, height int, crop Interesting, size Size) error { - out, err := vipsThumbnail(r.image, width, height, crop, size) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Embed embeds the given picture in a new one, i.e. the opposite of ExtractArea -func (r *ImageRef) Embed(left, top, width, height int, extend ExtendStrategy) error { - if r.Height() > r.PageHeight() { - out, err := vipsEmbedMultiPage(r.image, left, top, width, height, extend) - if err != nil { - return err - } - r.setImage(out) - } else { - out, err := vipsEmbed(r.image, left, top, width, height, extend) - if err != nil { - return err - } - r.setImage(out) - } - return nil -} - -// EmbedBackground embeds the given picture with a background color -func (r *ImageRef) EmbedBackground(left, top, width, height int, backgroundColor *Color) error { - c := &ColorRGBA{ - R: backgroundColor.R, - G: backgroundColor.G, - B: backgroundColor.B, - A: 255, - } - if r.Height() > r.PageHeight() { - out, err := vipsEmbedMultiPageBackground(r.image, left, top, width, height, c) - if err != nil { - return err - } - r.setImage(out) - } else { - out, err := vipsEmbedBackground(r.image, left, top, width, height, c) - if err != nil { - return err - } - r.setImage(out) - } - return nil -} - -// EmbedBackgroundRGBA embeds the given picture with a background rgba color -func (r *ImageRef) EmbedBackgroundRGBA(left, top, width, height int, backgroundColor *ColorRGBA) error { - if r.Height() > r.PageHeight() { - out, err := vipsEmbedMultiPageBackground(r.image, left, top, width, height, backgroundColor) - if err != nil { - return err - } - r.setImage(out) - } else { - out, err := vipsEmbedBackground(r.image, left, top, width, height, backgroundColor) - if err != nil { - return err - } - r.setImage(out) - } - return nil -} - -// Zoom zooms the image by repeating pixels (fast nearest-neighbour) -func (r *ImageRef) Zoom(xFactor int, yFactor int) error { - out, err := vipsGenZoom(r.image, xFactor, yFactor) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -func (r *ImageRef) Gravity(gravity Gravity, width int, height int) error { - out, err := vipsGenGravity(r.image, gravity, width, height, nil) - if err != nil { - return err - } - - r.setImage(out) - return nil -} - -// Flip flips the image either horizontally or vertically based on the parameter -func (r *ImageRef) Flip(direction Direction) error { - out, err := vipsFlip(r.image, direction) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Recomb recombines the image bands using the matrix provided -func (r *ImageRef) Recomb(matrix [][]float64) error { - numBands := r.Bands() - // Ensure the provided matrix is 3x3 - if len(matrix) != 3 || len(matrix[0]) != 3 || len(matrix[1]) != 3 || len(matrix[2]) != 3 { - return errors.New("Invalid recombination matrix") - } - // If the image is RGBA, expand the matrix to 4x4 - if numBands == 4 { - matrix = append(matrix, []float64{0, 0, 0, 1}) - for i := 0; i < 3; i++ { - matrix[i] = append(matrix[i], 0) - } - } else if numBands != 3 { - return errors.New("Unsupported number of bands") - } - - // Flatten the matrix - matrixValues := make([]float64, 0, numBands*numBands) - for _, row := range matrix { - for _, value := range row { - matrixValues = append(matrixValues, value) - } - } - - // Convert the Go slice to a C array and get its size - matrixPtr := unsafe.Pointer(&matrixValues[0]) - matrixSize := C.size_t(len(matrixValues) * 8) // 8 bytes for each float64 - - // Create a VipsImage from the matrix in memory - matrixImage := C.vips_image_new_from_memory(matrixPtr, matrixSize, C.int(numBands), C.int(numBands), 1, C.VIPS_FORMAT_DOUBLE) - defer clearImage(matrixImage) - - // Check for any Vips errors - errMsg := C.GoString(C.vips_error_buffer()) - if errMsg != "" { - C.vips_error_clear() - return errors.New("Vips error: " + errMsg) - } - - // Recombine the image using the matrix - out, err := vipsGenRecomb(r.image, matrixImage) - runtime.KeepAlive(matrixValues) - if err != nil { - return err - } - - r.setImage(out) - return nil -} - -// Rotate rotates the image by multiples of 90 degrees. To rotate by arbitrary angles use Similarity. -func (r *ImageRef) Rotate(angle Angle) error { - width := r.Width() - - if r.Pages() > 1 && (angle == Angle90 || angle == Angle270) { - if angle == Angle270 { - if err := r.Flip(DirectionHorizontal); err != nil { - return err - } - } - - if err := r.Grid(r.GetPageHeight(), r.Pages(), 1); err != nil { - return err - } - - if angle == Angle270 { - if err := r.Flip(DirectionHorizontal); err != nil { - return err - } - } - - } - - out, err := vipsGenRot(r.image, angle) - if err != nil { - return err - } - r.setImage(out) - - if r.Pages() > 1 && (angle == Angle90 || angle == Angle270) { - if err := r.SetPageHeight(width); err != nil { - return err - } - } - return nil -} - -// Similarity lets you scale, offset and rotate images by arbitrary angles in a single operation while defining the -// color of new background pixels. If the input image has no alpha channel, the alpha on `backgroundColor` will be -// ignored. You can add an alpha channel to an image with `BandJoinConst` (e.g. `img.BandJoinConst([]float64{255})`) or -// AddAlpha. -func (r *ImageRef) Similarity(scale float64, angle float64, backgroundColor *ColorRGBA, - idx float64, idy float64, odx float64, ody float64) error { - out, err := vipsSimilarity(r.image, scale, angle, backgroundColor, idx, idy, odx, ody) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Grid tiles the image pages into a matrix across*down -func (r *ImageRef) Grid(tileHeight, across, down int) error { - out, err := vipsGenGrid(r.image, tileHeight, across, down) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// SmartCrop will crop the image based on interesting factor -func (r *ImageRef) SmartCrop(width int, height int, interesting Interesting) error { - out, err := vipsSmartCrop(r.image, width, height, interesting) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Crop will crop the image based on coordinate and box size -func (r *ImageRef) Crop(left int, top int, width int, height int) error { - out, err := vipsCrop(r.image, left, top, width, height) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Label overlays a label on top of the image -func (r *ImageRef) Label(labelParams *LabelParams) error { - out, err := labelImage(r.image, labelParams) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// Replicate repeats an image many times across and down -func (r *ImageRef) Replicate(across int, down int) error { - out, err := vipsGenReplicate(r.image, across, down) - if err != nil { - return err - } - r.setImage(out) - return nil -} - -// ToBytes writes the image to memory in VIPs format and returns the raw bytes, useful for storage. -func (r *ImageRef) ToBytes() ([]byte, error) { - defer runtime.KeepAlive(r) - var cSize C.size_t - cData := C.vips_image_write_to_memory(r.image, &cSize) - if cData == nil { - return nil, errors.New("failed to write image to memory") - } - defer C.free(cData) - - data := C.GoBytes(unsafe.Pointer(cData), C.int(cSize)) - return data, nil -} - -func (r *ImageRef) determineInputICCProfile() (inputProfile string) { - if r.Interpretation() == InterpretationCMYK { - if !r.HasICCProfile() { - inputProfile = "cmyk" - } - } - return -} - -// ToImage converts a VIPs image to a golang image.Image object, useful for interoperability with other golang libraries -func (r *ImageRef) ToImage(params *ExportParams) (image.Image, error) { - imageBytes, _, err := r.Export(params) - if err != nil { - return nil, err - } - - reader := bytes.NewReader(imageBytes) - img, _, err := image.Decode(reader) - if err != nil { - return nil, err - } - - return img, nil -} - -// ToGoImage converts a vips image directly to a Go image.Image without encoding. -// This is significantly faster than ToImage() which round-trips through JPEG/PNG. -// The resulting image will be in sRGB color space with 8-bit depth. -func (r *ImageRef) ToGoImage() (image.Image, error) { - defer runtime.KeepAlive(r) - - // Work on a copy to avoid mutating the receiver - tmp, err := vipsGenCopy(r.image, nil) - if err != nil { - return nil, err - } - defer clearImage(tmp) - - // Convert to sRGB if needed (keep B_W for grayscale) - interp := Interpretation(int(tmp.Type)) - if interp != InterpretationSRGB && interp != InterpretationBW { - out, err := vipsToColorSpace(tmp, InterpretationSRGB) - if err != nil { - return nil, err - } - clearImage(tmp) - tmp = out - } - - // Cast to uchar if needed - if BandFormat(int(tmp.BandFmt)) != BandFormatUchar { - out, err := vipsGenCast(tmp, BandFormatUchar, nil) - if err != nil { - return nil, err - } - clearImage(tmp) - tmp = out - } - - // Extract raw pixel data - var cSize C.size_t - cData := C.vips_image_write_to_memory(tmp, &cSize) - if cData == nil { - return nil, errors.New("failed to write image to memory") - } - defer C.free(cData) - - width := int(tmp.Xsize) - height := int(tmp.Ysize) - bands := int(tmp.Bands) - pixels := C.GoBytes(unsafe.Pointer(cData), C.int(cSize)) - - switch bands { - case 1: - img := image.NewGray(image.Rect(0, 0, width, height)) - copy(img.Pix, pixels) - return img, nil - case 2: - // Grayscale + alpha - img := image.NewNRGBA(image.Rect(0, 0, width, height)) - srcIdx := 0 - dstIdx := 0 - for srcIdx+1 < len(pixels) { - v := pixels[srcIdx] - img.Pix[dstIdx] = v - img.Pix[dstIdx+1] = v - img.Pix[dstIdx+2] = v - img.Pix[dstIdx+3] = pixels[srcIdx+1] - srcIdx += 2 - dstIdx += 4 - } - return img, nil - case 3: - // RGB, add opaque alpha - img := image.NewNRGBA(image.Rect(0, 0, width, height)) - srcIdx := 0 - dstIdx := 0 - for srcIdx+2 < len(pixels) { - img.Pix[dstIdx] = pixels[srcIdx] - img.Pix[dstIdx+1] = pixels[srcIdx+1] - img.Pix[dstIdx+2] = pixels[srcIdx+2] - img.Pix[dstIdx+3] = 255 - srcIdx += 3 - dstIdx += 4 - } - return img, nil - case 4: - img := image.NewNRGBA(image.Rect(0, 0, width, height)) - copy(img.Pix, pixels) - return img, nil - default: - return nil, fmt.Errorf("unsupported number of bands: %d", bands) - } -} - // NewImageFromGoImage creates a new ImageRef from a Go image.Image. // The image is normalized to NRGBA (non-premultiplied RGBA, 8-bit) and // imported into libvips in sRGB color space. func NewImageFromGoImage(img image.Image) (*ImageRef, error) { - startupIfNeeded() + if err := startupIfNeeded(); err != nil { + return nil, err + } bounds := img.Bounds() width := bounds.Dx() @@ -2451,6 +715,41 @@ func NewImageFromGoImage(img image.Image) (*ImageRef, error) { return newImageRef(vipsImage, ImageTypeUnknown, ImageTypeUnknown, nil), nil } +func newImageRef(vipsImage *C.VipsImage, currentFormat ImageType, originalFormat ImageType, buf []byte) *ImageRef { + imageRef := &ImageRef{ + image: vipsImage, + format: currentFormat, + originalFormat: originalFormat, + buf: buf, + } + openImageRefs.Add(1) + runtime.SetFinalizer(imageRef, finalizeImage) + + return imageRef +} + +func finalizeImage(ref *ImageRef) { + govipsLog("govips", LogLevelDebug, fmt.Sprintf("closing image %p", ref)) + ref.Close() +} + +// Close manually closes the image and frees the memory. Calling Close() is optional. +// Images are automatically closed by GC. However, in high volume applications the GC +// can't keep up with the amount of memory, so you might want to manually close the images. +func (r *ImageRef) Close() { + r.lock.Lock() + + if r.image != nil { + clearImage(r.image) + r.image = nil + openImageRefs.Add(-1) + } + + r.buf = nil + + r.lock.Unlock() +} + // setImage resets the image for this image and frees the previous one func (r *ImageRef) setImage(image *C.VipsImage) { r.lock.Lock() @@ -2498,25 +797,3 @@ func (r *ImageRef) newMetadata(format ImageType) *ImageMetadata { Pages: r.Pages(), } } - -// Pixelate applies a simple pixelate filter to the image -func Pixelate(imageRef *ImageRef, factor float64) (err error) { - if factor < 1 { - return errors.New("factor must be greater then 1") - } - - width := imageRef.Width() - height := imageRef.Height() - - if err = imageRef.Resize(1/factor, KernelAuto); err != nil { - return - } - - hScale := float64(width) / float64(imageRef.Width()) - vScale := float64(height) / float64(imageRef.Height()) - if err = imageRef.ResizeWithVScale(hScale, vScale, KernelNearest); err != nil { - return - } - - return -} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/image_color.go b/vendor/github.com/davidbyttow/govips/v2/vips/image_color.go new file mode 100644 index 0000000000..8ce388f20d --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image_color.go @@ -0,0 +1,173 @@ +package vips + +// #include "image.h" +import "C" + +import ( + "errors" + "runtime" +) + +// ToColorSpace changes the color space of the image to the interpretation supplied as the parameter. +func (r *ImageRef) ToColorSpace(interpretation Interpretation) error { + defer runtime.KeepAlive(r) + out, err := vipsToColorSpace(r.image, interpretation) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Flatten removes the alpha channel from the image and replaces it with the background color +func (r *ImageRef) Flatten(backgroundColor *Color) error { + defer runtime.KeepAlive(r) + opts := &FlattenOptions{} + if backgroundColor != nil { + opts.Background = []float64{float64(backgroundColor.R), float64(backgroundColor.G), float64(backgroundColor.B)} + } + out, err := vipsGenFlatten(r.image, opts) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Modulate the colors +func (r *ImageRef) Modulate(brightness, saturation, hue float64) error { + defer runtime.KeepAlive(r) + var err error + var multiplications []float64 + var additions []float64 + + colorspace := r.ColorSpace() + if colorspace == InterpretationRGB { + colorspace = InterpretationSRGB + } + + multiplications = []float64{brightness, saturation, 1} + additions = []float64{0, 0, hue} + + if r.HasAlpha() { + multiplications = append(multiplications, 1) + additions = append(additions, 0) + } + + err = r.ToColorSpace(InterpretationLCH) + if err != nil { + return err + } + + err = r.Linear(multiplications, additions) + if err != nil { + return err + } + + err = r.ToColorSpace(colorspace) + if err != nil { + return err + } + + return nil +} + +// ModulateHSV modulates the image HSV values based on the supplier parameters. +func (r *ImageRef) ModulateHSV(brightness, saturation float64, hue int) error { + defer runtime.KeepAlive(r) + var err error + var multiplications []float64 + var additions []float64 + + colorspace := r.ColorSpace() + if colorspace == InterpretationRGB { + colorspace = InterpretationSRGB + } + + if r.HasAlpha() { + multiplications = []float64{1, saturation, brightness, 1} + additions = []float64{float64(hue), 0, 0, 0} + } else { + multiplications = []float64{1, saturation, brightness} + additions = []float64{float64(hue), 0, 0} + } + + err = r.ToColorSpace(InterpretationHSV) + if err != nil { + return err + } + + err = r.Linear(multiplications, additions) + if err != nil { + return err + } + + err = r.ToColorSpace(colorspace) + if err != nil { + return err + } + + return nil +} + +// Invert inverts the image +func (r *ImageRef) Invert() error { + defer runtime.KeepAlive(r) + out, err := vipsGenInvert(r.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Adjusts the image's gamma value. +// See https://www.libvips.org/API/current/libvips-conversion.html#vips-gamma +func (r *ImageRef) Gamma(gamma float64) error { + defer runtime.KeepAlive(r) + out, err := vipsGenGamma(r.image, &GammaOptions{Exponent: &gamma}) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Linear passes an image through a linear transformation (i.e. output = input * a + b). +// See https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-linear +func (r *ImageRef) Linear(a, b []float64) error { + defer runtime.KeepAlive(r) + if len(a) != len(b) { + return errors.New("a and b must be of same length") + } + + out, err := vipsGenLinear(r.image, a, b, nil) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Linear1 runs Linear() with a single constant. +// See https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-linear1 +func (r *ImageRef) Linear1(a, b float64) error { + defer runtime.KeepAlive(r) + out, err := vipsGenLinear(r.image, []float64{a}, []float64{b}, nil) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Cast converts the image to a target band format +func (r *ImageRef) Cast(format BandFormat) error { + defer runtime.KeepAlive(r) + out, err := vipsGenCast(r.image, format, nil) + if err != nil { + return err + } + r.setImage(out) + return nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/image_composite.go b/vendor/github.com/davidbyttow/govips/v2/vips/image_composite.go new file mode 100644 index 0000000000..dc037ac324 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image_composite.go @@ -0,0 +1,70 @@ +package vips + +// #include "image.h" +import "C" + +import "runtime" + +// CompositeMulti composites the given overlay image on top of the associated image with provided blending mode. +func (r *ImageRef) CompositeMulti(ins []*ImageComposite) error { + defer runtime.KeepAlive(r) + out, err := vipsComposite(toVipsCompositeStructs(r, ins)) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Composite composites the given overlay image on top of the associated image with provided blending mode. +func (r *ImageRef) Composite(overlay *ImageRef, mode BlendMode, x, y int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenComposite2(r.image, overlay.image, mode, &Composite2Options{X: &x, Y: &y}) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Insert draws the image on top of the associated image at the given coordinates. +func (r *ImageRef) Insert(sub *ImageRef, x, y int, expand bool, background *ColorRGBA) error { + defer runtime.KeepAlive(r) + insertOpts := &InsertOptions{Expand: &expand} + if background != nil { + insertOpts.Background = []float64{float64(background.R), float64(background.G), float64(background.B), float64(background.A)} + } + out, err := vipsGenInsert(r.image, sub.image, x, y, insertOpts) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Join joins this image with another in the direction specified +func (r *ImageRef) Join(in *ImageRef, dir Direction) error { + defer runtime.KeepAlive(r) + out, err := vipsJoin(r.image, in.image, dir) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// ArrayJoin joins an array of images together wrapping at each n images +func (r *ImageRef) ArrayJoin(images []*ImageRef, across int) error { + defer runtime.KeepAlive(r) + allImages := append([]*ImageRef{r}, images...) + inputs := make([]*C.VipsImage, len(allImages)) + for i := range inputs { + inputs[i] = allImages[i].image + } + out, err := vipsGenArrayjoin(inputs, &ArrayjoinOptions{Across: &across}) + if err != nil { + return err + } + r.setImage(out) + return nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/image_export.go b/vendor/github.com/davidbyttow/govips/v2/vips/image_export.go new file mode 100644 index 0000000000..129e14d5b1 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image_export.go @@ -0,0 +1,392 @@ +package vips + +// #include "image.h" +import "C" + +import ( + "bytes" + "errors" + "fmt" + "image" + "runtime" + "unsafe" +) + +// Export creates a byte array of the image for use. +// The function returns a byte array that can be written to a file e.g. via os.WriteFile(). +// N.B. govips does not currently have built-in support for directly exporting to a file. +// The function also returns a copy of the image metadata as well as an error. +// Deprecated: Use ExportNative or format-specific Export methods +func (r *ImageRef) Export(params *ExportParams) ([]byte, *ImageMetadata, error) { + if params == nil || params.Format == ImageTypeUnknown { + return r.ExportNative() + } + + format := params.Format + + if !IsTypeSupported(format) { + return nil, r.newMetadata(ImageTypeUnknown), fmt.Errorf("cannot save to %#v", ImageTypes[format]) + } + + switch format { + case ImageTypeGIF: + return r.ExportGIF(&GifExportParams{ + Quality: params.Quality, + }) + case ImageTypeWEBP: + return r.ExportWebp(&WebpExportParams{ + StripMetadata: params.StripMetadata, + Quality: params.Quality, + Lossless: params.Lossless, + ReductionEffort: params.Effort, + }) + case ImageTypePNG: + return r.ExportPng(&PngExportParams{ + StripMetadata: params.StripMetadata, + Compression: params.Compression, + Interlace: params.Interlaced, + }) + case ImageTypeTIFF: + compression := TiffCompressionLzw + if params.Lossless { + compression = TiffCompressionNone + } + return r.ExportTiff(&TiffExportParams{ + StripMetadata: params.StripMetadata, + Quality: params.Quality, + Compression: compression, + }) + case ImageTypeHEIF: + return r.ExportHeif(&HeifExportParams{ + Quality: params.Quality, + Lossless: params.Lossless, + }) + case ImageTypeAVIF: + return r.ExportAvif(&AvifExportParams{ + StripMetadata: params.StripMetadata, + Quality: params.Quality, + Lossless: params.Lossless, + Speed: params.Speed, + }) + case ImageTypeJXL: + return r.ExportJxl(&JxlExportParams{ + Quality: params.Quality, + Lossless: params.Lossless, + Effort: params.Effort, + }) + default: + format = ImageTypeJPEG + return r.ExportJpeg(&JpegExportParams{ + Quality: params.Quality, + StripMetadata: params.StripMetadata, + Interlace: params.Interlaced, + OptimizeCoding: params.OptimizeCoding, + SubsampleMode: params.SubsampleMode, + TrellisQuant: params.TrellisQuant, + OvershootDeringing: params.OvershootDeringing, + OptimizeScans: params.OptimizeScans, + QuantTable: params.QuantTable, + }) + } +} + +// ExportNative exports the image to a buffer based on its native format with default parameters. +func (r *ImageRef) ExportNative() ([]byte, *ImageMetadata, error) { + switch r.format { + case ImageTypeJPEG: + return r.ExportJpeg(NewJpegExportParams()) + case ImageTypePNG: + return r.ExportPng(NewPngExportParams()) + case ImageTypeWEBP: + return r.ExportWebp(NewWebpExportParams()) + case ImageTypeHEIF: + return r.ExportHeif(NewHeifExportParams()) + case ImageTypeTIFF: + return r.ExportTiff(NewTiffExportParams()) + case ImageTypeAVIF: + return r.ExportAvif(NewAvifExportParams()) + case ImageTypeJP2K: + return r.ExportJp2k(NewJp2kExportParams()) + case ImageTypeGIF: + return r.ExportGIF(NewGifExportParams()) + case ImageTypeJXL: + return r.ExportJxl(NewJxlExportParams()) + default: + return r.ExportJpeg(NewJpegExportParams()) + } +} + +// ExportJpeg exports the image as JPEG to a buffer. +func (r *ImageRef) ExportJpeg(params *JpegExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewJpegExportParams() + } + + buf, err := vipsSaveJPEGToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeJPEG), nil +} + +// ExportPng exports the image as PNG to a buffer. +func (r *ImageRef) ExportPng(params *PngExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewPngExportParams() + } + + buf, err := vipsSavePNGToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypePNG), nil +} + +// ExportWebp exports the image as WEBP to a buffer. +func (r *ImageRef) ExportWebp(params *WebpExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewWebpExportParams() + } + + paramsWithIccProfile := *params + paramsWithIccProfile.IccProfile = r.optimizedIccProfile + + buf, err := vipsSaveWebPToBuffer(r.image, paramsWithIccProfile) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeWEBP), nil +} + +// ExportHeif exports the image as HEIF to a buffer. +func (r *ImageRef) ExportHeif(params *HeifExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewHeifExportParams() + } + + buf, err := vipsSaveHEIFToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeHEIF), nil +} + +// ExportTiff exports the image as TIFF to a buffer. +func (r *ImageRef) ExportTiff(params *TiffExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewTiffExportParams() + } + + buf, err := vipsSaveTIFFToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeTIFF), nil +} + +// ExportGIF exports the image as GIF to a buffer. +func (r *ImageRef) ExportGIF(params *GifExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewGifExportParams() + } + + buf, err := vipsSaveGIFToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeGIF), nil +} + +// ExportAvif exports the image as AVIF to a buffer. +func (r *ImageRef) ExportAvif(params *AvifExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewAvifExportParams() + } + + buf, err := vipsSaveAVIFToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeAVIF), nil +} + +// ExportJp2k exports the image as JPEG2000 to a buffer. +func (r *ImageRef) ExportJp2k(params *Jp2kExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewJp2kExportParams() + } + + buf, err := vipsSaveJP2KToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeJP2K), nil +} + +// ExportJxl exports the image as JPEG XL to a buffer. +func (r *ImageRef) ExportJxl(params *JxlExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewJxlExportParams() + } + + buf, err := vipsSaveJxlToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeJXL), nil +} + +// ExportMagick exports the image as Format set in param to a buffer. +func (r *ImageRef) ExportMagick(params *MagickExportParams) ([]byte, *ImageMetadata, error) { + defer runtime.KeepAlive(r) + if params == nil { + params = NewMagickExportParams() + params.Format = "JPG" + } + + buf, err := vipsSaveMagickToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeMagick), nil +} + +// ToBytes writes the image to memory in VIPs format and returns the raw bytes, useful for storage. +func (r *ImageRef) ToBytes() ([]byte, error) { + defer runtime.KeepAlive(r) + var cSize C.size_t + cData := C.vips_image_write_to_memory(r.image, &cSize) + if cData == nil { + return nil, errors.New("failed to write image to memory") + } + defer C.free(cData) + + data := C.GoBytes(unsafe.Pointer(cData), C.int(cSize)) + return data, nil +} + +// ToImage converts a VIPs image to a golang image.Image object, useful for interoperability with other golang libraries. +// Deprecated: Use ToGoImage for a faster, direct conversion without encoding round-trip. +func (r *ImageRef) ToImage(params *ExportParams) (image.Image, error) { + imageBytes, _, err := r.ExportNative() + if err != nil { + return nil, err + } + + reader := bytes.NewReader(imageBytes) + img, _, err := image.Decode(reader) + if err != nil { + return nil, err + } + + return img, nil +} + +// ToGoImage converts a vips image directly to a Go image.Image without encoding. +// This is significantly faster than ToImage() which round-trips through JPEG/PNG. +// The resulting image will be in sRGB color space with 8-bit depth. +func (r *ImageRef) ToGoImage() (image.Image, error) { + defer runtime.KeepAlive(r) + + // Work on a copy to avoid mutating the receiver + tmp, err := vipsGenCopy(r.image, nil) + if err != nil { + return nil, err + } + defer clearImage(tmp) + + // Convert to sRGB if needed (keep B_W for grayscale) + interp := Interpretation(int(tmp.Type)) + if interp != InterpretationSRGB && interp != InterpretationBW { + out, err := vipsToColorSpace(tmp, InterpretationSRGB) + if err != nil { + return nil, err + } + clearImage(tmp) + tmp = out + } + + // Cast to uchar if needed + if BandFormat(int(tmp.BandFmt)) != BandFormatUchar { + out, err := vipsGenCast(tmp, BandFormatUchar, nil) + if err != nil { + return nil, err + } + clearImage(tmp) + tmp = out + } + + // Extract raw pixel data + var cSize C.size_t + cData := C.vips_image_write_to_memory(tmp, &cSize) + if cData == nil { + return nil, errors.New("failed to write image to memory") + } + defer C.free(cData) + + width := int(tmp.Xsize) + height := int(tmp.Ysize) + bands := int(tmp.Bands) + pixels := C.GoBytes(unsafe.Pointer(cData), C.int(cSize)) + + switch bands { + case 1: + img := image.NewGray(image.Rect(0, 0, width, height)) + copy(img.Pix, pixels) + return img, nil + case 2: + // Grayscale + alpha + img := image.NewNRGBA(image.Rect(0, 0, width, height)) + srcIdx := 0 + dstIdx := 0 + for srcIdx+1 < len(pixels) { + v := pixels[srcIdx] + img.Pix[dstIdx] = v + img.Pix[dstIdx+1] = v + img.Pix[dstIdx+2] = v + img.Pix[dstIdx+3] = pixels[srcIdx+1] + srcIdx += 2 + dstIdx += 4 + } + return img, nil + case 3: + // RGB, add opaque alpha + img := image.NewNRGBA(image.Rect(0, 0, width, height)) + srcIdx := 0 + dstIdx := 0 + for srcIdx+2 < len(pixels) { + img.Pix[dstIdx] = pixels[srcIdx] + img.Pix[dstIdx+1] = pixels[srcIdx+1] + img.Pix[dstIdx+2] = pixels[srcIdx+2] + img.Pix[dstIdx+3] = 255 + srcIdx += 3 + dstIdx += 4 + } + return img, nil + case 4: + img := image.NewNRGBA(image.Rect(0, 0, width, height)) + copy(img.Pix, pixels) + return img, nil + default: + return nil, fmt.Errorf("unsupported number of bands: %d", bands) + } +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/image_icc.go b/vendor/github.com/davidbyttow/govips/v2/vips/image_icc.go new file mode 100644 index 0000000000..4a6ff3a8d4 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image_icc.go @@ -0,0 +1,108 @@ +package vips + +// #include "image.h" +import "C" + +import ( + "fmt" + "runtime" +) + +// GetICCProfile retrieves the ICC profile data (if any) from the image. +func (r *ImageRef) GetICCProfile() []byte { + defer runtime.KeepAlive(r) + bytes, _ := vipsGetICCProfile(r.image) + return bytes +} + +// RemoveICCProfile removes the ICC Profile information from the image. +// Typically, browsers and other software assume images without profile to be in the sRGB color space. +func (r *ImageRef) RemoveICCProfile() error { + defer runtime.KeepAlive(r) + out, err := vipsGenCopy(r.image, nil) + if err != nil { + return err + } + + vipsRemoveICCProfile(out) + + r.setImage(out) + return nil +} + +// TransformICCProfileWithFallback transforms from the embedded ICC profile of the image to the ICC profile at the given path. +// The fallback ICC profile is used if the image does not have an embedded ICC profile. +func (r *ImageRef) TransformICCProfileWithFallback(targetProfilePath, fallbackProfilePath string) error { + defer runtime.KeepAlive(r) + if err := ensureLoadICCPath(&targetProfilePath); err != nil { + return err + } + if err := ensureLoadICCPath(&fallbackProfilePath); err != nil { + return err + } + + depth := 16 + if r.BandFormat() == BandFormatUchar || r.BandFormat() == BandFormatChar || r.BandFormat() == BandFormatNotSet { + depth = 8 + } + + out, err := vipsICCTransform(r.image, targetProfilePath, fallbackProfilePath, IntentPerceptual, depth, true) + if err != nil { + govipsLog("govips", LogLevelError, fmt.Sprintf("failed to do icc transform: %v", err.Error())) + return err + } + + r.setImage(out) + return nil +} + +// TransformICCProfile transforms from the embedded ICC profile of the image to the icc profile at the given path. +func (r *ImageRef) TransformICCProfile(outputProfilePath string) error { + return r.TransformICCProfileWithFallback(outputProfilePath, SRGBIEC6196621ICCProfilePath) +} + +// OptimizeICCProfile optimizes the ICC color profile of the image. +// For two color channel images, it sets a grayscale profile. +// For color images, it sets a CMYK or non-CMYK profile based on the image metadata. +func (r *ImageRef) OptimizeICCProfile() error { + defer runtime.KeepAlive(r) + inputProfile := r.determineInputICCProfile() + if !r.HasICCProfile() && (inputProfile == "") { + // No embedded ICC profile in the input image and no input profile determined, nothing to do. + return nil + } + + r.optimizedIccProfile = SRGBV2MicroICCProfilePath + if r.Bands() <= 2 { + r.optimizedIccProfile = SGrayV2MicroICCProfilePath + } + + if err := ensureLoadICCPath(&r.optimizedIccProfile); err != nil { + return err + } + + embedded := r.HasICCProfile() && (inputProfile == "") + + depth := 16 + if r.BandFormat() == BandFormatUchar || r.BandFormat() == BandFormatChar || r.BandFormat() == BandFormatNotSet { + depth = 8 + } + + out, err := vipsICCTransform(r.image, r.optimizedIccProfile, inputProfile, IntentPerceptual, depth, embedded) + if err != nil { + govipsLog("govips", LogLevelError, fmt.Sprintf("failed to do icc transform: %v", err.Error())) + return err + } + + r.setImage(out) + return nil +} + +func (r *ImageRef) determineInputICCProfile() (inputProfile string) { + if r.Interpretation() == InterpretationCMYK { + if !r.HasICCProfile() { + inputProfile = "cmyk" + } + } + return +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/image_metadata.go b/vendor/github.com/davidbyttow/govips/v2/vips/image_metadata.go new file mode 100644 index 0000000000..5b90bfc3b0 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image_metadata.go @@ -0,0 +1,338 @@ +package vips + +// #include "image.h" +import "C" + +import ( + "runtime" + "strings" +) + +// Format returns the current format of the vips image. +func (r *ImageRef) Format() ImageType { + return r.format +} + +// OriginalFormat returns the original format of the image when loaded. +// In some cases the loaded image is converted on load, for example, a BMP is automatically converted to PNG +// This method returns the format of the original buffer, as opposed to Format() with will return the format of the +// currently held buffer content. +func (r *ImageRef) OriginalFormat() ImageType { + return r.originalFormat +} + +// Width returns the width of this image. +func (r *ImageRef) Width() int { + return int(r.image.Xsize) +} + +// Height returns the height of this image. +func (r *ImageRef) Height() int { + return int(r.image.Ysize) +} + +// Bands returns the number of bands for this image. +func (r *ImageRef) Bands() int { + return int(r.image.Bands) +} + +// HasProfile returns if the image has an ICC profile embedded. +func (r *ImageRef) HasProfile() bool { + defer runtime.KeepAlive(r) + return vipsHasICCProfile(r.image) +} + +// HasICCProfile checks whether the image has an ICC profile embedded. Alias to HasProfile +func (r *ImageRef) HasICCProfile() bool { + return r.HasProfile() +} + +// HasIPTC returns a boolean whether the image in question has IPTC data associated with it. +func (r *ImageRef) HasIPTC() bool { + defer runtime.KeepAlive(r) + return vipsHasIPTC(r.image) +} + +// HasAlpha returns if the image has an alpha layer. +func (r *ImageRef) HasAlpha() bool { + defer runtime.KeepAlive(r) + return vipsHasAlpha(r.image) +} + +// Orientation returns the orientation number as it appears in the EXIF, if present +func (r *ImageRef) Orientation() int { + defer runtime.KeepAlive(r) + return vipsGetMetaOrientation(r.image) +} + +// Deprecated: use Orientation() instead +func (r *ImageRef) GetOrientation() int { + return r.Orientation() +} + +// SetOrientation sets the orientation in the EXIF header of the associated image. +func (r *ImageRef) SetOrientation(orientation int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenCopy(r.image, nil) + if err != nil { + return err + } + + vipsSetMetaOrientation(out, orientation) + + r.setImage(out) + return nil +} + +// RemoveOrientation removes the EXIF orientation information of the image. +func (r *ImageRef) RemoveOrientation() error { + defer runtime.KeepAlive(r) + out, err := vipsGenCopy(r.image, nil) + if err != nil { + return err + } + + vipsRemoveMetaOrientation(out) + + r.setImage(out) + return nil +} + +// ResX returns the X resolution +func (r *ImageRef) ResX() float64 { + return float64(r.image.Xres) +} + +// ResY returns the Y resolution +func (r *ImageRef) ResY() float64 { + return float64(r.image.Yres) +} + +// OffsetX returns the X offset +func (r *ImageRef) OffsetX() int { + return int(r.image.Xoffset) +} + +// OffsetY returns the Y offset +func (r *ImageRef) OffsetY() int { + return int(r.image.Yoffset) +} + +// BandFormat returns the current band format +func (r *ImageRef) BandFormat() BandFormat { + return BandFormat(int(r.image.BandFmt)) +} + +// Coding returns the image coding +func (r *ImageRef) Coding() Coding { + return Coding(int(r.image.Coding)) +} + +// Interpretation returns the current interpretation of the color space of the image. +func (r *ImageRef) Interpretation() Interpretation { + return Interpretation(int(r.image.Type)) +} + +// ColorSpace returns the interpretation of the current color space. Alias to Interpretation(). +func (r *ImageRef) ColorSpace() Interpretation { + return r.Interpretation() +} + +// IsColorSpaceSupported returns a boolean whether the image's color space is supported by libvips. +func (r *ImageRef) IsColorSpaceSupported() bool { + defer runtime.KeepAlive(r) + return vipsIsColorSpaceSupported(r.image) +} + +// Pages returns the number of pages in the Image +// For animated images this corresponds to the number of frames +func (r *ImageRef) Pages() int { + // libvips uses the same attribute (n_pages) to represent the number of pyramid layers in JP2K + // as we interpret the attribute as frames and JP2K does not support animation we override this with 1 + if r.format == ImageTypeJP2K { + return 1 + } + + defer runtime.KeepAlive(r) + return vipsGetImageNPages(r.image) +} + +// Deprecated: use Pages() instead +func (r *ImageRef) GetPages() int { + return r.Pages() +} + +// SetPages sets the number of pages in the Image +// For animated images this corresponds to the number of frames +func (r *ImageRef) SetPages(pages int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenCopy(r.image, nil) + if err != nil { + return err + } + + vipsSetImageNPages(out, pages) + + r.setImage(out) + return nil +} + +// PageHeight return the height of a single page +func (r *ImageRef) PageHeight() int { + defer runtime.KeepAlive(r) + return vipsGetPageHeight(r.image) +} + +// GetPageHeight return the height of a single page +// Deprecated use PageHeight() instead +func (r *ImageRef) GetPageHeight() int { + defer runtime.KeepAlive(r) + return vipsGetPageHeight(r.image) +} + +// SetPageHeight set the height of a page +// For animated images this is used when "unrolling" back to frames +func (r *ImageRef) SetPageHeight(height int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenCopy(r.image, nil) + if err != nil { + return err + } + + vipsSetPageHeight(out, height) + + r.setImage(out) + return nil +} + +// PageDelay get the page delay array for animation +func (r *ImageRef) PageDelay() ([]int, error) { + defer runtime.KeepAlive(r) + n := vipsGetImageNPages(r.image) + if n <= 1 { + // should not call if not multi page + return nil, nil + } + return vipsImageGetDelay(r.image, n) +} + +// SetPageDelay set the page delay array for animation +func (r *ImageRef) SetPageDelay(delay []int) error { + defer runtime.KeepAlive(r) + var data []C.int + for _, d := range delay { + data = append(data, C.int(d)) + } + return vipsImageSetDelay(r.image, data) +} + +// Loop returns the loop count for animated images. +// A value of 0 means infinite looping. +func (r *ImageRef) Loop() int { + defer runtime.KeepAlive(r) + return vipsImageGetLoop(r.image) +} + +// SetLoop sets the loop count for animated images. +// A value of 0 means infinite looping. +func (r *ImageRef) SetLoop(loop int) error { + defer runtime.KeepAlive(r) + vipsImageSetLoop(r.image, loop) + return nil +} + +// Background get the background of image. +func (r *ImageRef) Background() ([]float64, error) { + defer runtime.KeepAlive(r) + out, err := vipsImageGetBackground(r.image) + if err != nil { + return nil, err + } + return out, nil +} + +func (r *ImageRef) ImageFields() []string { + return r.GetFields() +} + +func (r *ImageRef) GetFields() []string { + defer runtime.KeepAlive(r) + return vipsImageGetFields(r.image) +} + +func (r *ImageRef) SetBlob(name string, data []byte) { + defer runtime.KeepAlive(r) + vipsImageSetBlob(r.image, name, data) +} + +func (r *ImageRef) GetBlob(name string) []byte { + defer runtime.KeepAlive(r) + return vipsImageGetBlob(r.image, name) +} + +func (r *ImageRef) SetDouble(name string, f float64) { + defer runtime.KeepAlive(r) + vipsImageSetDouble(r.image, name, f) +} + +func (r *ImageRef) GetDouble(name string) float64 { + defer runtime.KeepAlive(r) + return vipsImageGetDouble(r.image, name) +} + +func (r *ImageRef) SetInt(name string, i int) { + defer runtime.KeepAlive(r) + vipsImageSetInt(r.image, name, i) +} + +func (r *ImageRef) GetInt(name string) int { + defer runtime.KeepAlive(r) + return vipsImageGetInt(r.image, name) +} + +func (r *ImageRef) SetString(name string, str string) { + defer runtime.KeepAlive(r) + vipsImageSetString(r.image, name, str) +} + +func (r *ImageRef) GetString(name string) string { + defer runtime.KeepAlive(r) + return vipsImageGetString(r.image, name) +} + +func (r *ImageRef) GetAsString(name string) string { + defer runtime.KeepAlive(r) + return vipsImageGetAsString(r.image, name) +} + +func (r *ImageRef) HasExif() bool { + for _, field := range r.ImageFields() { + if strings.HasPrefix(field, "exif-") { + return true + } + } + + return false +} + +func (r *ImageRef) GetExif() map[string]string { + defer runtime.KeepAlive(r) + return vipsImageGetExifData(r.image) +} + +// RemoveMetadata removes the EXIF metadata from the image. +// N.B. this function won't remove the ICC profile, orientation and pages metadata +// because govips needs it to correctly display the image. +func (r *ImageRef) RemoveMetadata(keep ...string) error { + defer runtime.KeepAlive(r) + out, err := vipsGenCopy(r.image, nil) + if err != nil { + return err + } + + vipsRemoveMetadata(out, keep...) + + r.setImage(out) + + return nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/image_pixel.go b/vendor/github.com/davidbyttow/govips/v2/vips/image_pixel.go new file mode 100644 index 0000000000..9fdb3fbf5b --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image_pixel.go @@ -0,0 +1,390 @@ +package vips + +// #include "image.h" +import "C" + +import ( + "errors" + "runtime" +) + +// GaussianBlur blurs the image +// add support minAmpl +func (r *ImageRef) GaussianBlur(sigmas ...float64) error { + defer runtime.KeepAlive(r) + var ( + sigma = sigmas[0] + minAmpl = GaussBlurDefaultMinAMpl + ) + if len(sigmas) >= 2 { + minAmpl = sigmas[1] + } + out, err := vipsGenGaussblur(r.image, sigma, &GaussblurOptions{MinAmpl: &minAmpl}) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Sharpen sharpens the image +// sigma: sigma of the gaussian +// x1: flat/jaggy threshold +// m2: slope for jaggy areas +func (r *ImageRef) Sharpen(sigma float64, x1 float64, m2 float64) error { + defer runtime.KeepAlive(r) + out, err := vipsGenSharpen(r.image, &SharpenOptions{Sigma: &sigma, X1: &x1, M2: &m2}) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Apply Sobel edge detector to the image. +func (r *ImageRef) Sobel() error { + defer runtime.KeepAlive(r) + out, err := vipsGenSobel(r.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Rank does rank filtering on an image. A window of size width by height is passed over the image. +// At each position, the pixels inside the window are sorted into ascending order and the pixel at position +// index is output. index numbers from 0. +func (r *ImageRef) Rank(width int, height int, index int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenRank(r.image, width, height, index) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Mapim resamples an image using index to look up pixels +func (r *ImageRef) Mapim(index *ImageRef) error { + defer runtime.KeepAlive(r) + out, err := vipsGenMapim(r.image, index.image, nil) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Maplut maps an image through another image acting as a LUT (Look Up Table) +func (r *ImageRef) Maplut(lut *ImageRef) error { + defer runtime.KeepAlive(r) + out, err := vipsGenMaplut(r.image, lut.image, nil) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// ExtractBand extracts one or more bands out of the image (replacing the associated ImageRef) +func (r *ImageRef) ExtractBand(band int, num int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenExtractBand(r.image, band, &ExtractBandOptions{N: &num}) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// ExtractBandToImage extracts one or more bands out of the image to a new image +func (r *ImageRef) ExtractBandToImage(band int, num int) (*ImageRef, error) { + defer runtime.KeepAlive(r) + out, err := vipsGenExtractBand(r.image, band, &ExtractBandOptions{N: &num}) + if err != nil { + return nil, err + } + return newImageRef(out, ImageTypeUnknown, ImageTypeUnknown, nil), nil +} + +// BandJoin joins a set of images together, bandwise. +func (r *ImageRef) BandJoin(images ...*ImageRef) error { + defer runtime.KeepAlive(r) + vipsImages := []*C.VipsImage{r.image} + for _, vipsImage := range images { + vipsImages = append(vipsImages, vipsImage.image) + } + + out, err := vipsGenBandjoin(vipsImages) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// BandSplit split an n-band image into n separate images.. +func (r *ImageRef) BandSplit() ([]*ImageRef, error) { + defer runtime.KeepAlive(r) + var out []*ImageRef + n := 1 + for i := 0; i < r.Bands(); i++ { + img, err := vipsGenExtractBand(r.image, i, &ExtractBandOptions{N: &n}) + if err != nil { + return out, err + } + out = append(out, &ImageRef{image: img}) + } + return out, nil +} + +// BandJoinConst appends a set of constant bands to an image. +func (r *ImageRef) BandJoinConst(constants []float64) error { + defer runtime.KeepAlive(r) + if len(constants) == 0 { + return errors.New("BandJoinConst: empty constants slice") + } + out, err := vipsGenBandjoinConst(r.image, constants) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// AddAlpha adds an alpha channel to the associated image. +func (r *ImageRef) AddAlpha() error { + defer runtime.KeepAlive(r) + if vipsHasAlpha(r.image) { + return nil + } + + out, err := vipsAddAlpha(r.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// PremultiplyAlpha premultiplies the alpha channel. +// See https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-premultiply +func (r *ImageRef) PremultiplyAlpha() error { + defer runtime.KeepAlive(r) + if r.preMultiplication != nil || !vipsHasAlpha(r.image) { + return nil + } + + band := r.BandFormat() + + out, err := vipsGenPremultiply(r.image, nil) + if err != nil { + return err + } + r.preMultiplication = &PreMultiplicationState{ + bandFormat: band, + } + r.setImage(out) + return nil +} + +// UnpremultiplyAlpha unpremultiplies any alpha channel. +// See https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-unpremultiply +func (r *ImageRef) UnpremultiplyAlpha() error { + defer runtime.KeepAlive(r) + if r.preMultiplication == nil { + return nil + } + + unpremultiplied, err := vipsGenUnpremultiply(r.image, nil) + if err != nil { + return err + } + defer clearImage(unpremultiplied) + + out, err := vipsGenCast(unpremultiplied, r.preMultiplication.bandFormat, nil) + if err != nil { + return err + } + + r.preMultiplication = nil + r.setImage(out) + return nil +} + +// Add calculates a sum of the image + addend and stores it back in the image +func (r *ImageRef) Add(addend *ImageRef) error { + defer runtime.KeepAlive(r) + out, err := vipsGenAdd(r.image, addend.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Multiply calculates the product of the image * multiplier and stores it back in the image +func (r *ImageRef) Multiply(multiplier *ImageRef) error { + defer runtime.KeepAlive(r) + out, err := vipsGenMultiply(r.image, multiplier.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Divide calculates the product of the image / denominator and stores it back in the image +func (r *ImageRef) Divide(denominator *ImageRef) error { + defer runtime.KeepAlive(r) + out, err := vipsGenDivide(r.image, denominator.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Average finds the average value in an image +func (r *ImageRef) Average() (float64, error) { + defer runtime.KeepAlive(r) + out, err := vipsGenAvg(r.image) + if err != nil { + return 0, err + } + return out, nil +} + +// FindTrim returns the bounding box of the non-border part of the image +// Returned values are left, top, width, height +func (r *ImageRef) FindTrim(threshold float64, backgroundColor *Color) (int, int, int, int, error) { + defer runtime.KeepAlive(r) + return vipsFindTrim(r.image, threshold, backgroundColor) +} + +// GetPoint reads a single pixel on an image. +// The pixel values are returned in a slice of length n. +func (r *ImageRef) GetPoint(x int, y int) ([]float64, error) { + defer runtime.KeepAlive(r) + n := 3 + if vipsHasAlpha(r.image) { + n = 4 + } + return vipsGetPoint(r.image, n, x, y) +} + +// Stats find many image statistics in a single pass through the data. Image is changed into a one-band +// `BandFormatDouble` image of at least 10 columns by n + 1 (where n is number of bands in image in) +// rows. Columns are statistics, and are, in order: minimum, maximum, sum, sum of squares, mean, +// standard deviation, x coordinate of minimum, y coordinate of minimum, x coordinate of maximum, +// y coordinate of maximum. +// +// Row 0 has statistics for all bands together, row 1 has stats for band 1, and so on. +// +// If there is more than one maxima or minima, one of them will be chosen at random. +func (r *ImageRef) Stats() error { + defer runtime.KeepAlive(r) + out, err := vipsGenStats(r.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// HistogramFind find the histogram the image. +// Find the histogram for all bands (producing a one-band histogram). +// char and uchar images are cast to uchar before histogramming, all other image types are cast to ushort. +func (r *ImageRef) HistogramFind() error { + defer runtime.KeepAlive(r) + out, err := vipsGenHistFind(r.image, nil) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// HistogramCumulative form cumulative histogram. +func (r *ImageRef) HistogramCumulative() error { + defer runtime.KeepAlive(r) + out, err := vipsGenHistCum(r.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// HistogramNormalise +// The maximum of each band becomes equal to the maximum index, so for example the max for a uchar +// image becomes 255. Normalise each band separately. +func (r *ImageRef) HistogramNormalise() error { + defer runtime.KeepAlive(r) + out, err := vipsGenHistNorm(r.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// HistogramEntropy estimate image entropy from a histogram. Entropy is calculated as: +// `-sum(p * log2(p))` +// where p is histogram-value / sum-of-histogram-values. +func (r *ImageRef) HistogramEntropy() (float64, error) { + defer runtime.KeepAlive(r) + return vipsGenHistEntropy(r.image) +} + +// DrawRect draws an (optionally filled) rectangle with a single colour +func (r *ImageRef) DrawRect(ink ColorRGBA, left int, top int, width int, height int, fill bool) error { + defer runtime.KeepAlive(r) + err := vipsDrawRect(r.image, ink, left, top, width, height, fill) + if err != nil { + return err + } + return nil +} + +// Subtract calculate subtract operation between two images. +func (r *ImageRef) Subtract(in2 *ImageRef) error { + defer runtime.KeepAlive(r) + out, err := vipsGenSubtract(r.image, in2.image) + if err != nil { + return err + } + + r.setImage(out) + return nil +} + +// Abs calculate abs operation. +func (r *ImageRef) Abs() error { + defer runtime.KeepAlive(r) + out, err := vipsGenAbs(r.image) + if err != nil { + return err + } + + r.setImage(out) + return nil +} + +// Project calculate project operation. +func (r *ImageRef) Project() (*ImageRef, *ImageRef, error) { + defer runtime.KeepAlive(r) + col, row, err := vipsGenProject(r.image) + if err != nil { + return nil, nil, err + } + + return newImageRef(col, r.format, r.originalFormat, nil), newImageRef(row, r.format, r.originalFormat, nil), nil +} + +// Min finds the minimum value in an image. +func (r *ImageRef) Min() (float64, int, int, error) { + defer runtime.KeepAlive(r) + return vipsMin(r.image) +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/image_transform.go b/vendor/github.com/davidbyttow/govips/v2/vips/image_transform.go new file mode 100644 index 0000000000..db5bd020af --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image_transform.go @@ -0,0 +1,402 @@ +package vips + +// #include "image.h" +import "C" + +import ( + "errors" + "math" + "runtime" + "unsafe" +) + +// GetRotationAngleFromExif returns the angle which the image is currently rotated in. +// First returned value is the angle and second is a boolean indicating whether image is flipped. +// This is based on the EXIF orientation tag standard. +// If no proper orientation number is provided, the picture will be assumed to be upright. +func GetRotationAngleFromExif(orientation int) (Angle, bool) { + switch orientation { + case 0, 1, 2: + return Angle0, orientation == 2 + case 3, 4: + return Angle180, orientation == 4 + case 5, 8: + return Angle90, orientation == 5 + case 6, 7: + return Angle270, orientation == 7 + } + + return Angle0, false +} + +// AutoRotate rotates the image upright based on the EXIF Orientation tag. +// It also resets the orientation information in the EXIF tag to be 1 (i.e. upright). +// N.B. libvips does not flip images currently (i.e. no support for orientations 2, 4, 5 and 7). +// N.B. due to the HEIF image standard, HEIF images are always autorotated by default on load. +// Thus, calling AutoRotate for HEIF images is not needed. +// todo: use https://www.libvips.org/API/current/libvips-conversion.html#vips-autorot-remove-angle +func (r *ImageRef) AutoRotate() error { + defer runtime.KeepAlive(r) + out, _, _, err := vipsGenAutorot(r.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// ExtractArea crops the image to a specified area +func (r *ImageRef) ExtractArea(left, top, width, height int) error { + defer runtime.KeepAlive(r) + if r.Height() > r.PageHeight() { + // use animated extract area if more than 1 pages loaded + out, err := vipsExtractAreaMultiPage(r.image, left, top, width, height) + if err != nil { + return err + } + r.setImage(out) + } else { + out, err := vipsExtractArea(r.image, left, top, width, height) + if err != nil { + return err + } + r.setImage(out) + } + return nil +} + +// Resize resizes the image based on the scale, maintaining aspect ratio +func (r *ImageRef) Resize(scale float64, kernel Kernel) error { + return r.ResizeWithVScale(scale, -1, kernel) +} + +// ResizeWithVScale resizes the image with both horizontal and vertical scaling. +// The parameters are the scaling factors. +func (r *ImageRef) ResizeWithVScale(hScale, vScale float64, kernel Kernel) error { + defer runtime.KeepAlive(r) + if err := r.PremultiplyAlpha(); err != nil { + return err + } + + pages := r.Pages() + pageHeight := r.PageHeight() + + out, err := vipsResizeWithVScale(r.image, hScale, vScale, kernel) + if err != nil { + return err + } + r.setImage(out) + + if pages > 1 { + scale := hScale + if vScale != -1 { + scale = vScale + } + newPageHeight := int(math.Round(float64(pageHeight) * scale)) + if err := r.SetPageHeight(newPageHeight); err != nil { + return err + } + } + + return r.UnpremultiplyAlpha() +} + +// Thumbnail resizes the image to the given width and height. +// crop decides algorithm vips uses to shrink and crop to fill target, +func (r *ImageRef) Thumbnail(width, height int, crop Interesting) error { + defer runtime.KeepAlive(r) + out, err := vipsThumbnail(r.image, width, height, crop, SizeBoth) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// ThumbnailWithSize resizes the image to the given width and height. +// crop decides algorithm vips uses to shrink and crop to fill target, +// size controls upsize, downsize, both or force +func (r *ImageRef) ThumbnailWithSize(width, height int, crop Interesting, size Size) error { + defer runtime.KeepAlive(r) + out, err := vipsThumbnail(r.image, width, height, crop, size) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Embed embeds the given picture in a new one, i.e. the opposite of ExtractArea +func (r *ImageRef) Embed(left, top, width, height int, extend ExtendStrategy) error { + defer runtime.KeepAlive(r) + if r.Height() > r.PageHeight() { + out, err := vipsEmbedMultiPage(r.image, left, top, width, height, extend) + if err != nil { + return err + } + r.setImage(out) + } else { + out, err := vipsEmbed(r.image, left, top, width, height, extend) + if err != nil { + return err + } + r.setImage(out) + } + return nil +} + +// EmbedBackground embeds the given picture with a background color +func (r *ImageRef) EmbedBackground(left, top, width, height int, backgroundColor *Color) error { + defer runtime.KeepAlive(r) + c := &ColorRGBA{ + R: backgroundColor.R, + G: backgroundColor.G, + B: backgroundColor.B, + A: 255, + } + if r.Height() > r.PageHeight() { + out, err := vipsEmbedMultiPageBackground(r.image, left, top, width, height, c) + if err != nil { + return err + } + r.setImage(out) + } else { + out, err := vipsEmbedBackground(r.image, left, top, width, height, c) + if err != nil { + return err + } + r.setImage(out) + } + return nil +} + +// EmbedBackgroundRGBA embeds the given picture with a background rgba color +func (r *ImageRef) EmbedBackgroundRGBA(left, top, width, height int, backgroundColor *ColorRGBA) error { + defer runtime.KeepAlive(r) + if r.Height() > r.PageHeight() { + out, err := vipsEmbedMultiPageBackground(r.image, left, top, width, height, backgroundColor) + if err != nil { + return err + } + r.setImage(out) + } else { + out, err := vipsEmbedBackground(r.image, left, top, width, height, backgroundColor) + if err != nil { + return err + } + r.setImage(out) + } + return nil +} + +// Zoom zooms the image by repeating pixels (fast nearest-neighbour) +func (r *ImageRef) Zoom(xFactor int, yFactor int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenZoom(r.image, xFactor, yFactor) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +func (r *ImageRef) Gravity(gravity Gravity, width int, height int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenGravity(r.image, gravity, width, height, nil) + if err != nil { + return err + } + + r.setImage(out) + return nil +} + +// Flip flips the image either horizontally or vertically based on the parameter +func (r *ImageRef) Flip(direction Direction) error { + defer runtime.KeepAlive(r) + out, err := vipsFlip(r.image, direction) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Recomb recombines the image bands using the matrix provided +func (r *ImageRef) Recomb(matrix [][]float64) error { + defer runtime.KeepAlive(r) + numBands := r.Bands() + // Ensure the provided matrix is 3x3 + if len(matrix) != 3 || len(matrix[0]) != 3 || len(matrix[1]) != 3 || len(matrix[2]) != 3 { + return errors.New("Invalid recombination matrix") + } + // If the image is RGBA, expand the matrix to 4x4 + if numBands == 4 { + matrix = append(matrix, []float64{0, 0, 0, 1}) + for i := 0; i < 3; i++ { + matrix[i] = append(matrix[i], 0) + } + } else if numBands != 3 { + return errors.New("Unsupported number of bands") + } + + // Flatten the matrix + matrixValues := make([]float64, 0, numBands*numBands) + for _, row := range matrix { + for _, value := range row { + matrixValues = append(matrixValues, value) + } + } + + // Convert the Go slice to a C array and get its size + matrixPtr := unsafe.Pointer(&matrixValues[0]) + matrixSize := C.size_t(len(matrixValues) * 8) // 8 bytes for each float64 + + // Create a VipsImage from the matrix in memory + matrixImage := C.vips_image_new_from_memory(matrixPtr, matrixSize, C.int(numBands), C.int(numBands), 1, C.VIPS_FORMAT_DOUBLE) + defer clearImage(matrixImage) + + if matrixImage == nil { + return handleVipsError() + } + + // Recombine the image using the matrix + out, err := vipsGenRecomb(r.image, matrixImage) + runtime.KeepAlive(matrixValues) + if err != nil { + return err + } + + r.setImage(out) + return nil +} + +// Rotate rotates the image by multiples of 90 degrees. To rotate by arbitrary angles use Similarity. +func (r *ImageRef) Rotate(angle Angle) error { + defer runtime.KeepAlive(r) + width := r.Width() + + if r.Pages() > 1 && (angle == Angle90 || angle == Angle270) { + if angle == Angle270 { + if err := r.Flip(DirectionHorizontal); err != nil { + return err + } + } + + if err := r.Grid(r.PageHeight(), r.Pages(), 1); err != nil { + return err + } + + if angle == Angle270 { + if err := r.Flip(DirectionHorizontal); err != nil { + return err + } + } + + } + + out, err := vipsGenRot(r.image, angle) + if err != nil { + return err + } + r.setImage(out) + + if r.Pages() > 1 && (angle == Angle90 || angle == Angle270) { + if err := r.SetPageHeight(width); err != nil { + return err + } + } + return nil +} + +// Similarity lets you scale, offset and rotate images by arbitrary angles in a single operation while defining the +// color of new background pixels. If the input image has no alpha channel, the alpha on `backgroundColor` will be +// ignored. You can add an alpha channel to an image with `BandJoinConst` (e.g. `img.BandJoinConst([]float64{255})`) or +// AddAlpha. +func (r *ImageRef) Similarity(scale float64, angle float64, backgroundColor *ColorRGBA, + idx float64, idy float64, odx float64, ody float64) error { + defer runtime.KeepAlive(r) + out, err := vipsSimilarity(r.image, scale, angle, backgroundColor, idx, idy, odx, ody) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Grid tiles the image pages into a matrix across*down +func (r *ImageRef) Grid(tileHeight, across, down int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenGrid(r.image, tileHeight, across, down) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// SmartCrop will crop the image based on interesting factor +func (r *ImageRef) SmartCrop(width int, height int, interesting Interesting) error { + defer runtime.KeepAlive(r) + out, err := vipsSmartCrop(r.image, width, height, interesting) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Crop will crop the image based on coordinate and box size +func (r *ImageRef) Crop(left int, top int, width int, height int) error { + defer runtime.KeepAlive(r) + out, err := vipsCrop(r.image, left, top, width, height) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Label overlays a label on top of the image +func (r *ImageRef) Label(labelParams *LabelParams) error { + defer runtime.KeepAlive(r) + out, err := labelImage(r.image, labelParams) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Replicate repeats an image many times across and down +func (r *ImageRef) Replicate(across int, down int) error { + defer runtime.KeepAlive(r) + out, err := vipsGenReplicate(r.image, across, down) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// Pixelate applies a simple pixelate filter to the image +func Pixelate(imageRef *ImageRef, factor float64) (err error) { + if factor < 1 { + return errors.New("factor must be greater then 1") + } + + width := imageRef.Width() + height := imageRef.Height() + + if err = imageRef.Resize(1/factor, KernelAuto); err != nil { + return + } + + hScale := float64(width) / float64(imageRef.Width()) + vScale := float64(height) / float64(imageRef.Height()) + if err = imageRef.ResizeWithVScale(hScale, vScale, KernelNearest); err != nil { + return + } + + return +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/math.go b/vendor/github.com/davidbyttow/govips/v2/vips/math.go index c7176f1f45..b4e1bff344 100644 --- a/vendor/github.com/davidbyttow/govips/v2/vips/math.go +++ b/vendor/github.com/davidbyttow/govips/v2/vips/math.go @@ -13,7 +13,7 @@ func ValueOf(value float64) Scalar { return Scalar{value, false} } -// IsZero checkes whether the associated Scalar's value is zero. +// IsZero checks whether the associated Scalar's value is zero. func (s *Scalar) IsZero() bool { return s.Value == 0 && !s.Relative } diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/operations.c b/vendor/github.com/davidbyttow/govips/v2/vips/operations.c index 6ae1002573..c3f2bb4e43 100644 --- a/vendor/github.com/davidbyttow/govips/v2/vips/operations.c +++ b/vendor/github.com/davidbyttow/govips/v2/vips/operations.c @@ -383,7 +383,7 @@ int get_image_delay(VipsImage *in, int **out) { } void set_image_delay(VipsImage *in, const int *array, int n) { - return vips_image_set_array_int(in, "delay", array, n); + vips_image_set_array_int(in, "delay", array, n); } int get_image_loop(VipsImage *in) { @@ -445,7 +445,7 @@ int label(VipsImage *in, VipsImage **out, LabelOptions *o) { vips_embed(t[2], &t[3], o->OffsetX, o->OffsetY, t[2]->Xsize + o->OffsetX, t[2]->Ysize + o->OffsetY, NULL)) { g_object_unref(base); - return 1; + return -1; } if (vips_black(&t[4], 1, 1, NULL) || vips_linear(t[4], &t[5], ones, o->Color, 3, NULL) || @@ -454,11 +454,11 @@ int label(VipsImage *in, VipsImage **out, LabelOptions *o) { vips_embed(t[7], &t[8], 0, 0, in->Xsize, in->Ysize, "extend", VIPS_EXTEND_COPY, NULL)) { g_object_unref(base); - return 1; + return -1; } if (vips_ifthenelse(t[3], t[8], in, out, "blend", TRUE, NULL)) { g_object_unref(base); - return 1; + return -1; } g_object_unref(base); return 0; diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/operations.go b/vendor/github.com/davidbyttow/govips/v2/vips/operations.go index 0e60206f8d..2a1c122f78 100644 --- a/vendor/github.com/davidbyttow/govips/v2/vips/operations.go +++ b/vendor/github.com/davidbyttow/govips/v2/vips/operations.go @@ -30,14 +30,16 @@ func vipsFindTrim(in *C.VipsImage, threshold float64, backgroundColor *Color) (i func vipsGetPoint(in *C.VipsImage, n int, x int, y int) ([]float64, error) { incOpCounter("getpoint") var out *C.double - defer gFreePointer(unsafe.Pointer(out)) if err := C.getpoint(in, &out, C.int(n), C.int(x), C.int(y)); err != 0 { return nil, handleVipsError() } - // maximum n is 4 - return (*[4]float64)(unsafe.Pointer(out))[:n:n], nil + // 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 @@ -691,7 +693,6 @@ func vipsSetPageHeight(in *C.VipsImage, height int) { func vipsImageGetMetaLoader(in *C.VipsImage) (string, bool) { var out *C.char - defer freeCString(out) code := int(C.get_meta_loader(in, &out)) return C.GoString(out), code == 0 } @@ -699,7 +700,6 @@ func vipsImageGetMetaLoader(in *C.VipsImage) (string, bool) { func vipsImageGetDelay(in *C.VipsImage, n int) ([]int, error) { incOpCounter("imageGetDelay") var out *C.int - defer gFreePointer(unsafe.Pointer(out)) if err := C.get_image_delay(in, &out); err != 0 { return nil, handleVipsError() @@ -727,7 +727,6 @@ func vipsImageGetBackground(in *C.VipsImage) ([]float64, error) { incOpCounter("imageGetBackground") var out *C.double var n C.int - defer gFreePointer(unsafe.Pointer(out)) if err := C.get_background(in, &out, &n); err != 0 { return nil, handleVipsError() @@ -854,7 +853,6 @@ func vipsImageGetString(in *C.VipsImage, name string) string { cField := C.CString(name) defer freeCString(cField) var cFieldValue *C.char - defer freeCString(cFieldValue) if int(C.image_get_string(in, cField, &cFieldValue)) == 0 { return C.GoString(cFieldValue) } @@ -866,7 +864,7 @@ func vipsImageGetAsString(in *C.VipsImage, name string) string { cField := C.CString(name) defer freeCString(cField) var cFieldValue *C.char - defer freeCString(cFieldValue) + defer func() { freeCString(cFieldValue) }() if int(C.image_get_as_string(in, cField, &cFieldValue)) == 0 { return C.GoString(cFieldValue) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 2846e3dcec..ef456360a9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -357,8 +357,8 @@ github.com/cyphar/filepath-securejoin/pathrs-lite/procfs # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/davidbyttow/govips/v2 v2.17.0 -## explicit; go 1.23.0 +# github.com/davidbyttow/govips/v2 v2.18.0 +## explicit; go 1.25.0 github.com/davidbyttow/govips/v2/vips # github.com/deckarep/golang-set v1.8.0 ## explicit; go 1.17