800 lines
22 KiB
Go
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(),
|
|
}
|
|
}
|