Files
QSfera/Server/vendor/github.com/davidbyttow/govips/v2/vips/image.go
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

800 lines
22 KiB
Go

package vips
// #include "image.h"
import "C"
import (
"errors"
"fmt"
"image"
"image/draw"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"os"
"runtime"
"strconv"
"strings"
"sync"
"unsafe"
_ "golang.org/x/image/webp"
)
const GaussBlurDefaultMinAMpl = 0.2
// PreMultiplicationState stores the pre-multiplication band format of the image
type PreMultiplicationState struct {
bandFormat BandFormat
}
// ImageRef contains a libvips image and manages its lifecycle.
type ImageRef struct {
// NOTE: We keep a reference to this so that the input buffer is
// never garbage collected during processing. Some image loaders use random
// access transcoding and therefore need the original buffer to be in memory.
buf []byte
image *C.VipsImage
format ImageType
originalFormat ImageType
lock sync.Mutex
preMultiplication *PreMultiplicationState
optimizedIccProfile string
}
// ImageMetadata is a data structure holding the width, height, orientation and other metadata of the picture.
type ImageMetadata struct {
Format ImageType
Width int
Height int
Colorspace Interpretation
Orientation int
Pages int
}
type Parameter struct {
value interface{}
isSet bool
}
func (p *Parameter) IsSet() bool {
return p.isSet
}
func (p *Parameter) set(v interface{}) {
p.value = v
p.isSet = true
}
type BoolParameter struct {
Parameter
}
func (p *BoolParameter) Set(v bool) {
p.set(v)
}
func (p *BoolParameter) Get() bool {
return p.value.(bool)
}
type IntParameter struct {
Parameter
}
func (p *IntParameter) Set(v int) {
p.set(v)
}
func (p *IntParameter) Get() int {
return p.value.(int)
}
type Float64Parameter struct {
Parameter
}
func (p *Float64Parameter) Set(v float64) {
p.set(v)
}
func (p *Float64Parameter) Get() float64 {
return p.value.(float64)
}
// ImportParams are options for loading an image. Some are type-specific.
// For default loading, use NewImportParams() or specify nil
type ImportParams struct {
AutoRotate BoolParameter
FailOnError BoolParameter
Page IntParameter
NumPages IntParameter
Density IntParameter
JpegShrinkFactor IntParameter
WebpScaleFactor Float64Parameter
HeifThumbnail BoolParameter
SvgUnlimited BoolParameter
Access IntParameter
}
// NewImportParams creates default ImportParams
func NewImportParams() *ImportParams {
p := &ImportParams{}
p.FailOnError.Set(true)
return p
}
// OptionString convert import params to option_string
func (i *ImportParams) OptionString() string {
var values []string
if v := i.NumPages; v.IsSet() {
values = append(values, "n="+strconv.Itoa(v.Get()))
}
if v := i.Page; v.IsSet() {
values = append(values, "page="+strconv.Itoa(v.Get()))
}
if v := i.Density; v.IsSet() {
values = append(values, "dpi="+strconv.Itoa(v.Get()))
}
if v := i.FailOnError; v.IsSet() {
values = append(values, "fail="+boolToStr(v.Get()))
}
if v := i.JpegShrinkFactor; v.IsSet() {
values = append(values, "shrink="+strconv.Itoa(v.Get()))
}
if v := i.WebpScaleFactor; v.IsSet() {
values = append(values, "scale="+strconv.FormatFloat(v.Get(), 'f', -1, 64))
}
if v := i.AutoRotate; v.IsSet() {
values = append(values, "autorotate="+boolToStr(v.Get()))
}
if v := i.SvgUnlimited; v.IsSet() {
values = append(values, "unlimited="+boolToStr(v.Get()))
}
if v := i.HeifThumbnail; v.IsSet() {
values = append(values, "thumbnail="+boolToStr(v.Get()))
}
if v := i.Access; v.IsSet() {
values = append(values, "access="+strconv.Itoa(v.Get()))
}
return strings.Join(values, ",")
}
func boolToStr(v bool) string {
if v {
return "TRUE"
}
return "FALSE"
}
// ExportParams are options when exporting an image to file or buffer.
// Deprecated: Use format-specific params
type ExportParams struct {
Format ImageType
Quality int
Compression int
Interlaced bool
Lossless bool
Effort int
StripMetadata bool
OptimizeCoding bool // jpeg param
SubsampleMode SubsampleMode // jpeg param
TrellisQuant bool // jpeg param
OvershootDeringing bool // jpeg param
OptimizeScans bool // jpeg param
QuantTable int // jpeg param
Speed int // avif param
}
// NewDefaultExportParams creates default values for an export when image type is not JPEG, PNG or WEBP.
// By default, govips creates interlaced, lossy images with a quality of 80/100 and compression of 6/10.
// As these are default values for a wide variety of image formats, their application varies.
// Some formats use the quality parameters, some compression, etc.
// Deprecated: Use format-specific params
func NewDefaultExportParams() *ExportParams {
return &ExportParams{
Format: ImageTypeUnknown, // defaults to the starting encoder
Quality: 80,
Compression: 6,
Interlaced: true,
Lossless: false,
Effort: 4,
}
}
// NewDefaultJPEGExportParams creates default values for an export of a JPEG image.
// By default, govips creates interlaced JPEGs with a quality of 80/100.
// Deprecated: Use NewJpegExportParams
func NewDefaultJPEGExportParams() *ExportParams {
return &ExportParams{
Format: ImageTypeJPEG,
Quality: 80,
Interlaced: true,
}
}
// NewDefaultPNGExportParams creates default values for an export of a PNG image.
// By default, govips creates non-interlaced PNGs with a compression of 6/10.
// Deprecated: Use NewPngExportParams
func NewDefaultPNGExportParams() *ExportParams {
return &ExportParams{
Format: ImageTypePNG,
Compression: 6,
Interlaced: false,
}
}
// NewDefaultWEBPExportParams creates default values for an export of a WEBP image.
// By default, govips creates lossy images with a quality of 75/100.
// Deprecated: Use NewWebpExportParams
func NewDefaultWEBPExportParams() *ExportParams {
return &ExportParams{
Format: ImageTypeWEBP,
Quality: 75,
Lossless: false,
Effort: 4,
}
}
// JpegExportParams are options when exporting a JPEG to file or buffer
type JpegExportParams struct {
StripMetadata bool
Quality int
Interlace bool
OptimizeCoding bool
SubsampleMode SubsampleMode
TrellisQuant bool
OvershootDeringing bool
OptimizeScans bool
QuantTable int
}
// NewJpegExportParams creates default values for an export of a JPEG image.
// By default, govips creates interlaced JPEGs with a quality of 80/100.
func NewJpegExportParams() *JpegExportParams {
return &JpegExportParams{
Quality: 80,
Interlace: true,
}
}
// PngExportParams are options when exporting a PNG to file or buffer
type PngExportParams struct {
StripMetadata bool
Compression int
Filter PngFilter
Interlace bool
Quality int
Palette bool
Dither float64
Bitdepth int
Profile string
}
// NewPngExportParams creates default values for an export of a PNG image.
// By default, govips creates non-interlaced PNGs with a compression of 6/10.
func NewPngExportParams() *PngExportParams {
return &PngExportParams{
Compression: 6,
Filter: PngFilterNone,
Interlace: false,
Palette: false,
}
}
// WebpExportParams are options when exporting a WEBP to file or buffer
// see https://www.libvips.org/API/current/VipsForeignSave.html#vips-webpsave
// for details on each parameter
type WebpExportParams struct {
StripMetadata bool
Quality int
Lossless bool
NearLossless bool
ReductionEffort int
IccProfile string
MinSize bool
MinKeyFrames int
MaxKeyFrames int
}
// NewWebpExportParams creates default values for an export of a WEBP image.
// By default, govips creates lossy images with a quality of 75/100.
func NewWebpExportParams() *WebpExportParams {
return &WebpExportParams{
Quality: 75,
Lossless: false,
NearLossless: false,
ReductionEffort: 4,
}
}
// TiffExportParams are options when exporting a TIFF to file or buffer
type TiffExportParams struct {
StripMetadata bool
Quality int
Compression TiffCompression
Predictor TiffPredictor
Pyramid bool
Tile bool
TileHeight int
TileWidth int
}
// NewTiffExportParams creates default values for an export of a TIFF image.
func NewTiffExportParams() *TiffExportParams {
return &TiffExportParams{
Quality: 80,
Compression: TiffCompressionLzw,
Predictor: TiffPredictorHorizontal,
Pyramid: false,
Tile: false,
TileHeight: 256,
TileWidth: 256,
}
}
// GifExportParams are options when exporting a GIF to file or buffer.
//
// For vips 8.12+, native gifsave is used. The relevant parameters are Dither,
// Effort, and Bitdepth. Quality is ignored because native gifsave does not
// support a quality parameter.
//
// For vips below 8.12, magicksave is used as a fallback. The relevant
// parameters are Quality and Bitdepth.
//
// StripMetadata has no effect on GIF images.
type GifExportParams struct {
StripMetadata bool
// Quality is only used with vips < 8.12 (magicksave fallback).
// Ignored by native gifsave in vips 8.12+.
Quality int
Dither float64
Effort int
Bitdepth int
}
// NewGifExportParams creates default values for an export of a GIF image.
func NewGifExportParams() *GifExportParams {
return &GifExportParams{
Quality: 75,
Effort: 7,
Bitdepth: 8,
}
}
// HeifExportParams are options when exporting a HEIF to file or buffer
type HeifExportParams struct {
Quality int
Bitdepth int
Effort int
Lossless bool
}
// NewHeifExportParams creates default values for an export of a HEIF image.
func NewHeifExportParams() *HeifExportParams {
return &HeifExportParams{
Quality: 80,
Bitdepth: 8,
Effort: 5,
Lossless: false,
}
}
// AvifExportParams are options when exporting an AVIF to file or buffer.
type AvifExportParams struct {
StripMetadata bool
Quality int
Bitdepth int
Effort int
Lossless bool
// DEPRECATED - Use Effort instead.
Speed int
}
// NewAvifExportParams creates default values for an export of an AVIF image.
func NewAvifExportParams() *AvifExportParams {
return &AvifExportParams{
Quality: 80,
Bitdepth: 8,
Effort: 5,
Lossless: false,
}
}
// Jp2kExportParams are options when exporting an JPEG2000 to file or buffer.
type Jp2kExportParams struct {
Quality int
Lossless bool
TileWidth int
TileHeight int
SubsampleMode SubsampleMode
}
// NewJp2kExportParams creates default values for an export of an JPEG2000 image.
func NewJp2kExportParams() *Jp2kExportParams {
return &Jp2kExportParams{
Quality: 80,
Lossless: false,
TileWidth: 512,
TileHeight: 512,
}
}
// JxlExportParams are options when exporting an JXL to file or buffer.
type JxlExportParams struct {
Quality int
Lossless bool
Tier int
Distance float64
Effort int
}
// NewJxlExportParams creates default values for an export of an JXL image.
func NewJxlExportParams() *JxlExportParams {
return &JxlExportParams{
Quality: 75,
Lossless: false,
Effort: 7,
Distance: 1.0,
}
}
// MagickExportParams are options when exporting an image to file or buffer by ImageMagick.
type MagickExportParams struct {
Quality int
Format string
OptimizeGifFrames bool
OptimizeGifTransparency bool
BitDepth int
}
// NewMagickExportParams creates default values for an export of an image by ImageMagick.
func NewMagickExportParams() *MagickExportParams {
return &MagickExportParams{
Quality: 75,
}
}
// NewImageFromReader loads an ImageRef from the given reader
func NewImageFromReader(r io.Reader) (*ImageRef, error) {
buf, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return NewImageFromBuffer(buf)
}
// NewImageFromFile loads an image from file and creates a new ImageRef
func NewImageFromFile(file string) (*ImageRef, error) {
return LoadImageFromFile(file, nil)
}
// LoadImageFromFile loads an image from file and creates a new ImageRef
func LoadImageFromFile(file string, params *ImportParams) (*ImageRef, error) {
buf, err := os.ReadFile(file)
if err != nil {
return nil, err
}
govipsLog("govips", LogLevelDebug, fmt.Sprintf("creating imageRef from file %s", file))
return LoadImageFromBuffer(buf, params)
}
// NewImageFromBuffer loads an image buffer and creates a new Image
func NewImageFromBuffer(buf []byte) (*ImageRef, error) {
return LoadImageFromBuffer(buf, nil)
}
// LoadImageFromBuffer loads an image buffer and creates a new Image
func LoadImageFromBuffer(buf []byte, params *ImportParams) (*ImageRef, error) {
if err := startupIfNeeded(); err != nil {
return nil, err
}
if params == nil {
params = NewImportParams()
}
vipsImage, currentFormat, originalFormat, err := vipsLoadFromBuffer(buf, params)
if err != nil {
return nil, err
}
ref := newImageRef(vipsImage, currentFormat, originalFormat, buf)
govipsLog("govips", LogLevelDebug, fmt.Sprintf("created imageRef %p", ref))
return ref, nil
}
// NewThumbnailFromFile loads an image from file and creates a new ImageRef with thumbnail crop
func NewThumbnailFromFile(file string, width, height int, crop Interesting) (*ImageRef, error) {
return LoadThumbnailFromFile(file, width, height, crop, SizeBoth, nil)
}
// NewThumbnailFromBuffer loads an image buffer and creates a new Image with thumbnail crop
func NewThumbnailFromBuffer(buf []byte, width, height int, crop Interesting) (*ImageRef, error) {
return LoadThumbnailFromBuffer(buf, width, height, crop, SizeBoth, nil)
}
// NewThumbnailWithSizeFromFile loads an image from file and creates a new ImageRef with thumbnail crop and size
func NewThumbnailWithSizeFromFile(file string, width, height int, crop Interesting, size Size) (*ImageRef, error) {
return LoadThumbnailFromFile(file, width, height, crop, size, nil)
}
// LoadThumbnailFromFile loads an image from file and creates a new ImageRef with thumbnail crop and size
func LoadThumbnailFromFile(file string, width, height int, crop Interesting, size Size, params *ImportParams) (*ImageRef, error) {
if err := startupIfNeeded(); err != nil {
return nil, err
}
vipsImage, format, err := vipsThumbnailFromFile(file, width, height, crop, size, params)
if err != nil {
return nil, err
}
ref := newImageRef(vipsImage, format, format, nil)
govipsLog("govips", LogLevelDebug, fmt.Sprintf("created imageref %p", ref))
return ref, nil
}
// NewThumbnailWithSizeFromBuffer loads an image buffer and creates a new Image with thumbnail crop and size
func NewThumbnailWithSizeFromBuffer(buf []byte, width, height int, crop Interesting, size Size) (*ImageRef, error) {
return LoadThumbnailFromBuffer(buf, width, height, crop, size, nil)
}
// LoadThumbnailFromBuffer loads an image buffer and creates a new Image with thumbnail crop and size
func LoadThumbnailFromBuffer(buf []byte, width, height int, crop Interesting, size Size, params *ImportParams) (*ImageRef, error) {
if err := startupIfNeeded(); err != nil {
return nil, err
}
vipsImage, format, err := vipsThumbnailFromBuffer(buf, width, height, crop, size, params)
if err != nil {
return nil, err
}
ref := newImageRef(vipsImage, format, format, buf)
govipsLog("govips", LogLevelDebug, fmt.Sprintf("created imageref %p", ref))
return ref, nil
}
// Metadata returns the metadata (ImageMetadata struct) of the associated ImageRef
func (r *ImageRef) Metadata() *ImageMetadata {
return &ImageMetadata{
Format: r.Format(),
Width: r.Width(),
Height: r.Height(),
Orientation: r.Orientation(),
Colorspace: r.ColorSpace(),
Pages: r.Pages(),
}
}
// Copy creates a new copy of the given image.
func (r *ImageRef) Copy() (*ImageRef, error) {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, nil)
if err != nil {
return nil, err
}
return newImageRef(out, r.format, r.originalFormat, r.buf), nil
}
// Copy creates a new copy of the given image with the new X and Y resolution (PPI).
func (r *ImageRef) CopyChangingResolution(xres, yres float64) (*ImageRef, error) {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, &CopyOptions{Xres: &xres, Yres: &yres})
if err != nil {
return nil, err
}
return newImageRef(out, r.format, r.originalFormat, r.buf), nil
}
// Copy creates a new copy of the given image with the interpretation.
func (r *ImageRef) CopyChangingInterpretation(interpretation Interpretation) (*ImageRef, error) {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, &CopyOptions{Interpretation: &interpretation})
if err != nil {
return nil, err
}
return newImageRef(out, r.format, r.originalFormat, r.buf), nil
}
// XYZ creates a two-band uint32 image where the elements in the first band have the value of their x coordinate
// and elements in the second band have their y coordinate.
func XYZ(width, height int) (*ImageRef, error) {
vipsImage, err := vipsGenXyz(width, height, nil)
return newImageRef(vipsImage, ImageTypeUnknown, ImageTypeUnknown, nil), err
}
// Identity creates an identity lookup table, which will leave an image unchanged when applied with Maplut.
// Each entry in the table has a value equal to its position.
func Identity(ushort bool) (*ImageRef, error) {
img, err := vipsGenIdentity(&IdentityOptions{Ushort: &ushort})
return newImageRef(img, ImageTypeUnknown, ImageTypeUnknown, nil), err
}
// Black creates a new black image of the specified size
func Black(width, height int) (*ImageRef, error) {
vipsImage, err := vipsGenBlack(width, height, nil)
if err != nil {
return nil, err
}
return newImageRef(vipsImage, ImageTypeUnknown, ImageTypeUnknown, nil), nil
}
// Grey creates a horizontal gradient image (ramp from black to white).
// When uchar is true, pixel values are 0-255 uint8; when false, 0.0-1.0 float.
// Useful for creating gradient overlays when combined with rotation, BandJoin, and Composite.
func Grey(width, height int, uchar bool) (*ImageRef, error) {
img, err := vipsGenGrey(width, height, &GreyOptions{Uchar: &uchar})
if err != nil {
return nil, err
}
return newImageRef(img, ImageTypeUnknown, ImageTypeUnknown, nil), nil
}
// NewTransparentCanvas creates a fully transparent RGBA image of the given dimensions.
// The image is in sRGB color space with 4 bands (RGBA), all channels set to 0.
func NewTransparentCanvas(width, height int) (*ImageRef, error) {
ref, err := Black(width, height)
if err != nil {
return nil, err
}
if err := ref.ToColorSpace(InterpretationSRGB); err != nil {
ref.Close()
return nil, err
}
if err := ref.BandJoinConst([]float64{0}); err != nil {
ref.Close()
return nil, err
}
return ref, nil
}
// Text draws the string text to an image.
func Text(params *TextParams) (*ImageRef, error) {
img, err := vipsText(params)
return newImageRef(img, ImageTypeUnknown, ImageTypeUnknown, nil), err
}
// 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) {
if err := startupIfNeeded(); err != nil {
return nil, err
}
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
if width == 0 || height == 0 {
return nil, errors.New("image has zero dimensions")
}
// Normalize to NRGBA
var nrgba *image.NRGBA
if n, ok := img.(*image.NRGBA); ok && n.Rect.Min.X == 0 && n.Rect.Min.Y == 0 && n.Stride == width*4 {
nrgba = n
} else {
nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
draw.Draw(nrgba, nrgba.Bounds(), img, bounds.Min, draw.Src)
}
// Create vips image from pixel data (copies data, safe for Go GC)
vipsImage := C.create_image_from_memory_copy(
unsafe.Pointer(&nrgba.Pix[0]),
C.size_t(len(nrgba.Pix)),
C.int(width),
C.int(height),
4,
C.VIPS_FORMAT_UCHAR,
)
runtime.KeepAlive(nrgba)
if vipsImage == nil {
return nil, errors.New("failed to create vips image from memory")
}
// Set interpretation to sRGB
vipsImage.Type = C.VIPS_INTERPRETATION_sRGB
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()
defer r.lock.Unlock()
if r.image == image {
return
}
if r.image != nil {
clearImage(r.image)
}
r.image = image
}
func vipsHasAlpha(in *C.VipsImage) bool {
return int(C.has_alpha_channel(in)) > 0
}
func clearImage(ref *C.VipsImage) {
C.clear_image(&ref)
}
// Coding represents VIPS_CODING type
type Coding int
// Coding enum
//
//goland:noinspection GoUnusedConst
const (
CodingError Coding = C.VIPS_CODING_ERROR
CodingNone Coding = C.VIPS_CODING_NONE
CodingLABQ Coding = C.VIPS_CODING_LABQ
CodingRAD Coding = C.VIPS_CODING_RAD
)
func (r *ImageRef) newMetadata(format ImageType) *ImageMetadata {
return &ImageMetadata{
Format: format,
Width: r.Width(),
Height: r.Height(),
Colorspace: r.ColorSpace(),
Orientation: r.Orientation(),
Pages: r.Pages(),
}
}